Process

A running instance of a program is called a process.

If you have two terminal windows showing on your screen, then you are probably running the
same terminal program twice—you have two terminal processes. Each terminal window is probably running a shell; each running shell is another process.When you invoke a command from a shell, the corresponding program is executed in a new process; the shell process resumes when that process completes.

Advanced programmers often use multiple cooperating processes in a single application to enable the application to do more than one thing at once, to increase application robustness, and to make use of already-existing programs.

Most of the process manipulation functions described in this chapter are similar to those on other UNIX systems. Most are declared in the header file <unistd.h>; check the man page for each function to be sure.

Process Interactions

In C++, getopt() is a library function to parse CLI arguments

Usage:

  • getopt(argc, argv, "st:")
  • input: arguments and a string describing the desired format
  • output: returns the next argument and an option value
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// adapted from
// https://www.gnu.org/software/libc/manual/html_node/Example-of-Getopt.html
#include <iostream>
#include <unistd.h>

/// main() must be declared with arguments
/// otherwise command line arguments are ignored
int main(int argc, char **argv) {
bool aflag = 0;
bool bflag = 0;
std::string cvalue;
int cint_value;
int index;
int c;

opterr = 0;

// note the colon (:) to indicate that 'c' has a parameter and is not a switch
// expected options are '-a', '-b', and '-c value'
while ((c = getopt(argc, argv, "abc:")) != -1)
switch (c) {
case 'a':
aflag = true;
break;
case 'b':
bflag = true;
break;
case 'c':
cvalue = optarg;
cint_value = atoi(cvalue.c_str());
break;
case '?':
if (optopt == 'c')
std::cerr << "Error: option -" << optopt << " requires an argument." << std::endl;
else
std::cerr << "Error: unknown option: " << optopt << std::endl;
return 1;
default:
return 0;
}

std::cout << "aflag=" << aflag << " "
<< "bflag=" << bflag << " "
<< "cvalue=" << cvalue << " "
<< "cint_value=" << cint_value << std::endl;

if (optind < argc) {
std::cout << "Found positional arguments\n";
for (index = optind; index < argc; index++)
std::cout << "Non-option argument: " << argv[index] << "\n";
}

return 0;
}

Process

A process is a running instance of a program

Examples:

  • Each of the two running instances of Firefox
  • The shell and the ls command executed, each is a process

Advanced programmers use multiple processes to

  • Do several tasks at once
  • Increase robustness (one process fails, other still running)
  • Make use of already-existing processes

The main components of a process:

  • An executable piece of code (a program)
  • Data that is input or output by the program
  • Execution context (information about the program needed by OS)

Process - Hierarchy

Each process in a Linux system is identified by its unique process ID, sometimes referred to as pid.

  • Process IDs are 16-bit numbers that are assigned sequentially by Linux as new processes are created.

Each process (with some exceptions) has a parent process (indicated by ppid)

We can get the information within program

import the library unistd.h

  • getpid()
  • getppid()
1
2
3
4
5
6
7
8
#include <stdio.h> 
#include <unistd.h>
int main ()
{
printf ("The process ID is %d\n", (int) getpid ());
printf ("The parent process ID is %d\n", (int) getppid ());
return 0;
}

Observe that if you invoke this program several times, a different process ID is reported because each invocation is in a new process. However, if you invoke it every time from the same shell, the parent process ID (that is, the process ID of the shell process) is the same.

1
2
The process ID is 21062
The parent process ID is 20926

Process - Create

  • Running a program will automatically create (at least) a process
  • Creating process within your program:
    • Using system
      • Runs a shell (as a subprocess) to run the given commands

using system is not recommended because

  • The call to system relies on the installed shell
  • It brings the shell’s features, limitations, security flaws

Therefore, it’s preferable to use the fork and exec method for creating processes.

1
2
3
4
5
6
#include <stdlib.h>
int main () {
int return_value;
return_value = system ("ls -l /");
return return_value;
}

Process - Fork

  • Forks an execution of a process (creates a new copy of the process)
    • After a call to fork(), a new process is created (called child)
    • The original process (called parent) continues to execute concurrently
    • In the parent, fork() returns the process id of the child that was created
    • In the child, fork() returns 0 to indicate that this is a child process
    • The parent and child are independent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h> 
