Linux操作系统学习(文件IO)

news2025/2/25 12:03:26

文章目录

  • 基础IO
    • 系统相关接口
    • 文件描述符
    • 一切皆文件
    • 文件描述符的分配规则
    • 重定向
    • fork后的文件描述符

基础IO

系统相关接口

在C语言中对文件的操作有fopen打开、fclose关闭、fread读、fwrite写等函数;其实这些都是在系统调用接口上进行的封装。

这里介绍4个系统调用接口:

头文件#include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<unsitd.h>
打开int open( const char * pathname, int flags);
打开int open( const char * pathname,int flags, mode_t mode);
关闭int close(int fd);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
  1. open:打开一个文件

    参数介绍:

    参数功能
    pathname文件的路径,如 “./text.txt”
    flagsO_RDONLY是宏定义,代表一个比特位,这里的这个是只读打开。对应C语言中"W"的打开方式
    mode当创建新文件时才使用第三个参数,他是给文件设置权限,一般设置为0644(8进制)
    • O_RDONLY 只读打开
    • O_WRONLY 只写打开
    • O_RDWR 读、写打开
    • O_APPEND 每次写时都加到文件的尾端
    • O_CREAT 若此文件不存在则创建它(这时需要用第二个函数添加权限)
    • O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错。
    • O_TRUNC 如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0
    • O_NOCTTY 如果pathname指的是终端设备,则不将此设备分配作为此进程的控制终端。
    • O_NONBLOCK 如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I / O操作设置非阻塞方式。
    • O_SYNC 使每次write都等到物理I / O操作完成,即数据已经写到硬盘才返回。没有O_SYNC选项,则把数据写到缓冲区就返回,缓冲区会在合适的时候把缓冲区的数据一起写到硬盘,这样会减少操作硬盘的次数。

    返回值:

    • 返回最小的空闲的文件描述符(文件描述符在下面解释,这里先介绍函数用法),如果失败则返回-1并设置errno

    void test1()
    {
    	FILE* fd1 =  fopen("./test.txt","w");		//c语言是把权限和打开方式已经封装好了
    	if(fd1 == NULL)
       {
    		perror("fopen fail"); 
        	return 1;
        }
    
        int fd2 = open("./test.txt",O_WRONLY | O_CREAT,0644);
        if(fd2 < 0)
        {
            perror("open");
            return 1;
            }
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RAig0vD4-1677869105751)(G:\Typora\图片保存\image-20221207132305217.png)]

    ​ O_WRONLY | O_CREAT其实是两个比特位相或,每个比特位定义成一个宏并对应一种打开方式,这里是只写打开,若此文件不存在则创建它

  2. close:关闭一个文件(描述符)

    参数介绍:

    • fd代表一个文件描述符,文件描述符具体在后面解释

    返回值

    • 成功返回0,失败返回-1且设置errno
    void test1()
    {
    	FILE* fd1 =  fopen("./test.txt","w");		//c语言是把权限和打开方式已经封装好了
    	if(fd1 == NULL)
        {
    		perror("fopen fail"); 
        	return 1;
        }
        fclose(fd1);
    
        int fd2 = open("./test.txt",O_WRONLY | O_CREAT,0644);
        if(fd2 < 0)
        {
            perror("open");
            return 1;
        }
        close(fd2);
    }
    
  3. read:向以打开的文件读取数据

    参数介绍:

    • fd为文件描述符;
    • buf表示读出数据缓冲区地址;
    • count表示读出的字节数。

    返回值:

    • 若读取成功,则返回读到的字节数;

    • 若失败,返回-1;

    • 若已达到文件尾,则返回0

         char put[64];
         ssize_t ret = read(0,put,sizeof(put));		
         put[ret - 1] = 0;//清除\n,在63位置
         printf("%s\n",put);
      

      这里的0也是文件描述符代表键盘 stdin,也就是从键盘读取到put数组中,这里读取的字节数是64(最大值),但读到末尾就自动结束了

      ret接收到读取的有效字节数,因为\n也会被读取,且在最后一位,所以把最后一个字符设置成0在打印时就不会出现换行了

  4. write:向以打开的文件读写数据

    参数介绍:

    • fd为文件描述符;
    • buf指定的缓冲区,即指针,指向一段内存单元;
    • count表示写入的字节数。

    返回值:

    • 成功 :返回写入的字节数
    • 失败 :-1
       char put[64];
       ssize_t ret = read(0,put,sizeof(put));
       put[ret - 1] = 0;//清除\n
       printf("%s\n",put);
       
     
       const char* str = "hello\n";
       write(1,str,strlen(str));
    
    

    1是文件描述符代表显示器,这里就是从put里向屏幕写入(输出),写入的字节数是有效字符长度(strlen不包括\0)


