【Linux】学习-基础IO—上

news2024/12/24 11:41:09

Linux基础IO—上

复习c语言接口

你真的懂文件吗?

文件的打开与关闭

深入了解文件读与写(C语言级别)

系统文件I/O

我们知道,文件是放在磁盘(硬件)上的,我们用代码访问文件的思路是:

写代码 -> 编译 -> 生成可执行exe -> 运行 -> 访问文件…

我们利用c语言提供的库文件的接口来完成文件访问,但你有没有想过,C语言又是如何做到访问磁盘上的文件的呢?

也就是说,访问文件的本质上到底是谁在访问??

其实答案很显然,访问硬件,本质上只有操作系统OS能够访问!!

而普通用户想访问,只能让操作系统提供特定的接口

因此,调用库函数访问文件时,我们调用的接口实际上底层是封装了系统接口

这样再来看刚刚提出的问题:访问文件本质上是谁在访问 ??

首先,我们写完代码之后,通过编译器进行编译链接,会形成一个可执行程序。此时,可执行程序本质上是一个文件,它是放在磁盘上的,而当我们去运行此可执行程序是,本质上就是将文件内的代码和数据加载到内存当中,经过操作系统的先描述再组织变成进程,说白了就是程序需要运行起来调用系统接口才能访问文件,而程序运行起来就会变成进程,因此访问文件本质上是进程在访问文件!!

那既然这样,为什么计算机语言还要大费周章专门为访问文件提供接口呢?

其实是因为系统接口比较难!!因此语言上进行了封装,为了让接口更好的被使用!

这样也导致了不同的语言有着不同的文件访问接口,但系统层面的文件接口只有一套!!因此我们学习系统接口,能让我们对访问文件的理解更加深刻!

默认打开的三个流

都说Linux下一切皆文件,我们也学习认识了其实显示器和键盘(详见复习篇)也是一种文件,因此显示器显示内容以及在键盘上打字的过程不就是访问文件的过程吗?那访问文件是要通过编写代码,调用接口,编译后执行程序才能访问的啊!我们平常写代码也没打开过显示器和键盘这两个文件呀?

其实,是系统帮助我们做了这些事情!(人还怪好嘞!)

任何程序,不管有没有手动打开显示器和键盘,只要运行后经过先描述再组织成进程后,系统都会默认帮助任何进程打开三个流:标准输入流(stdin),标准输出流(stdout)以及标准错误流(stderr)

其中stdin对应的就是键盘(输入,是一种读操作,内存读文件也就是读取人通过键盘传达的信息)

stdout对应的就是显示器(输出,是一种写操作,内存通过显示器这个文件写入信息能够被人所读取)

stderr对应的也是显示器,只是写给人类的信息都是有关错误的信息

*extern FILE stdin;

*extern FILE stdout;

*extern FILE stderr;

这三个流的类型实际上是FILE *类型,也就是文件指针,更加印证了显示器和键盘也是一种文件

系统打开文件接口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: 若是创建文件,设置该文件被创建时的权限

flags参数:
O_RONLY:只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
O_TRUNC: 若文件已存在,先清空原本的文件内容

//返回值:
	成功:返回新打开文件的文件描述符(后面会介绍)
 失败:-1

当前路径 “.”

我们在Linux系统下,若没有将可执行程序添加到环境变量时,我们运行程序一般都会使用 “ ./+ xxx.exe” 的方式,而其中 “ ./ ”的含义就是当前路径下的.exe文件,那么当前路径的参照物是谁呢?当前路径的当前指的又是哪一条路径呢?

其实,当前路径指的是可执行程序运行时变成进程时所处的路径,也就是一个进程是可以知道当前程序执行时的位置的,这样一来,也能解释了为什么我们之前使用fopen时,若文件不存在会自动在当前路径下创建的原因了:

image-20230919205535264

当我运行程序后,在程序还没退出前,此程序会经过系统描述组织成进程,而此时可执行程序运行时所处的路径也会被进程所记录,这样一来,当前路径的概念也就确定了。

pathname参数

pathname表示需要打开或者创建的文件

当需要打开文件时,需要指定对应的路径和文件名,若没有路径则默认会在当前路径查找,若当前路径没有,则会默认在当前路径下创建一个新的文件。

flags参数