#include <sys/types.h>
#include <unistd.h>

int main () {
pid_t child_pid;

printf ("the main program process ID is %d\n", (int) getpid ());

child_pid = fork ();

if (child_pid != 0) {
printf ("this is the parent process, with id %d\n", (int) getpid ());
printf ("the child’s process ID is %d\n", (int) child_pid);
}
else
printf ("this is the child process, with id %d\n", (int) getpid ());

return 0;
}

Output

1
2
3
4
the main program process ID is 17609
this is the parent process, with id 17609
the child’s process ID is 17610
this is the child process, with id 17610

Process - Exec

  • exec() series of functions are used to start another program in the current process
  • After a call to exec() the current process is replaced with the image of the specified program
  • Different versions allow for different ways to pass command line arguments and environment settings
  • int execv(const char *file, char *const argv[ ])
    • file is a path to an executable
    • argv is an array of arguments. By convention, argv[0] is the name of the program being executed

Functions that contain the letter v in their names (execv, execvp, and execve) accept the argument list for the new program as a NULL-terminated array of pointers to strings. Functions that contain the letter l (execl, execlp, and execle) accept the argument list using the C language’s varargs mechanism.

To spawn a new process, you first use fork to make a copy of the current process.Then you use exec to transform one of these processes into an instance of the program you want to spawn.

The following example illustrates the use of execv to execute the ls shell command:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <unistd.h>
int main(void) {

char *argv[3];

argv[0] = (char *)"ls";
argv[1] = (char *)"-l";
argv[2] = nullptr;
std::cout << "[exec] executing '/bin/ls -l' using execv" << std::endl;

pid_t kid;
kid = fork();
printf ("the main program process ID is %d\n", (int) getpid ());
printf ("the child’s process ID is %d\n", (int) kid);
if (kid == 0) {
sleep(4); //Sleep for 4 seconds
execv("/bin/ls", argv);
// execl("/bin/ls", "ls", "-l", nullptr);
perror("Error from arie");
return 1;
}

int res;
waitpid(kid, &res, 0);
std::cout << "ls returned status: " << res << std::endl;

return 0;
}

Output

1
2
3
4
5
6
7
8
9
10
11
12
[exec] executing '/bin/ls -l' using execv
the main program process ID is 20394
the child’s process ID is 20395
the main program process ID is 20395
the child’s process ID is 0
total 1960
-rw-r--r-- 1 vines staff 14738 Feb 21 16:08 CMakeCache.txt
drwxr-xr-x 17 vines staff 544 Feb 21 16:09 CMakeFiles
-rw-r--r-- 1 vines staff 12911 Feb 21 16:08 Makefile
...

ls returned status: 0

Another simple example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
pid_t pid;
char *const parmList[] = {"/bin/ls", "-l", "/u/userid/dirname", NULL};

if ((pid = fork()) == -1)
perror("fork error");
else if (pid == 0) {
execv("/bin/ls", parmList);
printf("Return not expected. Must be an execv error.n");
}
}

Output

1
2
3
4
5
6
total 1960
-rw-r--r-- 1 vines staff 14738 Feb 21 16:08 CMakeCache.txt
drwxr-xr-x 17 vines staff 544 Feb 21 16:09 CMakeFiles
-rw-r--r-- 1 vines staff 12911 Feb 21 16:08 Makefile
...

Some practical example:

1
2
3
4
5
6
7
8
9
10
11
// Run C++ program (but the program takes argv as input)
argv[0] = (char*)"rgen";
execv("rgen", argv);

// Run C++ program (but the program does not take argv as input)
argv[0] = (char*)"a2";
argv[1] = nullptr;
execv("a2", argv);

// Run python program
execlp("python3", "python3", "a1.py", (char*) NULL);

Difference between exec family:

  • execve():

p : not present => name of the program to run will be taken from pathname

v : present => argument will be passed as array

e : present => environment will be taken from envp argument

  • execle():

p : not present => name of the program to run will be taken from pathname

l : present => argument will be passed as list

e : present => environment will be taken from envp argument

  • execlp():

p : present => name of the program to run will be taken from filename specified or system will search for program file in PATH variable.

l : present => argument will be passed as list

e : not present => environment will be taken from caller's environ

  • execvp():

p : present => name of the program to run will be taken from filename specified or system will search for program file in PATH variable.

