从0开始linux(20)——文件(1)文件描述符

欢迎来到博主的专栏:从0开始linux
博主ID:代码小豪

文章目录

    • 文件
    • 打开文件
    • 文件修饰符
    • 从linux源码了解文件描述符

文件

首先我们先来搞清楚文件是什么?文件其实就是一段数据,是一段存储在磁盘当中的数据,文件是由文件内容和文件属性组成的。

文件的属性即文件的权限,拥有者,内容大小,文件名等,而内容则是我们保存在文件当中的数据。

博主在C语言专栏当中写过关于C语言文件操作,比如fopen,fclose之类的函数,但是这些东西都是仅仅在语言层接触到了文件,我们还没有在系统方面真正了解文件这个概念。

首先,如果我们要对文件进行读写操作,首先第一步就是要打开文件,所谓的打开文件,其实就是将文件从磁盘,读取到内存当中,因为根据冯诺依曼体系,cpu不会对磁盘这些外设直接做I/O,而是与内存做I/O,因此打开文件的实质,其实是要将文件从磁盘中加载到内存。

而我们在进程(0)当中了解到,我们作为用户,也是不能直接与外设进行交互的,用户与外设之间隔着一个操作系统,所谓的打开文件,其实就是告诉操作系统,我要打开某某文件,接着操作系统再将文件从磁盘加载到内存当中。

既然你打开文件了,那么总有将其关闭的时候吧,那么关闭文件时,我们是不是要将文件的数据保存到磁盘当中呢?这又是一个内存与磁盘做I/O的操作,既然文件从磁盘加载到内存需要操作系统,那么文件从内存保存到磁盘,当然也离不开操作系统了,因此操作系统肯定与文件有着千丝万缕的关系,那么具体操作系统是如何管理文件的呢?请听我娓娓道来。

打开文件

既然我们打算在系统的层面了解文件,那么我们就抛弃语言层面的fopen接口,改为使用linux的系统调用。在linux中,打开文件的系统调用为open()函数。其函数原型如下:

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

open函数的头文件有:<sys/types.h>,<sys/stat.h>,<fcntl.h>

(首先,open函数是用C语言写的,但是C语言没有函数重载这个东西,因此博主也不清楚linux是怎么做到让c函数出现同名函数的情况,也许是宏替换?)

我们先从两参数的open函数说起,首先第一个参数叫做文件路径,即我们要打开的文件,其所处的路径,文件名,我们要用字符串的形式写出。而第二个参数称为标志位,我们常用的标志位有以下几种

  • O_RDONLY/O_WRONLY/O_RDWR,分别代表,以只读的方式打开文件,以只写的方式打开文件,以读写的方式打开文件
  • O_TRUNC,打开文件时,如果文件存在,而且打开方式为写(O_WRONLY或者O_RDWR),则将文件内的数据清空
  • O_APPEND,打开文件时,从文件末尾的位置写入数据
  • O_CREAT,打开文件时,若文件不存,则创建一个新的文件。

这些标志位其实可以组合使用,比如我们想用只写的打开某个文件时,顺便将文件的内容给清空了,那么就可以使用O_WRONLY和O_TRUNC组合起来的方式,调用open函数。此时有人可能发现了,open关于标记位的参数只有一个flag,怎么可能传递两个参数组合起来呢?

这是因为标记位采取了通过位运算的方式来执行结果,比如O_RDONLY可能在第5个bit位置为了1,因此当flag的第5个bit位为1时,函数就会进行相应的操作,比如说以只读的方式,打开文件。这么说大家可能还不能理解,我们可以简单的写一下有类似逻辑的代码,代码如下:

