文件 fd

news2024/11/12 1:05:47

目录

  • 1. 建立共识原理
  • 2. 回忆 C 文件接口
    • 2.1 当前工作路径
    • 2.2 w / a 方式写入
    • 2.3 默认打开的三个文件流
  • 3. 认识文件系统调用
    • 3.1 O_WRONLY|O_CREAT 写时创建
    • 3.2 O_TRUNC 截断长度(也即全覆盖式写入)
    • 3.3 O_APPEND 追加
  • 4. 浅谈文件访问的本质
    • 4.1 简证

1. 建立共识原理

  • 文件 = 内容 + 属性
  • 文件分为两种:打开的文件 和 没打开的文件
  • 文件是被谁打开的? ----- 进程。因此研究文件打开这个话题,本质就是在研究进程与文件的关系。
  • 没打开的文件,存储在磁盘;在磁盘上我们最关注什么问题?----- 没有被打开的文件非常多,文件如何被有序分类的放置好,因为我们要快速对文件进行增删查改等操作,所以我们需要快速的找到指定文件。

我们要打开一个文件,在打开之前,文件存储在磁盘中,而打开文件的本质就是访问文件,最终都是要通过代码去访问的(对文件增删查看等操作),编译完运行起来变为进程。换言之,访问文件一定是 cpu 去执行的,根据冯诺依曼体系结构,访问时,文件就一定需要被加载到内存中。

在代码编写上,我们是可以打开诸多个文件的,这也就代表着一个进程与打开文件的比例一定是 1: n,系统中存在着大量的进程,也就一定存在着更加大量的被打开的文件。操作系统都需要对进程做管理,因此大量的被打开的文件同样的需要被管理起来。如何管理?? ----- 先描述,再组织!

在内核中,一个被打开的文件都必须有自己的文件打开对象,包含文件的诸多属性。 例如:struct File { file_attributes; struct xxx* next };


2. 回忆 C 文件接口

FILE *fopen(const char *path, const char *mode);
示例:
FILE *fopen("log.txt", "w");

2.1 当前工作路径

这个 C 打开文件的库函数我们在熟悉不过了,并且以 w 的方式打开指定文件时,若该文件不存在,那么 fopen 这个函数会自动在当前路径下创建该文件,再打开它。

而这里的当前路径到底是什么呢?? ----- 当前运行的进程所处的工作目录 cwd。

换言之,只要我把当前正在运行的这个进程的 cwd 更改了,那么如果 fopen 时指定是路径依旧不带全局路径,那么就会在 cwd 所指向的目录下创建该文件。

chdir("/home/outlier");
FILE *fp = fopen("log111111111111111111.txt", "a");

在这里插入图片描述
在这里插入图片描述

2.2 w / a 方式写入

fopen w 的方式打开文件,对文件进行写入,我们还发现了,每次重新写入的时候,文件上一次的内容总是不见了。这不仅仅是 fopen 每次都会从文件的起始地址开始写入,而是在写入之前,还会将文件的长度置 0。man 手册中对 fopen 的读写方式是这样说的:

 w+    Open for reading and writing. The file is created if it does not exist, oth‐erwise it is  
       truncated. The stream is positioned at the beginning of the file.

在这里插入图片描述

在 linux 中,我们也可以通过 echo + 输出重定向 向一个文件写入啊。要向一个文件写入,那么就必须先打开文件。但是 > 重定向写入文件时,我们一样发生,上次的内容被清空了,只留下最新写入的内存。所以 > 的本质还是 “w”, 它做的事情还是 打开文件 + w 方式的覆盖式写入!如果你愿意,在代码中 fopen 之后,对文件不做任何写入,程序运行起来之后,也可以达到清空文件的效果。

a      Open for appending (writing at end of file). The file is created if it does not exist. 
       The stream is positioned at the end of the file.

