一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)

目录

文件

引入

一切皆文件的理解

引入

封装struct

标准流

介绍

示例

默认

指定

perror 

介绍

参数

示例

文件操作 

介绍

fopen的路径

引入

介绍

示例

原理

示例

系统调用(一)

引入

调用图

open

函数原型

flags -- 标识位

三个基础标识位 -- 读写

O_CREAT

O_TRUNC

O_APPEND

文件权限问题

mode -- 设置权限

返回值

文件描述符(fd)

引入

介绍

三个标准流

作用

os中有那么多文件和进程,如何将 进程 与 由该进程打开的文件 关联起来呢?

linux内核中的调用关系

查看fd

文件指针 ->_fileno

fileno

系统调用(二)

fopen底层调用过程

read

介绍

ssize_t

示例

write 

介绍

示例

O_TRUNC

O_APPEND

fwrite的底层调用过程 

close 


文件

引入

  • ls指令中有提到过一个概念 : 文件=内容+属性,ll命令可以打印出目录下文件的属性
  • 一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第1张图片
  • 所以,无论文件有无内容,它都有自己的属性,都需要将文件存储在磁盘上
  • 因为它被存在磁盘上,所以可以被我们操作

一切皆文件的理解

引入

  • 我们计算机上有非常多的硬件,像磁盘,显示器,网卡,显卡等,它们都可以被read/write
  • 但是每个硬件的底层都不同,所以他们的操作方法不同,各自io的操作也不同
  • 那么,这么多的硬件,os必须得对它们进行管理 -- 先描述,再组织
  • 所以可以设置一个struct,来存放每个硬件的信息操作方法(可以放函数指针)

封装struct

  • 这样,每个硬件都有自己的struct了,还需要把它们以某种方式组织起来
  • 一旦完成struct file的封装,对于上层来说,就没有硬件的区别了!!!都变成了同一类型的结构体对象!
  • 对于os来说,操作硬件,实际上就是操作它对应的结构体
  • 对于用户层来说,只要拿到它对应的指针就行
  • 访问同一种类型的对象(struct file),却能执行不同的功能,这就叫多态(在c++中介绍)
  • 一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第2张图片
  • 这样也就有了 linux下一切皆文件 的概念

标准流

介绍

之前c语言说过,执行程序的时候会自动打开三种标准流

  • 即标准输入流(stdin)、标准输出流(stdout)和标准错误流(stderr)
  • 这三种流分别用于处理输入和输出操作,是操作系统提供给程序的一种接口
  • 一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第3张图片

示例

默认

我们使用的io函数,当不指定路径时,会默认使用标准流

  • eg:scanf默认读取键盘输入,printf默认输出内容到显示器上
指定
  • 也可以指定写入标准输出中
  • 一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第4张图片
  • 这样可以看出来,实际上标准输出流是文件,stdout是文件指针
  • 同理,其他两种标准流也是这样

perror 

介绍

 perror函数会根据全局的错误码errno,将errno中保存的错误消息输出到标准错误流

很多函数在失败后,errno会被设置成相应的值,比如后面要介绍的open函数

参数

提供自定义的错误消息前缀,如果不需要前缀,可以将其设置为NULL

示例

当我们手动将errno设置为1,可以用perror打印出对应的错误信息:

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第5张图片

 

文件操作 

介绍

  • 对文件的操作 = 对内容操作 / 对属性操作 / 两者皆有
  • 但实际上,我们程序涉及到对文件的操作时,都需要先将程序运行起来(如果仅仅编译完成,形成一个可执行文件,还是没有进行文件操作的),此时就变为进程了
  • 所以,其实每次都是进程在操作文件
  • 本质:进程 和 被打开文件 之间进行交互

fopen的路径

引入

在使用fopen时,第一个参数是文件路径+文件名

但是,我们可以直接使用文件名,它会默认在当前路径下寻找该文件

介绍

但是!!!当前路径是什么路径啊!

先想想!