#include<stdio.h>
#define A (1<<0) //第一个bit位为1
#define B (1<<1) //第二个bit位为1
#define C (1<<2) //第三个bit位为1
#define D (1<<3) //第四个bit位为1
#define E (1<<4) //第五个bit位为1void PrintBitmap(int flag)
{if(flag&A) //如果flag的第一位为1printf("A");if(flag&B) //如果flag的第二位为1printf("B");if(flag&C) //如果flag的第三位为1printf("C");if(flag&D) //如果flag的第四位为1printf("D");if(flag&E) //如果flag的第五位为1printf("E");printf("\n");
}int main()
{printf("=======================================================\n");PrintBitmap(A);//Aprintf("=======================================================\n");PrintBitmap(A|B);//ABprintf("=======================================================\n");PrintBitmap(A|C);//ACprintf("=======================================================\n");PrintBitmap(A|B|C|D);//ABCDprintf("=======================================================\n");PrintBitmap(A|B|C|D|E);//ABCDEprintf("=======================================================\n");return 0;
}

标志位的逻辑原理与上述代码类似,主要是通过判断flag的某位的bit位是否为1来进行对应的操作。因此,如果我们想让打开log.txt文件,以只读,而且创建新文件的方式,那么传递给open函数的参数应为:

#include<sys/types.h>//别忘了包这三个头文件
#include<sys/stat.h>//别忘了包这三个头文件
#include<fcntl.h> //别忘了包这三个头文件
open("log.txt",O_WRONLY|O_CREAT);

此时我们ls -a -l,查看一下新创建的文件log.txt。
在这里插入图片描述
诶,这个创建的log.txt似乎有问题,我们检查一下,发现好像是权限设置出现问题了,没错如果我们使用fopen函数,那么创建的文件,其权限将会是我们的默认权限,但是对于系统调用来说,文件的权限也是需要我们自己设置的。而两参数形式的open函数,显然没有提供设置权限的接口,因此想要设置权限,我们得用三参数的open函数才行。

int open(const char *pathname, int flags, mode_t mode);

linux中,文件权限被分为了三组,分别是所属用户,所属组,与other,而每一组都有对应的三种权限,即r,w,x,而r权限的值为4,w权限的值为2,x权限的值为1(权限章节有提到)。因此,mode参数传入的值,实际上是三组用户,其对应的权限的值。以rw_rw_rw_为例,其mode值应为(0666)。因此我们可以这样写打开log.txt的open函数

open("log.txt",O_WRONLY|O_CREAT,0666);//推荐写四位数字,第一位为0,后面为权限值

接着我们运行该进程,查看log.txt的权限。
在这里插入图片描述
可以发现,log.txt变回了正常形态,但是似乎权限不太对啊,我们给log.txt设置的权限为0666,那么应该是rw-rw-rw-才对,但是实际上log.txt的权限为rw-rw-r–。

这是因为,linux中的每个用户,都会有一个权限修正值,叫做umask,我们可以使用umask指令查看umask值,由于umask值的存在,因此我们在创建文件时,我们自己设置的权限还会收到umask修正。因此实际权限=设置的权限+umask值修正,关于如何修改umask值,以及umaks值是如何修正权限的,博主不太想多聊,主要是由于篇幅原因。

文件修饰符

我们要知道,open函数其实是有返回值的,在linux的在线手册中谈到,如果open失败,则返回值为-1,如果打开成功,则返回值是一个叫做文件修饰符的东西(file descriptor)
在这里插入图片描述
那么这个文件修饰符到底是什么呢?

如果你学习过c/c++语言的文件操作函数,那么应该知道,当我们的c/c++程序运行时,会自动打开三个文件流,分别叫做标准输入流(stdio),标准输出流(stdout)以及标准错误流(stderr)。而这三个文件流其实在个人pc上对应的是键盘(stdin),显示器(stdout),显示器(stdout)。(外部输入设备怎么能是文件呢?这是一个伏笔。)

我们都知道,linux系统的作用,有进程管理,其实还有文件管理,而进程管理是通过PCB(即task_sturct)执行的,而文件是不是也有相应的文件结构呢?是的,这个文件结构叫做struct file