文件描述符

文件描述符是一个非负整数,本质上是一个索引值

我们知道文件一般是存储在磁盘当中的,那么试问创建一个空文件不写内容它占存储么?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-me60Sp2S-1677869105752)(G:\Typora\图片保存\image-20221207151529018.png)]

这里虽然显示的是0,但是其实他是占空间的,不然怎么证明它存在,(这里的0只是根据文件内容显示的)。

而实际上在我们创建文件时,会分为两个部分:属性+内容

属性记录了这个文件的创建时间、位置等等的信息。属性信息等等都可看成是一种数据,既然是数据就必然占用空间

而我们查看的大小、占用空间,实际上是记录的内容占用大小,是属性数据记录的

所以当我们创建一个文件时由:内容+属性两部分组成,而属性信息是只要在创建那一刻的同时也就创建了

​ 在内核中打开文件的也有着对应的结构体:struct file{ 文件属性 }; 也就是打开时操作系统把文件的属性信息描述成一个结构体。

有了这个文件属性信息结构体就可以找到文件内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wKnjNcYs-1677869105752)(G:\Typora\图片保存\image-20221207154731871.png)]

​ 我们知道程序运行起来后,会变成进程,一个进程可以打开多个文件;那打开多个文件 OS就必然要对这些文件进行管理,管理离不开“先描述,再组织”。

​ 那打开多个文件,就会有多个struct file,操作系统为了方便管理,会把它们组织成数据结构,例如打开5个文件,代码如下:

void test3()
{
  int fd1 = open("./test.txt",O_WRONLY | O_CREAT,0644);

  int fd2 = open("./test.txt",O_WRONLY | O_CREAT,0644);

  int fd3 = open("./test.txt",O_WRONLY | O_CREAT,0644);

  int fd4 = open("./test.txt",O_WRONLY | O_CREAT,0644);

  int fd5 = open("./test.txt",O_WRONLY | O_CREAT,0644);

   
  printf("fd1:%d,fd2:%d,fd3:%d,fd4:%d,fd5:%d\n",fd1,fd2,fd3,fd4,fd5);
  close(fd1);close(fd2);close(fd3);close(fd4);close(fd5);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fmQ8Jwif-1677869105752)(G:\Typora\图片保存\image-20221207150740802.png)]

运行结果是从3开始的连续数字,看起来像一个数组,就是少了0、1、2

其实0、1、2在我们学习C语言时都学过,分别是 :

  • 标准输入流 -> 键盘(stdin)

  • 标准输出流 ->显示器(stdout)

  • 标准错误流 ->显示器(stderr)

    这三种流我们也把他们看成打开的文件,并且他们三个是默认就打开的,是执行的进程继承bash的

那么加上这三个流和上面运行结果的fd,可以看成一个数组

  • 那打开多个文件,就会有多个struct file,操作系统为了方便管理,会把它们组织成数据结构:struct files_struct

  • 而这个数据结构中存在着一个数组:struct file array[ ]* ,是一个指针数组,指向的就是每个文件的struct file。

  • 而在进程的PCB中,就包含了 struct files_struct的指针

所以整体应该是这样:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-75ktpFuZ-1677869105753)(G:\Typora\图片保存\image-20221207160538889.png)]

​ 也就是每一个进程PCB都有一个 struct files_struct指针指向files struct,files_struct中存在着一个指针数组,当一个文件打开时就会形成一个struct file代表这个文件属性,指针数组就会存储这个结构体的地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OyUCGH6E-1677869105753)(G:\Typora\图片保存\image-20221207162057029.png)]

所以文件描述符就是,调用open打开文件时,返回的 struct_file存入数组位置的下标

