【Linux学习】基础IO——系统调用 | 文件描述符fd | 重定向

news2024/12/25 13:29:52

🐱作者:一只大喵咪1201
🐱专栏:《Linux学习》
🔥格言:你只管努力,剩下的交给时间!
图

基础IO

  • 🍎文件操作
    • 🍉使用C接口进行文件操作
    • 🍉文件操作的系统调用
  • 🍎文件描述符fd
    • 🍉文件描述符fd=0/1/2
    • 🍉文件描述符fd的分配规则
  • 🍎重定向
    • 🍉重定向系统调用
    • 🍉进程独立性
  • 🍎Linux下一切皆文件
  • 🍎总结

在正式开始介绍之前,本喵先重新谈论一下文件:

  1. 空文件也是要在内存中占据空间的,因为它还有属性数据。
  2. 文件 = 属性 + 内容
  3. 文件操作 = 对内容 + 对属性 或者对内容和属性的操作。
  4. 标定一个文件的时候,必须使用:路径 + 文件名,因为文件具有唯一性。
  5. 如果没有指名文件路径,默认是对当前路径的文件进行访问。
  6. 一个文件,在没有被打开的时候,是不能进行访问的。
  7. 二进制可执行文件,在没有运行的时候,所谓的文件操作都是没有执行的。
  8. 磁盘上的文件分为被打开的文件和没有被打开的文件。

结论:文件操作的本质,是进程和被打开文件之间的关系。

上面对文件的认识,是我们所有使用Linux用户的共识。

🍎文件操作

🍉使用C接口进行文件操作

图
图
在将我们的程序编译完成以后,再运行,发现生成了一个新的文件,并且文件中的内容和我们代码中写的一样。

  • 这个过程中,使用的是C语言的接口进行文件操作。
  • 以写的方式打开文件名问log.txt的文件,没有的这个文件的话就会创建。
  • 使用C接口向该文件中写入内容。

本喵曾在文章C语言文件操作一文中详细介绍过C语言的文件操作接口,有兴趣的小伙伴可以去看看。

不同的编程语言都有文件操作的接口,包括C++,Java,Python,php等等语言,并且它们的操作接口函数都不一样,但是它们所在的系统都是Linux系统。

无论上层语言如何变化,但是进行文件操作的时候,各种语言最终都会调用Linux的文件操作的系统调用接口。

🍉文件操作的系统调用

open函数:

图
可以看到,函数声明有两个,一个是两个参数的,一个是三个参数的,它们必然不是函数重载,因为Linux是用纯C实现的。

参数解释:

  • const char* pathname:这是文件路径,也就是我们要打开的文件所在的路径,其中包括文件名,如果没有路径只有文件名的话,默认在当前路径打开。
  • int flags:打开方式选项标志位。在使用C语言进行文件操作的时候,打开方式有“w”,“r”,“a”等方式,系统调用open也有,只是将这些标志放在了一个32位的变量中。图
    不同打开方式,其对应的比特位就会被置1。然后将这个设置好的flags变量传给open系统调用,就会按照相应的方式打开文件。
  • mode_t mode:它是权限值,如果这个文件不存在,那么以写的方式打开的时候就会创建这个文件,在创建文件的时候需要给这个文件设定权限(使用八进制数)。如果这个文件存在的话,那么就不用传第三个参数了,因为文件的权限已经确定了。
  • 返回值:是一个int类型的参数,具体的在后面本喵会介绍,但是如果打开失败就会返回-1。

图
如果有多个选项需要按位或在一起,共同组成int flags变量传给open系统调用。

常用选项功能
O_RDONLY只读
O_WRONLY只写
O_RDWR读写
O_CREAT以写方式打开时,如果文件不存在则创建文件
O_TRUNC以写方式打开时,清空文件中原内容再写
O_APPEND追加方式打开文件

此时我们没有给open传第三个参数。
图
执行我们写的代码后,log.txt文件是创建了,但是它是红色的,说明它有错误。可以看到它前面的权限是乱的,因为我们没有指定创建文件时的权限。

图
将第三个参数加上,因为创建的是普通文件,所以就给它的默认权限是0666。

