0.感性认识一切皆文件
linux认为,一切皆文件。
对文件而言:
曾经理解的文件:read 、 write
显示器:printf/cout ——》 一种write
键盘:scanf/cin ——》一种read
🧐什么叫做文件呢?
站在系统的角度,能够被input读取,或者能够被output写出的设备就叫文件!
侠义上的文件:普通的磁盘文件
广义上的文件:显示器,键盘,网卡,磁盘,显卡,声卡,几乎所有的外设,都可以称为文件
1. 回顾C中的文件操作
🥑C读写文件
文件操作:
首先要打开文件:打开成功,返回文件指针;打开失败,返回NULL
最后要关闭文件
FILE *fopen(const char *path, const char *mode);//路径 + 打开方式 int fclose(FILE *fp);
💦C写文件
们可以fputs/fgets以字符串形式读写;也可以fprintf/fscanf格式化读写
int fputs(const char *s, FILE *stream); 向特定文件流写入字符串
int fprintf(FILE *stream, const char *format, ...);
什么叫做当前路径?
当一个进程运行起来的时候,每个进程都会记录自己当前所处的工作路径
如果以w模式打开文件,默认把原始内容清掉,再写入(类似输入重定向)
如果要以追加方式写,则要以"a" append模式打开文件
💢细节提问: 此处的strlen要不要+1?
//进行文件操作 const char* s1 = "hello fwrite\n"; fwrite(s1, strlen(s1), 1, fp);
不要!,\0结尾是C语言的规定,文件用遵守吗?文件保存的是有效数据!
否则就会出现乱码
tips:快速清空文件
>log.txt //输入前已经清空文件,输入的又为空白
💦C读文件
fgets从特定文件流中按行读取,内容放在缓冲区。读取成功返回字符串起始地址,读失败返回NULL
char *fgets(char *s, int size, FILE *stream); //size:为缓冲区大小
🥑关于stdin stdout stderr
C语言默认会打开三个输入输出流:stdin、stdout、stderr, 它们的类型都是FILE*,这三个东西是什么呢?
C语言把它们当做文件看待;站在系统角度,stdin对应的硬件设备是键盘、stdout对应显示器、stderr对应显示器,本质上我们最终都是访问硬件。C++中也有cin、cout、cerr,几乎所有语言都提供标准输入、标准输出、标准错误
既然fputs是向文件写入,stdout也是FILE*类型,我们是不是可以向显示器标准输出打印了?这说明显示器被看做文件(有那味了) 喊出那句话:Linux下,一切皆文件
2.系统文件 I / O
通过之前的学习,这些文件操作最终都是访问硬件(显示器、键盘、磁盘)。众所周知,OS是硬件的管理者。所有语言上对“文件”的操作,都必须贯穿操作系统。然而OS不相信任何人,访问操作系统,就必须要通过系统接口!!
open/fclose,fread/fwrite,fputs/fgets,fgets/fputs 等库函数一定需要使用OS提供的系统调用接口,接下来我们就来学习文件的系统调用接口,才能做到万变不离其宗!!
🌈open & close
💢通过手册查找 man 2 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. O_RDONLY: 只读打开 O_WRONLY: 只写打开 O_RDWR : 读写打开 以上这三个常量,必须指定一个且只能指定一个 O_CREAT : 若文件不存在,则创建它。同时需要使用mode选项,来指明新文件的访问权限 O_APPEND: 追加写 mode: 设置默认权限信息
返回值说明:
return the new file descriptor, or -1 if an error occurred (in which case, errno is set appropriately). 成功: 新打开的文件描述符 失败: -1
💢man 2 close
#include <unistd.h> int close(int fd);
话不多说,用起来。open如果以写入方式打开且文件不存在,需要或|上O_CREAT,这与C中以"w"模式打开完全一样 (为什么是|呢,继续看下去吧)。如果我们先不带第三个参数去实现
发现open并没有帮我创建新文件,因为我刚刚用的是C语言的接口,C语言的接口创建难道在系统也一样?
可以看见文件的权限为什么是这样子的?有这个文件,要创建它,系统层面就必须指定权限是多少!,也是要用到我们的第三个参数了,我们采用权限设置的八进制方案——
我们在应用层看到一个很简单的动作,在系统接口层面甚至OS层面,可能做了非常多的动作! 我根本就不关心什么只写、创建、权限这些与系统相关的概念。语言为我们做了封装,我用就好了。
也就是:哪有什么岁月静好,有人负重前行罢了
fopen("./log.txt", "w"); int fd = open("./log.txt", O_WRONLY | O_CREAT, 0666);//umask过滤权限,可以把umask设为0
💢那第二个参数flags(int)为什么要把模式|在一起呢?
这是一种用户层给内核传递标志位的常用做法。
int有32个比特位,不重复的一个bit,就可以表示不同状态,就可以传递多个标志位且位运算效率较高。这些O_RDONLY、O_WRONLY、O_RDWR都是只有一个比特位是1的宏,并且相互不重复,这样|在一起,就能传递多个标志位
//用int中的不重复的一个bit,就可以表示不同状态 #define ONE 0x1 //0000 0001 #define TWO 0X2 //0000 0010 #define THREE 0X4 //0000 0100 //系统内部用 & 来验证标志位是否为1 void show(int flags)//0000 0011 { if(flags & ONE) printf("hello one\n");//0000 0011 & 0000 0001 为真 if(flags & TWO) printf("hello two\n"); if(flags & THREE) printf("hello three\n"); } int main() { show(ONE); show(ONE | TWO); //0000 0001 | 0000 0010 show(ONE | TWO | THREE); }
进入fcntl-linux.h此文件,可以看到
🌈read & write
找辣个男人问问 哈哈哈
💦man 2 write
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count);
参数:
buf: 用户缓冲区
count: 期望写的字节数
返回值:实际写入的字节数
又一次写入时,我们发现:
O_TRUNC: 打开文件的时候直接清空文件
O_TRUNC:
如果文件已经存在并且是一个常规文件,并且开放模式允许写入(即是0_RDNRor O_MwRONLY),那么它将被截断为长度为0(也就是清空文件)
O_APPEND: 追加文件
注意注意:写入文件的过程中,不需要写入\0!因为\0是C语言层面上规定字符串的结束标志,而写入文件关心的是字符串的内容,文件和语言不要搞混了
💦 man 2 read
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); 参数: buf: 读到的内容放在用户层缓冲区中,也就是自己定义缓冲区 count: 期望读多少个字节 返回值:实际读多少个字节
读文件的前提:文件已经存在,不涉及创建及权限的问题,那么用两个参数的open打开文件即可
3.文件描述符(fd)
通过上面的练习,我发现每次成功open的fd都是3
接下来,我们连续的打开文件,观察fd。我们知道打开文件失败返回-1,那么012去哪了呢?012消失的原因,要么是不让用,要么是被别人占用
事实上,当我们的程序运行起来变成进程,默认情况下,OS会帮助我们打开三个标准输入输出,012其实分别对应的就是标准输入、标准输出、标准错误
实践出真知:
c语言上的stdin标准输入、stdout标准输出、stderr标准错误,对应硬件设备也是键盘、显示器、显示器,这有什么关联呢?
话说回来,我们还是一直没搞懂FILE*是什么东西,FILE*open
FILE其实是一个struct结构体!是C语言库提供的,一般内部有多种成员
C文件 库函数内部 一定要调用 系统调用
在系统角度,只认识fd
所以FILE结构体里面,必定封装了fd
怎么样证明FILE结构体里面,必定封装了fd?
🎨file descriptor(fd文件描述符)
所有的文件操作都是进程执行对应的函数,即本质上是进程对文件的操作
🔸 如果一个文件没有被打开,这个文件是在磁盘上。如果我创建一个空文件,该文件也是要占用磁盘空间的,因为文件的属性早就存在了(包括名称、时间、类型、大小、权限、用户名所属组等等),属性也是数据,所谓“空文件”是指文件内容为空
🥑文件 = 内容 + 属性。对文件的操作也是分成两类的:对文件内容的操作 + 对文件属性的操作
🔸 要操作文件,必须打开文件(C语言fopen、系统上open),本质上,就是文件相关的属性信息从磁盘加载到内存的过程
文件:被进程打开的文件(内存文件),没有被打开的文件(磁盘文件)
操作系统中存在大量进程,一个进程可以打开多个文件:进程:文件 = 1:1。系统中会存在大量的被打开的文件!所以OS要不要把如此之多的文件在内存中也管理起来呢? 必须管理! 先描述再组织
我们操作系统是C语言写的,OS内部要为了管理每一个被打开的文件,构建struct file
//创建struct file 对象,充当一个被打开的文件 struct file { struct file * next; struct file * prev; //包含了一个被打开的文件的几乎所有的内容(不仅仅包含属性,权限,缓冲区) }
🌍文件描述符:01234567,从0开始,连续的小整数,会让我们联想到数组下标!
打开的这么多文件,怎么知道哪些是我们进程的呢?操作系统为了让进程和文件之间产生关联,进程在内核创建struct files_struct 的结构,这个结构包含了一个数组 struct file* fd_array[] ,也就是一个指针数组,把表述文件的结构体地址填入到特定下标中。