与 w 方式不同写入就是 a 方式写入,w 清空并从文件头开始写入,a 在上次写入的结尾,继续追加写入。

2.3 默认打开的三个文件流

在 C 程序启动时,会默认打开 stdin,stdout,stderr 这三个文件输入输出流,C++ 则有 cin, cout,cerr。
其中的 stdin 对应的是键盘文件,stdout,stderr 对应显示器文件

换言之,在 c 代码中,我们同样也可以实现向 stdout 这个文件流做写入操作

const char *s = "hello, linux!";
fprintf(stdout, "%s: %d\n", s, 1234);
fprintf(stderr, "%s: %d\n", s, 1234);

3. 认识文件系统调用

我们需要有一个共识:文件是存储在磁盘上的,而磁盘是外部设备,所以访问磁盘文件,本质就是访问硬件!但是访问硬件这件事,我们用户并不擅长啊!更重要的是,硬件是被操作系统所管理的,作为用户,我们无法绕开操作系统访问硬件,操作系统不允许这种操作,但是作为用户的我们又有访问硬件的需求,怎么办呢?? ----- 于是操作系统向外提供了系统调用接口,供用户访问磁盘文件。换言之,语言层面上的各种 fopen,fprint 等访问文件的库函数,底层一定是调用的系统接口。

3.1 O_WRONLY|O_CREAT 写时创建

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

int open(const char *pathname, int flags);		//通常用于文件已经创建的前提
int open(const char *pathname, int flags, mode_t mode);		

在这里插入图片描述

关于 flag 参数,O_RDONLY 只读,O_WRONLY 只写,O_RDWR 读写,必须包含这三大方式中的其中一中。flag 采用的是比特位作为标志进行传递的。

#define ONE (1<<0) 
#define TWO (1<<1) 
#define THREE (1<<2) 
#define FOUR (1<<3) 
void show(int flags)
{
    if(flags & ONE) printf("function1\n");
    if(flags & TWO) printf("function2\n");
    if(flags & THREE) printf("function3\n");
    if(flags & FOUR) printf("function4\n");
}

类似于上面的小demo,一种 flag 只会有一个比特位为1,可以将多个 flag 通过位运算组合起来使用。

如果 open 打开一个未存在的文件,并且 flag 只设置为 O_WRONLY,是不会自动创建文件的,需要再加一个 O_CREAT 进行或运算传递。

open("log.txt", O_WRONLY|O_CREAT);

在这里插入图片描述

但是这样还不够,open 创建文件时,如果不加以设置文件权限,就会出现 “乱码” 的现象。

open("log.txt", O_WRONLY|O_CREAT, 0666);

在讲 Linux中常见的权限问题 时,我们谈论到了目录起始权限是777,文件的起始权限是 666,而因为有 umask 的存在,创建出来的文件权限并不会是 666,而是 664,那么在 open 打开创建一个文件时,同样也会受到系统中 umask 的限制。

如果想要创建出来的文件权限,所见及所写,那么就需要在调用 open 时,先调用一下 umask,它也是一个系统调用,作用域只局限于该进程。

  • 在 C 代码中调用了 umask 系统调用修改了,指定了 umask,但是系统中也有一个 umask,那进程运行起来该听谁的呢?? ----- 局部优先原理。肯定是听该进程内设置的 umask。
umask(0);
open("log.txt", O_WRONLY|O_CREAT, 0666);

在这里插入图片描述

3.2 O_TRUNC 截断长度(也即全覆盖式写入)

RETURN VALUE:
    open()  and  creat()  return  the new file descriptor, or -1 if an error occurred (in
    which case, errno is set appropriately).
// 其中的 file descriptor 就是文件描述符,int 类型,可用于关闭文件


int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);
// 第一次文件内容
const char* s = "this is a system call!";
write(fd, s, strlen(s));		// write 也是系统调用
close(fd);	// 关闭文件
// 第二次
const char* s = "aaaa";
write(fd, s, strlen(s));