当我们使用open函数时,系统会在磁盘上的对应路径,找到对应的函数,并且将其加载到内存当中(如果没有,可以通过O_CREAT创建)。与之同时进行的,还有该文件对应的数据结构,即struct file。

当我们打开一个文件时,操作系统会将该文件加载到内存当中,并且创建一个该文件对应struct file,来管理该文件。
在这里插入图片描述
我们再来思考一个问题,那就是文件是谁打开的呢?有人这时就说了,这不是我自己打开的吗?这么讲当然没错,但是在仔细一点,文件其实是进程打开的,因为我们在进程中写了open函数,而当进程执行open函数时,文件才被打开,因此实际上一个文件何时被打开,何时关闭,何时创建,并非操作系统决定,而是由相应的进程决定,因此实际上文件与操作系统的关系图应该是这样。
在这里插入图片描述
而一个进程不仅仅要管理一个文件,而是能同时管理多个文件,因为前面提到了,一个c/c++进程,会默认打开三个文件,分别是stdin,stdout,和stderr,因此,一个进程并非只管理一个文件,而是多个文件,那么问题来了,系统怎么判断出这些文件到底谁是谁呢?就比如,进程1打开stdin和stdout,并且创建了对应的struct file,那么到底哪个struct file是stdin,哪个struct file是stdout?

操作系统为了分辨出进程,会给进程分配一个进程标识符,即pid,每个进程都有对应的pid,通过pid,系统就能找到对应的进程,并且对其进行操作。那么文件也是一样,由于文件是由进程打开的,为了区分出不同的文件,那么文件也要有对应的标识符,这个标识符就是文件描述符,即file describtor(后面简称fd)。

而open函数的返回值,就是返回被打开的文件对应的文件描述符,我们可以使用文件描述符来找到文件,以系统调用write为例

ssize_t write(int fd, const void *buf, size_t count);

write的作用是向fd(文件描述符)所指向的文件,写入count字节个,buf当中的数据,手册原文如下。
在这里插入图片描述
那么如果我们想在进程当中想log.txt文件写入数据,就应该写出这样的代码:

int main()
{int fd1=open("log.txt",O_WRONLY|O_CREAT,0666);//打开log.txt文件,并且获取其fdconst char str[]="hello world\n";//write函数包含在<unistd.h>头文件当中write(fd1,str,strlen(str));//向fd1(即log.txt)写入数据return 0;
}

接着我们运行该程序,并且查看log.txt的内容。可以发现log.txt被写入了数据
在这里插入图片描述
文件描述符,其实就是用来标识文件的序号,和pid的性质是一样的,如果我们想使用系统调用对文件进行操作,首先要做的就是使用open函数获取对应文件的文件描述符。

从linux源码了解文件描述符

在前面我们了解到,一个文件的打开,关闭,其实是由进程管理的,那么我们一定能在进程的PCB(task_struct)当中找到管理文件的成员。在linux当中,保存整个进程的所有文件信息的成员叫做files。源码如下:

struct task_struct
{//省略struct files_struct *files;//省略
}

files的是一个files_struct类型的结构体指针,那么这个files_struct的定义是什么?我们接着在源码当中找到
在这里插入图片描述

在files_struct当中,我们又找到了一个指针数组,它是一个指向struct file类型的结构体的指针数组,诶,struct file?这不是我们在开头提到的系统为文件记录文件信息的数据结构吗?没错,由于一个进程可以打开多个文件,因此每个进程都有其独立的文件组,当我们使用open函数打开文件时,本质上是将文件对应的struct file,加载到task_struct的fd_array当中,而fd其实就是该文件在fd_array的下标。

在这里插入图片描述
如果你不相信stdout的fd(文件描述符)是1,你可以尝试用write函数像fd=1的文件输出helloworld,接着运行看看结果。

