【Linux】学习-基础IO—上

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>





















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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

fast.ai 机器学习笔记(三)

机器学习 1&#xff1a;第 8 课 原文&#xff1a;medium.com/hiromi_suenaga/machine-learning-1-lesson-8-fa1a87064a53 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 来自机器学习课程的个人笔记。随着我继续复习课程以“真正”理解它&#xff0c;这些笔记将继续更…

【北邮鲁鹏老师计算机视觉课程笔记】03 edge 边缘检测

【北邮鲁鹏老师计算机视觉课程笔记】03 1 边缘检测 有几种边缘&#xff1f; ①实体上的边缘 ②深度上的边缘 ③符号的边缘 ④阴影产生的边缘 不同任务关注的边缘不一样 2 边缘的性质 边缘在信号突变的地方 在数学上如何寻找信号突变的地方&#xff1f;导数 用近似的方法 可以…

linux 08 文件查找

02. 第一. alias 第二. locate&#xff1a; locate 找不到最近的文件 更新locate 后

【深度学习每日小知识】卷积神经网络(CNN)

在深度学习领域&#xff0c;卷积神经网络&#xff08;CNN&#xff09;彻底改变了视觉分析领域。凭借从图像中提取复杂模式和特征的能力&#xff0c;CNN 已成为图像分类、目标检测和面部识别等任务不可或缺的一部分。本文全面概述了 CNN&#xff0c;探讨了其架构、训练过程、应用…

Elasticsearch:混合搜索是 GenAI 应用的未来

在这个竞争激烈的人工智能时代&#xff0c;自动化和数据为王。 从庞大的存储库中有效地自动化搜索和检索信息的过程的能力变得至关重要。 随着技术的进步&#xff0c;信息检索方法也在不断进步&#xff0c;从而导致了各种搜索机制的发展。 随着生成式人工智能模型成为吸引力的中…

陪护系统|陪护小程序提升长者护理服务质量的关键

在如今逐渐老龄化的社会中&#xff0c;老年人对更好的护理服务需求不断增加。科技的进步使得陪护小程序系统源码成为提供优质服务的重要途径之一。本文将从运营角度探讨如何优化陪护小程序系统源码&#xff0c;提升长者护理服务的质量。 首先&#xff0c;我们需要对软件的设计和…

MOMENTUM: 1

攻击机 192.168.223.128 目标机 192.168.223.146 主机发现 nmap -sP 192.168.223.0/24 端口扫描 nmap -sV -p- -A 192.168.223.146 开启了22 80端口 看一下web界面 随便打开看看 发现这里有个参数id&#xff0c;sql尝试无果&#xff0c;发现写入什么&#xff0c;网页显示…

MySQL篇----第十七篇

系列文章目录 文章目录 系列文章目录前言一、对于关系型数据库而言,索引是相当重要的概念,请回答有关索引的几个问题二、解释 MySQL 外连接、内连接与自连接的区别三、Myql 中的事务回滚机制概述前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分…

【java】笔记10:类与对象——本章练习

题目1&#xff1a; 代码如下&#xff1a; import java.util.Scanner; public class Input{public static void main(String[]args){Circle cnew Circle();PassObject yuannew PassObject();System.out.println("r""\t""times");yuan.printAreas…

AI大模型学习笔记之四:生成式人工智能(AIGC)是如何工作的?

OpenAI 发布 ChatGPT 已经1年多了&#xff0c;生成式人工智能&#xff08;AIGC&#xff09;也已经广为人知&#xff0c;我们常常津津乐道于 ChatGPT 和 Claude 这样的人工智能系统能够神奇地生成文本与我们对话&#xff0c;并且能够记忆上下文情境。 Midjunery和DALLE 这样的AI…

私有化部署一个自己的网盘

效果 安装 1.创建目录 cd /opt mkdir -p kod/{db,site} cd /opt/kod 2.环境文件 vim db.env 内容如下 MYSQL_PASSWORD123456 MYSQL_DATABASEkodbox MYSQL_USERkodbox 3.编写docker-compose.yml vim docker-compose.yml 内容如下 version: 3.5services:db:image: mar…

探索数据可视化:Matplotlib在Python中的高效应用

探索数据可视化&#xff1a;Matplotlib在Python中的高效应用 引言Matplotlib基础安装和配置Matplotlib基础概念绘制简单图表线形图散点图柱状图 图表定制和美化修改颜色、线型和标记添加标题、图例和标签使用样式表和自定义样式 高级图表类型绘制高级图表多图布局和复杂布局交互…

聚观早报 | iOS 17.4正式版将上线;魅族21 Pro或下月发布

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 2月5日消息 iOS 17.4正式版将上线 魅族21 Pro或下月发布 小米MIX Flip细节曝光 OPPO Find X7 Ultra卫星通信版 …

【java】12:封装

面向对象编程三大特征 1.基本介绍 面向对象编程有三大特征&#xff1a;封装、继承和多态。 2.封装介绍 封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起&#xff0c;数据被保护在内部&#xff0c;程序的其它部分只有通过被授权的操作[方法]&am…

Java中处理I/O操作的不同方式:BIO,NIO,AIO

Java中处理I/O操作的不同方式&#xff1a;BIO&#xff0c;NIO&#xff0c;AIO 亲爱的朋友&#xff0c; 在这美好的时刻&#xff0c;愿你感受到生活的温暖和欢乐。愿你的每一天都充满着笑容和满足&#xff0c;无论面对什么挑战都能勇往直前&#xff0c;化解困境。 希望你的心中充…

ubuntu22.04 安装部署04:经常死机,鼠标,键盘无响应

相关文章&#xff1a; ubuntu22.04 安装部署01&#xff1a;禁用内核更新 ubuntu22.04安装部署02&#xff1a;禁用显卡更新 ubuntu22.04安装部署03&#xff1a; 设置root密码 一、现象说明 1. 开机一小时后&#xff0c;突然之间网络掉线&#xff0c;鼠标、键盘无反应。 2.…

Makefile编译原理 make 中的路径搜索_2

一.make中的路径搜索 VPATH变量和vpath关键字同时指定搜索路径。 实验1 VPATH 和 vpath 同时指定搜索路径 mhrubuntu:~/work/makefile1/18$ tree . ├── inc │ └── func.h ├── main.c ├── makefile ├── src1 │ └── func.c └── src2 └── func.c mak…

面向对象编程:理解其核心概念与应用

引言 在编程的世界中&#xff0c;面向对象编程&#xff08;Object-Oriented Programming, OOP&#xff09;已成为一种主流的编程范式。它提供了一种组织和管理代码的有效方式&#xff0c;使得代码更加模块化、可重用和易于维护。本文将带您深入探讨面向对象编程的核心概念及其…

mysql、mybatis中SORT

SORT排序 根据数据表sys_series中HOT(int类型)进行升序排列: 原来的数据库中存储: 排序# 结果是HOT字段为null的所有数据都排在最前面,不为null的数据按升序排列 SELECT * FROM sys_series ORDER BY HOT;# 结果是HOT字段为null的所有数据都排在最后面,不为null的数据按数…

测试编码规范

0.测试代码和业务代码要分离 把测试代码和业务代码放进各自的所属的"盒子"中&#xff0c;互不干扰 Q:为什么要分离? 分门别类&#xff0c;避免混乱&#xff0c;方便维护 不在试卷上打草稿而是专门准备草稿纸 没人会在客厅做饭吧&#xff0c;不然要厨房干什么 Q:如…