fd本质是内核中,进程和文件关连的数组下标

所以close、read、write能对指定文件进行相关操作是因为传入了fd,通过下标号找到文件信息


一切皆文件

上面说了0、1、2分别代表输入流、输出流、错误流存在数组中,也就是说明它们也是文件,而他们分别对应的是键、显示器、显示器,

  • 这些外设的输入输出方法由驱动完成并提供,所以他们的输入输出方法肯定是不一样的;

  • 但是他们打开时也会提供一个struct file来描述这个外设的一些信息,但是他们的输入输出方法还没统一;

  • 所以在每个结构体里设置两个函数指针,指向驱动的输入输出函数;

有了这个指针,那么在上层看来所有的struct file没区别,不用关心他们是什么,反正调用接口都一样

所以在上层看来,这些都是文件,也就是一切皆文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jut0Aep9-1677869105753)(G:\Typora\图片保存\image-20221207170716565.png)]
所以在Linux中一切东西都可以通过文件的方式访问、管理。也就可以说一切皆文件。

( 包括之前说过的,一个程序的本质也是文件 = 代码+数据)


文件描述符的分配规则

既然 0、1、2也是文件描述符,那么我们把它们关闭,在打开我们自己的文件会发生什么?

请看代码:

void test4()
{
  close(2);
  close(0);
  int fd = open("./test.txt",O_WRONLY|O_CREAT,0644);
  if(fd < 0)
    perror("open:");
  printf("fd:%d\n",fd);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ms44Wepx-1677869105754)(G:\Typora\图片保存\image-20221207172236774.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3K3HByh2-1677869105754)(G:\Typora\图片保存\image-20221207172301336.png)]

说明:文件描述符的分配规则是,是最小下标且空闲的位置,再返回给新fd使用


重定向

既然1代表的是标准输出流stdout,那么我们打开一个文件并关闭1会怎么样

请看代码:

void test5()
{
  close(1);
  int fd = open("./test.txt",O_WRONLY|O_CREAT,0644);
  printf("fd:%d\n",fd);

  printf("hello\n");
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YfvSyOwd-1677869105754)(G:\Typora\图片保存\image-20221207173403729.png)]

我们知道printf就是向屏幕(显示器)打印,也就是stdout,而stdout具体是一个文件指针

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5v7yfnUI-1677869105755)(G:\Typora\图片保存\image-20221207174302714.png)]

​ 这个FILE本质上就是一个结构体名字typedef出来的,而stdout指向的结构体中固定有一个fd(1),这里可以验证一下

void test8()
{
  printf("stdin->%d\n",stdin->_fileno);
  printf("stdout->%d\n",stdout->_fileno);
  printf("stderr->%d\n",stderr->_fileno);
}
_fileno就是固定记录fd的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YKVdC6cR-1677869105755)(G:\Typora\图片保存\image-20221207182147649.png)]

虽然close(1)后把1给新的fd使用了,但是stdout绑定的是1,而printf是向stdout输出,stdout的文件描述固定是1,就会去数组找1这个位置的struct,所以就输出到了test.txt里

(可用fprintf验证,这里就不写了)

由此可以推出:stdout、stdin、stderr的fd一定绑定的是0、1、2,而他们相关的函数一定会去找0、1、2位置的struct file

void test6()
{
  close(0);
  int fd = open("./test.txt",O_RDONLY);
  printf("fd:%d\n",fd);
  char str[128];
  while( fgets(str,127,stdin) )		//每次读128个(数组下标是0开始要-1),到0结束
  {
    printf("%s",str);
  }
  close(fd); 
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fgwg8qsJ-1677869105755)(G:\Typora\图片保存\image-20221207181248360.png)]

​ 关闭stdin,打开test.txt并把文件描述符1给它,用fgets函数从stdin上读取字符,而stdin指向一个结构体,结构体内封装了fd==1,所以等于把之前写入test文件里的的数据写入到str数组里


重定向需要关闭再打开等等操作比较麻烦,还可以使用dup2这个函数来重定向

dup2重定向

用法如下:

fork后的文件描述符

