【Linux】同一个进程内open同一文件和父子进程间的文件共享

1. 一个进程open两次同一文件

1.1 笔试题目

//abc.txt
abcdefg

//main.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(){
        int fd1, fd2;
        char c;
        fd1 = open("abc.txt", O_RDONLY,0);
        fd2 = open("abc.txt", O_RDONLY,0);
        read(fd1, &c,1);
        printf("c=%c\n",c);
        read(fd2, &c, 1);
        printf("c=%c\n",c);

        return 0;
}

在秋招笔试中遇到这样一个题目,问输出是多少,答案如下所示。
在这里插入图片描述
open函数、read函数都是C语言用于打开文件、读取文件的库函数。这道题目考察的是,fd1和fd2都是指向同一个文件的文件描述符,它们之间的文件偏移是否是共用,还是各自存在一个文件偏移。

1.2 open函数和read函数

在这里插入图片描述
通过命令man 2 open可以看到函数功能描述,包含函数参数、参数说明等。

       #include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>

使用open函数需要包含以上的头文件,其中函数的声明在fcntl.h文件,前两个文件包含的是flags的宏定义。

int open(const char *pathname, int flags);

pathname是文件名的字符串数组,flags必须包含以下的访问模式标志位之一:O_RDONLY, O_WRONLY, O_RDWR。这三个标志位的宏定义就在前两个头文件里面,分别代表只读、只写、读写。这三个标志位是互斥的,只能三选一。

open函数会返回一个文件描述符,一个较小的、非负数整数,用于定位到该文件。它总是返回系统中未被使用的最小数值的文件描述符。如果打开文件存在错误,就会返回-1。

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

函数说明:
read函数试图从文件描述符fd所指定的文件读取内容到buf所指定的内存当中。在支持读取的文件中,读操作从当前文件偏移开始,随着读取字节的增加,文件偏移也会增加。如果文件偏移达到了文件末尾,无字节可读,read函数会返回0 。

参数说明:
fd:通过open函数返回的文件描述符;
buf:读出数据存放的地方
count:指定数组的大小

返回值:
如果读取成功,会返回读到内容的字节大小。如果出现错误,函数返回-1,并且会将errno这个全局变量设置成合适的值。

1.3 文件描述符

在这里插入图片描述
所有执行I/O操作的系统调用都以文件描述符,一个非负整数(通常是小整数),来指代打开的文件。每个进程都具备自己的文件描述符表。通过输出可以看到两次用open打开返回的文件描述符fd是不一样的。当一个程序以标准方式运行时,文件描述符0、1、2已经分别用于标准输入、标准输出和标准错误。所以才会从3开始技术。

文件描述与文件是如何对应起来的?要理解具体情况如何,需要查看由内核维护的3个数据结构。

  • 进程级的文件描述符表
  • 系统级的打开文件表
  • 文件系统的i-node表

针对每个进程,内核都会维护一个文件描述符表(open file descriptor)。该表的每一条目都记录了单个文件描述符的相关信息。而内核对所有打开的文件维护有一个系统级的打开文件表(open file table),并将表中各条目称为打开文件句柄(open file handel)。一个打开文件句柄存储了与一个打开文件相关的全部信息,如下所示

  • 当前文件偏移量(调用read函数和write函数时更新,或使用lseek函数直接修改)。
  • 文件访问模式
  • 打开文件时所使用的的状态标志
  • 对该文件i-node对象的引用
    在这里插入图片描述

上图源自《Liunx/UNIX系统编程手册》,在进程A中,文件描述符1和20都指向同一个打开的文件句柄。进程A的文件描述符2和进程B的文件描述符2都指向同一个打开的文件句柄。这种情形可能是调用fork()后出现。

此外,进程A的描述符0和进程B的描述符3分别指向不同的打开文件句柄,但这些句柄均指向i-node表中的相同条目(1976),即同一个文件。发生这种情况是因为,每个进程各自对同一文件发起了open调用或者同一进程两次打开同一文件。

