Learning_C
fork
fork
makes a copy of the clone of the current process, and its return value indicates which fork you're in at run time: 0
for the child fork and child_pid
if you are in the parent.
Interestingly, virtual addresses of variables stay the same, but since the process is cloned, the data between forks stays isolated:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
void wait_or_die() {
int rc = wait(NULL);
assert(rc > 0);
}
int fork_or_die() {
int rc = fork();
assert(rc >= 0);
return rc;
}
int main(int argc, char *argv[]) {
// process a
int *x = malloc(sizeof(int));
printf("BEFORE *x = 100:\n");
printf("pid: %d; &x: %p, *x: %d\n", getpid(), x, *x);
*x = 100;
printf("AFTER *x = 100:\n");
printf("pid: %d; &x: %p, *x: %d\n\n", getpid(), x, *x);
sleep(2);
if (fork_or_die() == 0) {
printf("BEFORE *x = 200:\n");
printf("pid: %d; &x: %p, *x: %d\n", getpid(), x, *x);
*x = 200;
printf("AFTER *x = 200:\n");
printf("pid: %d; &x: %p, *x: %d\n\n", getpid(), x, *x);
sleep(2);
// process b
exit(0);
}
printf("BEFORE *x = 300:\n");
printf("pid: %d; &x: %p, *x: %d\n", getpid(), x, *x);
*x = 300;
printf("AFTER *x = 300:\n");
printf("pid: %d; &x: %p, *x: %d\n\n", getpid(), x, *x);
wait_or_die();
printf("BEFORE *x = 400:\n");
printf("pid: %d; &x: %p, *x: %d\n", getpid(), x, *x);
*x = 400;
printf("AFTER *x = 400:\n");
printf("pid: %d; &x: %p, *x: %d\n\n", getpid(), x, *x);
return 0;
}
Results in:
BEFORE *x = 100:
pid: 15868; &x: 0x564b24d7c2a0, *x: 0
AFTER *x = 100:
pid: 15868; &x: 0x564b24d7c2a0, *x: 100
BEFORE *x = 300:
pid: 15868; &x: 0x564b24d7c2a0, *x: 100
AFTER *x = 300:
pid: 15868; &x: 0x564b24d7c2a0, *x: 300
BEFORE *x = 200:
pid: 15869; &x: 0x564b24d7c2a0, *x: 100
AFTER *x = 200:
pid: 15869; &x: 0x564b24d7c2a0, *x: 200
BEFORE *x = 400:
pid: 15868; &x: 0x564b24d7c2a0, *x: 300
AFTER *x = 400:
pid: 15868; &x: 0x564b24d7c2a0, *x: 400
(Note the *x
value before setting to 200 in the child is still 100 from before the fork.)
If a child process closes stdout, it can't print to it, but the parent still can.
exec
Calling exec
completely replaces the current process with a new, potentially different one. Combined with fork
this allows creation of arbitrary new processes.
wait
Calling wait
without a child process returns -1
.
pipe
The pipe sys call sets up a read descriptor and a write descriptor for two processes to communicate. This along with dup
can be used to simulate a shell pipe, where stdout from one child process goes to stdin of another:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
void wait_or_die() {
int rc = wait(NULL);
assert(rc > 0);
}
int fork_or_die() {
int rc = fork();
assert(rc >= 0);
return rc;
}
int main(int argc, char *argv[]) {
int pipefd[2];
if(pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
// first child writes to the pipe
if (fork_or_die() == 0) {
// close pipe read desc
close(pipefd[0]);
// close stdout
close(STDOUT_FILENO);
// replace stdout with the pipe write desc
dup(pipefd[1]);
printf("PRINT SOME STUFF\n");
exit(0);
}
// second child reads from the pipe
if (fork_or_die() == 0) {
// close pipe write desc
close(pipefd[1]);
// close stdin
close(STDIN_FILENO);
// replace stdin with the pipe read desc
dup(pipefd[0]);
char str[1000];
fgets(str, sizeof(str), stdin);
printf("Received: %s\n from stdin", str);
exit(0);
}
wait_or_die();
wait_or_die();
return 0;
}