前面说了是进程操作文件,所以应该是进程当前路径!!

那么,进程是如何得知自己的路径呢?

示例

当我们以w打开文件,如果该文件不存在会创建一个

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第6张图片

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第7张图片可以看到,成功在code所在目录下创建了一个文件

原理

  • 还记得我们的task_struct吗?它描述了一个进程的所有属性
  • 所以,它的路径肯定也包括在内 
  • 我们可以通过/proc文件系统来查看进程相关数据

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第8张图片

示例

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第9张图片

  • /proc/进程pid 的目录下可以看到进程的信息,其中就包括cwd(保存进程所在路径)
  • 这样每个文件的路径也就有了,os将cwd和文件名拼接在一起就是该文件的路径
  • 也就完成了fopen默认在当前路径下寻找文件的实现

系统调用(一)

引入

  • 上面只说了下c语言的文件操作函数,其他语言当然也有自己相应的函数
  • 但是,想要操作文件,绕不开的还是os
  • 因为文件在磁盘上,磁盘是硬件,我们作为用户无法操作硬件,只能给os派发任务,才能通过os来访问硬件->操作文件

  • 如何和os交互呢?
  • 就是使用os提供的接口-->系统调用
  • 我们在写代码时,使用的os就是当前平台,不同语言在同一平台上,其函数下封装的一定都是一样的系统调用
  • 因此学习系统调用,可以让我们更好的理解文件操作
  • 即使语言变化,原理还是相同的!

调用图

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第10张图片

open

函数原型

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第11张图片

pathname:路径+文件名


flags -- 标识位
  • 可以看到,有多个设置项,但只由一个int值即可传递
  • 说明os将flags看成32位bit,而不是整数
  • 每个设置项对应一位bit,当对应bit位为1时,就说明设置了该功能
  • 一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第12张图片
  • 当我们以只写作为open的参数时,会发现它不能创建一个新文件
  • 但fopen是可以的
  • 说明!!! fopen下的封装,默认有一些设置项
三个基础标识位 -- 读写

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第13张图片  

O_CREAT

用于在打开文件时创建新文件(如果文件不存在)

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第14张图片

是fopen中默认已经设置的

O_TRUNC

用于打开文件时,将其长度截断为0,即清空文件内容

也是fopen中以w方式打开文件函数中的一个标志位

O_APPEND

用于打开文件时指定文件使用追加写入方式

实现fopen中以a方式打开文件的标志位

文件权限问题

设置完成后,查看文件的权限:

  • 一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第15张图片
  • 可以发现,生成的log.txt权限并不是我们平常创建文件的那样,怎么会有s呢
  • s实际上是一种特殊的权限,好像如果没有手动设置权限的话,权限会随机生成(我遇到的情况是这样的)
  • 实际上,还有另一个参数可以设置权限
mode -- 设置权限

使用八进制来设置权限

比如,0666是我们普通文件的默认权限

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第16张图片

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第17张图片

  • 这样,我们open重新生成的log.txt就和我们touch创建的文件权限一样了
  • 但是,生成的为什么不是0666,而是0664呢?
  • 因为存在掩码(这里的掩码是0002),这样第三组的权限中就没有w了
返回值
  • 成功打开文件,返回一个新的文件描述符fd,通常是一个非负整数,这个文件描述符可以用于后续文件操作
  • 无法打开文件时,返回-1,表示打开文件失败

文件描述符(fd)

引入

一个进程可以打开多个文件,os中又可以运行多个进程,所以内存中会存在大量的文件数据

  • 那么,os要不要管理?
  • 要!
  • 怎么管理?
  • 先描述,再组织!
  • 还记得c语言中有FILE结构体吗,打开文件后,FILE*代表文件,使用该指针,就可以操作文件
  • 因此,os中内部肯定也是使用结构体来存储文件信息(struct file),上层的表现取决于下层

