Linux——初识文件

文件描述符

背景补充

  1. 文件=内容+属性,属性又被称为元数据
  2. 访问一个文件,必须把对应的文件打开,需要将其加载到内存中
  3. 如果一个文件没有被打开,那就说明其在磁盘上
  4. 是谁打开文件呢?用户通过bash启动进程来打开文件,在C语言中有着fopen函数进行打开文件,然而操作系统是软硬件资源的管理者,所以fopen必然会通过操作系统来打开文件
  5. 所以操作系统内一定存在大量被打开的文件,如此多的文件必定需要被管理,管理的方式就如同前文所说的一样:先描述再组织 ,那么一定存在一种数据结构来描述文件,就如同PCB一样
  6. 进程有task_struct,进程也会打开文件,所以研究文件就是研究进程与文件的关系

C语言库中的文件操作

打开文件必须要知道文件的路径+文件名,这就是为什么进程有cwd的原因之一,cwd意味当前工作目录,当只添加了文件名时,会在默认路径寻找该文件,默认路径就是cwd,如果用chdir将当前路径更改了,那么默认路径也会跟着改变

默认打开的文件

进程启动时默认打开三个输入输出流,分别是stdin,stdout,stderr,分别对应标准输入,标准输出,标准错误,其实就是打开三个文件,分别是键盘文件,显示器文件,显示器文件。

本质上向显示器打印就是向stdout文件写入,因为stdout也是FILE*类型。

文件的系统调用


open函数在man手册的第二页,说明其是系统调用,pathname就是要打开的文件路径+文件名,flags表示打开文件的方式,mode表示新建一个文件时所设置的权限。

打开文件的方式是通过位图标记的形式,以下的宏是一个数字,只有一个比特位是1。

系统调用与C语音库函数的最大区别就是返回值,open返回值是文件描述符

当我们使用C语言以'w''打开文件时,代码为fopen("log.txt",'w');用系统调用的函数代码为int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);如果是'a'那只用将O_TRUNC换为O_APPEND

为什么需要进行封装系统调用呢?不同系统的系统调用肯定是不一样的,通过语言将所有系统的系统调用进行封装后,就具有可移植性与跨平台性了,一旦有了这些性质,语言的使用者与竞争力就会大大增强了。如何做到跨平台性呢?对于C/C++这种编译性语言,采用条件编译的方式封装多种操作系统的系统调用。

文件描述符

文件描述符就是字面意思,是描述一个文件的,不论上层语言封装后是FILE*还是其他的,操作系统只认文件描述符fd,进行打印打开文件的fd,发现是从3开始的。

如同背景所说的一样,操作系统内部存在着多个被打开的文件,自然也需要管理,会通过双链表形式去管理struct file,其中直接或间接包含文件的属性与内容,在task_struct内一定会有一个文件描述符表的指针来指向文件描述符表,在文件描述符表的内部也一定会有一个指针数组,这些指针数组存储的是指向struct file的指针,数组的下标就是文件描述符

那么既然fd是数组下标,那么为什么是从3开始的呢?数组的0、1、2又是什么呢?

如前面所说,进程会默认打开标准输入、标准输出、标准错误,分别对应的就是0、1、2。

文件描述符的分配规则

众所周知,文件=内容+属性,磁盘里的文本当需要被读取时,操作系统会将属性与内容加载到内存中,内容在文件内核缓冲区里,struct_file中会存着指向属性与内容的指针,所以write函数根本就不是写入到文件中,本质是拷贝函数,把数据从用户空间拷贝到对应的文件内核缓冲区中,至于什么时候写入到磁盘文件,有OS自行决定。

文件描述符的分配规则为:给新打开的文件分配fd,从文件描述符表数组中寻找最小的没有被使用的下标。

如果将0关闭,那么再次打开一个文件,该文件的fd就是0,而由于上层是将0作为标准输入,那么输入时该文件的内容就会被输入,而不是通过终端,输出也一样。这就和重定向的作用类似。

dup2

man手册的2中有一个函数为dup2,作用是改变文件的fd。

dup2(fd,1)即为将1号指定的文件改变为fd,就可以用来进行重定向。比如dup(fd,0)就是输入重定向,改为1就是输出重定向,但不会改变fd。所以重定向的本质就是通过dup2函数进行更改文件描述符指向。

父子进程与文件的关系

当父进程进行fork创建子进程时,子进程会拷贝父进程的文件描述符表,就是指针的浅拷贝,不需要在拷贝文件,比如输入输出文件,此时父子进程均指向相同的文件。所以父子printf时会同时向显示器打印。虽然子进程并没有显式的打开0,1,2,但是子进程已经默认打开了,是通过继承父进程方式来的,如果子进程进行关闭0号文件,是不会影响父子进程的输入的!因为在file是有一个引用计数的,只有该引用计数为0,才会进行关闭,所以父子使用共同的文件是通过引用计数的方式使用的。

所以创建子进程时,子进程是如何看待父进程打开的文件?子进程会拷贝父进程的PCB,文件描述符表,对于共同的文件,通过引用计数,所以当子进程进行关闭共同文件时,是不会影响该文件的,只有当引用计数为0时才会影响。

然而当子进程进行关闭该文件时,子进程确实无法使用该文件了,但是父进程仍然可以使用。比如进程进行关闭输入文件,但是bash仍然可以正常的输入,只是该进程无法输入了,核心原因是,文件描述符表是进程私有的,每个进程都有自己独立的文件描述符表,当进程进行close时,第一步会将该表中的这一项删掉,并将该文件的引用计数--,但是其他进程并没有close并且该文件的引用计数不为0,所以可以正常使用。

所以父进程如果进行重定向了,之后进行fork创建子进程,该子进程也是被重定向了。

相关推荐
AlfredZhao2 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao17 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334661 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪1 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 天前
Linux 11 动态监控指令top
linux
小宇宙Zz2 天前
Maven依赖冲突
java·服务器·maven