【Linux系统编程】文件描述符与重定向

  #include 

       int dup(int oldfd);
       int dup2(int oldfd, int newfd);

dup函数是让最低位没有被使用的文件描述符也指向oldfd这个文件描述符所指向的文件,成功返回最低那个没有被使用的文件描述符,失败返回-1。

dup2函数是让newfd这个文件描述符也指向oldfd这个文件描述符所指向的文件,如果newfd已经被使用了,那么就关闭它所指向的文件,让newfd指向oldfd指向的文件。成功返回newfd,失败返回-1。所以dup2就是进阶版的dup函数,因为可以指定重定向位置。

#include               /* Obtain O_* constant definitions */
#include 

       int dup3(int oldfd, int newfd, int flags);

dup3自然是dup2函数的进阶版了。他多了一个参数flags,那这个参数是用来干嘛的呢?

看一下这段解释:

dup3() 与 dup2() 相同,不同之处在于:

* 调用方可以通过在标志中指定O_CLOEXEC来强制为新文件描述符设置关闭执行标志。

* 如果 oldfd 等于 newfd,则 dup3() 失败并显示错误 EINVAL。

我们知道fork函数在被调用后,操作系统会通过fork来使用父进程的数据结构,环境变量以及代码生成一个全新的子进程,并且子进程也拥有fork函数,这就会导致子进程的fork也有返回值。而由于子进程和父进程拥有一样的进程地址空间,在读取fork函数的返回值时会访问同一虚拟地址,但对应访问了两个物理空间,这就导致fork函数返回值有两个值(发生了写时拷贝)。事实上,父进程fork函数返回的是子进程的pid,子进程的fork函数返回的是0。
【Linux系统编程】文件描述符与重定向_第1张图片

当父进程使用fork函数时,操作系统会为子进程创建一个新的task_struct,新的页表,新的进程地址空间,页表和进程地址空间都是复制父进程的,实际上进程PCB也是很大程度上复制了一部分,其中就包括文件描述符表。这时候父子进程的资源基本上都是共享的。因为父子进程共享同一段物理空间同一段代码,而物理内存和虚拟地址之间需要用页表映射,当子进程对共享代码进行修改时会发生写时拷贝,所以接收fork函数返回值的变量有一样的虚拟地址但是值却不同。同理,子进程使用exec函数进行程序替换后,重新建立页表映射,重新开辟物理空间。

【Linux系统编程】文件描述符与重定向_第2张图片

 

操作系统为了管理一个进程,当进程创建时会先为其创建一个task_struct结构体描述这个进程的信息,方便系统管理它。这个task_struct中有一个成员files_struct,files_struct中有一个成员struct file * fd_array(结构体指针数组),数组每一个元素都是指向一个file结构体的指针。这个结构体指针数组就是我们说的文件描述符表,文件描述符就是file结构体指针数组下标

file结构体是进程打开一个文件时,操作系统为文件创建的内核数据结构,其中包含文件标志,文件偏移位置,文件inode(在磁盘文件系统中,一个文件有一个唯一的inode),以此就可以对文件进行操作。这就是所说的文件表项

【Linux系统编程】文件描述符与重定向_第3张图片

file结构体会有一个计数器,当多个文件描述符指向同一个文件时,只有所有文件描述符都被释放才这个file结构体才会被释放,否则它会一直保持在内核中。

在父子进程文件描述符表相同的前提下,当进行程序替换时,文件描述符会被释放,但是里面的数据却没有被释放,由于父进程中的文件描述符也指向了这个文件的原因,当子进程该文件描述符释放,对于file结构体来说也就是计数器减一的问题。也就是说还可以用原来的文件描述符(file指针依旧还在,只不过系统默认为这个位置空出来了)找到程序替换前被打开的文件并对其操作,这他妈不是非法操作了么。

可是我们既然已经进行程序替换了,并不需要操作原来的文件,所以我们需要关闭这些被打开的文件。但是你嘎嘎重定向了好多文件,一个一个关闭势必非常麻烦。所以使用O_CLOEXEC的flags标志,使得程序替换前自动关闭newfd,也就是关闭重定向。

你可能感兴趣的:(Linux系统编程,算法,数据结构)