linux系统文件流、缓冲及描述符与进程间关系详解
linux(unix)进程与文件的关系错综复杂,本教程试图详细的阐述这个问题。
包括:
1、linux多/单进程与多/单文件对于文件流和描述符在使用时的关联情况及一些需要注意的问题。
2、fork,vfork流缓冲等对文件操作的影响。
1、linux文件系统结构
首先补充一点基础知识,了解一下linux文件系统。如下图所示:

图1 磁盘,分区和文件系统
应该明白,上图所示结构是硬盘中文件存放方式的一种逻辑表现形式,与进程无关。对于其中一些术语,见下面的解释。
i节点:包含文件/目录的几乎全部-适用于放置在硬盘上的,需要长久保存的信息。
例如:文件所有者,文件类型,i节点号(存放在目录块中),主次设备号,连接计数,访问/修改时间,IO块长,文件字节数等等。
可以用stat函数(#include <sys/stat.h>)获取相关i节点信息信息。
2、简单的进程与文件关系
下面,我们了解一下单进程多文件以及多进程单文件间的关系,在不考虑fork(父子进程)的情况下,除了赋值语句给我们带来一些小麻烦以外,这个问题还是相当容易的。
2.1、一个进程同时打开多个文件:

图2 一个进程同时打开2个不同文件时内核数据结构
其中,v节点我们几乎已经介绍过了,因为它除了包含i节点之外,自身的内容实在是不怎么多,重点看一下文件表吧。
对于文件表,要注意,它并不是从在于硬盘中的东西,可以说,他是进程的一部分(可能是由操作系统内核负责维护,本人未考证,因为它到底是进程的还是内核的,对于我们要探讨的问题无关紧要)。文件表包括:
文件状态标志:包含读,写,添写,同步和非阻塞等各种文件打开/当前状态。
V节点:文件类型和对此文件进行各种操作的函数的指针,这些信息都是在打开文件时候由磁盘读入内存的。
I节点:同上节所述。
可用fcntl函数(#include <fcntl.h>)修改文件表内容。
2.2、多个无关联进程同时打开一个文件:
图3 两个进程同时打开同一个文件时内核数据结构
此时,2个文件分别使用不同的文件表,这说明不同进程间文件状态标志,文件偏移量等都是独立的。但他们共用同一个v节点表。对于这种结构的特性,理解起来因该是个轻松的事情。
3、文件描述符或流的复制
对于文件描述符或流的复制,很多情况我们会采用赋值语句,下面了解一个赋值和dup的不同之处,dup函数复制文件描述符后的内核数据结构:

图4 执行dup函数(#include <unistd.h>)复制文件描述符后,内核数据结构。
此时,2个fd文件标志同时使用同一文件表。
3.1、dup与赋值语句用于文件描述符的区别
为了了解dup与赋值语句用于文件描述符的区别,请看如下程序。
程序描述:
打开一个文件描述符,分别适用dup和赋值语句进行复制,复制之后,打印原始和被复制的文件描述符id,看看是否具有相同的值,然后关闭文件,测试关闭是否成功。
程序示例:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int sys_err(char *str)
{
puts(str);
exit(0);
}
int main(void)
{
int p,q;
if((p=open("c_fid.c", O_RDONLY)) == -1)
sys_err("open error");
q = dup(p);
puts("dup:");
printf("file p,q fd is:%d %d/n", q, p);
printf("close file p ok?: %d/n", close(p));
printf("close file q ok?: %d/n", close(q));
if((p=open("c_fid.c", O_RDONLY)) == -1)
sys_err("open error");
q = p;
puts("=:");
printf("file p,q fd is:%d %d/n", q, p);
printf("close file p ok?: %d/n", close(p));
printf("close file q ok?: %d/n", close(q));
return 0;
}
程序运行结果:
dup:
file p,q fd is:4 3 //文件p,q使用不同的文件描述符
close file p ok?: 0
close file q ok?: 0 //文件关闭成功
=:
file p,q fd is:3 3 //简单复制
close file p ok?: 0
close file q ok?: -1//关闭失败,原因是此描述符已经被关闭了
由此证明,dup是产生一个新的文件描述符id和指针,但是他们共用文件表,效果如图4,这时,关闭一个文件描述符,另外一个仍旧可用,文件表并不会被释放。而赋值语句不同,它只是简单的在另外一个变量中记录原始文件指针等,2个变量的文件描述符相同,进程表项中并不产生新的项目。
3.2、赋值语句复制标准流。
例:
#include <stdio.h>
#include <stdlib.h>
int sys_err(char *str)
{
puts(str);
exit(0);
}
int main(void)
{
FILE *p,*q;
if((p=fopen("c_fid.c", "r")) == NULL)
sys_err("open error");
q = p;
printf("FILE p,q fd is:%d %d/n", fileno(q),fileno(p));
printf("close file p ok?: %d/n", fclose(p));
printf("close file q ok?: %d/n", fclose(q));
return 0;
}
程序执行结果:
FILE p,q fd is:3 3 //2个流共用同一个文件描述符
close file p ok?: 0
*** glibc detected ***//2次关闭引起错误,造成程序崩溃。
…………
4、 引入fork后进程与文件关系以及流缓冲对文件操作的影响
4.1 fork

图5 使用fork之后,父进程、子进程之间对打开文件的共享
使用fork后,子进程会继承父进程所打开的文件表,此时,父子进程使用同一个文件表(可见,上面猜测文件表由内核维护因该是正确的,因为文件表并没有被复制),这说明2个进程将共用文件状态,文件偏移量等信息。如果使用close关闭一个进程中的文件描述符,那么另一个进程中,此描述符仍然有效,相应文件表也不会被释放。
需要注意的是,在使用c标准io进行文件读写时,先结束的进程会将缓冲区内数据也计入文件偏移量的长度,对于相应文件缓冲区类型和长度,可以使用setbuf,setvbuf(#include <stdio.h>)设置。
程序示例:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
int main(void)
{
int pid;
FILE *p;
char buff[20]={0};
int test=-2;
if((p=fopen("/media/lin_space/soft/netbeans-6.5-ml-cpp-linux.sh", "r")) == NULL)
{//文件大于4096字节
puts("open error.");exit(0);
}
if((pid=fork()) < 0)
{
puts("fork error");
exit(0);
}
else if(pid == 0)
{
sleep(2);
test = ftell(p);//返回当前偏移量
printf("/nchild - ftell is: %d/n", test);
if((buff[0] = fgetc(p)) != EOF)
printf("child - fgetc is: %c/n", buff[0]);
else
puts("child - fgetc error/n");
test = ftell(p);
printf("child - ftell is: %d/n", test);
}
else
{
test = ftell(p);
printf("/nparent - ftell is: %d/n", test);
if((buff[0] = fgetc(p)) != EOF)
printf("parent - fgetc is: %c/n", buff[0]);
else
puts("parent - fgetc error/n");
test = ftell(p);
printf("parent - ftell is: %d/n", test);
}
printf("parent and child - close file ok?: %d/n", fclose(p));
return 0;
}
程序执行结果:
parent - ftell is: 0
parent - fgetc is: #
parent - ftell is: 1
parent and child - close file ok?: 0
freec@freec-laptop:/media/lin_space/summa/apue/unit8$ //父进程结束
child - ftell is: 4096 //子进程文件偏移量为4096,原因是文件缓冲类型为全缓冲,缓冲区大小为4096
child - fgetc is: a
child - ftell is: 4097
parent and child - close file ok?: 0 //文件关闭成功
4.2 vfork
而对于vfork,虽然子进程运行于父进程的空间,但是子进程却拥有自己的进程表项(包含进程pid,fd标志,文件指针等),所以,在其中一个进程结束后,另外一个进程的文件描述符依旧有效,子进程得到的是父进程文件描述符的副本。
但是vfork对于标准流,情况则不同,一个进程结束后(如果不是使用_exit()结束进程),则此进程结束时可能会冲洗流缓冲区,并且关闭流,对于是否这样做,要取决于具体实现。
声明:原创作品,转载著名
另:本人求c程序员(linux/unix平台,北京地区)工作,简介:5年c编程经验,代码量20W+
联系:freecunix@gmail.com
分享到:
相关推荐
### Linux下的进程间通信详解 #### 一、引言 在多进程的环境中,进程间通信(IPC,Inter-Process Communication)是操作系统中一个至关重要的功能。Linux作为一款优秀的开源操作系统,提供了多种进程间通信机制,...
### Linux 进程间通信详解 #### 一、管道及有名管道 在深入探讨Linux中的进程间通信(IPC)机制之前,我们先了解一个基本概念:**进程**。进程是程序执行的一个实例,每个进程都有自己的地址空间。当多个进程需要...
### Linux环境下的进程间通信详解 #### 一、管道及有名管道 管道是早期Unix操作系统提供的最基础的进程间通信方式之一,它主要用于有亲缘关系的进程间的数据传输。 ##### 1.1 管道相关的关键概念 - **半双工特性...
### Linux进程间通信详解 #### 一、管道通信机制 管道是Linux系统中最早实现的进程间通信(IPC)机制之一。它允许数据在进程之间单向传递,主要用于具有亲缘关系的进程间通信,例如父子进程或兄弟进程。管道分为...
12. **管道和FIFO**:在嵌入式系统中,进程间的通信常常需要用到管道和FIFO(命名管道)。`pipe()`创建一个匿名管道,`mkfifo()`创建一个FIFO。 13. **套接字编程**:在嵌入式Linux中,网络通信通常涉及套接字。`...
文件I/O编程是任何系统编程的基础,教程会教授如何在Linux环境下进行文件操作,包括打开、读写、关闭文件,以及使用缓冲流和文件描述符等高级技巧。 进程和多线程编程是理解并发执行的关键。教程将解释进程间的通信...
在Linux中,所有设备都被视为文件,因此理解文件描述符、缓冲区管理、异步I/O和文件权限等概念至关重要。通过`plugin`这个文件名,我们可以推测这份资料可能包含有关动态插件或库的编程实践,这在Linux下通常涉及到...
在 Linux 中,每一个打开的文件都与一个称为**文件描述符**的非负整数相关联。这个整数用于唯一标识该文件,并且可以在后续的文件操作中使用。默认情况下,每个程序都有三个预定义的文件描述符: - **标准输入** ...
### 进程间通信:管道与消息缓冲队列详解 #### 实验背景与目标 在计算机操作系统领域,进程间通信(IPC,Inter-Process Communication)是核心概念之一,它允许不同进程之间交换数据和同步执行。本次实验聚焦于两...
Linux进程间通信(IPC)是一个复杂而丰富的主题,它包括多种通信机制,允许系统中的不同进程之间交换数据。这些机制可以分为几个主要的类别,比如无名管道、命名管道(FIFO)、信号、消息队列、共享内存和信号量。这些...
- 文件描述符和系统调用:理解文件描述符的概念,使用open、read、write、close等系统调用进行低级I/O操作。 - 流与缓冲区:了解缓冲区的概念,理解缓冲区对I/O性能的影响。 - 错误处理:学会检查和处理I/O操作中...
### Linux系统编程知识点详解 #### 一、绪论与核心概念 **1.1 什么是系统编程** 系统编程是指直接与计算机硬件交互的编程活动,它通常涉及底层操作系统的功能,如文件管理、进程控制等。系统编程的目标是提供一个...
### Linux环境进程间通信知识点详解 #### 一.Linux环境进程间通信(一):管道及有名管道 ##### 1、管道概述及相关API应用 **1.1 管道相关的关键概念** - **半双工特性**:管道是半双工的,这意味着数据只能在...
### 嵌入式Linux应用程序开发详解:第八章 进程间通信 #### 8.1 Linux下进程间通信概述 在嵌入式系统开发中,进程间通信(IPC, Inter-Process Communication)是非常重要的一个环节。随着多任务操作系统的广泛应用...