fork & exec时打开文件的变化

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

你可能感兴趣的:(fork & exec时打开文件的变化)