v : present => argument will be passed as array

e : not present => environment will be taken from caller's environ

  • execv():

p : not present => name of the program to run will be taken from pathname

v : present => argument will be passed as array

e : not present => environment will be taken from caller's environ

  • execl():

p : not present => name of the program to run will be taken from pathname

l : present => argument will be passed as list

e : not present => environment will be taken from caller's environ

Process - Kill

Run kill in the terminal (run kill with -KILL)

  • Does not terminate a process necessarily

Process - Signals

  • A special message sent to a process
  • Signals are asynchronous
  • Different types of signals (defined in signum.h)
    • SIGTERM : Termination
    • SIGINT : Terminal interrupt (Ctrl+C)
    • SIGKILL : Kill (can’t be caught or ignored)
    • SIGBUS : BUS error
    • SIGSEGV : Invalid memory segment access
    • SIGPIPE : Write on a pipe with no reader, Broken pipe
    • SIGSTOP : Stop executing (can’t be caught or ignored)

Process - Receiving / Sending Signals

Receiving a signal:

  • Default disposition
  • Signal handler procedure

Sending signal from one process to another process (SIGTERM, SIGKILL)

  • Usually the parent process sends signals to its children
  • int kill(pid_t pid, int sig)
  • Send a signal sig to a process pid

It’s a good practice to kill and wait for children to terminate before exiting

Process - Termination

What does it mean “a process terminates”?

Exit codes:

  • zero: successful termination
  • non-zero: termination with an error
1
exit(0);

Process - Zombie Processes

A zombie process is a process that exits and the parent is not handling the exit status

WAITING FOR A CHILD!

A parent process can wait for a child process to terminate

  • pid_t waitpid(pid_t pid, int *stat_loc, int options)
  • Blocks the parent until the process with the specified pid terminates
  • The return code from the terminating process is placed in stat_loc
  • options control whether the function blocks or not
    • 0 is a good choice for options

Interprocess Communication

Pipeline

Pipeline is an Unidirectional communication

  • Serial devices: the read and write order are the same
  • Pipeline synchronizes two processes

In a shell, use | for pipeline

1
ls | less

Pipe

A pipe is a communication device that permits unidirectional communication. Data written to the “write end” of the pipe is read back from the “read end.”

Pipes are serial devices; the data is always read from the pipe in the same order it was written.

Typically, a pipe is used to communicate between two threads in a single process or between parent and child processes.

pipe() creates a ONE directional pipe

  • Two file descriptors: one to write to and one to read from the pipe
  • A process can use the pipe by itself, but this is unusual
  • Typically, a parent process creates a pipe and shares it with a child, or between multiple children
  • Some processes read from it, and some write to it
  • There can be multiple writers and multiple readers
    • Although multiple writers is more common

To create a pipe, invoke the pipe command. Supply an integer array of size 2.The call to pipe stores the reading file descriptor in array position 0 and the writing file descriptor in position 1. For example, consider this code:

1
2
3
4
5
6
7
8
int pipe_fds[2]; 
int read_fd;
int write_fd;

pipe (pipe_fds);
read_fd = pipe_fds[0];
write_fd = pipe_fds[1];
// Data written to the file descriptor read_fd can be read back from write_fd.

Dup

dup2() duplicates a file descriptor

  • Used to redirect standard input, standard output, and standard error to a pipe (or another file)
  • STDOUT_FILENO is the number of the standard output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <string>

int main()
{
// The read end is at fds[0]
// The write end is at fds[1]
// declare fd
int fds[2];
pid_t pid;

//open the pipe
pipe(fds);

pid = fork();
if(pid == (pid_t) 0){ // child reads the message (msg)
// Message buffer
// char message[20];
printf("Hi\n");
// close the write end
close(fds[1]);
dup2(fds[0], STDIN_FILENO);

std::string message;
while(!std::cin.eof()) {
std::getline(std::cin, message);
std::cout << message
<< std::endl;
}
// close the read end
close(fds[0]);
}
else {
printf("Bye\n");
// close the read end
close(fds[0]);
write(fds[1], "Hello world!\n", 20);
write(fds[1], "Hello world!2\n", 20);
// close the write end
close(fds[1]);
}
return 0;
}

Output