在这里插入图片描述

现象:aaaa 覆盖了上一次的内容,但是并没有清空文件内容,因为上一次的其它内容还在。只是从文件头等长度覆盖而已。
也就是说,write 这个系统调用只会覆盖式写入,不会清空,是这样吗?? ------ 其实不是 write 的问题,是 open 写入方式的问题,flag 参数上我们只设置了 O_WRONLY|O_CREAT,如果想要达到每次打开文件都截断文件长度,使其为 0, 还需要加上 O_TRUNC,即

// 打开文件时,文件不存在自动创建,文件起始权限设置为 666。如果文件存在,清空文件内容。
int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);

3.3 O_APPEND 追加

// 打开文件时,文件不存在自动创建,文件起始权限设置为 666。如果文件存在,追加写入
// 追加写入与截断长度O_TRUNC是相矛盾的,无法同时存在
int fd = open("log.txt", O_WRONLY|O_CREAT|O_APPEND , 0666);

在了解了上述这些文件的系统调用,我们应该要明天,诸如 C 中的 fopen,fwrite 这样的库函数,底层一定是封装的系统调用。

FILE *fopen("log.txt", "w");  ===> int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
FILE *fopen("log.txt", "a");  ===> int fd = open("log.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);

4. 浅谈文件访问的本质

一个文件被打开,就需要被操作系统管理起来。管理的理念:先描述,再组织。

如何描述呢? ----- 还是 struct 结构体对象,其中必定要直接或间接的包含文件的各种属性,例如:

  • 文件所在磁盘的位置
  • 文件基本属性,权限,大小,读写位置,谁打开的…
  • 文件的内核缓冲区信息
  • struct file* next 指针

如何组织? ----- 操作系统会以双链表的形式,将所有文件对象连接起来,所以对文件的管理,就转换成对双链表的增删查改,新增一个文件,就创建一个 struct file 对象,插入到双链表中;关闭一个文件,在双链表中删除结点,释放 struct 对象,把数据刷新到磁盘。

文件打开都是通过代码实现的,编译运行后都会变成进程。换言之,我们需要知道,哪些文件被哪些进程所打开,即 要关联起进程与打开文件的关系。进程的 PCB 中会包含一个 struct file_struct* files 这样的结构体指针,指向的结构体内部含有一个数组 struct file_struct* fd_array[],数组内部存储的都是 struct file* 指针,指向的就是 stuct file 的地址(即被打开文件对象的地址)。而 这个数组就称为 文件描述符表!

在这里插入图片描述

因此 int open(const char *pathname, int flags, mode_t mode); open 的返回值就是记录文件对象地址在数组中所处的下标位置!
当我们 open 创建一个文件时,在文件对象的双链表中连接起来,然后把文件对象的地址填充到文件描述符表中,再把所在数组下标返回。当我们调用 write 对文件做写入操作时,传递的 fd 参数,就是通过进程的 PCB 中的 files_struct* 找到 files 这个结构体,然后根据 fd 索引到对应位置,根据里面的 strcut file* 指针找到指定的文件对象,对其做增删查改等操作!

4.1 简证

上面讲了那么多,fd 就是数组下标,如何证明呢??

int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);
int fd2 = open("log2.txt", O_WRONLY|O_CREAT, 0666);
int fd3 = open("log3.txt", O_WRONLY|O_CREAT, 0666);
int fd4 = open("log4.txt", O_WRONLY|O_CREAT, 0666);
printf("fd: %d\n", fd);
printf("fd2: %d\n", fd2);
printf("fd3: %d\n", fd3);
printf("fd4: %d\n", fd4);

在这里插入图片描述

数组下标是从0开始的,但是我打开的第一个文件,它的下标是3。那么 0 1 2 这三个位置去哪了呢??