图
可以看到,此时创建的文件就正常了,但是权限并不是我们设定的0666,而是0664,这是因为有默认权限掩码(umask)的影响。

图
我们也可以使用系统调用在程序中改变umask的值。

图
将默认权限掩码改成0以后,再创建的文件的权限就是我们设置的0666了。本喵曾在文章【Linux学习】权限详细讲解过权限相关的内容,有兴趣的小伙伴可以去看看。

close函数:

图
将打开文件时返回的int类型的fd值传给close系统调用后,这个被打开的文件就被关闭了。关闭成功返回0,如果关闭失败就返回-1。

write函数:

图
参数解释:

  • int fd:打开文件时返回的int类型整数(文件描述符)。
  • const void* buf:要写入的数组地址。对于系统调用来说,它并不在意写入的数据是什么类型的,它接收到的数据都是二进制的数字,然后按照字节为单位写入。
  • size_t count:要写入的字节个数。
  • 返回类型size_t:写入多个自己就返回多少。

清空方式写入:

图
图
可以看到,成功将字符串写入到了log.txt文件中。

追加方式写入:

图
使用追加方式打开文件,再多次使用系统调用write写入。

图
可以看到在原有的基础上,多了5行。

read函数:

图
参数解释:

  • int fd:打开文件时返回的文件描述符。
  • void* buf:从文件中读取的数据放在这个数组中,同样系统不管文件中的数据类型是什么,都是按字节放入这个数组中。
  • size_t count:要读取的字节个数。
  • ssize_t:读取了多少个返回多少。

图
此时log.txt中有五行内容,如上图绿色框中所示。

图

使用只读方式打开,并且将读取的内容放在ch数组中。

图
编译执行之后,成功打印出了log.txt中的内容。

  • 接收文件中数据的数组我们设定的大小是1024个字节,显然文件中的数据时没有这么大的。
  • read系统调用的第三个参数传的也是1024,但是肯定没有读取到1024个字节。
  • 每一个文件中都有一个文件结束符标志,在C语言中我们见过,也就是EOF,read函数会自动判断文件是否结束,所以即使设置读取的字节是1024个,但是能够符合我们对读取要求,将文件中的内容都读取出来。

以上便是我们常用的文件操作的系统调用。可以看到,对文件进行什么样的操作,取决于以什么样的方式打开文件,再用相应的操作函数去操作文件。

继续和C语言对应:

图
C语言的文件操作函数,封装了对应的系统调用接口函数。所以说,无论什么语言,文件操作相关的函数都是对系统调用的封装。

🍎文件描述符fd

在使用系统调用open时,返回的那个整数就是文件描述符。

图
将文件名使用宏的方式打开多个文件。

图

现在我们见到了文件描述符,发现它就是几个数字。

  • 前面本喵已经讲过,文件操作的本质就是进程被打开文件的关系。
  • 系统中会存在大量被打开的文件,而操作系统同样会管理这些被打开的文件。
  • 管理的方式和管理进程类似,也是采用先描述,再组织的方式。

当一个文件被打开后,操作系统会创建一个对应的结构体对象,类型是struct file。

struct file
{
	//文件大小
	//文件类型
	......
	//文件的各种属性
}
  • 每打开一个文件,操作系统就会创建这样的一个结构体对象将被打开的文件描述出来。
  • 将多个这样的结构体对象采用一定的方式组织起来,比如链表的方式,以方便操作系统管理这些被打开的文件。

在描述进程的结构体task_struct中,有一个指针,struct files_struct* files,这个指针指向一个结构体对象,该对象类型如下:

struct files_struct
{
	//......
	struct file* array[];
}
  • struct files_struct结构体中存在一个指针数组array,该数组中的指针指向的是一个个struct file类型的结构体对象。
  • 换言之,该数组中放的是被打开文件结构体对象的地址。
  • 每一个被指向的struct file结构体对象都描述着一个被打开的文件。

在前面我们看到,打印出来的fd值是连续的小整数,这些小整数就是struct files_struct 结构体中指针数组struct file* array[]的下标。

文件描述符的本质,就是数组的下标。