​ 我们利用fork创建了子进程后,使用进程替换不会影响这些文件打开操作,但是task_struct{mm_struc、struct files_struct* 等等}这些些结构属于的是内核,所以子进程也会有struct files_struct(例如子进程父进程都有页表一个道理),而子进程以父进程为模板初始化也就是说子进程的文件描述符对应的文件struct file是一样的。

对于指针这里只是浅拷贝,例如每个进程都打开了stdout、stdin、stderr都从bash一直往下继承的。

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

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

相关文章

【异常】因多租户字段缺少导致Error updating database. Column ‘tenant_id‘ cannot be null

一、报错内容 org.springframework.dao.DataIntegrityViolationException: ### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: Column tenant_id cannot be null ### The error may exist in com/xxx/cloud/mall/admin/mapper/Goods…

303. 区域和检索 - 数组不可变

303. 区域和检索 - 数组不可变 给定一个整数数组 nums&#xff0c;处理以下类型的多个查询: 计算索引 left 和 right &#xff08;包含 left 和 right&#xff09;之间的 nums 元素的 和 &#xff0c;其中 left < right 实现 NumArray 类&#xff1a; NumArray(int[] num…

英语六级的三大经典句型

目录 1.强调句型&#xff08;提前想好要写在哪个地方&#xff09; 2.虚拟语气 3.第三个句型 3.倒装&#xff08;写一到两个倒装&#xff09; &#xff08;1&#xff09;否定词放在句首就是倒装 &#xff08;2&#xff09;so...that句型结构的句子将so引导的部分置于句首时…

一、一篇文章打好高数基础-函数

1.连续函数的性质考点分析函数的连续性主要考察函数的奇偶性、有界性、单调性、周期性。例题判断函数的奇偶性的有界区间为&#xff08;&#xff09; A.(-1,0) B(0,1) C(1,2) D(2,3)2.闭区间上连续函数的性质考点分析闭区间上连续函数的性质主要考察函数的最大最小值定理、零点…

JavaScript 函数参数

JavaScript 函数对参数的值(arguments)没有进行任何的检查。JavaScript 函数参数与大多数其他语言的函数参数的区别在于&#xff1a;它不会关注有多少个参数被传递&#xff0c;不关注传递的参数的数据类型。函数显式参数与隐藏参数(arguments)在先前的教程中&#xff0c;我们已…

删除启动台(LaunchPad)残留的图标

忘记什么版本的时候以前在 “应用程序” 删除应用后&#xff0c;启动台自动更新删掉 不知道什么时候开始&#xff0c;直接在 “应用程序” 里删掉后&#xff0c;启动台居然不能删除了…… 10.13和10.14后&#xff0c;无论是按住 Option&#xff0c;还是按住 Control Option …

Fisco-Bcos的环境搭建及Data-Export导出组件的使用

注意&#xff1a;由于Data-Export组件暂时只支持Fisco-Bcos 2.x系列&#xff08;这个也是目前使用最多最稳定的系列&#xff09;&#xff0c;故这里使用的是目前最新的Fisco-Bcos 2.x。 Fisco-Bcos链环境搭建 区块链网络部署 主要一步步按照这个官方的操作即可区块链网络搭建…

飞桨paddlespech 语音唤醒初探

PaddleSpeech提供了MDTC模型&#xff08;paper: The NPU System for the 2020 Personalized Voice Trigger Challenge&#xff09;在Hey Snips数据集上的语音唤醒(KWS)的实现。这篇论文是用空洞时间卷积网络&#xff08;dilated temporal convolution network, DTCN&#xff09…

Java 类和对象简介

类是对象的抽象&#xff0c;是一组具有相同特性&#xff08;属性&#xff0c;事物的状态信息&#xff09;和行为&#xff08;事物能做什么&#xff09;的事物的集合&#xff0c;可以看做一类事物的模板。 对象是类的实例化&#xff0c;是具体的事物。 比如&#xff1a;人类和…

linux基本功之列之wget命令实战

文章目录前言一. wget命令介绍二. 语法格式及常用选项三. 参考案例3.1 下载单个文件3.2 使用wget -o 下载文件并改名3.3 -c 参数&#xff0c;下载断开链接时&#xff0c;可以恢复下载3.4 wget后台下载3.5 使用wget下载整个网站四. 补充与汇总常见用法总结前言 大家好&#xff…