介绍

  • fd用于标识和管理打开文件 或 其他I/O资源
  • 文件描述符的值通常是非负整数,通常从0开始递增
  • 文件描述符是一种抽象概念,它代表了程序在操作系统中打开的文件、设备或套接字的引用
  • 当应用程序打开一个文件时,操作系统会分配一个可用的文件描述符,并将其与该文件建立关联
三个标准流

  • 我们之前打印的fd是3
  • 那么0,1,2呢?总不能直接就从3开始吧,前面已经说过了,通常是从0开始的
  • 所以,这三个文件是谁呢?
  • 还记得前面说的吗,每个进程在其执行期间都会自动打开三个标准流:用于标准输入、标准输出和标准错误的输入和输出
  • 而那些标准流也就是被打开的文件,会被分配文件描述符,分别为0(stdin)、1(stdout)和2(stderr)
  • 所以,其他被打开的文件的fd就会从3开始噜

作用

fd用来操作文件(在后面的系统调用中可以看到,它作为函数的参数使用)

  • 因为os会为每个打开的文件分配fd
  • 所以,用来描述文件的结构体中一定就有fd
os中有那么多文件和进程,如何将 进程由该进程打开的文件 关联起来呢?
  • 这就需要fd发挥作用噜~
  • 想一想,fd是从0开始的连续整数(是不是和数组下标很像!!!),所以实际上它就是 联系进程和文件 的数组 的下标
  • 那么,用 fd 找到 文件 就和 下标访问 一样
  • 所以,进程的task_struct会有 指向这样的数组 的指针
  • 最终,对应关系如图所示:
  • 一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第18张图片
linux内核中的调用关系
  • 一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第19张图片
  • 那么,找到某个文件 -- 在该进程中的数组中,使用fd找到对应的文件结构体,就可以操作文件了

查看fd

文件指针 ->_fileno
  • 是C标准库中的一个内部成员,用于表示文件指针所关联的文件描述符
fileno
  • 是c库提供的函数,用于获取与文件指针关联的fd

 

系统调用(二)

fopen底层调用过程

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第20张图片

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第21张图片

  1. fopen中封装了open
  2. open会根据传入的路径,找到磁盘中的该文件
  3. 创建一个新的struct file结构体,将磁盘上的信息写入该结构体
  4. 用os提供给的fd,找到数组对应的位置,存放file*
  5. file内会保存open返回的fd
  6. 最后将file*指针返回给用户

 

read

介绍

读取文件内容到字符串中

返回读取的字节数

ssize_t
  • 是一个平台特定的类型,在不同的系统上可能有不同的字节大小
  • 通常用于与 文件和I/O操作相关 的函数
  • 以%zd形式打印
示例

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第22张图片

 

write 

介绍

将字符串内容写入文件中

示例

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第23张图片

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第24张图片 

成功写入文件:

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第25张图片

若换一个短一点的字符串,会发现,实际上我们此时以w打开并没有先清空文件

说明fwrite是默认封装了清空操作的,也就是对应的设置项

O_TRUNC

  •  这样就实现了清除功能
  • 此时的 open 才和 fopen中的以w打开 功能相同
  • (可以看到,比单纯的调用fopen复杂了很多,这就是底层实现和上层调用的差别)
O_APPEND

 上面已经实现了"w"的实现,若将其中的选项换成O_APPEND,则完成了"a"的实现一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第26张图片一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第27张图片

fwrite的底层调用过程 

一切皆文件的理解,标准流介绍,perror,fopen默认路径,系统调用open(标识位,fopen的底层调用过程),fd介绍(底层寻找文件过程),read,write(标识位,底层调用过程)_第28张图片

  • fwrite中封装了write
  • 通过传入的FILE*指针,可以找到FILE结构体中的fd
  • 因为是在进程中执行代码的,所以应该是自动在本身进程的task_struct找到*fs(数组地址)
  • 有了 数组地址 和 fd 后,write就可以找到对应的file对象
  • 就可以在那个file结构体中操作

 

close 

关闭文件

你可能感兴趣的:(linux,linux)