下面本喵通过一张示意图来展示一下:

图

  • 当一个程序被加载到内存中,操作系统会创建一个结构体struct task_struct对象,在该结构体中有一个指针struct files_struct* files,指向一个struct files_struct结构体对象。
  • 这个结构体也被叫做进程描述符表,该结构体中有一个数组struct file* array[],数组中存放的是被打开文件的结构体对象的地址。如上图中,下标为3,也就是fd的是3的时候,访问到的是struct file* array[3]。
  • 通过数组中访问到的地址,可以找到对应打开文件的结构体对象,如上图中的struct file log.txt。

只有被打开的文件才会在内存中创建struct file结构体对象,没有被打开的文件就静静的躺在磁盘上。

不是该进程打开的文件,该进程执行的文件描述符表中也没有这个文件的地址。

🍉文件描述符fd=0/1/2

在上面打开多个文件的时候,我们将打开文件的fd值打印出来,发现它是从3开始的。

图
那么fd = 0/1/2是什么呢?

C默认会打开三个输入输出流,分别是stdin,stdout,stderr。

图
可以看到,这三个流是FILE*类型的指针,暂时不用管FILE是什么,只需要知道它是一个结构体。

图
使用C语言的文件操作结构打开一个文件,再使用系统调用去向文件中写内容。

图

成功的写入了。

  • 系统调用write第一个参数需要传文件描述符fd。
  • 上面代码中,传入的是FILE->_fileno,并且成功运行。
  • 说明FILE中的_fileno就是文件描述符fd。

我们此时已经确定的知道了,FILE结构体中是有文件描述符的。

图
在之前的代码中,加上打印三个流的文件描述符的语句,如上图中红色框所示。

图
文件描述符0 1 2出现了。

  • fd = 0:标准输入流(stdin)
  • fd = 1:标准输出流(stdout)
  • fd = 2:标准错误(stderr)

此时我们便清楚了为什么我们打开的文件,文件描述符是从3开始的,因为012被默认打开的三个流占据了。

图
每个进程的文件描述符表如上图所示。

🍉文件描述符fd的分配规则

为什么我们打开的文件,fd是从3开始的?不是从5或者6开始的呢?

图
我们将fd=0的标准输入流关闭掉,再打开文件,并且打印fd值。

图
我们发现此时的fd成了0,而不是3了。

图
同样的,将fd=2的流关闭,在打开文件。

图
此时fd的值又成了2了。

根据这个现象,可以得出结论:文件描述符fd的分别规则是:从小到大,按顺序查找,将没有被占用的数组下标作为被打开文件的文件描述符fd值。

🍎重定向

前面我们只关闭过0和2,没有关闭过1,现在我们关闭一下1来看看。

图
将标准输出关闭,然后打开文件,并且打印出打开文件的文件描述符fd。

图
此时运行该程序后,什么都没有显示。

  • 因为将标准输出关闭了,所以无法显示。

根据前面分析的文件描述符分配规则,可以推断出,将标准输出关闭以后,再打开一个文件,此时这个文件的文件描述符fd等于1。

  • 在将fd=1关闭后,再打开一个文件,从小到大按顺序查找,发现数组下标为1的位置没有被占用,所以新打开文件的fd就等于1。
  • printf函数原本是要输出到标准输出的,也就是fd为1的数组中指向的struct file对象的地址。
  • 此时下标为1的数组中不再是标准输出了,而变成了我们新打开文件的地址。
  • 但是printf已经写死了,它仍然会写入到fd为1的文件中,所以原本打印在显示器上的内容此时会写入到新打开的文件中。

图
查看新打开的文件内容,发现是原本应该打印在屏幕上的内容。

图
同样的,将1关闭以后,以追加的方式打开一个文件,并且写入多行内容。

图
同样没有打印到屏幕上,而是打印到了新打开的文件中。

图

这种将本应该输出到标准输出改为输出到其他文件中的行为称为重定向。

重定向的本质上:上层语言使用的fd不变,在内核中改变fd对应的struct file*地址。

🍉重定向系统调用

