Overview
Terminology
Parallelism ![]()
How long does it take for each process to finish? How about all processes?
There are several states in which a process can be. They are mutually-exclusive, so a process can only be in exactly one of these states at any given time:
State Transitions![]()
Utilities like psps, pstree, top, htop (on Linux, with GUIs ksysguard, gnome-system-monitor), Task ManagerTask Manager and Process Explorer (on Windows), and Activity Monitor (on macOS) can give you lots of detailed information about all of the processes on a computer.
From the ps man page on Linux:
Sample output running: ps aux in a virtual machine.PROCESS STATE CODES Here are the different values that the s, stat and state output specifiers (header "STAT" or "S") will display to describe the state of a process. D Uninterruptible sleep (usually IO) R Running or runnable (on run queue) S Interruptible sleep (waiting for an event to complete) T Stopped, either by a job control signal or because it is being traced. W paging (not valid since the 2.6.xx kernel) X dead (should never be seen) Z Defunct ("zombie") process, terminated but not reaped by its parent. For BSD formats and when the stat keyword is used, additional characters may be displayed: < high-priority (not nice to other users) N low-priority (nice to other users) L has pages locked into memory (for real-time and custom IO) s is a session leader l is multi-threaded (using CLONE_THREAD, like NPTL pthreads do) + is in the foreground process group
Process Control Block (PCB)
Each process has a block of memory (typically a C struct) that contains all of the relevant information about a process. The PCB can contain a massive amount of information. Some of the info includes:
Linux task_struct
Linux task_struct (newer) (from kernel 5.4.0 sched.h)
The PCBs are kept on a linked-list structure that represents a queue for various devices (CPU, disk, etc.)
Operating System Concepts - 8th Edition Silberschatz, Galvin, Gagne ©2009
Process Scheduling
Process Creation
Process Creationfork Example A (forkA.c)
#include <stdio.h> /* printf */
#include <unistd.h> /* fork, getpid, getppid */
int main(void)
{
/* Print out process ID and parent's process ID */
printf("Starting... (PID: %i, PPID: %i)\n", getpid(), getppid());
/* Create a clone of ourself */
fork();
/* Print our PID and PPID */
printf("After fork. (PID: %i, PPID: %i)\n", getpid(), getppid());
return 0;
}
Output 1 Output 2 Output 3 Starting... (PID: 22561, PPID: 21955) After fork. (PID: 22561, PPID: 21955) After fork. (PID: 22562, PPID: 22561) Starting... (PID: 22633, PPID: 21955) After fork. (PID: 22633, PPID: 21955) After fork. (PID: 22634, PPID: 22633) Starting... (PID: 22638, PPID: 21955) After fork. (PID: 22638, PPID: 21955) After fork. (PID: 22639, PPID: 22638)
You can see that the first process has a parent PID of 21955 (the shell) and the second process has a parent PID of 22561, which is the PID of the first process. To get the PID of the BASH shell, type:
from the command line. Here's a snippet from the output of pstree from the third run of the program (including the PIDs):echo $BASHPID
init─┬─ModemManager───2*[{ModemManager}] ├─NetworkManager─┬─dhclient │ ├─dnsmasq │ └─3*[{NetworkManager}] ├─acpid ├─bluetoothd ├─master─┬─pickup │ └─qmgr ├─mdadm ├─mdm───mdm─┬─Xorg │ └─init─┬─VBoxSVC───15*[{VBoxSVC}] │ ├─VBoxXPCOMIPCD │ ├─dbus-daemon │ ├─kalarm───{kalarm} │ ├─kdeconnectd───{kdeconnectd} │ ├─kded4───5*[{kded4}] │ ├─kdeinit4─┬─/usr/bin/termin─┬─6*[bash] │ │ │ ├─bash───bash───yapp.exe───3*[{yapp.exe}] │ │ │ ├─bash(21955)───forkA(22638)───forkA(22639) │ │ │ ├─bash───pstree │ │ │ ├─bash───ssh │ │ │ ├─gnome-pty-helpe │ │ │ └─3*[{/usr/bin/termin}] │ │ ├─3*[/usr/bin/termin─┬─bash] │ │ │ ├─gnome-pty-helpe] │ │ │ └─3*[{/usr/bin/termin}]] │ │ ├─/usr/bin/termin─┬─3*[bash] │ │ │ ├─bash───bash───ssh │ │ │ ├─gnome-pty-helpe │ │ │ └─3*[{/usr/bin/termin}]
This briefly describes what the processes are: init → mdm → mdm → init → kdeinit4 → terminator → bash → forkA → forkA
Of course forking (cloning) a process alone is not very useful. We typically want the newly-created process (the child) to do something different from the original process (the parent), as the process hierarchy above shows.
Process Description Purpose init Upstart process management daemon boot mdm The MDM display manager login screen kdeinit4 KDE process launcher desktop terminator Mulitple GNOME terminals graphical terminal bash GNU Bourne-Again shell shell forkA Our program our program
A second fork Example (fork.c) This shows how we can detect which process is the parent (original) and which is the child (clone):
Actual Code | Child (conceptually) | Parent (conceptually) | ||
---|---|---|---|---|
|
/* child process is always 0 */ if (pid == 0) { for (i = 0; i < 10; i++) { printf("child process: %i\n",i); sleep(1); } printf("child exiting\n"); exit(0); } /* parent is non-zero (child's pid) */ else { printf("child pid = %i\n", pid); printf("waiting for child\n"); wait(NULL) printf("child terminated\n"); } printf("parent exiting\n"); return 0; } |
/* child process is always 0 */ if (pid == 0) { for (i = 0; i < 10; i++) { printf( |
Running the program 3 times. The child's output is in red:
Output 1 Output 2 Output 3 parent pid = 464 child pid = 3688 child process: 0 waiting for child child process: 1 child process: 2 child process: 3 child process: 4 child process: 5 child process: 6 child process: 7 child process: 8 child process: 9 child exiting child terminated parent exiting parent pid = 3936 child pid = 2756 waiting for child child process: 0 child process: 1 child process: 2 child process: 3 child process: 4 child process: 5 child process: 6 child process: 7 child process: 8 child process: 9 child exiting child terminated parent exiting parent pid = 2180 child pid = 212 waiting for child child process: 0 child process: 1 child process: 2 child process: 3 child process: 4 child process: 5 child process: 6 child process: 7 child process: 8 child process: 9 child exiting child terminated parent exiting
In normal, production code (read: homework assignments), you will want to handle failures (which are not uncommon):
pid = fork();
if (pid == 0) /* child process is always 0 */
{
/* do child stuff */
}
else if (pid > 0) /* parent process is non-zero (child's pid) */
{
/* do parent stuff */
}
else /* fork failed, pid is -1 */
{
/* handle error */
}
#include <stdio.h> /* printf */
#include <stdlib.h> /* exit */
#include <string.h> /* strcpy */
#include <unistd.h> /* fork, getpid, sleep */
#include <sys/wait.h> /* wait */
int main(void)
{
int pid;
char buffer[100] = "Shared data";
pid = getpid();
printf("parent: pid = %i\n", pid);
pid = fork();
if (pid == 0) /* child process is always 0 */
{
printf("child: buffer is %s\n", buffer);
strcpy(buffer, "Child data");
printf("child: buffer is %s\n", buffer);
printf("child: child exiting\n");
exit(0);
}
else /* parent process is non-zero (child's pid) */
{
sleep(1);
printf("parent: child pid = %i\n", pid);
printf("parent: waiting for child\n");
wait(NULL);
printf("parent: child terminated\n");
printf("parent: buffer is %s\n", buffer);
}
printf("parent: parent exiting\n");
return 0;
}
Output:parent: pid = 1396 child: buffer is Shared data child: buffer is Child data child: child exiting parent: child pid = 936 parent: waiting for child parent: child terminated parent: buffer is Shared data parent: parent exiting
#include <stdio.h> /* printf */
#include <stdlib.h> /* exit */
#include <unistd.h> /* fork, getpid */
#include <sys/wait.h> /* wait */
int main(void)
{
int pid;
pid = fork();
if (pid == 0) /* child */
{
exit(123); /* same as return 123; */
}
else /* parent */
{
int status;
wait(&status);
if (WIFEXITED(status))
{
int code = WEXITSTATUS(status); /* extract 8-bit exit code */
printf("child terminated with value %i\n", code);
}
}
return 0;
}
Output:Note that the only values you can return to the parent this way are 0 to 255 (an unsigned char) and you must use the macro WIFEXITED to check first.child terminated with value 123
/* DO NOT compile with optimizations enabled! */
#include <stdio.h> /* printf, stdout */
#include <stdlib.h> /* exit */
#include <unistd.h> /* fork, getpid */
#include <sys/wait.h> /* wait */
int main(void)
{
int pid;
pid = fork();
if (pid == 0) /* child */
{
int x;
printf("child pid: %i\n", getpid());
sleep(10); /* give user a chance to send a signal */
/* Uncomment one of these to see what happens */
//x = 3 / pid; /* SIGFPE, divide by zero */
//abort(); /* SIGABRT, abort the program */
//x = *(int *)0xFFFFFFF; /* SIGSEGV, dereference arbitrary address */
exit(123); /* normal exit, code retrieved by parent */
}
else /* parent */
{
int exit_code;
int status;
wait(&status);
printf("child terminated with value %08X, %i\n", status, status); /* raw value */
exit_code = WEXITSTATUS(status); /* extract exit code */
if (WIFEXITED(status)) /* normal exit */
printf("child terminated normally with exit code %i\n", exit_code);
else /* abnormal exit */
printf("child terminated abnormally, code: %i\n", exit_code);
}
return 0;
}
Output: (normal exit)
Output: (divide by zero)child pid: 1328 child terminated with value 00007B00, 31488 child terminated normally with exit code 123
This value is the signal that was sent to the application:child pid: 1535 child terminated with value 00000008, 8 child terminated abnormally, code: 0
Send some other signal to the child while it's sleeping and see the result.1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
Why does it say Don't compile with optimizations?
#include <stdio.h> /* printf */
#include <stdlib.h> /* exit */
#include <unistd.h> /* fork, getpid, sleep */
#include <sys/wait.h> /* wait, waitpid */
/* Doesn't return */
void dochild(int count)
{
int pid = getpid();
printf("[%i] child process\n", pid);
sleep(count);
printf("[%i] child exiting\n", pid);
exit(count * 10);
}
int main(void)
{
int i, cpid[3];
int ppid = getpid();
printf("parent pid = %i\n", ppid);
cpid[0] = fork();
if (cpid[0] == 0) /* child process is always 0 */
dochild(1);
cpid[1] = fork();
if (cpid[1] == 0) /* child process is always 0 */
dochild(2);
cpid[2] = fork();
if (cpid[2] == 0) /* child process is always 0 */
dochild(3);
/* parent process is non-zero (child's pid) */
printf("waiting for children\n");
for (i = 0; i < 3; i++)
{
int status;
printf("waiting for child pid: %i\n", cpid[i]);
waitpid(cpid[i], &status, 0);
if (WIFEXITED(status))
printf("[%i] child ended normally: %i\n", cpid[i], WEXITSTATUS(status));
else
printf("[%i] child ended abnormally\n", cpid[i]);
}
printf("children terminated\n");
printf("parent exiting\n");
return 0;
}
Output 1 Output 2 Output 3 parent pid = 1736 [2496] child process [3640] child process [2176] child process waiting for children waiting for child pid: 2496 [2496] child exiting [2496] child ended normally: 10 waiting for child pid: 3640 [3640] child exiting [3640] child ended normally: 20 waiting for child pid: 2176 [2176] child exiting [2176] child ended normally: 30 children terminated parent exiting parent pid = 3364 [1308] child process [2240] child process waiting for children waiting for child pid: 1308 [2364] child process [1308] child exiting [1308] child ended normally: 10 waiting for child pid: 2240 [2240] child exiting [2240] child ended normally: 20 waiting for child pid: 2364 [2364] child exiting [2364] child ended normally: 30 children terminated parent exiting parent pid = 3188 [2420] child process [3620] child process waiting for children [2084] child process waiting for child pid: 2420 [2420] child exiting [2420] child ended normally: 10 waiting for child pid: 3620 [3620] child exiting [3620] child ended normally: 20 waiting for child pid: 2084 [2084] child exiting [2084] child ended normally: 30 children terminated parent exiting
child ended normally: 10 child ended normally: 20 child ended normally: 30
if (count == 3) abort();
Setting it to a non-zero value will tell the scheduler to favor the child over the parent.cat /proc/sys/kernel/sched_child_runs_first
#include <stdio.h> /* fprintf, stdout */
#include <stdlib.h> /* exit */
#include <unistd.h> /* fork, getpid */
#include <sys/wait.h> /* wait, waitpid */
void dochild(int count)
{
int pid = getpid();
fprintf(stdout,"[%i] child process\n", pid);
fprintf(stdout, "[%i] child exiting\n", pid);
exit(count * 10);
}
int main(void)
{
int i, cpid[3];
int ppid = getpid();
fprintf(stdout, "parent pid = %d\n", ppid);
cpid[0] = fork();
if (cpid[0] == 0) /* child process is always 0 */
dochild(1);
cpid[1] = fork();
if (cpid[1] == 0) /* child process is always 0 */
dochild(2);
cpid[2] = fork();
if (cpid[2] == 0) /* child process is always 0 */
dochild(3);
/* parent process is non-zero (child's pid) */
fprintf(stdout, "waiting for children\n");
for (i = 0; i < 3; i++)
{
int status, pid;
fprintf(stdout, "waiting for child pid: %d\n", cpid[i]);
/* Wait for any child to finish */
pid = waitpid(-1, &status, 0);
if (WIFEXITED(status))
fprintf(stdout, "[%i] child ended normally: %i\n", pid, WEXITSTATUS(status));
else
fprintf(stdout, "[%i] child ended abnormally, status: %i\n", pid, status);
}
fprintf(stdout, "children terminated\n");
fprintf(stdout, "parent exiting\n");
return 0;
}
Output 1 Output 2 Output 3 parent pid = 24634 [24635] child process [24635] child exiting waiting for children waiting for child pid: 24635 [24637] child process [24636] child process [24637] child exiting [24635] child ended normally: 10 [24636] child exiting waiting for child pid: 24636 [24636] child ended normally: 20 waiting for child pid: 24637 [24637] child ended normally: 30 children terminated parent exiting parent pid = 24700 waiting for children [24701] child process waiting for child pid: 24701 [24702] child process [24703] child process [24703] child exiting [24701] child exiting [24702] child exiting [24701] child ended normally: 10 waiting for child pid: 24702 [24703] child ended normally: 30 waiting for child pid: 24703 [24702] child ended normally: 20 children terminated parent exiting parent pid = 24748 [24749] child process waiting for children [24750] child process [24750] child exiting [24749] child exiting waiting for child pid: 24749 [24750] child ended normally: 20 waiting for child pid: 24750 [24751] child process [24751] child exiting [24749] child ended normally: 10 waiting for child pid: 24751 [24751] child ended normally: 30 children terminated parent exiting
Forking, Buffering, and Redirection
There are two major types of buffering: line buffering and full buffering. stdout is line-buffered, which means that when a newline is encountered, all of the bytes up to and including the newline are then written to stdout. The implications of this are, for example, if you were writing output using printf and your program crashed before printf encountered the newline, some bytes may never have made it to the screen.
This behavior may lead to some surprises when a parent and child process are both writing buffered output to stdout. Some examples will demonstrate.
Here is a very simple example. (fork-redirect.c)
#include <stdio.h> /* printf */
#include <stdlib.h> /* exit */
#include <unistd.h> /* fork, getpid */
#include <sys/wait.h> /* wait */
int main(void)
{
int pid;
/* The text will be sent to the output when the newline is encountered. */
printf("This should only print once. (%i)\n", getpid());
pid = fork();
if(pid == 0)
{
printf("This is the child (pid: %i).\n", getpid());
exit(123);
}
else if (pid > 0)
{
wait(NULL);
printf("This is the parent (pid: %i).\n", getpid());
}
else
printf("Fork failed.\n");
return 0;
}
This is the output (as expected):
Now, let's remove the newline from the first print statement and see what the output looks like. We're changing this:This should only print once. (9206) This is the child (pid: 9207). This is the parent (pid: 9206).
to this:/* The text will be sent to the output when the newline is encountered. */ printf("This should only print once. (%i)\n", getpid());
Running the program now shows this output:/* There is no newline, so the text won't be sent until the buffer is full. */ printf("This should only print once. (%i)", getpid());
The first thing to notice is that there is no newline after the first line prints. This is expected, of course, since we removed it. However, you'll notice that the first line is now printed twice. One line is printed in the parent and the other is printed in the child.This should only print once. (9436)This is the child (pid: 9437). This should only print once. (9436)This is the parent (pid: 9436).
Why is that?
This is the result of line-buffered output:
The result is that the first line is seen twice. Steps 4 and 5 could be swapped if you have a situation where the parent code happened to run before the child code did.
So, how do we "fix" that?
One solution is to make sure to flush the buffer before calling fork. This ensures that the contents are sent to the output before creating the child process. Placing this code after the first printf and before the fork:
Now, we see this as the output:fflush(stdout);
There is still no newline after the first line (expected), but the line is only sent once to the output.This should only print once. (9817)This is the child (pid: 9818). This is the parent (pid: 9817).
Another solution is to turn off buffering for stdout using setvbuf:
Add this line of code before calling printf:int setvbuf (FILE *stream, char *buf, int mode, size_t size);
The third parameter is the intesting one and can be one of three:setvbuf(stdout, NULL, _IONBF, 0);
The example above shows how this "problem" is the result of not printing a new line. However, there is still a problem when you do print the newline. It occurs when you redirect the output to a file.
Suppose we put the newline back into the printf statement:
We saw that this no longer caused the text to be printed twice (once in the parent and once in the child). However, if you were to run the program and redirect the output to file:/* The text will be sent to the output when the newline is encountered. */ printf("This should only print once. (%i)\n", getpid());
this is what you would see in the file:./fork-redirect > out.txt
We're back to the same problem we had without the newline. So, why is this?This should only print once. (18897) This is the child (pid: 18899). This should only print once. (18897) This is the parent (pid: 18897).
It turns out that, when you redirect stdout, the OS (or shell) is setting up a pipe. (Much like the pipes you've been using at the command line). Instead of being line-buffered, the pipe is fully-buffered, which means that the output is only sent to stdout when the buffer is full. It doesn't matter if there are newlines in the text or not.
The solution is the same as before. Either you can flush the buffer before the fork, or you can set stdout back to line-buffered (or even no buffering). Either of these will do the trick:
orsetvbuf(stdout, NULL, _IOLBF, 0); /* line-buffered */
setvbuf(stdout, NULL, _IONBF, 0); /* no buffering */
The recommended approach is to just use fflush when you need to make sure the buffer is sent to the output. It's easier to use and understand (most students and beginners have never heard of setvbuf). It's also more efficient because it only affects the last printf statement, not every printf statement.
The exec Function
int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ...); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[], char *const envp[]);
execl (variadic list) | execv (array) | |
---|---|---|
|
#include <stdio.h> /* printf */ #include <stdlib.h> /* exit */ #include <unistd.h> /* fork, getpid */ #include <sys/wait.h> /* wait */ int main(void) { int pid; pid = fork(); if (pid == 0) /* child */ { char *args[] = {"geany", "execl.c", "execv.c", NULL}; printf("child: executing a program...\n"); execv("/usr/bin/geany", args); printf("child: if you see this, the exec failed\n"); perror("geany"); exit(10); /* arbitrary exit code */ } else /* parent */ { int code, status; printf("parent: waiting for child to terminate\n"); wait(&status); code = WEXITSTATUS(status); printf("parent: child terminated with value %i\n", code); } return 0; } |
Output:parent: waiting for child to terminate child: executing a program... parent: child terminated with value 0
VERY IMPORTANT!
It's important to realize the distinction between the program being executed ("/usr/bin/geany") and argv[0] ("geany"). The convention is that argv[0] match the name of the executable without the path.
- argv[0] is "geany"
- argv[1] is "execl.c"
- argv[2] is "execv.c"
Changing /usr/bin/geany to /usr/bin/foobar:
Output:parent: waiting for child to terminate child: executing a program... child: if you see this, the exec failed parent: child terminated with value 10
This is the child. It simply prints out its PID and any command line arguments:
And this code exec's the child using both execl and execv:#include <stdio.h> /* printf */ #include <unistd.h> /* getpid */ int main(int argc, char **argv) { int i; printf("[%i] new child\n", getpid()); for (i = 0; i < argc; i++) printf("arg[%i] is %s\n", i, argv[i]); return 123; }
execl (variadic list) | execv (array) | |
---|---|---|
#include <stdio.h> /* printf */ #include <stdlib.h> /* exit */ #include <unistd.h> /* fork, getpid, exec */ #include <sys/wait.h> /* wait */ int main(void) { int pid; pid = fork(); if (pid == 0) /* child */ { printf("child: executing a program via execl ...\n"); /* program 0 1 2 3 4 */ execl("./child", "child", "one", "two", "3", "4 and 5", NULL); printf("child: if you see this, the exec failed\n"); perror("child"); exit(10); /* arbitrary exit code */ } else /* parent */ { int code, status; printf("parent: waiting for child to terminate\n"); wait(&status); code = WEXITSTATUS(status); printf("parent: child terminated with value: %i\n", code); } return 0; } |
#include <stdio.h> /* printf */ #include <stdlib.h> /* exit */ #include <unistd.h> /* fork, getpid */ #include <sys/wait.h> /* wait */ int main(void) { int pid; pid = fork(); if (pid == 0) /* child */ { /* 0 1 2 3 4 */ char *args[] = {"child", "one", "two", "3", "4 and 5", NULL}; printf("child: executing a program via execv ...\n"); execv("./child", args); printf("child: if you see this, the exec failed\n"); perror("child"); exit(10); /* arbitrary exit code */ } else /* parent */ { int code, status; printf("parent: waiting for child to terminate\n"); wait(&status); code = WEXITSTATUS(status); printf("parent: child terminated with value: %i\n", code); } return 0; } |
prints out:perror("foobar");
foobar: No such file or directory
Process Termination
After exit(), the exit status must be transmitted to the parent process. There are three cases. If the parent has set SA_NOCLDWAIT, or has set the SIGCHLD handler to SIG_IGN, the status is discarded. If the parent was waiting on the child it is notified of the exit status. In both cases the exiting process dies immediately. If the parent has not indicated that it is not interested in the exit status, but is not waiting, the exiting process turns into a "zombie" process (which is nothing but a container for the single byte representing the exit status) so that the parent can learn the exit status when it later calls one of the wait() functions.
/home/chico/bin/wd 4000 4000 valgrind $(VALGRIND_OPTIONS) 2>> diff.txt ./$(PRG) | /home/chico/bin/throttle 3000 -we > /dev/null
#include <stdio.h> /* printf, perror */
#include <stdlib.h> /* exit */
#include <unistd.h> /* fork, getpid, sleep */
#include <sys/wait.h> /* wait */
int main(void)
{
int i, pid;
pid = fork();
if (pid == 0) /* child process is always 0 */
{
printf("child: PID: %i, parent PID: %i\n", getpid(), getppid());
printf("child: exiting...\n");
sleep(3);
exit(0);
}
else if (pid > 0) /* parent process is non-zero (child's pid) */
{
sleep(10); /* The child will be a zombie for 7 seconds */
wait(NULL);
printf("parent: PID: %i, waiting for child pid: %d\n", getpid(), pid);
printf("parent: child terminated\n");
sleep(5);
}
else /* fork failed */
{
perror("fork failed");
return 1;
}
printf("parent: exiting\n");
return 0;
}
Interprocess Communication (IPC)
We're going to look at 4 methods of interprocess communication:
In order to keep these examples simple and understandable, most error handling has been removed. In a real-world program (like the homework assignments), failing to check the return values of these IPC functions can result in a lot of debugging because something failed. Also, failing to check the return values will cause you to receive a lower grade on your assignments.
#include <stdio.h> /* printf */
#include <stdlib.h> /* exit */
#include <string.h> /* strcpy */
#include <unistd.h> /* sleep, fork */
#include <sys/shm.h> /* shmget, shmat, shmdt, shmctl */
#include <sys/wait.h> /* wait */
int main(void)
{
int pid;
int shmid; /* return value from fork/shmget */
char *buffer; /* shared buffer */
key_t key = 123; /* arbitrary key (0x7b) */
shmid = shmget(key, 1024, 0600 | IPC_CREAT);
buffer = (char *) shmat(shmid, NULL, 0);
strcpy(buffer,"");
pid = fork();
if (pid == 0) /* child */
{
printf("child: putting message in buffer\n");
strcpy(buffer, "There's a fine line between clever and stupid.");
shmdt(buffer); /* detach memory from child process */
printf("child: sleeping for 5 seconds...\n");
sleep(5);
printf("child: exiting\n");
exit(0);
}
else /* parent */
{
printf("parent: waiting for child to exit...\n");
wait(NULL);
printf("parent: message from child is %s\n", buffer);
shmdt(buffer); /* detach memory from parent process */
shmctl(shmid, IPC_RMID, 0); /* delete memory block */
printf("parent: exiting\n");
}
return 0;
}
Output:parent: waiting for child to exit... child: putting message in buffer child: sleeping for 5 seconds... child: exiting parent: message from child is There's a fine line between clever and stupid. parent: exiting
This is especially true if you create the memory exclusively:Note: If your program doesn't release the shared memory (e.g. due to it crashing), you must remove it manually or subsequent attempts to create the shared memory may fail.
In any event, make sure you are checking the return value from shmget so that if the call fails, you won't waste time wondering why your program is not working.shmid = shmget(key, 1024, 0600 | IPC_CREAT | IPC_EXCL);
which impiles IPC_CREAT and IPC_EXCL. You can also generate (almost) unique keys using ftok.shmid = shmget(IPC_PRIVATE, 1024, 0600);
This code is the parent process and this code is for the child processes.
malloc | shared memory | |
---|---|---|
#include <stdio.h> /* perror */ #include <stdlib.h> /* malloc, free, exit */ void f1(void) { char *buffer; /* non-shared buffer */ /* Allocate memory */ buffer = malloc(1024); if (!buffer) { perror("malloc"); exit(3); } /* Use the memory as you normally would */ free(buffer); /* Free the memory block */ } |
#include <stdio.h> /* perror */ #include <stdlib.h> /* exit */ #include <sys/shm.h> /* shmget, shmat, shmdt, shmctl */ void f2(void) { char *buffer; /* shared buffer */ int shmid; /* return value from shmget */ key_t key = 123; /* arbitrary key (0x7b) */ /* Allocate memory */ shmid = shmget(key, 1024, 0600 | IPC_CREAT); if (shmid == -1) { perror("shmget"); exit(1); } /* Attach to it */ buffer = (char *) shmat(shmid, NULL, 0); if (buffer == (char *)-1) { perror("shmat"); shmctl(shmid, IPC_RMID, 0); /* free memory block */ exit(2); } /* Use the memory as you normally would */ shmdt(buffer); /* Detach from the memory */ shmctl(shmid, IPC_RMID, 0); /* Free the memory block */ } |
Shown again without any error handling:
malloc | shared memory | |
---|---|---|
void f1(void) { char *buffer; /* non-shared buffer */ buffer = malloc(1024); /* Allocate memory */ /* Use the memory as you normally would */ free(buffer); /* Free the memory block */ } |
void f2(void) { char *buffer; /* shared buffer */ int shmid; /* return value from shmget */ key_t key = 123; /* arbitrary key (0x7b) */ shmid = shmget(key, 1024, 0600 | IPC_CREAT); /* Allocate memory */ buffer = (char *) shmat(shmid, NULL, 0); /* Attach to it */ /* Use the memory as you normally would */ shmdt(buffer); /* Detach from the memory */ shmctl(shmid, IPC_RMID, 0); /* Free the memory block */ } |
#include <stdio.h> /* printf */
#include <stdlib.h> /* exit */
#include <string.h> /* strcpy */
#include <unistd.h> /* fork */
#include <sys/msg.h> /* msgget, msgsnd, msgrcv, msgctl */
#include <sys/wait.h> /* wait */
#define BUFSIZE 1024
#define MSG_STRUCT 1
/* Our user-defined structure */
typedef struct
{
long int type; /* must be long int */
char buffer[BUFSIZE]; /* can be anything */
}msg_struct;
int main(void)
{
int pid;
int queue_id; /* shared id */
msg_struct msg; /* our message */
key_t key = 123; /* arbitrary key */
queue_id = msgget(key, 0600 | IPC_CREAT);
pid = fork();
if (pid == 0) /* child */
{
printf("child: sending messages\n");
msg.type = MSG_STRUCT;
strcpy(msg.buffer, "This is message number one...");
msgsnd(queue_id, &msg, BUFSIZE, 0);
sleep(5);
strcpy(msg.buffer, "This is message number two...");
msgsnd(queue_id, &msg, BUFSIZE, 0);
sleep(5);
printf("child: exiting\n");
exit(0);
}
else /* parent */
{
/*sleep(2);*/
printf("parent: waiting on child\n");
wait(NULL);
printf("parent: receiving messages\n");
while (msgrcv(queue_id, &msg, BUFSIZE, 0, IPC_NOWAIT) != -1)
{
if (msg.type == MSG_STRUCT)
printf("message: %s\n", msg.buffer);
else
printf("unknown message\n");
}
msgctl(queue_id, IPC_RMID, NULL);
printf("parent: exiting\n");
}
return 0;
}
Output:parent: waiting on child child: sending messages child: exiting parent: receiving messages message: This is message number one... message: This is message number two... parent: exiting
Shared data: (in a header file, client-server-msg.h)
Process #1 code (Sending process, msg-client.c)#define BUFSIZE 1024 #define MSG_STRUCT 1 #define MSG_QUIT 2 #define SHARED_KEY 12345 /* 0x3039 */ typedef struct { long int type; /* must be long int */ char buffer[BUFSIZE]; /* can be anything */ }msg_struct;
#include <stdio.h> /* printf, sprintf */
#include <unistd.h> /* sleep */
#include <sys/msg.h> /* msgget, msgsnd, msgctl */
#include "client-server-msg.h" /* shared data */
int main(void)
{
int i;
key_t key = SHARED_KEY;
msg_struct msg;
int queue_id = msgget(key, 0600 | IPC_CREAT);
msg.type = MSG_STRUCT;
for (i = 0; i < 10; i++)
{
printf("client writing: %i\n", i);
sprintf(msg.buffer, "Hello %i", i);
msgsnd(queue_id, &msg, BUFSIZE, 0);
sleep(1);
}
printf("client writing end message\n");
msg.type = MSG_QUIT;
msgsnd(queue_id, &msg, BUFSIZE, 0);
return 0;
}
Process #2 code (Receiving process, msg-server.c)
#include <stdio.h> /* printf */
#include <sys/msg.h> /* msgget, msgrcv, msgctl */
#include "client-server-msg.h" /* shared data */
int main(void)
{
msg_struct msg;
key_t key = SHARED_KEY;
int queue_id = msgget(key, 0600 | IPC_CREAT);
int done = 0;
while(!done)
{
/* If no message is waiting, msgrcv returns -1 */
while (msgrcv(queue_id, &msg, BUFSIZE, 0, 0 /*IPC_NOWAIT*/) != -1)
{
if (msg.type == MSG_STRUCT)
printf("message: %s\n", msg.buffer);
else if ( msg.type == MSG_QUIT ) /* Remove if you want the server */
{ /* to run indefinitely */
done = 1;
break;
}
else
printf("unknown message\n");
}
}
printf("server shutting down\n");
msgctl(queue_id, IPC_RMID, NULL);
return 0;
}
Sending process Receiving process client writing: 0 client writing: 1 client writing: 2 client writing: 3 client writing: 4 client writing: 5 client writing: 6 client writing: 7 client writing: 8 client writing: 9 client writing end message message: Hello 0 message: Hello 1 message: Hello 2 message: Hello 3 message: Hello 4 message: Hello 5 message: Hello 6 message: Hello 7 message: Hello 8 message: Hello 9 server shutting down
can be pictured like this (from The Linux Programming Interface book)ls | wc
#include <stdio.h> /* printf, fgets */
#include <stdlib.h> /* exit */
#include <string.h> /* strlen */
#include <unistd.h> /* fork, pipe, read, write, close */
#include <sys/wait.h> /* wait */
int main(void)
{
int pid;
char buffer[1024];
int fd[2];
pipe(fd); /* fd[0] is for read, fd[1] is for write */
pid = fork();
if (pid == 0) /* child */
{
int count;
close(fd[0]); /* close unused end (read), child will write */
/* prompt user for input */
printf("input: ");
fgets(buffer, sizeof(buffer), stdin);
printf("child: message is %s", buffer);
/* write to the pipe (include NUL terminator!) */
count = write(fd[1], buffer, strlen(buffer) + 1);
printf("child: wrote %i bytes\n", count);
exit(0);
}
else /* parent */
{
int count;
close(fd[1]); /* close unused end (write), parent will read */
/* read from the pipe */
count = read(fd[0], buffer, sizeof(buffer));
printf("parent: message is %s", buffer);
printf("parent: read %i bytes\n", count);
wait(NULL); /* reap the child */
}
return 0;
}
Output: (User types 22 characters)input: This is from teh user! child: message is This is from teh user! child: wrote 24 bytes parent: message is This is from teh user! parent: read 24 bytesOutput:input: 12345 child: message is 12345 child: wrote 7 bytes parent: message is 12345 parent: read 7 bytes
Self check: Programming Problem 3.18 from the suggested textbook.
"Design a program using ordinary pipes in which one process sends a string message to a second process, and the second process reverses the case of each character in the message and sends it back to the first process. For example, if the first process sends the message Hi There, the second process will return hI tHERE. This will require using two pipes, one for sending the original message from the first to the second process, and the other for sending the modified message from the second back to the first process."
#include <stdio.h> /* printf, fgets */
#include <stdlib.h> /* exit */
#include <string.h> /* strlen */
#include <ctype.h> /* isalpha, toupper */
#include <unistd.h> /* pipe, read, write, close */
#include <sys/wait.h> /* wait */
void revcase(char *buffer)
{
int i;
int len = strlen(buffer);
for (i = 0; i < len; i++)
{
if (isupper(buffer[i]))
buffer[i] = tolower(buffer[i]);
else if (islower(buffer[i]))
buffer[i] = toupper(buffer[i]);
}
}
int main(void)
{
int pid;
/* setup stuff */
pid = fork();
if (pid == 0) /* child */
{
/* DO STUFF */
exit(0);
}
else /* parent */
{
/* DO STUFF */
wait(NULL);
}
return 0;
}
Using popen for pipes
If you just need to setup a pipe between two processes, there is a function called popen which makes things easier. It basically performs the fork, exec, and pipe stuff for you. Since many programs just need this kind of behavior, it can be a real convenience.This sample code simply prints out the strings: one two three four five six seven to the screen (stdout): (popen0.c)
#include <stdio.h> /* printf */
int main(void)
{
int i;
char *array[] = {"one", "two", "three", "four", "five", "six", "seven"};
int size = sizeof(array) / sizeof(*array);
/* Print to stdout */
for(i = 0; i < size; i++)
printf("%s\n", array[i]);
return 0;
}
Output:
If we wanted the output sorted, we would pipe the output to the standard sort program that is available on all POSIX systems using the pipe symbol:one two three four five six seven
Output:./popen0 | sort
But, suppose we wanted to sort the data within our program and not require the user to have to do the piping on the command line? That's where the popen function comes in handy.five four one seven six three two
This example shows how you can use the sort program to sort your data from within your program. So, instead of using printf to print to stdout, we're using fprintf to print to the sort process! (popen1.c)
/* compile with -D_BSD_SOURCE if using -ansi */
#include <stdio.h> /* fprintf, popen, pclose, perror */
int main(void)
{
int i;
FILE *pfp;
char *array[] = {"one", "two", "three", "four", "five", "six", "seven"};
int size = sizeof(array) / sizeof(*array);
/* Create write-only pipe (i.e. open program for writing) */
pfp = popen("sort", "w");
if (!pfp)
{
perror("popen");
return 1;
}
/* Print to pipe (write to sort process) */
for(i = 0; i < size; i++)
fprintf(pfp, "%s\n", array[i]);
/* Close the pipe */
pclose(pfp);
return 0;
}
Output:
five four one seven six three two
Diagram from The Linux Programming Interface book: ![]()
This example shows how you can setup a pipe within your C program just as if you were using the command line: (popen2.c) This is equivalent to the command line: ls /usr/bin | sort -r
/* compile with -D_BSD_SOURCE if using -ansi */
#include <stdio.h> /* popen, perror, fprintf, pclose, fgets */
#define BUFSIZE 100
int main(void)
{
FILE *inpipe, *outpipe;
char buffer[BUFSIZE];
/* read pipe from ls (i.e. open ls program for reading) */
inpipe = popen("ls /usr/bin", "r");
if (!inpipe)
{
perror("popen read:");
return 1;
}
/* write pipe to sort (i.e. open sort program for writing) */
outpipe = popen("sort -r", "w");
if (!outpipe)
{
perror("popen write:");
return 2;
}
/* read from ls and write to sort (reversed) */
/* it's this: ls /usr/bin | sort -r */
while(fgets(buffer, BUFSIZE, inpipe))
fprintf(outpipe, "%s", buffer);
/* clean up */
pclose(inpipe);
pclose(outpipe);
return 0;
}
Partial output:
On my system there are over 4,000 lines!zxpdf zsoelim zsh zrun zlib-flate zjsdecode zipsplit zipnote zipinfo zipgrep zipdetails zipcloak zip zim . . . aainfo aaflip aafire a5toa4 a5booklet a2ping a2p 7zr 7za 7z 2to3-3.4 2to3-2.7 2to3 [
Capturing output from the compiler: (popen3.c)
/* compile with -D_BSD_SOURCE if using -ansi */
#include <stdio.h> /* popen, perror, printf, pclose, fgets */
#define BUFSIZE 100
int main(void)
{
FILE *inpipe;
char buffer[BUFSIZE];
/* read pipe from gcc */
inpipe = popen("gcc foo.c", "r");
if (!inpipe)
{
perror("popen read:");
return 1;
}
/* Read from compiler and output to screen */
while(fgets(buffer, BUFSIZE, inpipe))
printf("%s", buffer);
/* clean up */
pclose(inpipe);
return 0;
}
This is foo.c:
Output:int main(void) { return; /* Missing return value */ }
With this knowledge, you can now you can start writing your own IDE (e.g. Visual Studio)!foo.c: In function 'main': foo.c:3:3: warning: 'return' with no value, in function returning non-void return; ^
Create your own IDE on Windows with child processes and pipes.
Win32 Process Creation
#include <iostream>
#include <windows.h>
int main(void)
{
STARTUPINFO start_info;
PROCESS _INFORMATION proc_info;
DWORD pid = GetCurrentProcessId();
std::cout << "parent pid = " << pid << std::endl;
// allocate memory and set to 0
ZeroMemory(&start_info, sizeof(STARTUPINFO));
ZeroMemory(&proc_info, sizeof(PROCESS_INFORMATION));
std::cout << "creating child process" << std::endl;
const char *program = "c:\\windows\\system32\\notepad.exe";
BOOL err = CreateProcess(program, // program to run
0, // command line
0, // security attributes
0, // thread attributes
FALSE, // don't inherit handles
0, // creation flags (none)
0, // use parent's environment
0, // use parent's directory
&start_info, // start up info
&proc_info // process info
);
if (!err)
{
std::cout << "Error creating process" << std::endl;
return -1;
}
std::cout << "waiting for child to terminate" << std::endl;
WaitForSingleObject(proc_info.hProcess, INFINITE);
std::cout << "parent terminating" << std::endl;
CloseHandle(proc_info.hProcess);
CloseHandle(proc_info.hThread);
return 0;
}
Creating Multiple Processes Example
#include <stdio.h>
#include <windows.h>
int main(void)
{
const int COUNT = 2;
HANDLE proc[COUNT], thread[COUNT];
const char *programs[] = {"c:\\windows\\system32\\notepad.exe",
"c:\\windows\\system32\\mspaint.exe",
};
for (int i = 0; i < COUNT; ++i)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
CreateProcess(programs[i], 0, 0, 0, FALSE, 0, 0, 0, &si, &pi);
proc[i] = pi.hProcess;
thread[i] = pi.hThread;
}
WaitForMultipleObjects(COUNT, proc, TRUE, INFINITE);
for (int i = 0; i < COUNT; ++i)
{
printf("Process: %i, Thread: %i ended.\n", proc[i], thread[i]);
CloseHandle(proc[i]);
CloseHandle(thread[i]);
}
return 0;
}