目录
1.系统调用的打开/读/写文件操作
2.如何理解Linux底下一切皆文件
2.1设备属性
2.2设备的操作方法
3.如何实现Linus底下一切皆文件
4.源码查看
5.系统和语言文件操作二者关系
5.1 flags选项和C语言的"w""a"方式 二者的关系
5.2 系统的文件描述符fd和语言的FILE类型 二者的关系
5.3 封装
5.3.1 为什么要封装
5.3.2 怎样封装
6.联系&拓展
1.系统调用的打开/读/写文件操作
- man 2 read
- man 2 write
读read的本质:是将内核级缓存的内容 拷贝到 用户级缓存区。
若内核级缓存没有内容呢❓
- 如果没有数据,把当前进程放到磁盘设备的等待队列里面。
- 磁盘开始加载数据到内核级缓存中。
- 加载完毕,把进程唤醒,放入运行队列。
写/修改write的本质:是将用户级缓存区的内容 拷贝到 内核级缓存当中,由OS定时刷新到外设。
若原来内核级缓存区有内容呢❓
即便内核级缓存有内容,需要再写入和修改,OS都必须先把内容从磁盘读到内存缓存中
write、read函数本质是拷贝函数
综上所述:无论读写操作,都必须在合适的时候,让OS把文件的内容读到文件缓冲区。
所以,对文件的修改是内核级的修改,不是磁盘级别。无论是全部修改完,只修改一个字节,都必须先加载到内核级缓存,在内存中修改完,由OS定期刷新到外设。
- man 2 open
open是在干什么❓数据什么时候被刷新到外设(不管,OS安排)❓
创建文件对象struct file
开辟文件缓冲区的空间,加载文件数据(延后)
查询进程所对应的文件描述符表
文件对象struct file的地址,填入对应的文件描述符表中(下标对应)
返回文件描述符表的下标☞文件描述符
此刻就完成了,文件的打开工作。所以在应用层/用户层 读写数据,就是把应用层的数据和内核级的数据的相互交换/拷贝。
2.如何理解Linux底下一切皆文件
- 磁盘中的文件,被打开的文件,OS会在内核中创建对应的文件对象struct file,给每个文件对象分配的数组下标(文件描述符),是从3开始。3456.....
- OS默认打开设备文件,设备文件的文件描述符是0/1/2,底层是硬件,和磁盘上的普通文件不是一回事。如何理解呢硬件是文件❓
- 这些硬件被打开,也必须在内核中创建对应的struct file,才能被归类到文件描述符表中,理解硬件<->文件的问题❓本质是:Linux底下一切皆文件。
- 什么叫一切皆文件呢❓硬件也是文件。但是很明显,键盘/显示器/鼠标等是设备,不是文件。
- 站在Linux系统角度,把硬件当作文件。这是怎么做到的❓
- 0:标准输入,键盘。
- 1:标准输出,显示器。
- 2:标准错误,显示器。
- 冯诺依曼体系中,所有的设备,叫做外设,IO设备。
- 设备=设备的属性+设备的操作方法(重点先放在操作方法上)
- OS是硬件的管理者,所以OS对每一种设备做管理。(先描述在组织)
- 每一种设备在OS内核中都要存在对应的PCB☞struct device
2.1设备属性
- 设备文件struct file☞有文件的属性和指针☞指向设备的属性☞struct device
- struct device中的设备属性/类别都是一样(设备名/设备厂家等等),只是属性值是不一样的。
- struct file☞文件相关属性+设备文件相关属性struct device
2.2设备的操作方法
- 每种设备都有设备各自的操作方法
- 注❗键盘没有写方法write。显示器没有读方法read。可以将设备没有的方法设置为NULL即可。
- 不同的设备的操作方法一定是不一样的❗
- 设备的操作方法,都是由驱动层来完成的。
- 驱动层:工程师来完成的,键盘工程师/显示器工程师等。
- 每种工程师要写出来访问外设的操作方法☞函数 void k_read()
- 设备文件对象struct file里面还会包含设备操作方法的函数指针void (*read)(......) ☞函数 void k_read()
站在OS角度,每一个被打开的设备,都必须在OS内核中创建一个设备对应的设备文件对象struct file。(在打开这个设备的时候,就必须创建struct file)
- 属性角度:struct file里面包含一些指针☞指向设备文件相关的属性以及文件相关属性。
- 操作方法角度:struct file里面包含函数指针☞指向外设的操作方法(函数)
- 用户就不需要再考虑硬件的底层的差异。因为全抽象成 设备文件struct file结构体对象中☞struct file包含设备操作方法的函数指针void (*read)(......)
- 虽然底层设备的操作方法(函数实现)一定都是不一样的。
- 同样不同的设备的不同操作方法(函数实现)可以抽象成一样的 返回值和参数
- 实际上抽象struct file里面的函数指针(读方法/写方法等等)
- 打开设备文件的本质:是创建了struct file后,将其中的函数指针指向这个设备的底层操作方法的函数。
综上所述:访问一个外设,只要找到这个设备的文件,直接调用函数指针指向的函数(操作方法),既可以访问设备了。同时也获取了设备文件的属性和文件的相关属性。
所以,struct file可以是键盘设备文件/显示设备文件等等,也有存在内核级缓存,写和读就是内核级缓存和键盘/显示器硬件交互。(图)
所以,每一个被打开的文件除了拥有内核级缓存,还需要一张操作底层方法的指针表。根本不需要知道这个struct file文件代表什么。它底层都会指向各自的属性和操作方法的。(图)
3.如何实现Linus底下一切皆文件
- Linux底下一切皆文件的技术被称为:多态
- struct file可以找到不同设备的属性和操作方法。(用C语言来实现类)
- 类=属性+方法
- 用C语言实现类:C语言结构体是只能放属性不能放方法,但是可以放函数指针(相当于放方法☞指向函数)。所以属性+函数指针(指向方法)
- C语言实现的多态技术❗(图)
- 虚拟文件系统:在Linux/软件工程领域 的问题 都可以直接或间接添加软件层来解决。图
4.源码查看
【设备文件的操作方法函数指针】
【设备文件的操作方法函数】
5.系统和语言文件操作二者关系
5.1 flags选项和C语言的"w""a"方式 二者的关系
在C语言中,使用的fopen/fclose等接口是库函数。
语言层面上学习文件时,对应的库函数接口,底层都是系统调用。只不过做了封装。
所有的C语言上的文件操作函数,本质底层都是对系统调用的封装 (图)
5.2 系统的文件描述符fd和语言的FILE类型 二者的关系
- 进程打开文件☞进程维护的表:文件描述符表☞下标(文件描述符)☞ struct file
- 在OS内,系统在访问文件的时候,只认文件描述符fd❗
- 如何理解C语言通过FILE*访问文件呢❓
C语言编程语言:没有文件描述符fd,在访问文件使用的都是文件指针FILE*FlLE是一个C语言提供的结构体类型。(课件最后)
FILE在语言层面上是一个结构体。在系统层面上OS只认识文件描述符fd。
所以,FILE结构里面内部一定封装了文件描述符fd。文件流必须封装了文件描述符fd❗
证明FILE结构里面内部一定封装了文件描述符fd
- man 3 fwrite
第一个参数:缓冲区/字符串
第二个参数: 基本单位的大小
第三个参数: 写几个基本单位
第四个参数:往哪个文件流中写
返回值:实际写了多少个基本单元。
stdin / stdout / stderr 和 0/1/2
综上所述:语言层面上学习文件时,对应的接口,底层都是系统调用。只不过做了封装
在类型上。系统底层访问文件只能用文件描述符,语言上就用结构体封装了文件描述符。图
【stdin / stdout / stderr 和 0/1/2】
5.3 封装
5.3.1 为什么要封装
- 写代码,推荐使用语言提供的文件操作,不推荐系统调用(除非非用不可)。
- Linux/MAC/windows操作系统一定是不一样的,体现在方方面面,系统调用接口也都不一样。
- 如果在Linux上使用Linux系统调用接口写的代码,不一样能在其他操作系统上编译通过使用。
- 系统调用不具有跨平台性
- C/C++编程语言是具有可移植性,跨平台性的。
- C/C++编程语言写的代码,无论是在Linux下还是其他系统下面都可以编译通过使用。
- 为什么要封装❓
- 所以语言都想要具有跨平台性,所有语言要对不同的平台的系统调用进行封装(图)
5.3.2 怎样封装
- 语言为什么具体跨平台性❓语言怎么做到具体跨平台性❓图
- 编程语言的源代码中的标准库☞库的设计者
- 在条件编译上 设计☞不同系统的条件下各自编译一份☞使用哪套系统就下载哪套编译完成的库即可
- 同一语言☞针对不同的平台☞具有不同的标准库☞根据不同平台系统的需求下载不同的库
- 库的底层☞不同平台系统 实现 是不同的☞但是语言会把他们封装成相同的。
- 编程语言的标准库在不同系统的底层实现上是不一样的,但是经过编程语言的封装所使用的库函数接口是一样的。所以使用哪个平台,就安装此平台的语言的标准库。所以编程语言是具有跨平台性的。
6.联系&拓展
- 打开文件就是打开一个设备(显示器),其实是终端。
- 一个进程在运行时,默认会打开三个输入输出流。
- /proc目录下是当前正在进程的pid
- /proc/pid 目录下pid的进程的所启用文件的文件描述符/可执行程序/当前工作路径等等
- 文件描述符☞终端(键盘/显示器)
- /dev/pts/4就是的终端文件。进程把/dev/pts/4设备文件打开写入,相当于往终端写入。
- 每次登录云服务器,shell是第一个进程,它就把0/1/2打开了。后面每打开一个终端,就会把shell创建的/dev/pts文件再次打开,增加新的终端对应的设备文件。
- 终端不是显示器,显示器只有一个。但是可以有很多终端。