上面重定向的实现总感觉怪怪的,还需要关闭,然后再打开新文件,而且也不是很方便,所以操作系统提供了一个系统调用,可以直接实现重定向。

图
我们常用的函数是dup2。

参数解释:

  • 第一个参数是我们新打开文件的fd。
  • 第二个参数是标准输出到fd,也就是1。

图

图
原本应该输出到显示器上的内容输出到了文件log.txt中。

我们上面一直演示的都是本应该输出到显示器重定向输出到了文件中,这种从显示器到文件的重定向叫做输出重定向。

在shell中有命令可以直接实现输出重定向:

图
ll命令原本是将文件包括属性显示到屏幕上的,使用大于号>输出重定向到了log.txt文件中,如上图绿色框中所示。

图

  • 使用只读方式打开文件log.txt该文件原本就存在。
    图
    文件中内容是如上图中红色框。
  • 将原本struct file* array[]数组中下标0的内容改成下标为fd的内容,也就是dup2(fd,0)的作用。
  • 使用标准输入函数fgets,从标准输入流也就是键盘中读取字符串。
  • 屏幕上打印读取到的内容。

图
运行时直接输出log.txt中的内容,没有从键盘获取数据。也就是说,fgets函数是从文件中获取到内容,而不是标准输入。

这种从标准输入到文件的重定向叫做输入重定向。

shell中同样有输入重定向的命令:是小于号<,具体本喵就不显示了。

图
使用追加方式打开文件,并且采用输出重定向方式输出内容。

图

在原本文件内容都基础上追加内容。

这种以追加方式打开文件,并且采用输出重定向的方式称为追加重定向。

shell中同样有追加重定向:

图

使用双大于号>>,实现了追加重定向,在原本log.txt内容都基础上追加内容。

🍉进程独立性

子进程重定向了以后,会影响父进程吗?根据进程独立性我们可以知道,肯定是不会影响到。

图
在子进程中进行输出重定向,父进程同样在标准输出打印。

图
子进程中的重定向,并没有影响父进程标准输出的打印。

图

  • 有两个进程,一个父进程,一个子进程,操作系统维护着两个task_struct结构体,如上图红色框所示。
  • 每个进程的PCB中都有一个struct files_struct*的指针files。它们各自指向的struct files_struct结构体中都有一个文件描述符表。
  • 两个文件描述符表中的内容在子进程刚创建时是一样的,所以它们都指向相同的被打开的文件。
  • 当子进程将自己文件描述符表中下标为1的文件关闭以后,并不影响父进程文件描述符表中下标为1的数组中的内容。

每个进程都会维护自己的文件描述符表,所以多个进程就会存在多个文件描述符表,但是这些表中的指针指向的被打开文件只有一套。

某个进程进行文件的打开与关闭操作时,只需要修改自己的文件描述符表就可以,不会对其他进程造成任何影响。

🍎Linux下一切皆文件

Linux下一切皆文件,这句话相信每一个学习Linux的人都听过,那么如何理解呢?

同样以文件操作的角度来看待硬件,如下图所示:

tu

  • 每一个硬件,操作系统都会维护一个struct file类型的结构体,硬件的各种信息都在这个结构体中,并且还有对应读写函数指针(对硬件的操作主要就是读写)。
  • 每个硬件的具体读写函数的实现方式都在驱动层中,使用到相应的硬件时,操作系统会通过维护的结构体中的函数指针调用相应的读写函数。
  1. 站在操作系统的角度来看下层,无论驱动层和硬件层中有什么,在它看来都是struct file结构体,都是通过维护这个结构体来控制各种硬件。
  2. 站在操作系统的角度来看上层,无论用户层以及系统调用有什么,在它看来都是一个个进程,都是一个个的task_struct结构体,都是通过维护这个结构体来调度各个进程的。

真正的文件在操作系统中的体现也是结构体,操作系统维护的同样是被打开文件的结构体而不是文件本身。

一切皆文件是指:在操作系统中一切都是结构体。

🍎总结

这篇文章主要讲解的是基础IO的应用,包括文件操作的系统调用,文件描述符fd的本质,重定向,以及如何理解Linux下一切皆文件。

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

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

