The fork()
system call
To create a new process, we can use the fork()
system call.
This creates a copy of the process the function has been called in (same file descriptors, and same running program), but with a different process id. It returns the pid of the child process (if called in the child, it returns 0).
The strange thing is the fact that for the OS, it looks like there are two copies of the same p1
program running (since it’s running in both the parent and the child), and both are about to return from the fork()
syscall. The child process doesn’t start running at main()
, but it comes to life like if it has called fork()
itself.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
printf("hello (pid:%d)\n", (int)getpid());
int rc = fork();
if (rc < 0) {
// fork failed
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { //if process id is 0, then we are in the child process
printf("child (pid:%d)\n", (int)getpid());
} else {
// If process is is not 0, then we are in the parent process
printf("parent of %d (pid:%d)\n", rc, (int)getpid());
}
return 0;
}
The output is also non-deterministic, meaning that sometimes the parent’s branch is executed first, and sometimes the child’s branch is executed first. The order is determined by the CPU Scheduler.
The wait()
system call
We use the wait()
system call when we want a process to be suspended. For example it can be useful after a fork()
if we want the parent process to wait for the child process to finish before continuing executing. This makes the execution deterministic (the child always executes before the parent).
The exec()
system call
In order to change the program executing in one process, we need to call the exec()
syscall. It’s as minimalistic as fork()
, leaving each detail of the process the same, and changing only the running program.
Here is an example on how we can call the program wc
word count on the child process after a fork()
:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
printf("hello (pid:%d)\n", (int)getpid());
int rc = fork();
if (rc < 0) { // fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { // child (new process)
printf("child (pid:%d)\n", (int)getpid());
char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc"
myargs[1] = strdup("cshell.c"); // arg: input file
myargs[2] = NULL; // mark end of array
execvp(myargs[0], myargs); // runs word count
printf("this shouldn’t print out");
} else { // parent goes down this path
int rc_wait = wait(NULL);
printf("parent of %d (rc_wait:%d) (pid:%d)\n", rc, rc_wait, (int)getpid());
}
return 0;
}
The output will be:
hello (pid:14188)
child (pid:14189)
26 108 823 cshell.c
parent of 14189 (rc_wait:14189) (pid:14188)
The
wc
command returns the lines, words and bytes found in the program passed as input.
The peculiarity of the exec()
system call is the fact that it does not create a new process, but it transforms the currently running program into a different running program.
Other syscalls
Other syscalls include:
read()
andwrite()
syscalls take in a file descriptorfd
as a parameter, which tells where to read and write to. Each process has a number of file descriptors, which are identifying numbers that refers to connections that are currently open to read and write operations. File descriptors can be obtained as a result of theopen()
function when a file is opened, or when a new Pipe is created.kill()
is used to stop the currently running process.
tags:#programming-languages/c resources: writing a custom shell in c : r/learnprogramming, pages.cs.wisc.edu/~remzi/OSTEP/cpu-api.pdf