flags简称标志位,对应的是打开或创建文件时,所赋予文件的权限:

  • O_RONLY:只读打开

  • O_WRONLY: 只写打开

  • O_RDWR : 读,写打开

这三个常量,必须指定一个且只能指定一个

  • O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限

  • O_APPEND: 以追加写的方式打开

//举例:若需要以只读的方式创建并打开文件:
open("test.txt",O_RONLY | O_CREAT,0666);

若需要传多个选项则需要在中间用分隔符 “|” 隔开

这看似很方便且简单的传选项的方式,底层是如何实现的呢?

其实,我们以用户的视角在应用层上看到的一个看似简单且方便的动作,在系统接口层面,可能是经过了非常多复杂的动作完成的!

而flags的底层实现正是印证了这一说法,flags的底层需要用到位图思想!

flags参数的类型为整型,有32个比特位,每个比特位都可以用来设置成选项的标志位,而每一个标志位都可以实现不同的功能,使得函数效率大大提高,降低了冗余

我们直接模仿实现一下标志位功能:

image-20230919212923143

image-20230919212737157

这就是flags参数的底层原理

mode参数

mode参数是提供给用户如有创建文件的需求时能够给文件设置权限的参数,比如我想给用户设置的权限为

rw-rw-rw-,对应的参数就为0666,若这一块忘记的可以复习权限Linux权限一章

注意:若发现设置的值跟实际的不一致,可能是被umask值过滤了,要注意umask值默认掩码的存在,我们可以设置当前进程的umask值,这样不会影响其他进程image-20230919213510385

系统关闭文件接口close

这个跟我们学习过的fclose是一样的,只不过传的参数不同,fclose传的参数是打开文件时返回的文件指针,而close传的是打开文件时返回的文件描述符fd;

int close(fd);
//关闭成功返回0,失败则返回-1;

系统"写"文件接口write

系统接口对应向文件内写入内容的接口为write:

//原型
ssize_t write(int fd,const void* buf,size_t count);
//第一个参数为打开文件时返回的文件描述符
//第二个参数为从内存的哪个位置的内容向文件中写入
//第三个参数为写入的大小,单位为字节

image-20230920124457496

image-20230920124542802

返回值:成功时返回实际写入的字节数的大小,失败则返回-1

示例:

// test: write
int main()
{
  int fd = open("test.txt", O_WRONLY | O_CREAT ,0666);
  if (fd < 0)
  {
    perror("open");
    exit(1);
  }
    
  const char *buffer = "hello write!\n";
  write(fd, buffer, strlen(buffer));//这里用的是指针,因此使用strlen

  close(fd);
  return 0;
}

image-20230920130830925

系统"读"文件接口read

(注意看文件接口的小技巧就是站在内存的角度)

系统接口中对应向文件内读出内容到内存中的接口为read:

//原型
ssize_t read(int fd, void *buf, size_t count);
//第一个参数为打开文件时返回的文件描述符
//第二个参数为从文件中读出内容后放入内存中的位置
//第三个参数为读出内容的大小,单位为字节

image-20230920131146013

image-20230920131227424

返回值:返回实际读出内容的大小,单位为字节,若返回0,则代表已经读到了文件末尾,若读取失败,则返回-1;

示例:

//test:read
  int main()
  {
    int fd=open("test.txt",O_RDONLY);
    if(fd<0)
    {
      perror("read");
    }

    char buffer[128];
    memset(buffer,0,sizeof buffer);

    ssize_t s=read(fd,buffer,sizeof buffer);
    if(s<0)
    {
      perror("read");
    }
    printf("%s",buffer);
    return 0;
  }

image-20230920131930140

文件描述符file description

我们在学习open接口时,若成功打开文件,则会返回一个整型:

image-20230920132206404

这个整型就称为文件描述符file description,那么什么是文件描述符呢,在我们之前的学习中,fopen成功打开后返回的是一个维护FILE结构体的结构体指针FILE*(详见深入了解文件一章中关于文件指针的部分占位符),他跟文件描述符又有什么关系呢?

首先我们要明白,FILE结构体是C语言标准库所提供的用来便于管理文件的结构体,而库函数的内部一定是封装了系统层面的东西用来标识文件的,在系统的角度上看,他只认文件描述符,而不认识文件指针,因此FILE的底层必定是封装了文件描述符才得以管理文件的.

文件描述符既然这么重要,那他究竟是什么?

让我们一步一步的来探索:

一般情况下,进程要访问文件,肯定要打开文件的,那么一个进程可以打开多个文件吗,还是一个文件能被多个进程访问呢?其实,一般而言,进程:文件的比例为1:n,大量的文件在系统中被打开,就像大量杂乱无章的资料被扔在了领导的桌子上一样,系统肯定是会非常不满的,因此,系统为了使这些杂乱无章的被打开的文件被有条不紊的整理并管理好,他对这些文件进行了先描述,再组织的过程,也就形成了FILE结构体,再把这些FILE结构体以双链表的形式链接了起来,此双链表上串的都是被打开的文件,也就是从磁盘上加载到内存里的文件(因为只有被加载到内存中才能被进程直接访问)。但这仅仅是把文件给管理好了,进程要访问文件除了要打开文件还要和这个被打开的文件建立好联系,以便访问时能在双链表的众多文件中找,也就是被管理起来的文件,需要有一种方法知道该文件是被哪个进程所打开的并同时做好管理。

image-20230920200748959

如何建立进程与被打开文件间的联系呢?

刚刚我们提到被打开文件被描述组织成结构体后被放入了双链表中,而为了将其中的文件与进程联系起来,在内存中还存在着 files_struct 结构体,在此结构体内,有一个指针数组,而其中存放的指针,正是指向这些被打开文件的结构体指针!而其中数组的下标,就是我们对应的文件描述符!这样真相就水落石出了,原来文件描述符是指针数组的下标!!

但还要与进程建立链接还需要一部,那就是让进程每次都能够找到存放指针数组的结构体files_struct,因此,我们的程序在经过编译运行后被描述组织成进程后,进程中的进程控制块内还会存放一个files*,用来指向 files_struct 结构体,这样一来,进程就不怕把打开的文件弄丢了!

由于前面我们学习过系统会默认帮助我们打开三个流,标准输入流,标准输出流与标准错误流,而这三个流所对应的硬件是键盘与显示器,而我们又知道Linux下一切皆文件,因此,这三个也属于被打开的文件,既然是被打开的文件,那同样也经过了被描述并组织成结构体FILE,并将指向该结构体的指针放入files_struct中的指针数组fd_array中,对应的数组下标分别是:

标准输入流:0

标准输出流:1

标准错误流:2

现在我们已经了解了文件描述符是什么以及认识了进程是如何与被打开文件是如何联系起来的,下面让我们来将打开访问文件时的整套流程(以文件已经被创建为前提):

首先,有一个在磁盘上的文件,此时小陈同学写了一份代码,需要打开这份文件,小陈的代码中使用了系统调用接口open函数,此时系统就会用他自己的方法在路径中找到这个文件,并将此文件通过描述组织成FILE结构体并放入双链表中, 然后将指向此结构体的结构体指针写入files_struct中的指针数组fd_array中,写入成功后则返回fd给小陈接收,此时进程中也存在指向files_struct结构体的指针,小陈也知道了他打开文件的文件描述符,因此要访问这个文件时,只需要告诉接口read或者write他需要访问他手上拿的fd所对应的这个文件,那么接口就会通过他的方法在双链表中找到这个文件并写入或者读出小陈所需要的东西,这样也就完成了对文件的打开与访问操作!!!

文件描述符的分配规则

我们直接通过代码来测试一下文件描述符的分配规则:

// test: file description
int main()
{

  int fd0 = open("test_fd1", O_RDONLY | O_CREAT,0666);
  int fd1 = open("test_fd2", O_RDONLY | O_CREAT,0666);
  int fd2= open("test_fd3", O_RDONLY | O_CREAT,0666);
  int fd3 = open("test_fd4", O_RDONLY | O_CREAT,0666);
  
  printf("fd0: %d\n", fd0);
  printf("fd1: %d\n", fd1);
  printf("fd2: %d\n", fd2);
  printf("fd3: %d\n", fd3);
  close(fd0);
  close(fd1);
  close(fd2);
  close(fd3);
  return 0;
}

image-20230920212450365

运行后发现fd是从3开始按照打开的顺序往后依次递增,鉴于我们前面提到的三个标准流占用了0,1,2文件描述符,初步推断,文件描述符是通过文件打开的顺序依次往后填入的,但为了进一步验证,我们再做如下测试:

// test:fd
int main()
{
  close(0);
  // close(2);
  int fd = open("myfile", O_RDONLY | O_CREAT,0666);
  if (fd < 0)
  {
    perror("open");
    return 1;
  }
  printf("fd: %d\n", fd);
  close(fd);
  return 0;
}

这时运行后我们发现结果是image-20230920212828119

通过验证,我们基本可以确定文件描述符的分配规则:遍历指针数组并找到没有被使用的最小下标作为新的文件描述符

;
// close(2);
int fd = open(“myfile”, O_RDONLY | O_CREAT,0666);
if (fd < 0)
{
perror(“open”);
return 1;
}
printf(“fd: %d\n”, fd);
close(fd);
return 0;
}


这时运行后我们发现结果是[外链图片转存中...(img-dlV5UjFS-1707561019361)]

<font color='red'>通过验证,我们基本可以确定文件描述符的分配规则:遍历指针数组并找到没有被使用的最小下标作为新的文件描述符</font>





















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

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

相关文章

uniapp小程序端使用计算属性动态绑定style样式踩坑

踩坑点: 使用uniapp编译小程序端动态绑定复杂style使用计算属性方式&#xff0c;return必须返回json字符串格式&#xff0c;不能返回object&#xff0c;否则会不起作用。 代码总览 视图层 逻辑层&#xff08;注意这里是使用的计算属性哈&#xff09; 这里我封装成了一个个性化…

泽攸科技ZEM系列台扫助力环境科研创新:可见光催化抗生素降解的探索

环境污染和能源短缺是当今人类社会面临的最严重威胁之一。为了克服这些问题&#xff0c;特别是在污水处理过程中&#xff0c;寻找新的技术来实现清洁、高效、经济的发展显得尤为重要。在各种工业废水中&#xff0c;抗生素的过量排放引起了广泛关注。抗生素的残留会污染土壤、水…

学会这几个Dynamo数据处理技巧,效率翻倍提升

最近一直在写Dynamo程序&#xff0c;但是很多东西是不能分享出来的&#xff0c;但是一些方法是可以分享的&#xff0c;整理了一些&#xff0c;今天先分享几个给大家。话不多说&#xff0c;直接进入主题&#xff1a; 1、快速筛选出一组物体中&#xff0c;有哪些物体与另一组物体…

【Maven】依赖、构建管理 继承与聚合 快速学习(3.6.3 )

文章目录 Maven是什么&#xff1f;一、Maven安装和配置本地配置文件设置idea配置本地maven 二、基于IDEA的Maven工程创建2.1 Maven工程GAVP属性2.2 Idea构建Maven JavaEE工程 三、Maven工程项目结构说明四、Maven核心功能依赖和构建管理4.1 依赖管理和配置4.2 依赖传递和冲突4.…

C# OCR识别图片中的文字

1、从NuGet里面安装Spire.OCR 2、安装之后&#xff0c;找到安装路径下&#xff0c;默认生成的packages文件夹&#xff0c;复制该文件夹路径下的 6 个dll文件到程序的根目录 3、调用读取方法 OcrScanner scanner new OcrScanner(); string path "C:\1.png"; scann…

编译环境搭建及基础实验

1.VS code安装 Linux 版本安装 把资料盘里的安装包.deb拷贝到Ubuntu中&#xff0c; 使用如下命令安装&#xff1a; 软件图标都在目录/usr/share/applications 中&#xff0c;如图路径 复制到桌面中 Visual Studio Code 插件的安装 我们需要按照的插件有下面几个&#xff1a;…

