文章目录
基础IO预备小知识
1:文件 = 文件内容(数据和代码) + 属性(数据)
2:文件的操作范围: a: 对内容 b:对属性
3:文件在磁盘(硬件)中存放,文件通过编译链接才能被进程访问。其中,访问文件的本质其实就是进程访问文件。
文件封装的特性
系统接口与封装
我们知道,用户向硬件写入时只有操作系统有权利,普通用户想对硬件进行写入只能让OS提供相关系统接口——文件类的系统调用接口,但是直接使用文件接口比较困难。所以通常各种上层语言中会对这些接口进行封装来为了让接口更好的使用。这就导致不同的语言,就有不同的语言级别的文件访问接口。因为,它们封装的接口在一个系统中只有一套,所以必须学习。
文件封装的跨平台性
我们知道,所有访问文件的操作,都必须使用OS相关接口。但是一旦进行对系统接口进行编写有关文件的代码,由于各种语言的特性,就可能无法在其他平台上运行了。因为其他语言的代码根本就不具备跨平台性。
所以,一些语言文件接口要经过相关封装来保持它的跨平台性。
例如C语言和C++保持跨平台性:
c语言和C++保持跨平台性指将所有平台的代码都编译阿链接实现一遍,但是例如C++会通过条件编译,会将不需要的额语言进行动态裁剪,进而实现C++跨平台的作用。
文件与进程的关系
例如;
显示器:Printf/Cout ——>进程向显示器写入数据。(站在OS角度,显示器的打印动作,是进程将数据写在屏幕上。)
键盘:scanf/cin——> 输入数据,将输入的数据传递给进程,被进程所读取答。
键盘将用户输入的数据读取后加载到进程,进程再将数据写入到显示器中,或者磁盘中。
总结:
普通文件通过fopen打开后fread读取文件数据到进程内部中(也在内存中),这个动作的过程称为input。进程通过fwrite函数读取到另外一个文件称为output。
什么叫做文件?
站在系统角度:
能够被inpuit读取的文件,或者能够被output写出的设备的统称为文件。
狭义上的文件: 普通的磁盘文件。
广义上的文件:显示器,键盘,网卡,显卡,网卡,声卡等几乎所有的外设,都可称之为文件。
回顾C语言相关文件IO
什么叫做当前路径?
我们原来认为,当前当前路径就是源代码所处的路径。
比如,当我们用写的方式打开文件。如果没有该文件的话,就会自动生成。
运行结果:
可见当没有打开的文件时,会在该程序的源代码的路径中生成log.txt文件。
可是,当我们将可执行程序拷贝到另外一个目录运行时:
也在myfile同一目录下生成log.txt文件,并且是运行的可执行程序创建了该文件,说明是进程创建了该文件。
现在,我们让myfile文件不断运行,使用命令行脚本
ps ajx | head -1 && ps ajx | grep myfile
查看该进程的PID。
获取当前进程的PID之后再通过ls /proc/6199 -l 命令查看该进程的具体信息。
发现该进程记录它本身的工作目录和工作路径。
当我将myfile可执行程序切换到/home/yzh/test.c目录中,运行并查看该进程的具体信息时:
发现cwd也跟着发生了变化,变成了/home/yzh/test.c,而这个目录也就是当前进程的工作目录。
总结:
当一个进程运行起来的时候,每一个进程都会记录这自己的当前路径,当可执行程序的工作目录发生了变化,所以每个进程的工作路径也会发生变化。
所以,进程执行时,当打开一个文件时,形成该文件时,会将进程的目录与文件的名字在底层进行拼接进而产生了该文件的路径。
那么最终结论为:
所谓当前路径,就是进程运行起来的工作路径。
C语言输出函数与文件打开方式
以"w"的方式打开文件
C语言三个输出函数:fwrite,fprintf,fputs.
对于系统来讲,输出的过程本质就是进程对文件的写入。
运行结果如下:
我们这重点将下c语言文件输出函数:fwrite;
此时,当前我们用fopen函数以"w"的方式打开时,只执行fwritec语言文件输出函数代码时;
在log.txt代码运行后,即在文件写入之前,原来的内容都已经被清空了!
总结:
文件以w的方式打开,类似于输出重定向。例如:>log.txt的意思就是不在log.txt文件中写入内容,而只将·将文件以“w"的方式打开,打开的时候就会将log.txt文件清空。
以"a"的方式打开文件
当我们用"a"的方式打开文件时:
两次运行该可执行程序:
可见文本内容在原来文本的内容上追加。
以"w"的方式读取文件
以"w"的方式读取文件,本质上是进程读取文件,我们称这一过程为input(输入);
例如:
我们使用fgets函数按照行数不断读取sizeof(line)个数的字符,读入到line数组中保存,最后再使用fprintf函数将line数组中的数据打印到标准输出流中。我们不用担心数组溢出等情况,因为fgets函数如果没没读到’\n’,那么只会在n-1的位置上停止读取,留一个位置给’\0’。
运行结果如下;
此时,我们显示写 main函数的命令行参数,将需要打开的文件用命令行参数代替,这样当我们运行可执行该程序,shell会根据我们所传的命令行参数不同,读取不同的文件储存在数组后,再打印该数组。
运行结果如下:
我们在执行该程序时,传递什么文件名就会输出对应的该文件名文本内容,这不久对应了Linux中cat命令吗?
标准输入,标准输出,标准错误
例如:
在C语言上,键盘,显示器等硬件都被C语言看作FILE类型的指针。所以,普通文件和硬件在C语言是一样的,统一被看作FILE看待。
这也更加体现了一切皆文件!
直接使用系统相关接口
open函数
open函数系统文件及相关形参:
open函数必须所带的选项之一:
open函数参数flags的介绍
fopen函数中flags可以一次传递多个标志位,从而实现各种特定的功能。
使用位操作来实现传递多个标志位:
通过比特位的运算,传递不同的flags来实现不同的特定功能。
运行结果如下:
总结:
open函数形参flags中的含义便传递的选项无非就是位图的标志位,为了实现特定的功能从而将特定的标志位进行组合。
open函数flags形参的使用
当我们使用系统接口open函数以O_WRONLY(只读)的方式打开文件时:
编译链接运行结果:
此时打开文件时却并没有C语言文件接口fopen函数在没有log.txt文件时便会创建这一功能!
当我们向open函数标记位flags传递有关文件不在时便创造该文件的宏时:
**
再次运行结果如下:
发现在没有创建log.xtx文件时,open函数可以创建该文件。
可是,我们创建的log.txt文件的权限是随机的。
open函数mode形参
mode形参是关于控制我们需要创建文件并控制该文件的权限时。
可以利用八进制传递给open函数的第三个形参来控制权限,并希望该文件的权限为我们希望拥有组,所属组,orther组均为读写权限。但是又因为我们知道
系统最终权限 = 起始权限 ~ ( -umask) 所以,我们可以将使用umask(0)函数来减少对系统最终权限的影响。
运行结果如下:
此时log.txt文件便是我们所需要的权限。
write函数
此时,通过open打开文件之后,我们便开始进行文件输入了。
write函数三个形参分别指:
fp: 要写入的文件名打
buf : 要写入的数据
count: 要写入的数据个数
联合起来的意思就是:
将要写入的数据按照需要的个数写入在指定的fp文件当中。
例如:
我们使用系统接口write函数来将字符串写入到log.txt文件中。
运行结果如下:
成功打印log.txt文件内容。
write函数中第一个参数fd
我们分别在系统中打开4个文件,并输出4个打开文件的返回值。
编译运行时输出的结果为:
fd分别为 3,4,5。此时,我们便有一个问题,fd中的0,1,2分别表示什么,它们去哪里了呢?
这时,我们需要知道三个对应关系。
证明上图的关联性:
我们使用fprintf 函数 将字符串"hello world" 写入到显示器中,使用write系统接口写入到 fd 为 1 中。
运行结果如下:
这两个字符串都写入了屏幕中,可见fd “1” 的效果 等于 输出流stdout。
我们可以将使用fcanf函数将stdin中获取的数据以"%d"的格式存放于地址a中。
也可以使用系统接口read函数,将"0"的数据按所所需要读取个数的字符储存在buf数组中。
运行结果如下:
可见stdin 和 0 的效果都是一样的。
fd和stdin,stdout,stderr的关系
FILE是什么?
FILE在C语言中是一个struct结构体,由C标准库提供,C语言中库函函数内部一定会调用系统接口,并且在使用系统接口读取文件中必须要用fd,说明FILE结构体里面必定封装了fd,fd也表示文件。
我们知道stdin,stdout,stderr——都是文件指针类型FILE* 说明其中也必定有fd。
证明:
我们分别打印stdin,stdout,stderr中所指文件的标识符。
运行结果如下:
正好是我们以前所缺失的 0,1,2 !
fd是什么?
fd简单来说就是一个数组的下标。
一般而言:
因为 进程:打开的文件 = 1:n的关系。如果一个进程要打开多个文件,那么在系统中就会存在许多被打开的文件,所以,OS要将如此多被打开的文件进行统一管理。在Linux系统内核中,OS内部为了管理每一个被打开来的文件,会先构建一个struct file 文件对象(该对象里面包含了文件的所有内容)来充当一个文件,再用双链表进行组织。简单来说,就是先描述再组织。
fd在Linux内核中,本质上就是一个数组下标。当我们使用open函数接口打开文件时,系统会在内核中创建一个文件对象,并将该文件对象的地址填入到struct file* 数组中,并会将该文件对象的对应下标fd返回给用户,用户根据fd就可以调用所需要的系统接口。所以PCB能够根据fd找到对应目标文件的地址,进而通过哈希索引找到对应的文件对象,而这个文件对象包括了文件的内容和文件的基本属性。
open函数实现"w",“a”,"r"功能
(一)open函数实现“W”功能
综合以上操作,我们已经能简单的使用open函数打开文件进行相应的写入文件操作,可是我还想继续向log.txt文件中再一次写入实现C语言文件接口fopen函数以"w"打开的功能呢?
当我们只以写的方式打开文件,并将字符串写入时。
我们发现,相比原来的文本内容,本次写入文件是对原来的内容进行覆盖式的写入。
我们发现标志位有一个宏:
O_TRUNC : 如果在打开文件时有文本内容的话,会对该文本内容进行清空。
当我们加上O_TRUNC标记位时:
运行结果如下:
此时打原来文本的内容已经全部清空
总结:
所以,fopen(“log.txt”,“w”) = open(“log.txt”, O_WRONLY,|O_CREAT|O_TRUNC,0666)。
(二)open函数实现 "a"功能
在open函数接口中,还有一种标记位。O_APPEND:以追加的方式打开文件。
我们向标记位中添加了标记位O_APPEND,并对打开的文件进行写入。
运行结果如下:
当我们多次运行可执行程序时,发现能够对该文本内容进行持续性追加。
总结:
fopen(“log.txt”,“a”) = open( “log.txt”,"O_WRONLY|O_CREAT|O_APPEND,0666)
(三)open函数与read函数接口实现
系统接口: read
读取对应文件fd中字符串s的count个数的字符,存放在缓冲区buf中。
open函数flags标记位改为:O_RDONLY以只读的方式打开文件。
并将缓冲区buff进行初始化赋值位’\0’,将读取的数据存放在缓冲区中打印输出。
运行结果如下:
成功将log.txt文件读取。