C. Good Subarrays(前缀和)

C. Good Subarrays一、问题二、分析三、代码一、问题 二、分析 这道题目的意思就是给我们一个数组&#xff0c;然后我们从数组中选取一个连续的区间&#xff0c;这个区间满足条件&#xff1a;区间内的元素和等于区间的长度。 对于区间和问题我们先想到的是前缀和的算法。 那…

【测绘程序设计】——空间直角坐标转换

测绘工程中经常遇到空间直角坐标转换——比如,北京54(或西安80)空间直角坐标转换成CGCS2000(或WGS-84)空间直角坐标,常用转换模型包括:①布尔沙模型(国家级及省级范围);②莫洛坚斯基模型(省级以下范围);③三维四参数(小于22局部区域) 等。   本文分享了基于布…

Scala变量和数据类型

文章目录Scala变量和数据类型一、注释1. 基本语法2. 案例实操3. 代码规范二、变量和常量&#xff08;重点&#xff09;1. 回顾&#xff1a;Java 变量和常量语法2. 基本语法3. 案例实操三、标识符的命名规范1. 命名规则2. 案例实操四、字符串输出1. 基本语法2. 案例实操五、键盘…

【大数据监控】Grafana、Spark、HDFS、YARN、Hbase指标性能监控安装部署详细文档

目录Grafana简介下载软件包安装部署修改配置文件创建用户创建Systemd服务启动 GrafanaSpark应用监控 Graphite_exporterHDFS 监控YARN 监控HBase 监控Grafana 简介 Grafana 是一款开源的数据可视化工具&#xff0c;使用 Grafana 可以非常轻松的将数据转成图表(如下图)的展现形…

Java实现单向链表

✅作者简介&#xff1a;热爱Java后端开发的一名学习者&#xff0c;大家可以跟我一起讨论各种问题喔。 &#x1f34e;个人主页&#xff1a;Hhzzy99 &#x1f34a;个人信条&#xff1a;坚持就是胜利&#xff01; &#x1f49e;当前专栏&#xff1a;Java数据结构与算法 &#x1f9…

DevOps in China:15年来,DevOps在中国经历了什么?

纵观21世纪软件工程的发展&#xff0c;2009年一定是具有里程碑意义的一年。 自21世纪初&#xff0c;为了使软件快速适应复杂多变的市场环境、满足频繁变化的业务需求&#xff0c;软件开发开始不断应用以Scrum、精益开发、看板、极限编程为代表的敏捷开发方法&#xff0c;但此类…

STC单片机使用FatFs文件系统读取SD卡指定文件内容

STC单片机使用FatFs文件系统读取SD卡指定文件内容 🎞通过串口打印SD卡指定文件内容效果演示: 🌿所使用的单片机型号:STC15F2K60S2🌴晶振频率:11.059MHz🔗串口波特率: 9600🌼所使用的是迷你SD模块进行测试:⛳注意事项 🌿项目工程使用的内部ram比较大,需在工程…

SpringBoot项目监控-Prometheus+Grafana

目录 介绍 Prometheus Grafana 使用场景 特性 简单使用 SpringBoot配置 引入依赖 配置文件application.properties 启动 应用添加到Prometheus 下载 配置 启动 使用 Grafana Dashboard 展示应用数据 配置Prometheus数据源 配置监控面板 效果 其他 介绍 Pro…

RFID高频读写器在STM32中的应用

文章目录一、RFID简述二、产品参数及硬件连接三、模块测试四、RFID读写操作五、项目源码一、RFID简述 RFID是Radio Frequency Identification的缩写&#xff0c;是一种无接触自动识别技术&#xff0c;利用射频信号及其空间耦合传输特性&#xff0c;实现对静止的或移动中的待识别…

不懂命令, 如何将代码托管到Gitee上

1.注册码云注册地址 : https://gitee.com2. 新建仓库第一步 : 创建仓库第二步 : 给仓库起名字创建好仓库后, 我们就有了一个网络上的仓库 : 3. 将网络上的仓库克隆到本地在克隆仓库之前, 我们需要先在电脑上安装以下两个工具 >>这两个软件一定要按顺序安装, 先安装第一个…