1
2
3
4
Bye
Hi
Hello world!
Hello Hello world!2

Another example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <unistd.h>

void child(void) {

char *args[3];
args[0] = (char *)"ls";
args[1] = (char *)"-l";
args[2] = nullptr;
execv("/bin/ls", args);
}
int main(void) {

int CtoP[2];
pipe(CtoP);

auto fid = fork();
if (fid == 0) {
dup2(CtoP[1], STDOUT_FILENO);
close(CtoP[0]);
close(CtoP[1]);
child();
std::exit(0);
} else if (fid < 0) {
std::exit(1);
}

// child is running

dup2(CtoP[0], STDIN_FILENO);
close(CtoP[0]);
close(CtoP[1]);

while (!std::cin.eof()) {
std::string line;
std::getline(std::cin, line);
std::cout << "[P]: " << line << "\n";
}

int status;
waitpid(fid, &status, 0);
std::cout << "child finished with status: " << status << "\n";
}

Output:

1
2
3
4
5
6
7
[P]: total 1968
[P]: -rw-r--r-- 1 vines staff 14738 Feb 21 16:21 CMakeCache.txt
[P]: drwxr-xr-x 17 vines staff 544 Feb 21 16:22 CMakeFiles
[P]: -rw-r--r-- 1 vines staff 12911 Feb 21 16:21 Makefile
...
[P]:
child finished with status: 0

Using Pipe and Dup2

Redirecting the Standard Input, Output, and Error Streams

Frequently, you’ll want to create a child process and set up one end of a pipe as its standard input or standard output.

Using the dup2 call, you can equate one file descriptor with another. For example, to redirect a process’s standard input to a file descriptor fd, use this line:

1
2
3
dup2 (fd[0], STDIN_FILENO);
//or
dup2(fd[1], STDOUT_FILENO);

img

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// based on the example from
// https://stackoverflow.com/questions/13041416/redirect-stdout-of-two-processes-to-another-processs-stdin-in-linux-c
#include <vector>

#include <iostream>
#include <signal.h>
#include <unistd.h>

/// Entry point of process A
int procA(void) {
// Process A writing to C
for (int i = 0; i < 100; i++) {
std::cout << "Hi" << std::endl;
usleep(5000);
}
std::cout << "[A] Sleeping" << std::endl;
sleep(6);
std::cout << "[A] Exiting" << std::endl;
return 0;
}

/// Entry point of process B
int procB(void) {
// Process B writing to C
while (!std::cin.eof()) {
// read a line of input until EOL and store in a string
std::string line;
std::getline(std::cin, line);
if (line.size() > 0)
std::cout << line << std::endl;
}
std::cout << "[B] saw EOF" << std::endl;
return 0;
}

/// Entry point of process C
int procC(void) {
// Process C reading from both A and B
while (!std::cin.eof()) {
// read a line of input until EOL and store in a string
std::string line;
std::getline(std::cin, line);
if (line.size() > 0)
std::cout << "[C]: " << line << std::endl;
}
return 0;
}

int main(void) {
std::vector<pid_t> kids;
// create a pipe
int ABtoC[2];
pipe(ABtoC);

pid_t child_pid;
child_pid = fork();
if (child_pid == 0) {
// redirect stdout to the pipe
dup2(ABtoC[1], STDOUT_FILENO);
close(ABtoC[0]);
close(ABtoC[1]); // Close this too!

return procA();
} else if (child_pid < 0) {
std::cerr << "Error: could not fork\n";
return 1;
}

kids.push_back(child_pid);

child_pid = fork();
if (child_pid == 0) {
// redirect stdin from the pipe
dup2(ABtoC[0], STDIN_FILENO);
close(ABtoC[1]);
close(ABtoC[0]);

return procC();
} else if (child_pid < 0) {
std::cerr << "Error: could not fork\n";
return 1;
}

kids.push_back(child_pid);
child_pid = 0;

// redirect stdout to the pipe
dup2(ABtoC[1], STDOUT_FILENO);
close(ABtoC[0]);
close(ABtoC[1]);

// start process B
int res = procB();

// send kill signal to all children
for (pid_t k : kids) {
int status;
kill(k, SIGTERM);
waitpid(k, &status, 0);
}

return res;
}