相关文章

Ingress

Ingres 目录 文章目录Ingres目录本节实战1、Ingress是什么2、定义1.rules2.Resource3.pathType4.IngressClass5.TLS3、Ingress-controller1.什么是Ingress-controller2.其他ingress-controller控制器FAQ零碎ingress就是借用service实现服务发现机制关于我最后本节实战 无 1、…

MySQL基础(一)SQL分类、导入、SELECT语句,运算符

目录 MySQL安装以及相关工具 SQL分类 导入数据 最基本的SELECT语句 SELECT FROM 列的别名 去除重复行 着重号 查询常数 描述表结构 过滤数据&#xff08;重要&#xff09; 运算符 算数运算符 比较运算符 符号运算符 非符号运算符 逻辑运算符 位运算符 MySQL安…

【C++】继承与多态

目录前言1. 继承1.1 继承的概念1.2 继承的定义1.3 切片赋值1.4 继承中的作用域1.5 派生类的默认成员函数1.6 继承与友元、静态成员1.7 多继承、菱形继承、菱形虚拟继承1.7.1 区分单继承与多继承1.7.2 菱形继承1.7.3 菱形虚拟继承1.7.4 菱形虚拟继承的原理2. 多态2.1 概念2.2 多…

Elasticsearch实战之(商品搜索API实现)

Elasticsearch实战之&#xff08;商品搜索API实现&#xff09; 1、案例介绍 某医药电商H5商城基于Elasticsearch实现商品搜索 2、案例分析 2.1、数据来源 商品库 - 平台运营维护商品库 - 供应商维护 2.2、数据同步 2.2.1、同步双写 写入 MySQL&#xff0c;直接也同步往…

如何使用C2concealer生成随机化的C2 Malleable配置文件

关于C2concealer C2concealer是一款功能强大的命令行工具&#xff0c;在该工具的帮助下&#xff0c;广大研究人员可以轻松生成随机化的C2 Malleable配置文件&#xff0c;以便在Cobalt Strike中使用。 工具运行机制 开发人员对Cobalt Strike文档进行了详细的研究&#xff0c;…

【转载】2020融云:基于WebRTC的低延迟视频直播

原文直接访问本文是读书笔记。基于WebRTC的低延迟视频直播 需要学习rtp包的缓存设计,于是找到了这一篇文章rtp包缓存 如何适应直播需求?直播与实时通信的区别 流量更少: RTMP或者HLS主要基于TCP传输,WebRTC是基于UDP的传输, **UDP协议的头小。**TCP为了保证传输质量,因…

Zotero设置毕业论文/中文期刊参考文献格式

大家在使用zotero时很容易遇到的问题&#xff1a; 英文参考文献中有多个作者时出现“等”&#xff0c;而不是用"et al"引文最后面有不需要的DOI号&#xff0c;或者论文链接对于一些期刊分类上会出现OL字样&#xff0c;即[J/OL]作者名为全大写 本文主要解决以上几个…

string函数以及string常用接口

本文介绍的是C关键字string中一些重要用法&#xff0c;以及各种字符串序列的处理操作 ——飘飘何所似&#xff0c;天地一沙鸥 文章目录前言一、string&#xff08;字符串类&#xff09;二、string类对象的容量操作2.1 size/length2.2 capacity2.3 empty/clear2.4 resize/reser…

教你如何搭建设备-保养管理系统,demo可分享

1、简介1.1、案例简介本文将介绍&#xff0c;如何搭建设备-保养管理。1.2、应用场景设备管理员进行制定设备保养计划、记录设备保养信息、可以查看设备保养日历。2、设置方法2.1、表单搭建1&#xff09;新建表单【设备档案-履历表】&#xff0c;字段设置如下&#xff1a;名称类…

SSM SpringBoot vue 在线教学质量评价系统

SSM SpringBoot vue 在线教学质量评价系统 SSM 在线教学质量评价系统 功能介绍 首页 图片轮播展示 登录 学生注册 教师注册 督导注册 教师展示 教师详情 学生评价 课程信息 课程详情 提交选修该课 学生选课 学生留言 个人中心 后台管理 管理员或学生或教师或督导登录 个人中…

