在分析linux系统调用fork,linux系统调用execve时,已经知道:
1.fork时,子进程会复制父进程的打开文件描述符表
2.exec时,进程的打开文件描述符表保持不变
用以下代码观察fork,exec打开文件的变化情况:
父进程fork子进程,睡眠一定时间(方便命令行查看打开文件);
子进程fork孙进程,睡眠一定时间;
孙进程exec新程序,新程序也睡眠一定时间
/* openfiles.c */
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <fcntl.h>
4 #include <errno.h>
5
6 #define BUFSIZE 50
7 #define error() \
8 do { \
9 char buf[BUFSIZE]; \
10 snprintf(buf, BUFSIZE, "[%s][%d][%d]\n", __FILE__, __LINE__, errno); \
11 perror(buf); \
12 } while(0);
13
14 int main(int argc, char *argv[])
15 {
16 int pid, fd;
17 int nsecs;
18 char snsecs[20] = {0};
19 int err;
20 if (argc != 2) {
21 printf("command format: %s sleeptime\n", argv[0]);
22 return -1;
23 }
24
25 nsecs = atoi(argv[1]);
26 if (nsecs < 0 || nsecs > 120) {
27 nsecs = 120;
28 }
29 sprintf(snsecs, "%d", nsecs);
30
31 fd = open(argv[0], O_RDONLY);
32 pid = fork();
22 return -1;
23 }
24
25 nsecs = atoi(argv[1]);
26 if (nsecs < 0 || nsecs > 120) {
27 nsecs = 120;
28 }
29 sprintf(snsecs, "%d", nsecs);
30
31 fd = open(argv[0], O_RDONLY);
32 pid = fork();
33 if (pid == 0) {
34 pid = fork();
35 if(pid == 0) {
36 err = execl("./exec_openfiles", "exec_openfiles", snsecs, (char *) 0);
37 if (err) {
38 error();
39 return -1;
40 }
41 } else if (pid < 0) {
42 error();
43 return -1;
44 }
45 } else if (pid < 0) {
46 error();
47 return -1;
48 }
49
50 sleep(nsecs);
51 close(fd);
52 return 0;
53 }
/* exec_openfiles.c */
1 #include <stdio.h>
2 #include <fcntl.h>
3
4 int main(int argc, char* argv[])
5 {
6 int fd;
7 int nsecs;
8
9 if (argc != 2) {
10 printf("command format: %s sleeptime \n", argv[0]);
11 return -1;
12 }
13
14 nsecs = atoi(argv[1]);
15 if (nsecs <= 0 || nsecs > 120) {
16 nsecs = 120;
17 }
18
19 fd = open(argv[0], O_RDONLY);
20 sleep(nsecs);
21 close(fd);
22 return 0;
23 }
/* Makefile */
1 all:
2 gcc openfiles.c -o openfiles
3 gcc exec_openfiles.c -o exec_openfiles
用以下命令观察三个进程打开文件的变化情况:
查看当前终端并运行openfiles:
[redhat@localhost fork_exec_openfiles]$ tty
/dev/pts/6
[redhat@localhost fork_exec_openfiles]$ ./openfiles 90
通过终端查看刚才运行的进程:
[redhat@localhost fork_exec_openfiles]$ ps -t pts/6 -f
UID PID PPID C STIME TTY TIME CMD
redhat 8165 13780 0 13:20 pts/6 00:00:00 ./openfiles 90
redhat 8166 8165 0 13:20 pts/6 00:00:00 ./openfiles 90
redhat 8167 8166 0 13:20 pts/6 00:00:00 exec_openfiles 90
redhat 13780 2851 0 Jun29 pts/6 00:00:00 bash
通过lsof命令观察以上进程打开的文件:
[redhat@localhost fork_exec_openfiles]$ lsof -p 8165
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
openfiles 8165 redhat cwd DIR 253,0 4096 1068237 /home/redhat/code/syscall/fork_exec_openfiles
openfiles 8165 redhat rtd DIR 253,0 4096 2 /
openfiles 8165 redhat txt REG 253,0 6245 1068231 /home/redhat/code/syscall/fork_exec_openfiles/openfiles
openfiles 8165 redhat mem REG 253,0 141536 134697 /lib/ld-2.12.so
openfiles 8165 redhat mem REG 253,0 1880776 134698 /lib/libc-2.12.so
openfiles 8165 redhat 0u CHR 136,6 0t0 9 /dev/pts/6
openfiles 8165 redhat 1u CHR 136,6 0t0 9 /dev/pts/6
openfiles 8165 redhat 2u CHR 136,6 0t0 9 /dev/pts/6
openfiles 8165 redhat 3r REG 253,0 6245 1068231 /home/redhat/code/syscall/fork_exec_openfiles/openfiles
[redhat@localhost fork_exec_openfiles]$ lsof -p 8166
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
openfiles 8166 redhat cwd DIR 253,0 4096 1068237 /home/redhat/code/syscall/fork_exec_openfiles
openfiles 8166 redhat rtd DIR 253,0 4096 2 /
openfiles 8166 redhat txt REG 253,0 6245 1068231 /home/redhat/code/syscall/fork_exec_openfiles/openfiles
openfiles 8166 redhat mem REG 253,0 141536 134697 /lib/ld-2.12.so
openfiles 8166 redhat mem REG 253,0 1880776 134698 /lib/libc-2.12.so
openfiles 8166 redhat 0u CHR 136,6 0t0 9 /dev/pts/6
openfiles 8166 redhat 1u CHR 136,6 0t0 9 /dev/pts/6
openfiles 8166 redhat 2u CHR 136,6 0t0 9 /dev/pts/6
openfiles 8166 redhat 3r REG 253,0 6245 1068231 /home/redhat/code/syscall/fork_exec_openfiles/openfiles
openfiles 8166 redhat 4r REG 253,0 6245 1068231 /home/redhat/code/syscall/fork_exec_openfiles/openfiles
[redhat@localhost fork_exec_openfiles]$ lsof -p 8167
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
exec_open 8167 redhat cwd DIR 253,0 4096 1068237 /home/redhat/code/syscall/fork_exec_openfiles
exec_open 8167 redhat rtd DIR 253,0 4096 2 /
exec_open 8167 redhat txt REG 253,0 5184 1068238 /home/redhat/code/syscall/fork_exec_openfiles/exec_openfiles
exec_open 8167 redhat mem REG 253,0 141536 134697 /lib/ld-2.12.so
exec_open 8167 redhat mem REG 253,0 1880776 134698 /lib/libc-2.12.so
exec_open 8167 redhat 0u CHR 136,6 0t0 9 /dev/pts/6
exec_open 8167 redhat 1u CHR 136,6 0t0 9 /dev/pts/6
exec_open 8167 redhat 2u CHR 136,6 0t0 9 /dev/pts/6
exec_open 8167 redhat 3r REG 253,0 6245 1068231 /home/redhat/code/syscall/fork_exec_openfiles/openfiles
exec_open 8167 redhat 4r REG 253,0 5184 1068238 /home/redhat/code/syscall/fork_exec_openfiles/exec_openfiles
注:
cwd:当前目录
rtd:根目录
txt:程序代码段(可看出exec后的代码段不同)
mem:映射到进程地址空间的动态库
0u,1u,2u:标准输入(对应当前的虚拟终端),标准输出,标准错误
3r,4r:打开的文件
内核导出到proc文件系统中的进程打开文件信息:
[redhat@localhost fork_exec_openfiles]$ ll /proc/8165/fd
总用量 0
lrwx------. 1 redhat redhat 64 6月 30 13:45 0 -> /dev/pts/6
lrwx------. 1 redhat redhat 64 6月 30 13:45 1 -> /dev/pts/6
lrwx------. 1 redhat redhat 64 6月 30 13:45 2 -> /dev/pts/6
lr-x------. 1 redhat redhat 64 6月 30 13:45 3 -> /home/redhat/code/syscall/fork_exec_openfiles/openfiles
[redhat@localhost fork_exec_openfiles]$ ll /proc/8166/fd
总用量 0
lrwx------. 1 redhat redhat 64 6月 30 13:45 0 -> /dev/pts/6
lrwx------. 1 redhat redhat 64 6月 30 13:45 1 -> /dev/pts/6
lrwx------. 1 redhat redhat 64 6月 30 13:45 2 -> /dev/pts/6
lr-x------. 1 redhat redhat 64 6月 30 13:45 3 -> /home/redhat/code/syscall/fork_exec_openfiles/openfiles
lr-x------. 1 redhat redhat 64 6月 30 13:45 4 -> /home/redhat/code/syscall/fork_exec_openfiles/openfiles
[redhat@localhost fork_exec_openfiles]$ ll /proc/8167/fd
总用量 0
lrwx------. 1 redhat redhat 64 6月 30 13:45 0 -> /dev/pts/6
lrwx------. 1 redhat redhat 64 6月 30 13:45 1 -> /dev/pts/6
lrwx------. 1 redhat redhat 64 6月 30 13:45 2 -> /dev/pts/6
lr-x------. 1 redhat redhat 64 6月 30 13:45 3 -> /home/redhat/code/syscall/fork_exec_openfiles/openfiles
lr-x------. 1 redhat redhat 64 6月 30 13:45 4 -> /home/redhat/code/syscall/fork_exec_openfiles/exec_openfiles
父进程:8165
fork的子进程:8166
exec的孙进程:8167
由以上数据可以看出:
1.对比父进程与子进程打开的文件,可知fork后子进程会保持父进程打开的文件不变;父子进程打开的文件在fork之后会相互独立,如上例的子进程新打开的文件4不会出现在父进程打开文件表中。
2.对比子进程与孙进程打开的文件,可知exec后孙进程会保持子进程打开的文件不变(注意子进程文件4是在exec之后打开的);子孙进程打开的文件在exec之后相互独立,如上例的子进程打开的文件4不会出现在孙进程中,孙进程打开的文件4不会出现在子进程中。
所以可以通过打开的文件描述符实现父子进程的通信。
如以下命令
[redhat@localhost fork_exec_openfiles]$ ps -o pid,ppid,comm | cat
PID PPID COMMAND
8771 13780 ps
8772 13780 cat
13780 2851 bash
就用到了管道和打开文件描述符表来实现通信:
1.shell创建一个管道,并fork两个进程,8771和8772
2.将进程8771的标准输出dup2到管道的写端(如有必要dup2会自动关闭标准输出),然后exec装入ps镜像,ps开始执行,输出写到管道中
3.将进程8772的标准输入dup2到管道的读端(如有必要dup2会自动关闭标准输入),然后exec装入cat镜像,cat开始执行,从管道中读数据
由于fork与exec过程中,打开文件描述符表都不变,所以可以通过以上步骤来实现进程间管道通信。