int main()
{const char str[]="hello world\n";//write函数包含在<unistd.h>头文件当中write(1,str,strlen(str));//向1(即stdout)写入数据return 0;
}

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

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

相关文章

【汇编语言】[BX]和loop指令(二)——在Debug中跟踪用loop指令实现的循环程序

文章目录 前言1. 题目引入1.1 问题一1.2 问题二1.3 问题三1.4 代码实现 2. 程序跟踪2.1 加载程序&#xff0c;用r命令查看寄存器内容2.2 用U命令查看内存中的程序2.3 用T命令进行程序跟踪2.4 用P命令使得程序返回 3. 循环次数更多的程序3.1 代码实现3.2 一个新的命令——g3.3 如…

Unity照片墙效果

Unity照片墙效果&#xff0c;如下效果展示 。 工程源码

鸿蒙HarmonyOS开发生日选择弹框

鸿蒙HarmonyOS开发生日选择弹框 生日选择弹框和城市选择弹框差不多&#xff0c;都是通过观察上一个数据变化来设置自己的数据 一、思路&#xff1a; 一个弹框上建三个compoent&#xff0c;一个年&#xff0c;一个月&#xff0c;一个日。日的数据是根据年和月进行变化的 二、…

浏览器内核版本更新:Chrome 130✔

SunBrowser 内核版本更新至 Chrome 130&#xff0c;UA 同步更新至 130。 如何更新浏览器内核版本&#xff1f; 本地设置更新 在 AdsPower 客户端点击右上角的[设置]&#xff0c;再点击[本地设置]&#xff0c;下滑找到版本信息&#xff0c;选中需要的内核版本立即下载。 新建浏…

【实践】某央企研究院如何打造IT监控告警平台?

01客户简介&#xff1a; 案例客户为某央企下属研究院。 02痛点分析&#xff1a; 随着信创国产化持续推进&#xff0c;案例客户已完成部分IT核心系统的替代&#xff0c;部署了一系列国产软硬件设施&#xff0c;如Kylinv10操作系统、融智通网络设备等。由于信创生态不够成熟&a…

SpringBoot在线教育系统:集成第三方服务

5系统详细实现 5.1 普通管理员管理 管理员可以对普通管理员账号信息进行添加修改删除操作。具体界面的展示如图5.1所示。 图5.1 普通管理员管理界面 5.2 课程管理员管理 管理员可以对课程管理员进行添加修改删除操作。具体界面如图5.2所示。 图5.2 课程管理员管理界面 5.3 …

vscode | 开发神器vscode快捷键删除和恢复

目录 快捷键不好使了删除快捷键恢复删除的快捷键 在vscode使用的过程中&#xff0c;随着我们自身需求的不断变化&#xff0c;安装的插件将会持续增长&#xff0c;那么随之而来的就会带来一个问题&#xff1a;插件的快捷键重复。快捷键重复导致的问题就是快捷键不好使了&#xf…

C++优选算法九 链表

一、常用技巧 画图&#xff01;直观形象&#xff0c;便于理解。引入虚拟“头”结点。不吝啬空间。快慢双指针&#xff1a; 判环 找链表中环的入口 找链表中倒数第n个结点 二、常用操作 创建一个新结点尾插头插 三、示例题目 1.两数相加. - 力扣&#xff08…

计算机网络:网络层 —— 虚拟专用网 VPN

文章目录 虚拟专用网 VPN 概述内联网 VPN外联网 VPN 虚拟专用网 VPN 概述 虚拟专用网&#xff08;Virtual Private Network&#xff0c;VPN&#xff09;&#xff1a;利用公用的因特网作为本机构各专用网之间的通信载体&#xff0c;这样形成的网络又称为虚拟专用网。 出于安全…

Web 安全基础知识梳理大全,零基础入门到精通,收藏这篇就够了

