[Linux]基础IO(上)--理解文件系统调用、文件描述符、万物皆文件

一、文件的理解

        每种语言都有进行文件操作的函数接口,例如C语言的fopen、fwrite、fprintf等等,但是进行文件操作的前提是代码已经跑起来,因为文件的打开与关闭要通过CPU来运行程序代码,所以打开文件的本质是进程打开文件,文件操作也是进程进行文件操作。

        既然文件的打开与关闭是依靠进程的,而操作系统中避免不了存在许多进程,那么一定有大量打开的文件,所以OS就需要将这些文件管理起来(先描述在组织),所以在OS内部,每一个被打开的文件应该有一个专门描述文件的结构,类似于PCB

        文件没有被打开的时候是在哪里呢?

------解释:

        文件没有被打开的时候是存在磁盘中的,而磁盘属于硬件,用户要进行文件操作是不可以直接访问硬件的,因为操作系统不相信任何人,用户只能通过操作系统的函数调用来进行文件操作,而我们在平常一般使用的是语言层面的文件函数接口,不同语言对于文件的函数调用却是有差异的,但它们都是对系统调用接口的封装。

二、文件系统调用接口介绍

1. 打开文件:open
   #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);
  • pathname: 要打开或创建的目标文件
  • flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行 “” 运算,构成flags。
  • mode:给新创建的文件设置权限
  • flag参数:
  1. O_RDONLY: 只读打开
  2. O_WRONLY: 只写打开
  3. O_RDWR : 读,写打开
  4. 这三个常量,必须指定一个且只能指定一个
  5. O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
  6. O_APPEND: 追加写
  7. O_TRUNC :如果文件存在再次以写方式打开清空文件
2. 关闭文件:close
#include <unistd.h>int close(int fd);
3. write
#include <unistd.h>ssize_t write(int fd, const void *buf, size_t count);
4. read
#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);
代码演示:

以写的方式打开文件,若文件不存在则创建文件,再次向文件写入向后追加

  #include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<string.h>int main(){int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);const char* buf="hello Linux file!";                                                                                                                                                       write(fd,buf,strlen(buf));close(fd);return 0;}

5. 如何理解open中的标记选项

        在日常写程序时,我们想创建一个标记一般会直接定义 int flag ,如果需要两个则定义

int flag1 、int flag2 ,若需要多个标记的话这样写不仅难以维护而且有点浪费空间。

       做标记无非就是要表达有或没有、是或不是的信息,一个整形变量有32个比特位,

每个比特位都单独可以做为标记位,这是OS设计系统调用的常用方式。接下来我们自己模拟实现一个位图的函数理解一下:

  #include<stdio.h>#define ONE (1)      //1#define TWO (1<<1)   //2#define THREE (1<<2) //4#define FOUR (1<<3)  //8void print(int flag){if(flag&ONE)printf("one\n"); //替换成其他功能if(flag&TWO)printf("two\n");if(flag&THREE)printf("three\n");if(flag&FOUR)printf("four\n");}int main(){print(ONE);printf("\n");print(TWO);printf("\n");print(ONE|TWO);printf("\n");print(ONE|TWO|THREE);printf("\n");return 0;                                                                                                                                                                                  }

 三.理解open返回值(文件描述符)

为什么fd是从3开始的呢?0、1、2去哪里了呢?

--- 0:标准输入流(键盘)

--- 1:标准输出流(显示器)

--- 2:标准错误流(显示器)

这三个流在程序启动时就自动开启了 ,Linux下万物皆文件,操作系统将这三个标准也视为文件,所以文件创建时是从3开始的

既然0、1就可代表键盘与显示器,那向显示器打印信息除了printf还可以这样做:

为什么一个整数就可以代表一个文件呢?文件描述符的本质是什么?

---- 上面说过,操作系统需要将打开的文件进行管理,区分哪些文件被打开了,哪些文件马上就要关闭等等信息,在Linux下,每一个打开的文件都会被struct file的结构体所描述,为了便于管理,会将这些结构体用双链表进行连接管理,这个结构体中还存在一个指针,表示文件内核级的缓冲区,想要读取磁盘的文件中内容,只需将内容放入该缓冲区,程序就可以使用这些内容了,同理若想要向磁盘中的文件写入内容,需要将内容先写入缓冲区,再讲缓冲区的内容导入文件,这样对文件管理只需要对文件的struct file进行管理了

