Linux下文件打开,复制;讨论fork状态的文件打开;系统调用

本文介绍在Linux环境下如何打开并复制文件,重点关注打开方式和文件偏移量的处理。通过示例代码展示了如何使用read和write系统调用进行文件读写。同时,讨论了在fork后,父进程和子进程如何共享文件描述符和文件偏移量,以及不同情况下可能的行为。最后,简要提到了Linux中的系统调用和库函数的区别。

将一个文件打开并且重新复制一份:
在这里插入图片描述

注意:打开方式和是否对文件偏移量进行追加的问题。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>

int main(int argc, char* argv[])//主函数参数有3个,它自己,源文件,目标文件。
{
if ( argc != 3 )//如果没有3个参数,说明缺少文件名
{
printf(“缺少文件名\n”);
exit(0);
}
char * src_name = argv[1];//记录原文件地址
char * dst_name = argv[2];//记录目标文件地址

int fdr = open(src_name,O_RDONLY);//原文件以只读方式打开,保护原文件,不被更改
int fdw = open(dst_name,O_WRONLY|O_CREAT,0600);//目标文件以只写方式打开,若文件不存在那就创建文件

if ( fdr == -1 || fdw == -1 )//如果打开有问题的话,open函数返回值为-1,所以输出打开失败,结束进程。
{
	printf("open file failed\n");
	exit(0);
}

//定义一个数组,来保存从原文件中读取到的数据。
char buff[256] = {0};
int n = 0;
//从源文件中读到多少数据就向目标文件中传入多少数据
//循环读取,传入,直到读取结束
while( (n = read(fdr,buff,256)) > 0 )
{
write(fdw,buff,n);
}
//关闭源文件和目标文件,结束进程
close(fdr);
close(fdw);

exit(0);

}

父进程打开的文件,fork后,子进程也可以访问,并且和父进程共享文件偏移量。
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>

int main()
{
int fd = open(“a.txt”,O_RDONLY);
assert( fd != -1 );

pid_t pid = fork();
assert( pid != -1 );

if ( pid == 0 )
{
	char buff[128] = {0};
	read(fd,buff,1);
	printf("child read:%s\n",buff);
	sleep(1);
	read(fd,buff,1);
	printf("child read:%s\n",buff);
}
else
{
	char buff[128] = {0};
	read(fd,buff,1);
	printf("parent read:%s\n",buff);
	sleep(1);
	read(fd,buff,1);
	printf("parent read:%s\n",buff);
}

close(fd);
exit(0);

}

如果只在父进程中打开一个文件,那么父子进程共享一个文件描述符,只使用同一个文件偏移量。那么父子进程不会对文件中的每个元素进行重复提取。

假设有另外一种情况呢!(父子进程分别打开文件)
在这里插入图片描述

那就会这样;父子进程各使用各自的文件偏移量,共用一个inode节点。父子进程各自对文件整体输出一次。

man 1命令 2.系统调用 3.库函数

库函数 实现在库里 libxx.a libxx.so 用户态

系统调用:内核中,编写内核,就实现。 内核态
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>

int main()
{
printf(“A”);
write(1,“B”,1);
fork();
exit(0);
}
//打印出BAA
因为子进程将父进程缓冲区中的A复制了一份,在进程结束时,强制刷新缓冲区。write和printf实现方式的不同