[C#]winform制作圆形进度条好用的圆环圆形进度条控件和使用方法

【创建圆形进度条流程】 在C# WinForms应用程序中创建一个圆形进度条&#xff08;通常用作仪表盘的显示&#xff09;可以通过多种方式实现。下面是一个简单的例子&#xff0c;演示如何使用System.Drawing命名空间中的图形绘制功能来绘制一个基本的圆形进度条。 首先&#xff0…

随机MM引流源码PHP开源版

引流源码最新随机MM开源版PHP源码&#xff0c;非常简洁好看的单页全解代码没任何加密 直接上传即可用无需数据库支持主机空间

linux应用 进程间通信之信号量(System V)

1、定义 System V 信号量是一种用于进程间同步和互斥的机制&#xff0c;它是 System V IPC&#xff08;Inter-Process Communication&#xff0c;进程间通信&#xff09;机制的一部分。信号量通常用于控制对共享资源的访问&#xff0c;以避免竞争条件&#xff08;race conditi…

【EAI 017】Interactive Language: Talking to Robots in Real Time

论文标题&#xff1a;Interactive Language: Talking to Robots in Real Time 论文作者&#xff1a;Corey Lynch, Ayzaan Wahid, Jonathan Tompson Tianli Ding, James Betker, Robert Baruch, Travis Armstrong, Pete Florence 作者单位&#xff1a;Robotics at Google 论文原…

口腔助手|口腔挂号预约小程序|基于微信小程序的口腔门诊预约系统的设计与实现(源码+数据库+文档)

口腔小程序目录 目录 基于微信小程序的口腔门诊预约系统的设计与实现 一、前言 二、系统功能设计 三、系统实现 1、小程序前台界面实现 2、后台管理员模块实现 四、数据库设计 1、实体ER图 2、具体的表设计如下所示&#xff1a; 五、核心代码 六、论文参考 七、最新…

【软件设计师】——面向对象设计原则

为了提高软件的可维护性、可复用性&#xff0c;增加软件的可扩展性和灵活性&#xff0c;在面向对象编程的过程中我们需要遵守以下六条原则。 开闭原则 定义&#xff1a; 编写的代码需要对 扩展开放 对 修改关闭 &#xff0c;实现 热插拔 的效果。 例&#xff1a;在编写不同皮…

【Web】小白友好的Java内存马基础学习笔记

目录 简介 文件马与内存马的比较 文件马原理 内存马原理 内存马使用场景 内存马分类 内存马注入方式 这篇文章主要是概念性的&#xff0c;具体技术细节不做探究&#xff0c;重点在祛魅。 简介 内存马&#xff08;Memory Shellcode&#xff09;是一种恶意攻击技术&…

【Linux】学习-动静态库

动静态库 头文件与库的区别 头文件一般而言&#xff0c;是声明和宏定义。头文件是在预处理阶段使用的 库文件是已经编译好的二进制代码。是一种目标文件&#xff0c;库文件是在链接阶段使用的 对于头文件和库我们可以这样理解&#xff0c;就是头文件提供的是一个函数的声明&…

Hive正则表达式

Hive版本&#xff1a;hive-3.1.2 一、Hive的正则表达式概述 正则表达式是一种用于匹配和操作文本的强大工具&#xff0c;它是由一系列字符和特殊字符组成的模式&#xff0c;用于描述要匹配的文本模式。 Hive的正则表达式灵活使用解决HQL开发过程中的很多问题&#xff0c;本篇文…

基于SpringBoot+Vue的服装销售商城系统

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是墨韵&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c;不进则退。学习如赶路&#xff0c;不能慢一步。 目录 一、项目简介 二、开发技术与环…

无人机系统组装与调试,多旋翼无人机组装与调试技术详解,无人机飞控系统原理

多旋翼无人机飞控系统的组装 在开始组装前&#xff0c;确保您已准备好所有必要的工具和材料。这包括螺丝刀、电烙铁、焊台、杜邦线、飞控板、GPS模块、电机、桨叶等。 飞控安装 安全开关安装&#xff0c;将安全开关固定在机架上。将安全开关的线插到飞控SWITCH插口上。 电调…

如何从 iPhone 恢复已删除的视频:简单有效方法

无论您是在尝试释放空间时不小心删除了 iPhone 上的视频&#xff0c;还是在出厂时清空了手机&#xff0c;现在所有数据都消失了&#xff0c;都不要放弃。有一些方法可以恢复这些视频。 在本文中&#xff0c;我们将向您展示六种最有效的数据恢复方法&#xff0c;可以帮助您从 i…

直播app开发,技术驱动的实时互动新纪元

随着互联网技术的快速发展&#xff0c;直播已成为我们日常生活的重要组成部分。从娱乐、教育到商业活动&#xff0c;直播的广泛应用正在改变着我们的生活和工作方式。在这一变革中&#xff0c;直播开发扮演着至关重要的角色。本文将探讨直播开发的核心理念、技术挑战以及未来的…

离散数学——图论(笔记及思维导图)

离散数学——图论&#xff08;笔记及思维导图&#xff09; 目录 大纲 内容 参考 大纲 内容 参考 笔记来自【电子科大】离散数学 王丽杰