Prev: the-abstraction-the-process Next: mechanism-limited-direct-execution
The Fork system call is for creating a new process. It creates a copy of its parent and then gets its own address space.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
("hello world (pid:%d)\n", (int) getpid());
printfint rc = fork();
if (rc < 0) { // fork failed; exit
(stderr, "fork failed\n");
fprintf(1);
exit} else if (rc == 0) { // child (new process)
("hello, I am child (pid:%d)\n", (int) getpid());
printf} else { // parent goes down this path (main)
("hello, I am parent of %d (pid:%d)\n",
printf, (int) getpid());
rc}
return 0;
}
This prints out:
prompt> ./p1
hello world (pid:29146)
hello, I am parent of 29147 (pid:29146)
hello, I am child (pid:29147)
prompt>
When calling fork, the parent gets the pid of the newly-created child, whereas the child receives a return code of 0, which allows you to handle the parent and child’s path.
What happens if we want either the parent or child to execute first? Add a wait() system call.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
("hello world (pid:%d)\n", (int) getpid());
printfint rc = fork();
if (rc < 0) { // fork failed; exit
(stderr, "fork failed\n");
fprintf(1);
exit} else if (rc == 0) { // child (new process)
("hello, I am child (pid:%d)\n", (int) getpid());
printf} else { // parent goes down this path (main)
int rc_wait = wait(NULL);
("hello, I am parent of %d (rc_wait:%d) (pid:%d)\n",
printf, rc_wait, (int) getpid());
rc}
return 0;
}
You can use exec()
to run a different program than the
parent process.
<stdio.h>
include #include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
("hello world (pid:%d)\n", (int) getpid());
printfint rc = fork();
if (rc < 0) { // fork failed; exit
(stderr, "fork failed\n");
fprintf(1);
exit} else if (rc == 0) { // child (new process)
("hello, I am child (pid:%d)\n", (int) getpid());
printfchar *myargs[3];
[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p3.c"); // argument: file to count
myargs[2] = NULL; // marks end of array
myargs(myargs[0], myargs); // runs word count
execvp("this shouldn’t print out");
printf} else { // parent goes down this path (main)
int rc_wait = wait(NULL);
("hello, I am parent of %d (rc_wait:%d) (pid:%d)\n",
printf, rc_wait, (int) getpid());
rc}
return 0;
}
This runs wc
in an odd way. Exec loads code and static
data from that executable and overwrites its current code segment with
that data. Then it runs that process, transforming itself into that
program.
With these design decisions, we can easily design a shell.
By separating fork and exec, you can easily create a shell that handles this command:
wc p3.c > newfile.txt
This is done by redirecting the output of the command
wc p3.c
to newfile.txt by closing stdout and opening
newfile.txt.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
int rc = fork();
if (rc < 0) { // fork failed; exit
(stderr, "fork failed\n");
fprintf(1);
exit} else if (rc == 0) { // child: redirect standard output to a file
(STDOUT_FILENO);
close("./p4.output", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);
open
// now exec "wc"...
char *myargs[3];
[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p4.c"); // argument: file to count
myargs[2] = NULL; // marks end of array
myargs(myargs[0], myargs); // runs word count
execvp} else { // parent goes down this path (main)
int rc_wait = wait(NULL);
}
return 0;
}
Besides fork, exec, and wait, there are other useful commands, like
kill
, which sends signals to a process. Ctrl-C sends a
SIGINT (interrupt) and Ctrl-Z sends a SIGTSTP (stop) command, pausing
the process to be rerun later with fg
.
A process can use the signal()
system call to catch
various signals, and react to those (e.g. cleanup on Ctrl-C).
Because having a process receive signals from multiple users would run into concurrency issues, we limit the users who can send signals (e.g. only root and the current user).
For example ps
shows which processes are running.
top
says which processes are using the most CPU and memory.
kill
and killall
can send signals to
processes.
Prev: the-abstraction-the-process Next: mechanism-limited-direct-execution