在分析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过程中,打开文件描述符表都不变,所以可以通过以上步骤来实现进程间管道通信。