29-fork 函数与文件共享

不知道大家考虑过这样的问题没:如果进程在 fork 之前打开了一些文件,那么 fork 完之后,这些文件的描述符是共享的,还是不共享的?

聪明的同学阅读了上篇《进程空间》的相关内容,脑子应该立即反应过来:父子进程的进程地址空间都是隔离的啊!所以打开的文件,应该也互不影响吧!

No! No! No! 很抱歉,上一篇我的确讲过进程空间是隔离的。为了循序渐近和压缩篇幅,我不得不相关内容挪到此篇。

进程 4GB 空间并不是完全隔离的。

实际上进程空间被分割为用户空间和内核空间。对于32 位 linux 来说,从 0-3GB 的空间是用户空间,从 3GB - 4GB 是内核空间。对于一个进程来说,是绝对无法读写内核空间的。

最重要的一点,或者精确一点,进程的用户空间是隔离的,而内核空间是共享的。看起来有点像下面的图。


这里写图片描述
图 1 进程的用户空间和内核空间

理解这一点也相当重要,这将为未来的进程通信带来可能!

1. 回忆描述符

对于一个进程来说,它所有打开的描述符,都会有记录。而且这些记录,保存该进程的 PCB 结构体中(PCB位于内核空间),该结构体有一个成员 struct file *flip[NR_OPEN],就保存了所有打开的文件(linux 0.11)。如图 2。


这里写图片描述
图2 进程打开的文件

有一点要注意的是,struct file 结构体中的 f_inode 并不是真的直接指向磁盘文件,这中间需要经过若干的步骤,不过为了方便起见和理解,这里直接指向了磁盘文件。

2. fork 后的样子

当图 2 所示的进程 fork 后,为变成下面这个样子。


这里写图片描述
图3 fork 后的两个进程打开的文件

这时候,struct file 中的 f_count 都会自增 1.

上图告诉我们的一个事实是,fork 完后的父子进程,共享 struct file 结构(因为该结构位于内核空间)。在《APUE》 这本书中,把 struct file 称为文件表。

3. 实验

理解了前面的内容后,不如做个实验看看是否真的是这样。下面这段程序在 fork 之前以写的方式创建了一个文件 test.txt。然后 fork 出的子进程立即向文件中写入“world”,然后睡眠5秒。而父进程在 fork 后睡眠3秒后向 test.txt 写入 "hello",并关闭描述符。子进程恢复后,又向 test.txt 文件中写入 "lalala"后关闭描述符,结束。

  • 代码
// forkwrite.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main() {
  int fd = open("test.txt", O_WRONLY | O_CREAT, 0664);
  if (fd == -1) {
    perror("open");
    return 1;
  }
  printf("I'm father\n");
  printf("before fork\n");

  pid_t pid = fork();
  if (pid > 0) {
    sleep(3);
    printf("I'm father; I'm writing test.txt...\n");
    write(fd, "hello", 5); 
    close(fd);
  }
  else if (pid == 0) {
    printf("I'm child; I'm writing test.txt...\n");
    write(fd, "world", 5); 
    sleep(5);
    write(fd, "lalala", 6); 
    close(fd);
  }
  else {
    perror("fork");
    return 1;
  }
  return 0;  
}
  • 编译
$ gcc forkwrite.c -o forkwrite
  • 运行
$ ./forkwrite
  • 结果

屏幕打印:

I'm father
before fork
I'm child; I'm writing test.txt...
I'm father; I'm writing test.txt...

生成的 test.txt 文件内容:

worldhellolalala

4. 总结

  • 理解用户空间和内核空间
  • 知道内核空间是所有进程共享的
  • 加深对文件描述符的理解

除了打开的文件外,父进程的很多其他性质也会被子进程共享,比如各种 ID 号、当前工作目录、根目录、资源的限制、信号屏蔽字、进程环境、文件打开执行时关闭标志、共享存储段。

相关推荐
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页