目录
int open(const char *pathname, int flags, mode_t mode);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
周边文件(fd的理解,fd和FILE关系,fd分配规则,fd和重定向,缓冲区?.....)
一、预备知识
- 文件 = 内容 + 属性
- 对文件的操作类别:对内容,对属性
- 文件存放在磁盘中,如果我们想要访问文件需要经过:写代码->编译->生成.exe文件->运行->访问文件,这些过程,那么有一个问题,本质到底是谁在访问文件呢?
答案:是进程在访问文件
如果我们想要访问文件,那么本质就是需要我们去操作硬件,去实现硬件的IO操作,但是我们用户是没有权限去直接操作硬件的,我们只能通过操作系统提供的系统接口,通过这些接口去告诉操作系统我们想要做什么,从而间接的访问硬件进行文件的写入等操作。
那么用户去请求是通过什么方式去向操作系统沟通的呢?是程序吗?显然不是的,程序在我们写的时候,是写在磁盘上的,在程序没有运行起来时,就是一堆没有用的数据,只有我们将程序生成的可执行程序运行起来的时候,OS会在内存中创建一个进程,这个进程是动起来的,而我们写的程序是在进程创建成功后才有效,所以本质是进程通过去调用系统接口去访问的文件。
为什么没有听系统过文件接口?
上面我们会涉及文件类的系统调用接口,但是我们之前学习语言的时候,是没有听过这些接口的?为什么呢?
- 系统调用接口比较难
- 跨平台性
比较难:语言对这些系统接口进行了封装,为了让接口更好用,但同时也导致了不同的语言,有不同语言级别的文件访问方式,但是封装的都是系统接口,所以我们为什么要学习操作系统层面的文件系统接口?因为这样的OS接口只有一套!!
跨平台性:如果语言不提供对文件的系统接口封装,那么所有的文件操作就都必须只能使用系统接口,但是这样的接口只适用于一种操作系统,一旦使用操作系统编写代码,那么这个进程就无法在其他平台正常运行了!语言是如何实现跨平台性的呢?很简单粗暴,直接将所有平台的代码都实现了一遍,再用条件编译,动态裁剪;
显示器是硬件吗?
printf向显示器打印的时候,为什么没有觉得奇怪呢?其实向显示器打印的过程也是一种写入,和磁盘写入到文件没有本质的区别!
Linux认为一切皆文件
我们可以感性的认识一下:
曾经的文件:read、write
显示器:printf、cout -> 本质是一种write
键盘:scanf、cin -> 本质一种read
现在我们谈谈什么叫文件?
站在系统的角度,能够input读取,或者能够被output写入的设备就叫做文件!
狭义文件:普通的磁盘文件;
广义文件:磁盘、显示器、网卡、显卡、....几乎所有的外设,都可以称之为文件!
二、复习C接口
-
什么叫做当前路径?
进程运行时,会记录当前的工作目录!
-
直接使用系统接口
C库函数:fopen、fread、fwrite、fclose
系统调用:open、read、write、close
C文件函数:(54条消息) 【C语言】文件操作详解_绅士·永的博客-CSDN博客
系统调用:
int open(const char *pathname, int flags, mode_t mode);
当 open() 调用 成功, 它会 返回 一个 新的 文件描述符 (永远取未用 描述符的 最小值). 这个调用 创建 一个 新的 打开文件, 即 分配 一个 新的 独一无 二的 文件描述符, 不会与 运行中的 任何 其他程序 共享 (但 可以 通过 fork (2) 系统调用 实现 共享)
flags标志位:
如何给函数传递标志位!如果学过STM32固件库的GPIO_Pin_x的初始化操作,其实就是那个参数传递的过程;
O_WRONLY:文件只写
O_RDONLY:文件只读
O_TRUNC: 清空文件内容
O_APPEND:文件 以 追加 模式 打开
O_CREAT:若文件 不存在 将 创建 一个 新 文件
mode:权限设置,普通文件设置为0666;
//以读的方式打开文件,并且清空文件
int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT, 0666);
15 if (fd < 0)
16 {
17 perror("open");
18 return 1;
19 }
int close(int fd);
关闭 一个 文件 描述符 , 使它 不在 指向 任何 文件 和 可以 在 新的 文件 操作 中 被 再次 使用.
int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT, 0666);
15 if (fd < 0)
16 {
17 perror("open");
18 return 1;
19 }
20
21 dup2(fd, 1);//这里做了一个重定向的操作,可以不用管它
22 fprintf(stdout, "%s\n", argv[1]); //stdout -> 1 -> 显示器
23
24 close(fd);//关闭文件
ssize_t read(int fd, void *buf, size_t count);
从文件描述符 fd 中读取 count 字节的数据并放入从 buf 开始的缓冲区中.
如果 count 为零,read()返回0,不执行其他任何操作. 如果 count 大于SSIZE_MAX,那么结果将不可预料.
33 char buffer[64];
34 memset(buffer, '\0', sizeof(buffer));
35 read(fd, buffer, sizeof(buffer));//将文件的内容读取到buffer中去
ssize_t write(int fd, const void *buf, size_t count);
向文件描述符 fd 所引用的文件中写入 从 buf 开始的缓冲区中 count 字节的数据
总结:我们在应用层看到的一个简单的动作,在系统接口层面甚至可能OS层面,可能做了非常多的事情
-
分析系统接口的细节,引入fd(文件描述符)
int fd1 = open("log.txt", O_RDONLY, 0666);// rw-rw-rw-
16 printf("open success, fd1:%d\n", fd1);
17 int fd2 = open("log.txt", O_RDONLY, 0666);// rw-rw-rw-
18 printf("open success, fd2:%d\n", fd2);
19 int fd3 = open("log.txt", O_RDONLY, 0666);// rw-rw-rw-
20 printf("open success, fd3:%d\n", fd3);
21 int fd4 = open("log.txt", O_RDONLY, 0666);// rw-rw-rw-
22 printf("open success, fd4:%d\n", fd4);
上面的代码如何理解?0,1,2去哪了呢?
程序在运行时,会默认打开3个流
stdin(标准输入流):0
stdout(标准输出流):1
stderr(标准错误流:2
那么FILE又是什么鬼呢?FILE是一个结构体!是由C语言提供封装的;并且在FILE结构体种一定是封装了fd的,即站在系统的角度,是只认识fd的!
-
周边文件(fd的理解,fd和FILE关系,fd分配规则,fd和重定向,缓冲区?.....)
文件:
1.被进程打开的文件
2.没有被进程打开的文件(磁盘上,文件 = 内容 + 属性)
那么fd到底是什么呢?
进程要访问文件,必须先打开文件,一个进程是可以打开多个文件的,并且文件要被访问,前提是加载到内存中才能被直接访问!!而且如果多个进程都要打开自己的文件,那么OS就需要将这些大量的打开文件管理起来,那么是如何管理的呢?模式是先描述,在组织!!
那么在内核中,如何看待被打开的文件?OS内部为了管理每一个被打开的文件,构建了了一个struct file结构体:文件对象很多是用双链表组织起来的;
文件对象里包含了文件的所有内容;
过程:可执行程序运行加载到内存时,OS创建一个该进程对应的PCB结构体,该结构体中有一个struct file结构体的指针,其指向了一个struct file的数组,每打开一个文件,这个数组就会增加1,fd就是struct file文件对象对应的在这个数组的下标,通过这个fd下标就可以访问该fd对应的文件。
我们可以解释一下程序运行时打开的三个流
struct file[0]:对应的是stdin
struct file[1]:对应的是stdout
struct file[2]:对应的是stderr
总结:fd在内核中,本质就是一个数组下标!!