什么是IO?
I input;O output 以我目前的理解,我认为IO就是“写进去”与“拿出来”的过程。
比如说文件IO,就是把内容写进文件里面,得到文件里的内容的过程。
那么,往文件内容写东西,得到文件内容,有哪些“手段”呢?
先来看C语言为我们提供了哪些手段(函数接口)
fopen--打开文件
函数原型:
参数1:filename 可以使用绝对路径和相对路径(什么是绝对路径和相对路径?)
参数2:mode 以哪种方式打开文件
具体有以下几种打开方式
fwrite--对文件进行写入
参数原型:
参数1:ptr 指向任意数据类型的指针,他所指向的内容将写入stream中去
参数2:size,ptr指向数据类型的大小
参数3:count 写多少个size大小的元素到stream中去
参数4:stream FILE*类型的指针(FILE其实是一个结构体类型)
fread--读取文件内容
函数原型:
参数与fwrite类似,只是将stream里的文件内容写到ptr中
fclose--关闭文件
参数 stream要关闭的文件名
操作实例:对一个文件进行写入
可以看到,内容已经被写入到test.txt中去了
操作实例,对一个文件进行读取
运行结果:
成功读取到了test.txt中的内容
接下来再看看LINUX提供的系统调用接口
在此之前,先说明一下LINUX提供的系统接口与C语言提供的接口之间的关系,简单说就是,C语言提供的接口是对系统接口的的封装,即C接口的底层是使用系统接口实现的
open--打开文件
函数原型:
参数1 pathname 文件名(相对路径/绝对路径)
参数2 flags 以哪种方式打开文件(O_WRONLY(只读),O_CREAT(不存在则创建文
件)O_TRUNC(每次打开文件时清空文件)等等)
参数3 mode 设置文件权限
返回值:返回一个文件描述符(fd),它是非负的整数,被用于read、write等系统调用。成功返回的文件描述符会是最小的、当前没有为该进程所打开的文件描述符
write--对文件进行写入
函数原型:
参数1 fd 文件描述符
参数2 buf 一个指向任意数据类型的指针,其指向内容将被写到文件中去
参数3 count 写count个字节到文件中去
返回值:成功返回写入的字节个数,失败返回-1.
read--读取文件
函数原型:
将fd所在文件中至多count个字节存储到buf指针所指向空间中去
参数与write一致返回值:成功返回成功读到的字节个数,失败返回-1
close--关闭文件
函数原型
使文件描述符不再指代一个文件
返回值:成功返回0 失败返回-1
操作实例,对一个文件进行写入
运行结果:log1.txt中成功写入了msg的内容
操作实例,读取一个文件的内容
运行结果:文件内容成功存入rdmsg中
文件描述符--fd(file descriptor)
是谁打开了文件?是进程。为了让进程知道哪些文件属于自己,所以要建立进程与文件之间的关系,因此在PCB中会存在一个指向files_struct的结构体类型指针,该结构体存有一个
struct files* fd array[32]的数组,数组元素是指向文件的指针,而fd就是这些数组的下标。
可以说,文件描述符是用户使用文件的媒介。
值得注意的是,当一个进程启动时,OS会自动为它打开0、1、2下标所对应的IO流,他们分别是标准输入流、标准输出流、标准错误流。也就是进程不用打开这三个文件,也可以向他们进行写入/读取。
文件描述符分配规则
优先分配进程最近的,未被使用的数组下标作为文件描述符。
验证:
运行结果:
fd的返回值是3,符合预期,因为0 1 2下标已经分配给了键盘、显示器、显示器文件
利用fd完成文件重定向
什么是文件重定向:更改struct files* fd array[32]数组元素的指向。
如何实现文件重定向:以输出重定向为例
运行结果:
分析:close(1)的作用是取消进程与显示器文件的联系,可以理解fd_array[1]不再指向显示器文件,随后我们又创建了log.txt文件,根据文件描述符分配规则,fd_array[1]指向了log.txt,因此当我们再向1号下标所指代的文件中写入,直接写入log.txt而不是显示器文件
追加重定向:
每次进行写入时文件偏移量都位于文件的末尾
输入重定向:
将写入键盘文件的数据写入到log.txt中
FILE与fd
在前面的接口中,有C提供的,例如fopen,fwrite等,返回值是FILE*,也有LINUX提供的系统调用接口,例如open,write等,返回值是fd。C提供的接口其实是对系统调用接口的封装(例如fopen底层一定封装了open),方便用户使用。所以FILE是C接口层面的概念,fd是系统调用层面的概念。
什么是FILE
- FILE是一个结构体,要想成功访问文件,那么FILE结构体中就一定有存储fd的变量。
- FILE是结构体_IO_FILE的重命名
如下是_IO_FILE的成员变量,_fileno是用于存放fd的
C缓冲区与内核缓冲区
什么是缓冲区?
缓冲区,是一块内存的空间,是用于存放数据的地方
有哪些缓冲区?
就我目前而知,缓冲区有用户级缓冲区(C语言提供的)以及内核缓冲区。
这两个缓冲区之间的关系?
用户先将数据刷新到用户级缓冲区,再等待合适的时机,将这些数据刷新到内核缓冲区。OS再根据自己的刷新策略将数据刷新到外设。
如何维护C缓冲区?
依靠FILE结构体,FILE结构体中有大量缓冲区相关字段
谈谈刷新时机
(访问文件,就是访问外设,访问显示器文件,就是访问显示器)
1.行刷新,碰到\n就刷新数据到内核缓冲区,通常是向显示器文件中写入
2.全缓冲,写满缓冲区才刷新数据到内核缓冲区,通常是向普通文件中写入
3.无刷新,直接刷新,不用等待,常见的是向stderr中写入
验证:
1.行刷新
按照我们上面说的,前面三个printf的内容会被刷到内核缓冲区,并打印到显示器上,后面三个不会
运行结果:
与预期一致
值得注意的是,我们在代码的末端加入了一个close(1),也就是说我这个进程无法再访问1所指向的文件,去掉close(1)会发生什么?
运行结果:
我们发现,所有的数据都被打印出来了,由此可以得到一个结论:进程退出时,缓冲区的文件也会被刷新到内核缓冲区中(前提是缓冲区文件向内核缓冲区刷新的通道没有被关闭,也就是close(1))
全缓冲
运行结果:
我们发现log.txt的大小是0,这足以说明:数据现在仍然存在于用户级缓冲区当中,还没有刷新到内核缓冲区,那么log.txt的大小自然是0
现在,我们让数据多写一些到用户级缓冲区当中去
运行结果
用户级缓冲区的数据果然刷新到了磁盘文件上。
无刷新
运行结果:
可以看到,我们既没有加\n,写入的数据大小也不是很大,但它仍然成功写入了stderr中(也就是显示器文件),这足以说明往stderr中写入时采用的是无刷新策略。
文件系统
大部分文件为普通文件,他们存储在磁盘上,当进程调用文件时才会被加载到内存之中。
所以磁盘是什么呢?
- 磁盘是大容量的存储介质,既可以输入又可以输出
- 磁盘的组成:盘面、磁头
- 盘面上:扇区、磁道
- 柱面
(上图转引自:Linux基础IO_读写文件时,更改起始读写位置的函数名字-CSDN博客)
如何对存储在磁盘上的文件进行定位
1.确定文件所在磁头的那一个盘面
2.确定文件所在扇面的磁道
3.确定文件所在磁道的扇区
这就是CHS定位法
如何看待磁盘呢?
扇区是访问磁盘的基本单位(512byte),由于磁盘本质上是由一个一个的扇区组成的,因此整个磁盘可以被看作是一个很大的数组。
操作系统是管理软硬件的软件,所以他也会对磁盘做管理。由于磁盘空间太大,所以操作系统会对磁盘进去分组管理。
分区组成:是将磁盘分成若干个分区,每个分区由启动块,和块组组成
块组组成:
(以下六点转载自:Linux基础IO_读写文件时,更改起始读写位置的函数名字-CSDN博客)
- Super Block: 存放文件系统本身的结构信息。记录的信息主要有:Data Block和inode的总量、未使用的Data Block和inode的数量、一个Data Block和inode的大小、最近一次挂载的时间、最近一次写入数据的时间、最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了。
- Group Descriptor Table: 块组描述符表,描述该分区当中块组的属性信息。
- Block Bitmap: 块位图当中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用。
- inode Bitmap: inode位图当中记录着每个inode是否空闲可用。
- inode Table: 存放文件属性,即每个文件的inode。
- Data Blocks: 存放文件内容。以块为基本单位(4KB)
何为inode?
inode存放了一个文件的所有属性,它是一个结构体,大小是128字节,其中有两个成员变量为我们比较关注,inode number即inode编号,int blocks[NUM]即文件内容存储于哪些块中。
为什么要有inode编号?
一个文件一个inode,文件数量太多,也就是inode也太多,所以需要inode编号来对inode进行区分。
inode编号的作用?
主要用于查找一个文件的inode,找到了inode就知道了文件的属性,知道了文件的内容存储在哪里,也就可以对文件进行修改。可是我们查找文件都是使用文件名的,并不知道inode编号。
目录会为我们解决这个问题——
Linux下一切皆文件,目录也是文件,它也有自己的inode结构和存储数据的块,它存储的就是当前目录下文件名与inode编号的映射关系。
如何删除一个文件?
inode bitmap与block bitmap置为0
如何新建一个文件?
根据目录的inode编号,找到当前分区,查询inode bitmap,为文件创建一个inode结构,并将文件属性填充进inode,将该文件的文件名和inode指针添加到目录文件的数据块中。
软/硬链接
什么是软链接:
软链接其实是指创造一个文件,使它存储的是另一个文件的绝对路径,该文件有自己独立的inode,这相当于windows操作系统中创建快捷方式的行为
如何使用软链接:
1.创建软链接(对softlink创造一个softlink-s的软链接)
运行结果一致
inode编号不同
软链接文件和源文件具有关联性,即源文件删除,软链接文件就无效了
什么是硬链接:
相当于对文件取别名,即inode编号相同,存储数据相同,但文件名不同
如何使用硬链接:
运行结果:
inode一致,文件大小一致
硬链接文件 与源文件具有独立性 ,既然一方被删除也不会影响另一方
硬链接数:
硬链接数变成了2,对具有相同inode编号的文件来说,具有几个文件名就有几个硬链接数
特别的,对于目录来说,由于它的硬链接数默认是2,这是由于目录下有隐藏的.文件,它与目录有着相同的inode编号
以上便是对linux基础io的知识总结,各位大佬若有指教,欢迎在评论区留言,俺不胜感激!