上述证明,两个不同的文件描述符,若指向同一打开文件句柄,将共享同一文件偏移量。因此,如果通过其中一个文件描述符来修改文件偏移量(open函数、write函数、lseek函数),那么另外一个文件描述符也会观察到这一变化。无论这两个文件描述属于不同进程,还是同一个进程。

1.4 题目分析

        fd1 = open("abc.txt", O_RDONLY,0);
        fd2 = open("abc.txt", O_RDONLY,0);

回到最初的题目,在同一个进程中调用了两次open,其实和两个进程各自open一次是一样,只要调用open函数,就会创建一个打开文件表条目,即打开文件句柄。在这里插入图片描述
如上图所示,fd1和fd2分别对应着两个打开文件句柄,都有各自的文件偏移量,最终都对应“abc.txt”这个文件。这也是为什么对他们read函数,都是读出了第一个字符的原因,因为他们不共用文件句柄。

2. 父子进程open同一个文件

2.1 笔试题目

//abc.txt
abcdefg

//main.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(){
        int fd;
        char c;
        fd = open("abc.txt", O_RDONLY,0);

        if (fork()==0){
                read(fd,&c,1);
                exit(0);
        }
		
		wait(NULL);
        read(fd, &c, 1);
        printf("c=%c\n",c);

        return 0;
}
~    

该笔试题目问中断输出什么?

2.2 fork函数、exit函数、wait函数

   #include <unistd.h>
   pid_t fork(void);

系统调用fork允许进程(父进程)创建新进程(子进程),子进程几乎是父进程的翻版,子进程获得父进程的栈、数据段、堆和执行文本段的拷贝。

库函数exit(status)终止一进程,把进程占用的所有资源(内存、文件描述符等)归还内核。父进程可使用wait()来获取该状态。

系统调用wait(&status)的目的有二:其一,如果子进程尚未调用exit终止,那么wait会挂起父进程直至子进程终止;其二,子进程的终止状态通过wait()的status参数返回。
在这里插入图片描述
理解fork()的诀窍是,要意识到,完成对其调用后将存在两个进程,且每个进程都会从fork()的返回处继续执行。

这两个进程将执行相同的程序文本段,开始时子进程的栈、数据以及栈段是对父进程对应部分的完全复制。执行fork()后,每个进程均可修改各自的栈数据、堆中的变量,两个进程互不影响。

在这里插入图片描述
在父进程中,fork()将返回新创建子进程的进程ID,而fork()在子进程中则返回0.

2.3 父、子进程间的文件共享

执行fork()时,子进程会获得父进程所有文件描述符的副本。这些副本的创建方式类似于dup(),这意味着父、子进程中对应的文件描述符指向打开文件表中相同条目,具备相同的打开文件句柄。打开文件句柄中包含文件偏移量。如果子进程更新了文件偏移量,那么这种改变也会反应到父进程当中去。

在这里插入图片描述

2.4 题目解析

在这里插入图片描述
这题目其实就是考察子进程使用read函数会不会影响到父进程的文件偏移量。这里为什么会存在一个wait函数。

这是因为调用fork函数之后,父进程和子进程到底哪个抢到了CPU执行权是不确定的。如果没有wait函数,父进程抢到了执行权,直接执行printf输出,那就压根不用考虑子进程的影响了。

当执行fork函数之后,子进程会满足if判断条件,进入函数体执行read函数,将文件偏移量变为1,然后执行exit(0)结束进程。父进程和子进程共享打开文件手柄,虽然父进程第一次使用read函数,但此时文件偏移量是1.

父进程执行到wait函数一直等待子进程的结束,当子进程结束后,执行read函数,此时读到的是"abc.txt"的第二个字符,所以最终输出的字符是b。
在这里插入图片描述
可以试试删除了wait函数,看谁抢到CPU。看来还是父进程比较厉害,抢到了CPU,首先执行完了printf。
在这里插入图片描述
通过lseek函数,可以返回当前文件偏移位置。
在这里插入图片描述
在这里插入图片描述

《Liunx/UNIX编程手册》

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值