对文件管理的实质是进程对文件进行管理,那进程是如何管理自己文件的呢?

        进程在创建时会创建自己的PCB,一个进程可以打开多个文件,为了区分这些文件具体是哪些进程管理的,每个进程的PCB中存在着一个struct files_struct* 的指针,这个指针用来管理进程打开的文件,为了进程与文件的对应关系,struct files_struct结构体中保存着struct file* [ ]的指针数组,既然是数组一定会有数组下标,则操作系统会将0号下标指针指向保存标准输入的struct file的地址,将1号下标指针指向保存标准输出的struct file的地址,将2号下标指针指向保存标准错误的struct file的地址,同理将3号下标指针指向自己打开的文件struct file的地址

综上所述:

# 文件操作符的本质就是文件映射关系数组的下标,所以一个整数就可以访问一个文件

# open过程的操作:

    1.创建file

    2.开辟文件缓冲区,传输文件数据(延后)

    3.查询文件描述符表,构建映射关系

    4.返回下标

四.理解Linux下万物皆文件

        在上文中,我们谈到操作系统将键盘与显示器等也看做文件,但是他们本身为硬件,该怎么理解万物皆文件呢?在Linux下是怎么做到的?

        对于设备其实我们关心的数据就两个,属性与操作方法,对于属性一个结构体就可以描述,而方法的话,不同设备的方法一定是不同的,但是可以把他们的参数与返回值设置的类型相同。

        操作系统在打开一个设备时,就会创建其对应的struct file,该结构体除了一些关于文件信息的内容,还会存在一些函数指针来实指向对应设备的方法,struct file结构体中一定还有一个指针指向设备的属性,可以让我们访问到,所以想要访问一个设备,只需要找到其对应的struct file通过内部函数指针或者指向描述属性结构体的指针就可以操作这个设备。在Linux下一套叫做vfs(虚拟文件系统)

         所以在上层看来,不论是磁盘级的文件还是设备级的文件不做区分,只将他们看做struct file,实现了万物皆文件

五、重新理解不同语言对文件操作的封装

        上文解释到在Linux操作系统下,进程是依靠文件描述符来操控文件的,但是C语言中对于文件操作的函数却使用的是FILE*的指针,连stdout、stdin、stderr也是FILE*类型的,其实FILE是一个结构体,封装了对于文件信息的描述,其内部也一定包含了文件描述符。

  #include<stdio.h>  #include<sys/types.h>  #include<sys/stat.h>  #include<fcntl.h>  #include<unistd.h>  #include<string.h>  int main()  {  FILE* fp=fopen("log.txt","w");  if(fp==NULL)  {perror("fopen");return 1;}printf("stdin:%d\n",stdin->_fileno);printf("stdout:%d\n",stdout->_fileno);   printf("stderr:%d\n",stderr->_fileno);   printf("fp:%d\n",fp->_fileno);                                                                                                                                                             return 0;                                                                                                                                                }                                                                                                                                                          

语言的文件操作函数,本质底层都是对系统调用的封装!

那C语言为什么要这样做呢?

        因为C语言(其他语言也是这样)想实现跨平台性,不同操作系统的系统调用是不同的,仅使用系统调用的代码不具有跨平台性,所以语言会对系统调用进行封装,在哪个操作系统下就使用该操作系统的系统调用,这样就可以实现语言的跨平台性。

        

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024 ccfcsp认证打卡 2021 12 01 序列查询

2021 12-1 序列查询 题解1题解2区别第一种算法&#xff1a;第二种算法&#xff1a; 题解1 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);// 输入n表示商品数目&#xff0c;N表示总数int n sc.n…

ChatGPT如何升级为GPT-4在国内

通过 WildCard 可以把ChatGPT升级为GPT-4 地址 1: 2155 Bailey Hill Rd 城市: Eugene 邮编: 97405 州: Oregon ChatGPT Plus/Team 一键升级&#xff0c;几分钟即可自动升级到 ChatGPT Plus。 选择我的邮箱账号符合要求 复制这个页面的链接即可 复制上面的link 到请在…

Harbor部署