文章一开始的时候就说过,C 语言会默认为我们打开三个输入输出流 stdin,stdout,stderr。我们可以再来证明一下,这三个默认打开的,就是存储在 fd_array[ ] 数组中的0、1、2号下标处。

char input[1024] = {0};
read(0, input, sizeof(input));
printf("ehco: %s\n", input);
printf("--------------------\n");
const char* s = "hello, linux\n";
write(1, s,strlen(s));
write(2, s,strlen(s));

在这里插入图片描述
wirte 和 read 这两个系统调用,第一个参数传递的可是 fd 啊!fd 是什么?? fd 不就数组下标吗,所以这次我直接往 0 1 2 下标做读写,0 号下标对应的就是键盘文件,而 1 号 显示器文件,2 号也是显示器,分别对应 C 默认打开的 stdin,stdout,stderr。

  • C语言程序默认打开三个标准输入输出流,真的是 C 语言做的事情吗??又或者这是语言上的特性吗??
    c/c++、java等语言都会默认会打开三个输入输出流,这是操作系统的特性!是操作系统做的事情,因为一起程序运行起来都会变成进程,进程会默认打开键盘、显示器,而进程是操作系统的!

  • 为什么操作系统要默认打开键盘和显示器呢??
    因为用户有需求!当我们启动操作系统时,操作系统就会默认打开键盘文件和显示器文件;当启动一个进程时,操作系统只需要讲键盘文件和显示器文件的地址填充到 fd_array[ ] 数组的 0 1 2 号下标处即可。

我们说过,C 中的 fopen 是对系统调用 open 的封装,但是他们两个的返回值好像看起来就不一样,一个是 FILE,一个是 int,FILE 就是 C 库自己封装的结构体,这个结构体里面必定封装了文件描述符。

再次证明:

printf("stdin->fd: %d\n", stdin->_fileno);
printf("stdout->fd: %d\n", stdout->_fileno);
printf("stderr->fd: %d\n", stderr->_fileno);

在这里插入图片描述
C 中的 stdin,stdout,stderr 也必定封装了 fd 这样的字段(即 _fileno),而三个默认打开的输入输出流,fd 依次就是 0 1 2。这就是我们打开的文件,fd 一定是从 3 开始的,不可能从 0 1 2 这几个下标开始(在不关闭文件描述符 0 1 2 的前提下)。

close(1);

int ret = printf("stdin->fd: %d\n", stdin->_fileno);
printf("stdout->fd: %d\n", stdout->_fileno);
printf("stderr->fd: %d\n", stderr->_fileno);

fprintf(stderr, "printf ret: %d\n", ret);

在这里插入图片描述

当我们 close(1) 关闭输出流之后,printf 确实就无法使用了。而把 1 号文件描述符关了,还有 2 号指向显示器文件,所以stderr 依旧可以正常写入。

我们要知道,一个文件是可以被多个进程打开的;一个进程中,不同的文件描述符中 struct file* 也可以指向同一个文件。在显示器文件中会维护一个引用计数 count,当有一个 struct file* 指向显示器文件,count++。当我们调用 close(1) 时,操作系统就对该进程的 1号文件描述符的指向置空,显示器文件中的 count - 1,再判断 count == 0 ? 如果还没有等于 0,就说明还有其它的 文件描述符指向该文件,等于 0 时,就回收该文件对象。

所以一句话总结:任何编程语言,都会默认打开三个输入输出(因为进程会默认打开)一个用于输入,两个用于输出,并且这些输入输出流一定直接或间接的包含 fd 字段,如果没有包含 fd,那么就无法访问文件。


如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!

感谢各位观看!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2101486.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高级算法设计与分析 学习笔记 2 希尔排序 线性时间内的排序——计数排序,基数排序,桶排序

希尔排序&#xff08;比较排序&#xff09; 要选定一个步长&#xff08;比如4&#xff09;&#xff0c;那么0&#xff0c;4&#xff0c;8……它们是一组&#xff0c;1&#xff0c;5&#xff0c;9……他们是一组。分组排完之后再换成2步长&#xff0c;最后改成1&#xff0c;就行…