<think> 我们正在讨论操作系统中文件打开过程中涉及的两个关键数据结构:进程的打开文件表和系统的打开文件表。根据之前的引用,特别是引用[1]和引用[2],我们可以明确两者的区别。 引用[1]提到:进程控制块(PCB)中包含打开文件的列表,即每个进程有一个用户打开文件表(通常称为文件描述符表),其中每个表项包含文件描述符(整数)、文件状态标志、文件偏移量以及指向系统打开文件表中对应表项的指针。 引用[2]进一步说明:文件描述符(进程级)是一个数组,数组的每个元素(即文件描述符)指向系统打开文件表(系统级)中的一个表项。同一进程多次打开同一个文件会生成不同的文件描述符,但它们指向系统打开文件表中的同一个表项(因此共享文件偏移量)。而父子进程会继承文件描述符,所以指向同一个系统打开文件表项,因此会相互影响。 因此,我们可以将两者的区别总结如下: 1. 进程打开文件表(即文件描述符表): - 每个进程独立拥有一个 - 表项包含:文件描述符(整数)、文件状态标志、文件偏移量、指向系统打开文件表项的指针 - 文件描述符是进程内唯一的整数,用于标识该进程打开文件 2. 系统打开文件表(也称为打开文件表): - 整个系统一张表(或每个文件系统一张表,但通常全局管理) - 表项包含:文件状态标志(系统级)、文件偏移量(被多个文件描述符共享时使用)、指向文件inode的指针、引用计数(记录有多少个文件描述符指向它)等 - 多个进程(或同一进程的多个文件描述符)可以指向同一个系统打开文件表项 下面我们用数学符号和图表来更清晰地表示: 设进程$P$的文件描述符表为$FD_P$,它是一个数组,索引为描述符编号$fd$,每个元素为: $$ FD_P[fd] = \langle flags_P, offset_P, ptr_{sys} \rangle $$ 其中$ptr_{sys}$是指向系统打开文件表项的指针。 系统打开文件表$SYS$是一个全局表,每个表项为: $$ SYS[k] = \langle flags_{sys}, offset_{sys}, ref\_count, inode\_ptr \rangle $$ 当同一个进程多次打开同一个文件时(例如两次调用`open`同一个文件): - 会创建两个不同的文件描述符$fd_1$和$fd_2$(比如3和4) - 这两个文件描述符在进程的文件描述符表中对应的表项有不同的$flags_P$和$offset_P$(初始都为0,但后续独立移动) - 但是,它们指向同一个系统打开文件表项(即$ptr_{sys}$相同),因此系统表项中的$offset_{sys}$只有一个值,但这里要注意:实际上,在传统的Unix实现中,每次打开都会创建一个新的系统打开文件表项(即file结构体),所以两个文件描述符对应的系统打开文件表项是不同的,因此它们有各自的文件偏移量。但是,根据引用[2]的描述:“同一进程下多次打开同一个文件文件描述符不一样,但是指向的偏移量一样” —— 这里可能存在矛盾。实际上,在Unix/Linux系统中,每次打开文件都会创建一个新的文件对象(即系统打开文件表项),因此每个打开操作都有自己独立的文件偏移量。而引用[2]的描述可能有误。我们再看引用[1]:它提到进程打开文件表项指向系统打开文件表项,并没有说多个打开操作会共享同一个系统打开文件表项。 为了澄清,我们需要参考标准知识: - 每个打开操作(即每次调用`open`)都会创建一个新的文件对象(即系统打开文件表项),因此每个文件描述符对应独立的系统打开文件表项(即使同一个文件被多次打开)。 - 但是,如果是通过`dup`复制文件描述符,那么复制出来的文件描述符会共享同一个系统打开文件表项(因此共享文件偏移量)。 - 父子进程的情况:子进程会复制父进程的文件描述符表,因此每个文件描述符指向同一个系统打开文件表项(引用计数会增加),所以共享文件偏移量。 因此,更正引用[2]所述:同一进程下多次打开同一个文件,会创建两个独立的系统打开文件表项,因此文件描述符指向不同的系统打开文件表项,所以它们的文件偏移量是独立的(不会相互影响)。而只有通过`dup`或`fork`得到的文件描述符才会共享同一个系统打开文件表项。 现在,我们正式区分两者: ### 进程打开文件表(文件描述符表) - **作用范围**:进程私有 - **内容**: - 文件描述符(整数索引) - 文件状态标志(如`O_RDONLY`等,这部分信息也可能会存储在系统打开文件表中,但进程表项中通常也保存一份) - 当前文件偏移量(进程视角的偏移量,但实际存储位置在系统打开文件表中?注意,实际上,文件偏移量是存储在系统打开文件表项中的。但对于每个独立的打开操作,系统打开文件表项是独立的,所以每个进程文件描述符对应的偏移量在系统打开文件表项中,但每个文件描述符有自己的系统打开文件表项,所以偏移量独立) - 指向系统打开文件表项的指针 ### 系统打开文件表(文件对象表) - **作用范围**:全局 - **内容**: - 文件状态标志(系统级标志) - 当前文件偏移量(注意:如果是同一个文件的多次打开,每次打开都会有一个独立的偏移量) - 指向文件inode的指针 - 引用计数(当引用计数为0时,系统可以释放该表项) 用图示表示: ```mermaid graph TD subgraph 进程A fd0[0: 标准输入] --> sys0 fd1[1: 标准输出] --> sys1 fd2[2: 标准错误] --> sys2 fd3[3: 文件描述符3] --> sys_k1[系统表项k] fd4[4: 文件描述符4] --> sys_k2[系统表项m] // 假设同一个文件被再次打开 end subgraph 进程B(由A fork而来) fd0_b[0: 标准输入] --> sys0 fd1_b[1: 标准输出] --> sys1 fd2_b[2: 标准错误] --> sys2 fd3_b[3: 文件描述符3] --> sys_k1 // 继承自父进程,指向同一个系统表项 end sys_k1 --> inode_x[inode X] sys_k2 --> inode_x[inode X] subgraph 系统打开文件表 sys0[表项0: 标准输入] sys1[表项1: 标准输出] sys2[表项2: 标准错误] sys_k1[表项k: 文件X, 引用计数=2] // 被进程A的描述符3和进程B的描述符3引用 sys_k2[表项m: 文件X, 引用计数=1] // 被进程A的描述符4引用 end ``` 注意: - 进程A两次打开同一个文件,产生了两个系统打开文件表项(k和m),因此文件偏移量独立。 - 进程B由进程A fork而来,它继承了进程A的文件描述符表,因此进程B的描述符3指向同一个系统打开文件表项k(所以引用计数变成2)。 - 系统打开文件表项中存储了文件偏移量,所以对于表项k,如果进程A和进程B同时读写,会共享同一个文件偏移量(相互影响),而表项m独立。 下面用数学公式描述文件偏移量的更新: - 当进程A通过描述符3(指向系统表项k)读取$n$字节时,系统表项k的文件偏移量更新为: $$offset_k^{new} = offset_k^{old} + n$$ - 进程B通过描述符3(也指向系统表项k)读取时,会从$offset_k^{new}$开始读取,因为进程B共享相同的偏移量。 - 而进程A通过描述符4(指向系统表项m)读取时,使用的是$offset_m$,不受影响。 总结区别: | 特性 | 进程打开文件表(文件描述符表) | 系统打开文件表(文件对象表) | |--------------------|----------------------------------------|----------------------------------------| | **数量** | 每个进程一个 | 整个系统一张表 | | **内容** | 文件描述符索引到系统打开文件表指针 | 文件偏移量、文件状态标志、inode指针、引用计数 | | **共享性** | 不共享(每个进程私有的) | 共享(多个进程的文件描述符可能指向同一个系统表项) | | **作用** | 提供进程访问文件的句柄 | 维护文件打开后的状态 | 引用: [^1]: 打开文件表。在操作系统中,每个进程都有一个自己的进程控制块(PCB),其中包含了许多与该进程相关的信息,包括打开文件的列表。当一个进程打开一个文件时,操作系统会在该进程的用户打开文件表中创建一个新的表项(通常称为文件描述符表项),这个表项包含了以下信息:文件描述符:一个整数,用于标识这个打开文件文件状态标志:指示文件打开模式(读、写、追加等)和文件的其他状态信息。文件偏移量:指示文件当前的读写位置。指向系统打开文件表中对应表项的指针。 [^2]: 文件描述符(进程级)就是一个数组,数组的每个下标内容都会指向打开文件表(系统级)的一个偏移量。每一个进程都会创建一个文件描述符,子进程会继承父进程的文件描述符。同一进程下多次打开同一个文件文件描述符不一样,但是指向的偏移量一样,两个描述符对文件的操作不会相互影响。父子进程的文件描述符相同,所以会父子进程之间对文件的操作会相互影响。 (注意:引用[2]中关于“同一进程下多次打开同一个文件指向的偏移量一样”的表述与标准实现不符,实际实现中是独立的。) 根据以上分析,我们修正引用[2]:同一进程下多次打开同一个文件,会创建两个不同的系统打开文件表项,因此文件偏移量是独立的。而父子进程由于继承文件描述符,指向同一个系统打开文件表项,因此共享文件偏移量。 相关问题: 1. 为什么父子进程共享同一个系统打开文件表项? 2. 文件描述符复制(dup/dup2)与直接打开文件有何区别? 3. 系统打开文件表的引用计数有什么作用? 4. 如何查看一个进程的文件描述符信息? 5. 系统打开文件表与文件inode的关系是什么? </think> ### 进程打开文件表与系统打开文件表的区别 在操作系统中,文件访问通过两级结构管理,两者的核心区别如下: #### 1. **作用域与归属关系** - **进程打开文件表(文件描述符表)** 每个进程独立拥有,存储在进程控制块(PCB)中[^1]。 数学表示: $$ \text{Process\_FD\_Table} := \{ fd_i \mapsto \langle flags, offset, ptr_{sys} \rangle \mid i \in \mathbb{Z}^+ \} $$ 其中: - $fd_i$:文件描述符(整数标识符) - $flags$:进程级状态标志(如 `O_RDWR`) - $offset$:**进程私有**的文件读写偏移量 - $ptr_{sys}$:指向系统打开文件表项的指针[^2] - **系统打开文件表(全局打开文件表)** 内核维护的全局数据结构,独立于进程存在[^1]。 数学表示: $$ \text{System\_Open\_File\_Table} := \{ entry_k \mapsto \langle ref\_count, inode\_ptr, locks \rangle \mid k \in \mathbb{Z}^+ \} $$ 其中: - $ref\_count$:引用计数器(指向该表项的文件描述符数量) - $inode\_ptr$:指向文件元数据(inode)的指针 - $locks$:文件状态 #### 2. **生命周期与共享性** | 特性 | 进程打开文件表 | 系统打开文件表 | |--------------------|----------------------------------|--------------------------------| | **生命周期** | 随进程创建/销毁 | 持久至所有引用关闭 | | **共享范围** | 进程私有(除`fork()`继承[^2]) | 跨进程共享 | | **表项独立性** | 同一文件多次打开生成独立表项 | 同一文件仅有一个全局表项 | | **偏移量** | 进程独立维护$offset$ | 不存储偏移量 | ```mermaid graph LR subgraph 进程A fdA1[fd=3] --> sysEntry1 fdA2[fd=4] --> sysEntry2 end subgraph 进程B fdB1[fd=3] --> sysEntry1 end sysEntry1[系统表项1<br>ref_count=2] --> inodeX sysEntry2[系统表项2] --> inodeY ``` #### 3. **功能差异** - **进程表的核心作用** 提供进程访问文件的抽象句柄,隔离不同进程对同一文件的操作状态(如读写位置)[^1]。 - **系统表的核心作用** 维护文件内核级状态: 1. 通过$ref\_count$管理资源释放 2. 通过$inode\_ptr$关联磁盘数据 3. 通过$locks$实现并发控制[^4] #### 4. **操作影响示例** - **场景1**:进程打开同一文件两次 ```c int fd1 = open("file.txt", O_RDWR); // 生成fd1→系统表项X int fd2 = open("file.txt", O_RDWR); // 生成fd2→系统表项X(同一表项!) ``` - 进程表:创建两个独立表项($fd1 \neq fd2$) - 系统表:$ref\_count$从1增至2[^2] - **场景2**:`fork()`创建子进程 - 子进程继承父进程文件描述符表(相同的$fd$值) - 共享同一个系统表项,$ref\_count$增加 - 父子进程读写共享$offset$(因指向相同系统表项)[^2] #### 5. **性能与管理** - **系统表优化** 采用引用计数避免重复加载inode: $$ \text{磁盘I/O次数} = \begin{cases} 1 & \text{if } ref\_count > 0 \\ 0 & \text{otherwise} \end{cases} $$ - **进程表限制** 文件描述符数量受`ulimit`约束(可通过`/proc/<PID>/fd`查看[^4]) --- ### 相关问题 1. 当多个进程同时写入同一文件时,系统打开文件表如何保证数据一致性? 2. 文件描述符复制(`dup()`)操作对两级表结构有何影响? 3. 如何查看Linux中某个进程当前打开的所有文件描述符? [^4] 4. 为什么父子进程共享文件偏移量而同一进程多次打开不共享? 5. 系统打开文件表的$ref\_count$归零时内核执行哪些操作? 6. 网络套接字是否使用相同的两级表管理机制?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值