fork() 系统调用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
printf("hello world (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("hello, I am child (pid:%d)\n", (int) getpid());
} else {
// parent goes down this path (original process)
printf("hello, I am parent of %d (pid:%d)\n",
rc, (int) getpid());
}
return 0;
}
➜ c5 gcc -o p1 p1.c -Wall
➜ c5 ./p1
hello world (pid:29943)
hello, I am parent of 29944 (pid:29943)
hello, I am child (pid:29944)
上面这个例子发生的事情:
- 主进程pid为29943,一开始输出了一个hello world
- 主进程调用fork()系统调用。新创建的进程和调用进程基本上完全一样。对OS来说,此时有几乎两个完全一样的p1进程在执行,且都从fork()函数返回。新进程为child,原进程为parent。child不会从main而是从fork()系统调用返回。
- parent从fork()返回的是child的pid,而child从fork()返回的是0.
wait()系统调用
有时候父进程需要等子进程执行完毕。可以调用wait()
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
printf("hello world (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("hello, I am child (pid:%d)\n", (int) getpid());
sleep(1);
} else {
// parent goes down this path (original process)
int wc = wait(NULL);
printf("hello, I am parent of %d (wc:%d) (pid:%d)\n",
rc, wc, (int) getpid());
}
return 0;
}
➜ c5 ./p2
hello world (pid:2070)
hello, I am child (pid:2071)
hello, I am parent of 2071 (wc:2071) (pid:2070)
父进程调用wait(),延迟自己执行直至子进程执行完毕。
wait (NULL)作用?
https://stackoverflow.com/questions/42426816/how-does-waitnull-exactly-work
wait(NULL) will block parent process until any of its children has finished. If child terminates before parent process reaches wait(NULL) then the child process turns to a zombie process until its parent waits on it and its released from memory.
If parent process doesn't wait for its child, and parent finishes first, then the child process becomes orphan and is assigned to init as its child. And init will wait and release the process entry in the process table.
In other words: parent process will be blocked until child process returns an exit status to the operating system which is then returned to parent process. If child finishes before parent reaches wait(NULL) then it will read the exit status, release the process entry in the process table and continue execution until it finishes as well.
exec()系统调用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
printf("hello world (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("hello, I am child (pid:%d)\n", (int) getpid());
char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p3.c"); // argument: file to count
myargs[2] = NULL; // marks end of array
execvp(myargs[0], myargs); // runs word count
printf("this shouldn't print out");
} else {
// parent goes down this path (original process)
int wc = wait(NULL);
printf("hello, I am parent of %d (wc:%d) (pid:%d)\n",
rc, wc, (int) getpid());
}
return 0;
}
➜ c5 ./p3
hello world (pid:7124)
hello, I am child (pid:7125)
29 123 965 p3.c
hello, I am parent of 7125 (wc:7125) (pid:7124)
可以让子进程和父进程执行不同的程序。
What it does: given the name of an executable (e.g., wc),
and some arguments (e.g., p3.c), it loads code (and static data) from that
executable and overwrites its current code segment (and current static
data) with it; the heap and stack and other parts of the memory space of
the program are re-initialized. Then the OS simply runs that program,
passing in any arguments as the argv of that process. Thus, it does not
create a new process; rather, it transforms the currently running program
(formerly p3) into a different running program (wc). After the exec()
in the child, it is almost as if p3.c never ran; a successful call to exec()
never returns.
如上设计API的目的
分离fork()和exec()在构建UNIX shell的时候很有用。可以给shell在fork之后exec之前运行代码的机会。
➜ c5 wc p3.c > newfile.txt
shell在获取了用户输入后,从文件系统找到这个可执行程序,调用fork()创建新进程,并调用exec()的某个变体来执行可执行程序,调用wait()来等待该命令的完成。子进程结束后,shell从wait()返回并再次输出一个字符串,等待用户输入下一条命令。
上面的语句,当完成wc子进程的创建,shell在调用exec()之前先关闭了标准输出,打开了文件newfile.txt。
可参考下面的代码。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
int rc = fork();
if (rc < 0) {
// fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
// child: redirect standard output to a file
close(STDOUT_FILENO);
open("./p4.output", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);
// now exec "wc"...
char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p4.c"); // argument: file to count
myargs[2] = NULL; // marks end of array
execvp(myargs[0], myargs); // runs word count
} else {
// parent goes down this path (original process)
int wc = wait(NULL);
assert(wc >= 0);
}
return 0;
}
homework
1、Write a program that calls fork(). Before calling fork(), have the main process access a variable (e.g., x) and set its value to something (e.g., 100). What value is the variable in the child process? What happens to the variable when both the child and parent change the value of x?
父子进程访问一个变量
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void){
int x = 100;
int rc = fork();
if(rc < 0) {
printf("failed to fork\n");
} else if(rc == 0){
printf("child process get x: %d\n", x);
exit(0);
} else {
printf("parent process set x to: %d\n", x);
}
return 0;
}
parent process set x to: 100
child process get x: 100
父子进程访问一个变量并分别改变它
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void){
int x = 100;
int rc = fork();
if(rc < 0) {
printf("failed to fork\n");
} else if(rc == 0){
x = 99;
printf("child process get x: %d\n", x);
exit(0);
} else {
x = 999;
printf("parent process set x to: %d\n", x);
}
return 0;
}
parent process set x to: 999
child process get x: 99
2、 Write a program that opens a file (with the open() system call) and then calls fork() to create a new process. Can both the child and parent access the file descriptor returned by open()? What happens when they are writing to the file concurrently, i.e., at the same time?
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
int main(void){
int fd, rc;
fd = open("output.txt", O_CREAT|O_WRONLY|O_APPEND);
char buf1[] = "content1\n";
char buf2[] = "content2\n";
rc = fork();
if(rc < 0) {
printf("failed to fork\n");
} else if(rc == 0){
write(fd, buf1, sizeof(buf1) - 1); //remove '\0'
exit(0);
} else {
write(fd, buf2, sizeof(buf2) - 1); //remove '\0'
}
return 0;
}
output.txt中的结果
content2
content1