一、各种linux虚拟机忘记密码 1、红帽忘记密码修改root密码 1 在重启的时候 e 进入 2 在linux16 后面找到UTF-8 在后面加 rd.break 然后ctrlx 3 这时候可以输入mount 看一下 会发现根为 /sysroot/ 没有w权限&#xff0c;只有ro权限 4 输入 mount -o remount,r…

非凸科技助力第49届ICPC亚洲区域赛(成都)成功举办

10月26日-27日&#xff0c;由电子科技大学承办、非凸科技与华为共同支持的第49届ICPC国际大学生程序设计竞赛亚洲区域赛&#xff08;成都&#xff09;在郫都区体育中心体育馆顺利举行。非凸科技期待与产学研各界专家、青年才俊一起&#xff0c;推动基础科学理论研究的重大突破&…

ssm051网上医院预约挂号系统+jsp(论文+源码)_kaic

本科毕业设计论文 题目&#xff1a;网上医院预约挂号系统设计与实现 系 别&#xff1a; XX系&#xff08;全称&#xff09; 专 业&#xff1a; 软件工程 班 级&#xff1a; 软件工程15201 学生姓名&#xff1a; 学生学号&#xff1a; 指导教师&#xff1a…

EtherCAT转ModbusTCP相关技术

EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 MS-GW15 概述 MS-GW15 是 EtherCAT 和 Modbus TCP 协议转换网关&#xff0c;为用户提供一种 PLC 扩展的集成解决方案&#xff0c;可以轻松容易将 Modbu…

qt QTextStream详解

1、概述 QTextStream类是Qt框架中用于处理文本输入输出的类。它提供了一种方便的方式&#xff0c;可以从各种QIODevice&#xff08;如QFile、QBuffer、QTcpSocket等&#xff09;中读取文本数据&#xff0c;或者将文本数据写入这些设备中。QTextStream能够自动处理字符编码的转…

大数据-201 数据挖掘 机器学习理论 - 决策树 局部最优 剪枝 分裂 二叉分裂

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

项目_Linux_网络编程_私人云盘

概述 项目功能总述&#xff1a; 该项目使用TCP进行通信&#xff0c;实现文件的上传和下载。云盘的文件同步有手动同步、实时同步、定时同步这三种。本项目主要实现的是手动同步的功能&#xff0c;重点训练在如何使用TCP进行文件传输。 选择TCP的原因&#xff1a; 文件的传输…

细腻的链接:C++ list 之美的解读

细腻的链接&#xff1a;C list 之美的解读 前言&#xff1a; 小编在前几日刚写过关于vector容器的内容&#xff0c;现在小编list容器也学了一大部分了&#xff0c;小编先提前说一下学这部分的感悟&#xff0c;这个部分是我学C以来第一次感到有难度的地方&#xff0c;特别是在…

Java之包,抽象类,接口

目录 包 导入包 静态导入 将类放入包 常见的系统包 抽象类 语法规则 注意事项&#xff1a; 抽象类的作用 接口 实现多个接口 接口间的继承 接口使用实例 &#xff08;法一&#xff09;实现Comparable接口的compareTo()方法 &#xff08;法二&#xff09;实现Comp…

qt QDragEnterEvent详解

1、概述 QDragEnterEvent是Qt框架中用于处理拖放进入事件的一个类。当用户将一个拖拽对象&#xff08;如文件、文本或其他数据&#xff09;拖动到支持拖放操作的窗口部件&#xff08;widget&#xff09;上时&#xff0c;系统会触发QDragEnterEvent事件。这个类允许开发者在拖拽…

永恒之蓝漏洞复现

永恒之蓝漏洞复现 1 实验准备 1台靶机 win7 关闭防火墙 控制面板->系统和安全->Windows 防火墙 192.168.184.131 1台攻击者 kali 192.168.184.129 2 实施攻击 kali操作 1.输入msfconsole回车 2.搜索ms17_010模块 msf6 > search ms17_010 3.选择编号为3的模块 use 3…