output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[C]: Hi
[C]: Hi
[C]: Hi
...
[C]: Hi
[C]: Hi
[C]: Hi
[C]: [A] Sleeping
[C]: [A] Exiting
dsda
[C]: dsda
aa
[C]: aa
aaa
[C]: aaa
sss
[C]: sss

or

1
2
3
[C]: Hi
[C]: Hi
[C]: [B] saw EOF # Triggered ctrl+d

or

1
2
3
4
5
[C]: Hi
[C]: Hi
...
[C]: [A] Sleeping
[C]: [B] saw EOF # Triggered ctrl+d

Minimal example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <vector>

#include <iostream>
#include <signal.h>
#include <unistd.h>

/// Entry point of process A
int procA(void) {
// Process A writing to C
for (int i = 0; i < 100; i++) {
std::cout << "Hi" << std::endl;
usleep(5000);
}
std::cout << "[A] Sleeping" << std::endl;
sleep(6);
std::cout << "[A] Exiting" << std::endl;
return 0;
}

/// Entry point of process C
int procC(void) {
// Process C reading from both A and B
while (!std::cin.eof()) {
// read a line of input until EOL and store in a string
std::string line;
std::getline(std::cin, line);
if (line.size() > 0)
std::cout << "[C]: " << line << std::endl;
}
return 0;
}

int main(void) {
std::vector<pid_t> kids;
// create a pipe
int ABtoC[2];
pipe(ABtoC);

pid_t child_pid;
child_pid = fork();
if (child_pid == 0) {
// redirect stdout to the pipe
dup2(ABtoC[1], STDOUT_FILENO);
close(ABtoC[0]);
close(ABtoC[1]); // Close this too!

return procA();
} else if (child_pid < 0) {
std::cerr << "Error: could not fork\n";
return 1;
}

kids.push_back(child_pid);

child_pid = fork();
if (child_pid == 0) {
// redirect stdin from the pipe
dup2(ABtoC[0], STDIN_FILENO);
close(ABtoC[1]);
close(ABtoC[0]);

return procC();
} else if (child_pid < 0) {
std::cerr << "Error: could not fork\n";
return 1;
}

kids.push_back(child_pid);

//Hold the program
int monitor;
wait(&monitor);

// send kill signal to all children
for (pid_t k : kids) {
int status;
kill(k, SIGTERM);
waitpid(k, &status, 0);
}

return 0;
}

output

1
2
3
4
5
6
7
8
9
[C]: Hi
[C]: Hi
[C]: Hi
...
[C]: Hi
[C]: Hi
[C]: Hi
[C]: [A] Sleeping
[C]: [A] Exiting

Practical Example:

  • output of rgen is connected to the input of a1
  • output of a1 is connected to the input of a2
  • output of a2 is then print out by the procPrinter
  • When procPrinter take ctrl+d, it will exit

2 pipes needed (rgenToA1, a1ToA2)

Note that

  • pipe[0] - the read end of the pipe - is a file descriptor used to read from the pipe
  • pipe[1] - the write end of the pipe - is a file descriptor used to write to the pipe

rgen write output to the rgenToA1 pipe, therefore rgenToA1[1] is used through dup2

a1 read input from the rgenToA1 pipe, therefore rgenToA1[0] is used through dup2

a1 write output to the a1ToA2 pipe, therefore a1ToA2[1] is used through dup2

a2 read input from the a1ToA2 pipe, therefore a1ToA2[0] is used through dup2

procPrinter then subscribe to a1ToA2[1] pipe because it takes input as well

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Entry point of process Printer
int procPrinter(void) {
// Process Printer reading
while (!std::cin.eof()) {
// read a line of input until EOL and store in a string
std::string line;
std::getline(std::cin, line);
if (line.size() > 0)
//std::cout << "[Printer]: " << line << std::endl;
std::cout << line << std::endl;
}
return 0;
}

