行业知识
【Linux取经路】基础I/O之被打开的文件——文件描述符的引入
文章目录
- 一、明确基本共识
- 二、C语言文件接口回顾
- 2.1 文件的打开操作
- 2.2 文件的读取写入操作
- 2.3 三个标准输入输出流
- 三、文件有关的系统调用
- 3.1 open
- 3.1.1 比特位级别的标志位传递方式
- 3.2 write
- 3.2.1 模拟实现 w 选项
- 3.2.2 模拟实现 a 选项
- 3.3 read
- 四、访问文件的本质
- 4.1 再来认识 FILE
- 4.2 再来理解关闭文件
- 五、���语
一、明确基本共识
-
文件等于内容加属性,内容和和属性都是数据,不管是内容还是属性都要在磁盘中保存。
-
文件分为打开的文件和没打开的文件。
-
打开的文件本质是进程打开的,要研究打开的文件,本质是研究进程和文件的关系。
-
对文件的所有操作(打开文件、读取文件、向文件写入)等,都是通过代码来实现的,而代码最终是由 CPU 去执行的,根据冯诺依曼结构体系,CPU 不能直接和外设打交道,因此一个被打开的文件,第一步一定是先将其加载到内存。
-
一个进程能够打开多个文件,所以在操作系统内部一定存在大量的被打开的文件,操作系统还是通过先描述,再组织的方式对打开的文件进行管理。每个被打开的文件都必须有自己的文件打开对象,其中一一定包含了文件的很多属性,将这些文件对象以某种特殊的数据结构组织起来,最终对文件的管理,就变成了对某种数据结构的维护(增删查改)。
-
没打开的文件一般都是在磁盘上放着,对于没打开的文件,由于没打开的文件非常多,所以对于没打开的文件我们最关心文件如何被分门别类的放置好,分门别类的放置好是为了快速的进行增删查改。
二、C语言文件接口回顾
2.1 文件的打开操作
// 文件打开接口FILE *fopen(const char *path, const char *mode);
第一个参数 path ,表示要打开的文件路径,或者文件名。如果只有文件名前面没写路径,表示打开当前路径下的文件。这里又涉及到当前路径,在前一篇文章中实现 cd 指令的时候就讲过什么是当前路径。总的来说,当前工作路径是一个进程 PCB 中维护的一个属性。一个可执行程序在被加载到内存成为进程创建出对应的 PCB 对象的时候,PCB 对象中就维护了一个叫做 cwd 的属性,该属性就表示进程当前的工作路径。
如果 fopen 函数的第一个参数只传递了文件名,最终在打开文件的时候,操作系统会去 cwd 指向的工作路径下查找该文件。
第二个参数 mode,这个参数有很多可选项,今天只介绍个别选项,关于所有选项的详细介绍请看我之前的文章【C语言进阶】文件操作。
-
w选项:只要是以 w 选项打开的文件,在写入之前都会对文件做清空处理,然后从头开始写入。
-
a选项:在文件结尾进行追加写。
小Tips:我们之前介绍的重定向,> 本质上就对应使用的是 w 选项,>> 本质上就对应使用的是 a 选项。
2.2 文件的读取写入操作
和文件读取写入的相关接口,以及使用方法,今天也不过多介绍,详细介绍请看我之前写的文章【C语言进阶】文件操作。今天只想通过 fwrite 接口跟大家明确一件事情。
// fwrite 接口声明size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
int main(){ FILE *fp = fopen("log.txt", "w"); if (fp == NULL) { // 打开失败 perror("fopen"); return errno; } // 打开成功,对文件进行相关的操作 // ... char* str = "Hello Linux!"; fwrite(str, strlen(str), 1, fp); // 操作结束,关闭文件 fclose(fp); return 0;}fwrite 接口的第二个参数 size 表示每一个要写入的对象的大小。在向文件写入字符串的时候,该参数是字符串的长度还是字符串的长度加一呢?因为 strlen 计算出来的字符串长度是不包含结尾的 \0,加一的小伙伴觉得要把 \0 也写到文件里面,但是 \0 真的需要写入文件嘛?其实 \0 并不需要写入文件中,因为字符串以 \0 结尾只是 C 语言这么规定的,我们把一个字符串写入文件后,可能通过其它的语言去读取该文件,我们并不希望读到与该字符串无关的内容 \0。下面是加一的结果:
\0 也是字符,只不过不可显,在被写入到文件后,vim 编辑器会把它识别成 ^@,对 Hello Linux 来说,^@ 就是多余的无用字符。我们不希望它在文件中出现。
2.3 三个标准输入输出流
C程序在启动时候,默认会打开三个标准流文件:
-
stdin:标准输入流——键盘文件
-
stdout:标准输出流——显示器文件
-
stderr:标准错误流——显示器文件
三、文件有关的系统调用
文件最初是在磁盘上的,磁盘是外部设备,访问磁盘文件其实是访问硬件,在计算机层状结构中,硬件是处于最底层的,操作系统帮我们把这些硬件管理起来,并且操作系统是不相信用户的,因此操作系统不允许我们直接去访问硬件,而是给我们提供了系统调用接口,几乎所有的库只要是访问硬件设备,必定要封装系统调用。也就是说我们平时在C语言里面使用的 fopen、printf、fprintf、fscanf等函数都一定是封装了系统调用。
3.1 open
#include #include #include int open(const char *pathname, int flags);int open(const char *pathname, int flags, mode_t mode);
-
第一参数 pathname:表示要打开或创建的目标文件
-
第二个参数 flags:标志位选项。O_RDPPNLY:只读打开;O_WRONLY:只写打开;O_RDWR:读,写打开。这三个常量,必须指定一个且只能指定一个。O_CREAT:若文件不存在,则创建它。需要使用 mode 参数,来指明新文件的访问权限。O_APPEND:追加写;O_TRUNC:文件打开的时候先清空。
-
第三个参数 mode:新创建文件的默认权限,要考虑权限掩码,可以配合 umask 系统调用接口来设置自己想要的效果。umask 系统调用产生的效果就只对当前进程创建的文件有关。
-
返回值:成功,返回新打开的文件描述符,关于文件描述符是什么,将在后文为大家介绍;失败,返回-1。
小Tips:open 函数具体使用哪个,和具体的应用场景有关,如目标文件不存在,需要 open 创建,则第三个参数表示创建文件的默认权限。如果不需要创建新文件,使用两个参数的 open。
3.1.1 比特位级别的标志位传递方式
#define ONE (1 if(flags & ONE) printf("function1\n"); if(flags & TWO) printf("function2\n"); if(flags & FOU) printf("function3\n"); if(flags & EIG) printf("function4\n"); return;}int main(){ printf("--------------------------------------\n"); show(ONE); printf("--------------------------------------\n"); show(ONE | TWO); printf("--------------------------------------\n"); show(ONE | TWO | FOU ); printf("--------------------------------------\n"); show(ONE | TWO | FOU | EIG); printf("--------------------------------------\n"); return 0;} umask(0); // 将权限掩码设置成全0 int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); // 以读的方式打开,若文件不存在就创建,打开文件时清空 if(fd fd: %d\n", stdout->_fileno); printf("stderr->fd: %d\n", stderr->_fileno); fprintf(stderr, "printf ret: %d\n", ret); return 0;}代码分析:close(1) 表示将标准输出关闭,1下标指向显示器文件,printf 就是向标准输出中进行写入,关闭后,三条 printf 函数都没有将内容成功打印到显示器上。根据上面的分析,虽然把标准输出关了,但是标准错误也指向显示器,所以在调用 fprintf 向标准错误中写入时,我们可以在显示器上看到打印结果。其次,printf 执行成功,返回值表示写入的字符个数,可以看出虽然我们通过系统调用直接把标准输出给关了,但是 printf 还是认为它写入成功。
五、结语
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!
-
-
-
-
上一篇:宝塔面板怎么升级磁盘?
下一篇:服务器ip和端口怎么查?