项目经理处理团队冲突 5大注意事项

1、在时间、场景、体验矩阵中的5种处理方式 第一种方式&#xff1a;强迫命令&#xff0c;即职位高的一方在不考虑对方感受的情况下&#xff0c;强迫职位低的一方接受自己的意见。这种处理方式的适用场景为重要且紧急&#xff0c;这种方式团队成员的体验感低。 第二种方式&#…

Linux 学习笔记(一):终端 和 Shell 的区别和联系

一、Linux 介绍 1、什么是 Linux Linux 就是一个操作系统&#xff0c;全称 GNU/Linux&#xff0c;是一种类 Unix 操作系统Linux 一开始是没有图形界面的&#xff0c;所有操作都靠 命令 完成。如 磁盘操作、文件存取、目录操作、进程管理、文件权限 等等&#xff0c;可以说 Li…

Android Handler机制(二) Handler 实现原理

一. 前言 接上一篇文章为什么设计Handler , 我们来继续讲解一下Handler的实现原理, 俗话说一个好汉三个帮, 接下来一步一步引入各个主角,并说明它们在Handler机制中扮演的角色和作用. 二. Handler实现原理 首先我们先确定一个结论: 使用 Handler 是希望它被实例化在哪个线程&a…

不同序列模型的输入和输出总结

不同序列模型的输入和输出总结 文章目录不同序列模型的输入和输出总结RNNLSTMGRURNN RNN 是迭代输出&#xff1a; 输入第一个 -> 输出第二个&#xff0c; 输入第二个 -> 输出第三个&#xff0c; 输出倒数第二个 -> 输出最后一个。 LSTM LSTM 也是迭代输出&#xff…

Ep_操作系统面试题-什么是协程

协程 是一种 比线程更加轻量级的存 在&#xff0c;一个线程可以拥有多个协程。是一个特殊的 函数 &#xff0c;这个函数可以在某个地方挂起&#xff0c;并且可以重新在挂起处外继续运行。协程 不是被操作系统内核所管理 &#xff0c; 而完全是由程序所控制&#xff08;也就是在…

冯诺依曼体系结构与操作系统的理解

✅<1>主页&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;操作系统 &#x1f4ac;<3>前言&#xff1a;今天来介绍一下冯诺依曼体系结构&#xff0c;和操作系统的理解。 目录 1.冯诺依曼体系结构 冯诺依曼体系的工作原理&#xff1a; 为…

注意力机制详解系列(四):混合注意力机制

👨‍💻作者简介: 大数据专业硕士在读,CSDN人工智能领域博客专家,阿里云专家博主,专注大数据与人工智能知识分享。 🎉专栏推荐: 目前在写CV方向专栏,更新不限于目标检测、OCR、图像分类、图像分割等方向,目前活动仅19.9,虽然付费但会长期更新,感兴趣的小伙伴可以…

超详细Eclipse配置JDK

在此附上Eclipse安装教程 超详细Eclipse安装教程 在此附上JDK1.8安装配置教程 超详细JDK1.8安装与配置 ①打开Eclipse–>点击Window–>点击Preferences ②找到Java–>找到Installed JREs–>点击Add… ③选中Standard VM–>之后点击Next ④点击Directory找…

Substrate 基础教程(Tutorials) -- 模拟网络 添加可信节点

三、模拟网络 本教程基本介绍了如何使用一个私有验证器&#xff08;validators&#xff09;的授权集合来启动私有区块链网络。 Substrate节点模板使用授权共识模型(authority consensus model)&#xff0c;该模型将块生产限制为授权帐户的旋转列表(rotating list)。授权帐户(…

活动目录(Active Directory)批量用户管理

在大型复杂的 Windows 网络中&#xff0c;批量管理 AD 用户可能是一项挑战。此外&#xff0c;使用本机工具和 PowerShell 脚本需要深入了解 AD 和脚本&#xff0c;才能在 AD 中完成批量用户管理。 ADManager Plus是一款AD管理和报告软件&#xff0c;允许您使用CSV创建和管理多…