int main(int argc, char **argv)
{
// You must create your pipe before that you fork your process.
// https://stackoverflow.com/questions/44422206/simple-pipe-program-does-reads-strange-characters

//======================================================

std::vector<pid_t> kids;
pid_t kid;

/*
pipe[0] - the read end of the pipe - is a file descriptor used to read from the pipe
pipe[1] - the write end of the pipe - is a file descriptor used to write to the pipe
*/
int rgenToA1[2];
pipe(rgenToA1); // create the pipe before
int a1ToA2[2];
pipe(a1ToA2); // create the pipe before

//======================================================
// rgen running
//======================================================
kid = fork(); // so child and parent share the same pipe
if (kid == 0)
{
// Child duplicated
// default file descriptor number is 0 for STDIN_FILENO
// default file descriptor number is 1 for STDOUT_FILENO
dup2(rgenToA1[1], STDOUT_FILENO); // write to the pipe
//close unused pipe ends
close(rgenToA1[0]);
close(rgenToA1[1]);

argv[0] = (char*)"rgen";
execv("rgen", argv);
perror("Error: cannot run rgen \n");
return 1;
}
else if (kid < 0)
{
std::cerr << "Error: could not fork rgen" << std::endl;
return 1;
}
kids.push_back(kid);


//======================================================
// a1 reading from rgen
// a1 writing to a2
//======================================================
kid = fork(); // so child and parent share the same pipe
if (kid == 0)
{
dup2(rgenToA1[0], STDIN_FILENO); // read from the pipe
close(rgenToA1[0]);
close(rgenToA1[1]);

dup2(a1ToA2[1], STDOUT_FILENO); // write to the pipe
close(a1ToA2[0]);
close(a1ToA2[1]);

execlp("python3", "python3", "a1.py", (char*) NULL);
perror("Error: cannot run a1 \n");
return 1;
}
kids.push_back(kid);

//======================================================
// a2 reading from a1
//======================================================
kid = fork(); // so child and parent share the same pipe
if (kid == 0)
{
// Child duplicated
// default file descriptor number is 0 for STDIN_FILENO
// default file descriptor number is 1 for STDOUT_FILENO
dup2(a1ToA2[0], STDIN_FILENO); // read from the pipe
//close unused pipe ends
close(a1ToA2[1]);
close(a1ToA2[0]);

argv[0] = (char*)"a2";
argv[1] = nullptr;
execv("a2", argv);
perror("Error: cannot run a2 \n");
return 1;
}
kids.push_back(kid);

//======================================================
// Listener
//======================================================
kid = fork(); // so child and parent share the same pipe
if (kid == 0)
{
// Child duplicated
// default file descriptor number is 0 for STDIN_FILENO
// default file descriptor number is 1 for STDOUT_FILENO
dup2(a1ToA2[1], STDOUT_FILENO); // write to the pipe
//close unused pipe ends
close(a1ToA2[0]);
close(a1ToA2[1]);

return procPrinter();
}
else if (kid < 0)
{
std::cerr << "Error: could not fork procPrinter" << std::endl;
return 1;
}
kids.push_back(kid);

//Hold the program
int monitor;
wait(&monitor);

// send kill signal to all children
for (pid_t k : kids) {
int status;
kill(k, SIGTERM);
waitpid(k, &status, 0);
}

return 0;
}

Written Example:

You are given two programs A and B. You would like to execute the two programs in parallel such that the standard input of B is connected to the standard output of A and the standard input of A is connected to the standard output of B. Sketch a code for the main() function of the parent process that creates the above configuration. You might need to use the following system calls: fork(), dup2(), pipe(), close(), and exec(). You can write exec(A) to mean that program A is to be executed (i.e., don’t worry about the exact syntax of exec() family of commands).

  • output of A is connected to the input of B
  • output of B is connected to the input of A

2 pipes are needed because each single direction require a pipe. We name it AtoB and BtoA.

Note that

  • pipe[0] - the read end of the pipe - is a file descriptor used to read from the pipe
  • pipe[1] - the write end of the pipe - is a file descriptor used to write to the pipe

A write output to the AtoB pipe, therefore AtoB[1] is used through dup2

A read input from the BtoA pipe, therefore BtoA[0] is used through dup2

B write output to the BtoA pipe, therefore BtoA[1] is used through dup2