Harbor部署 下载和安装 github下载地址&#xff1a;https://github.com/goharbor/harbor/releases 解压和配置 # 解压tgz包 tar -zxvf harbor-offline-installer-v2.10.1.tgz # 进入目录后进行复制配置文件 cd harbor/ # 创建一个配置文件 cp harbor.yml.tmpl harbor.yml …

Oracle利用BBED恢复崩溃实例(ORA-01092,ORA-00704,ORA-01578)

BBED修复数据损坏引起的数据库崩溃&#xff08;ORA-01092,ORA-00704,ORA-01578&#xff09;(2021年某苏州国企的案例&#xff09; 1.Symptom 用户一个边缘系统出现数据文件损坏&#xff0c;且没有备份&#xff0c;数据库无法启动 报错如下&#xff0c;发现是oracle bootstra…

pnpm、monorepo分包管理、多包管理、npm、vite、前端工程化、保姆级教程

浅尝pnpm monorepo 多包管理方案 &#x1f4a1;tips: 创建pnpm monorope多包管理框架流程 初始化 mkdir taurus & cd taurus pnpm init创建基础文件 创建文件pnpm-workspace.yaml packages:- packages/**创建文件夹packages/ -packages/ -package.json -pnpm-workspace…

Net8 ABP VNext完美集成FreeSql、SqlSugar,实现聚合根增删改查,完全去掉EFCore

没有基础的&#xff0c;请参考上一篇 彩蛋到最后一张图里找 参考链接 结果直接上图&#xff0c;没有任何业务代码 启动后&#xff0c;已经有了基本的CRUD功能&#xff0c;还扩展了批量删除&#xff0c;与动态查询 动态查询截图&#xff0c;支持分页&#xff0c;排序 实现原理…

《让你的时间多一倍》逃离时间陷阱,你没有自己想的那么懒 - 三余书屋 3ysw.net

让你的时间多一倍 今天我们来阅读法比安奥利卡尔的作品《让你的时间多一倍》。或许你会心生疑虑&#xff0c;这本书是否又是一本沉闷的时间管理指南&#xff1f;但我要告诉你的是&#xff0c;尽管时间管理这个话题已经为大众所熟知&#xff0c;这本书却为我们揭示了一个全新的…

SpringBoot实现RabbitMQ的定向交换机(SpringAMQP 实现Direct定向交换机)

文章目录 Direct 交换机特点实战声明交换及其队列(以注解方式)发消息 应用 上一篇文章中的 Fanout 模式&#xff0c;一条消息&#xff0c;会被所有订阅其交换机的队列都消费。 但是&#xff0c;在某些场景下&#xff0c;我们希望不同的消息被不同的队列消费。这时就要用到 Dir…

RabbitMQ 延时消息实现

1. 实现方式 1. 设置队列过期时间&#xff1a;延迟队列消息过期 死信队列&#xff0c;所有消息过期时间一致 2. 设置消息的过期时间&#xff1a;此种方式下有缺陷&#xff0c;MQ只会判断队列第一条消息是否过期&#xff0c;会导致消息的阻塞需要额外安装 rabbitmq_delayed_me…

实时语音识别(Python+HTML实战)

项目下载地址&#xff1a;FunASR 1 安装库文件 项目提示所需要下载的库文件&#xff1a;pip install -U funasr 和 pip install modelscope 运行过程中&#xff0c;我发现还需要下载以下库文件才能正常运行&#xff1a; 下载&#xff1a;pip install websockets&#xff0c;pi…

三大热门猫咪主食冻干测评:希喂、VE、PR主食冻干PK

很多铲屎官在选购主食冻干时进口、国产犹豫不决&#xff0c;总觉得进口的主食冻干品控、配方会比国产的更好&#xff0c;但是进口的营养指标又不如国产、价格也令人望而却步。正是这样的摇摆不定&#xff0c;最后抱着试一试的心态盲入主食冻干&#xff0c;运气好的买回家的主食…

基于递归残差U-Net的医学图像分割

基于递归残差U-Net的医学图像分割 摘要相关工作方法---3 RU-Net and R2U-Net Architectures---3 RU-Net和R2 U-Net架构 【2019】Recurrent residual U-Net for medical image segmentation 摘要 基于深度学习&#xff08;DL&#xff09;的语义分割方法在过去几年中一直提供最先…