牛心包瓣类医用生物瓣膜厚度无损检测

关键字&#xff1a;牛心包瓣膜&#xff0c;牛心包瓣叶&#xff0c;生物瓣膜&#xff0c;生物心脏膜&#xff0c;测厚仪&#xff0c;瓣膜生产&#xff0c;瓣膜检测设备&#xff0c; 牛心包瓣叶的厚度和轮廓所需的高精度取决于多个因素&#xff0c;包括瓣膜的制造标准、临床应用需…

百度飞将 paddle ,实现贝叶斯神经网络 bayesue neure network bnn,aistudio公开项目 复现效果不好

论文复现赛&#xff1a;贝叶斯神经网络 - 飞桨AI Studio星河社区 https://github.com/hrdwsong/BayesianCNN-Paddle 论文复现&#xff1a;Weight Uncertainty in Neural Networks 本项目复现时遇到一个比较大的问题&#xff0c;用pytorch顺利跑通源代码后&#xff0c;修改至pad…

【每日一练】python之tkinter的Label标签基础用法

""" 什么是tkinter窗口&#xff1f;tkinter是python中一个标准的库&#xff0c;用于创建图形界面&#xff08;GUI&#xff09;应用程序&#xff0c;它提供了一组工具和组件&#xff0c;使开发者能够在Python中创建窗口、按钮、标签、文本框、菜单等各种界面元素…

基于协同过滤的电影推荐系统

推荐系统已经成为当今互联网平台不可或缺的一部分&#xff0c;尤其是在电影、音乐和电子商务等领域。本文将带您深入探讨如何利用协同过滤算法&#xff0c;构建一个功能齐全的电影推荐系统。我们将结合Python、Django框架以及协同过滤算法&#xff0c;逐步实现这一目标。 完整…

Go父类调用子类方法(虚函数调用)

前言 在Go语言中&#xff0c;支持组合而不是继承。网上都说可以通过接口和结构体内嵌来模拟面向对象编程中的子类和父类关系。但给的例子或写法感觉都不是很好&#xff0c;难以达到我的目的&#xff08;比如通过模板模式实现代码的重用等&#xff09;。因此调查了一下实现方式…

内裤洗衣机需要一人一台吗?快来围观2024年五大好货集合

随着市面上的内衣抑菌产品越来越多&#xff0c;内衣洗衣机的质量也是参差不齐&#xff0c;一些网红跨界品牌内衣洗衣机的用料和做工品质较差&#xff0c;使用过程中出现清洗不干净和稳定性不足等问题。那么选购内衣洗衣机需要注意什么呢&#xff1f;我作为一名小家电测评博主&a…

pikachu文件包含漏洞靶场(本地文件包含+远程文件包含关卡)

本地文件包含 1.来到关卡随便点击一个提交 可以发现这里可以读取文件 这是1.txt内容 能读取到上一级文件那么也就可以读取本地文件 上传一个jpg文件 拿去连就ok了 远程包含 包含写木马的文件 该文件内容如下&#xff0c;其作用是在fi_remote.php文件的同级目录下新建一个文…

Java 基于微信小程序的小区服务管理系统,附源码

博主介绍&#xff1a;✌stormjun、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&…

【赵渝强老师】MongoDB的WiredTiger存储引擎

WiredTiger提供文档级别&#xff08;Document-Level&#xff09;的并发控制&#xff0c;检查点&#xff08;CheckPoint&#xff09;&#xff0c;数据压缩和本地数据加密&#xff08; Native Encryption&#xff09;等功能。从MongoDB 3.2 版本开始&#xff0c;WiredTiger成为Mo…

大带宽服务器推流延迟怎么回事

大带宽服务器推流延迟的原因可能涉及多个方面&#xff0c;包括编码解码的延迟、网络传输延迟、CDN分发延迟以及播放端的缓冲处理等。下面将详细解释各个影响因素&#xff0c;并提出相应的优化建议&#xff1a; 1. 编码解码的延迟 视频编码格式的影响&#xff1a;不同的编码格式…

net、udp、tcp

Makefile的main.c文件中的全局变量SONG song,要在fun.c文件里面写成extern SONG song 编译方法 第一次编写 或 网络编程 物理层的网线规定有八根,颜色不一样,功能不一样,光猫把光信号转换成电信号,光纤10Gb WiFi叫无线局域网,一般也就50米左右,手机流量叫蜂窝网络,…

无限延展(倒推法)

本题不妨逆推。 对于长度为的字符串 ,若要求第位的延展,考虑 在最后一次延展前的位置。 若延展结束后的长度为,每次考虑以下内容: 若 ​,说明本次伸展无效, ,

CTFHub技能树-备份文件下载-bak文件

当开发人员在线上环境中对源代码进行了备份操作&#xff0c;并且将备份文件放在了 web 目录下&#xff0c;就会引起网站源码泄露。 使用dirsearch扫描出index.php.bak 有些时候网站管理员可能为了方便&#xff0c;会在修改某个文件的时候先复制一份&#xff0c;将其命名为xxx.b…

没关系,会一手Git版本控制就行(全)

Git版本控制 文章目录 Git版本控制1. 版本控制1.1 概述1.2 版本控制优点1.3 本地版本控制系统&#xff08;离线版&#xff09;1.4 集中化的版本控制系统&#xff08;联网版&#xff09;1.5 分布式版本控制系统&#xff08;离线联网版&#xff09; 2. Git概述2.1 Git基本工作流程…

BUUCTF PWN wp--ciscn_2019_n_8

第一步 checksec一下&#xff0c;本题为32位。 分析一下保护机制&#xff1a; 一、RELRO: Partial RELRO Partial RELRO 提供了一定程度的保护。在这种情况下&#xff0c;部分重定位表在程序启动时被设置为只读。这可以防止一些针对重定位表的攻击&#xff0c;比如通过篡改重…

Elasticsearch 介绍

1、课程介绍 1.1 ES 8.x 演化进程 版本号发布日期多少个次要版本迭代历时8.02022年2月11日&#xff1f;至今7.02019年4月11日17个次要版本34个月6.02017年11月15日8个次要版本17个月5.02016年10月27日6个次要版本13个月 2、Elasticsearch 是什么 2.1 概念 2.1.1 标准定义 …

QLineEdit中文本显示不全,部分字符显示空白问题

环境 QT5.14.2 Windows 7 现象 触发某个条件后&#xff0c;使用QLineEdit的setText方法设置文本后&#xff0c;文本部分字符缺失&#xff0c;现象如下&#xff08;小数点后都是4位&#xff09;&#xff1a; 解决办法 设置根据显示器的像素密度进行自动缩放&#xff1b;再主…

VMware启动报错-Intel VT-x处于禁用状态,进入BIOS更改CPU设置

问题描述 VMware启动虚拟机失败&#xff0c;报错显示Intel VT-x处于禁用状态。 原因分析 打开主机的任务管理器&#xff0c;找到CPU&#xff0c;发现虚拟化处于禁用状态&#xff0c;查阅资料之后发现&#xff0c;进入BIOS模式将CPU虚拟化禁用开启即可解决。 解决步骤 1、…

KAN 学习 Day1 —— 模型框架解析及 HelloKAN

说明 最近了解到了一个新东西——KAN&#xff0c;我的毕设导师给推荐的船新框架。我看过很多剖析其原理的文章&#xff0c;发现大家对其持有的观点都各不相同&#xff0c;有的说可以颠覆传统MLP&#xff0c;有的说可以和Transformer同等地位&#xff0c;但是也有人说它训练速度…