B read input from the AtoB pipe, therefore AtoB[0] is used through dup2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
int main() {
std::vector kids;
pid_t kid;

int AtoB[2];
int BtoA[2];
pipe(AtoB);
pipe(BtoA);

// A
kid = fork();
if (kid == 0)
{
dup2(AtoB[1], STDOUT_FILENO);
close(AtoB[0]);
close(AtoB[1]);

dup2(BtoA[0], STDIN_FILENO);
close(BtoA[0]);
close(BtoA[1]);

exec(A);
return 1;
}
kids.push_back(kid);

// B
kid = fork();
if (kid == 0)
{
dup2(BtoA[1], STDOUT_FILENO);
close(BtoA[0]);
close(BtoA[1]);

dup2(AtoB[0], STDIN_FILENO);
close(AtoB[0]);
close(AtoB[1]);

exec(B);
return 1;
}
kids.push_back(kid);

// send kill signal to all children
// ...

return 0;
}

Popen / Pclose

All-together package

A common use of pipes is to send data to or receive data from a program being run in a subprocess.The popen and pclose functions ease this paradigm by eliminating the need to invoke pipe, fork, dup2, exec, and fdopen.

Combining pipe, fork, dup2, exec, and fdopen!

  • The call to popen creates a child process executing the given command
  • The second argument to peopen “w” or “r” indicates whether the process wants to read from or write to the child process
  • Return value from popen is one end of a pipe
  • The other end is connected to the standard input/output of the child process
  • WARNING: popen uses shell to execute the given command!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <string>
#include <sstream>


int main() {
char message[20];
FILE* fds = popen("./calc.out", "r");
while ( fgets(message, 20, fds) != NULL )
std::cout << message;
return pclose(fds);
}
  • Note that popen() can only open the pipe in read or write mode, not both.

popen works in one direction. If you need to read and write, You can create a pipe with pipe(), span a new process by fork() and exec functions and then redirect its input and outputs with dup2().

FIFO

A first-in, first-out (FIFO) file is a pipe that has a name in the filesystem. Any process can open or close the FIFO; the processes on either end of the pipe need not be related to each other. FIFOs are also called named pipes.

For processes that are unrelated (no parent-child relationship)

  • In the shell

    • mkfifo
  • In the program

    • Open and use a fifo exactly like a file:
    1
    2
    3
    int fd = open (fifo_path, O_WRONLY);
    write (fd, data, data_length);
    close (fd);

/dev/urandom for random numbers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// an example of reading random numbers from /dev/urandom
// https://stackoverflow.com/questions/35726331/c-extracting-random-numbers-from-dev-urandom
#include <fstream>
#include <iostream>
int main(void) {

// open /dev/urandom to read
std::ifstream urandom("/dev/urandom");

// check that it did not fail
if (urandom.fail()) {
std::cerr << "Error: cannot open /dev/urandom\n";
return 1;
}

// read a random 8-bit value.
// Have to use read() method for low-level reading
char ch = 'a';
urandom.read(&ch, 1);
// cast to integer to see the numeric value of the character
std::cout << "Random character: " << (unsigned int)ch << "\n";

// read another 8-bit value
urandom.read(&ch, 1);
std::cout << "Random character: " << (unsigned int)ch << "\n";

// read a random unsigned int
unsigned int num = 42;
urandom.read((char *)&num, sizeof(int));
std::cout << "Random character: " << num << "\n";

// close random stream
urandom.close();
return 0;
}

Practical Usage of /dev/urandom:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
int getRandomInt(int lower, int upper)
{
int randInt;

// open /dev/urandom to read
std::ifstream urandom("/dev/urandom");
// check that it did not fail
if (urandom.fail())
{
std::cerr << "Error: cannot open /dev/urandom" << std::endl;
exit(1);
}

char ch = 'a';
urandom.read(&ch, 1);
randInt = (int)ch;

if (lower > upper)
{
std::cerr << "Error: getRandomInt() lower should not be > upper" << std::endl;
exit(1);
}
int range = upper - lower;
int numsBetweenRange = range + 1;

randInt = randInt % (numsBetweenRange);
//^If randInt is a negative number the moudle result change
// e.g. -85%3 = -1 but not 1
randInt = (randInt + numsBetweenRange) % numsBetweenRange; // Bring it back to positive
randInt = randInt + lower;
return randInt;
}

char getRandomChar()
{
char firstChar = 'a';
int randInt;
int totalChars = 26;
randInt = getRandomInt(0, totalChars-1);
char newChar = firstChar + randInt;
return newChar;
}

std::string getRandomWord(int length)
{
std::string word = "";
for(int i=0; i<length; i++)
{
word += getRandomChar();
}
return word;
}