✅<1>主页::我的代码爱吃辣
📃<2>知识讲解:Linux——文件系统
☂️<3>开发环境:Centos7
💬<4>前言:是不是只有C/C++有文件操作呢?python,java,php,go ..... 他们都是有文件操作?他们的文件操作一样吗?他们都有文件操作,且根据语言的语法不同,文件操作也是不同的。有没有一种同意的视角,看待所有语言的文件操作呢?
目录
测试代码:
- #include
- #include
- int main()
- {
- FILE *fp = fopen("myfile", "w");
- if (!fp)
- {
- printf("fopen error!\n");
- }
- const char *msg = "hello Linux!\n";
- const char *msg2 = "hello C++!\n";
- int count = 5;
- while (count--)
- {
- // 向文件中写入,
- // 参数1:写入的数据C++
- // 参数2:写入的字符个数
- // 参数3:写入的数据元素的个数
- // 参数4:写入的文件结构体指针
- fwrite(msg, strlen(msg), 1, fp);
- }
- int n = 5;
- while (n--)
- {
- // 向文件中写入,
- // 参数1:写入的文件结构体指针
- // 参数2:格式化写入
- fprintf(fp, "[%d]:%s", n, msg2);
- }
- fclose(fp);
- return 0;
- }
测试结果:
- #include
- #include
- int main()
- {
- FILE *fp = fopen("myfile", "r");
- if (!fp)
- {
- printf("fopen error!\n");
- }
- char buf[1024];
- const char *msg = "hello bit!\n";
- while (1)
- {
- // 注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明
- size_t s = fread(buf, 1, strlen(msg), fp);
- if (s > 0)
- {
- buf[s] = 0;
- printf("%s", buf);
- }
- if (feof(fp))
- {
- break;
- }
- }
- fclose(fp);
- return 0;
- }
- #include
- #include
- int main()
- {
- const char *msg = "hello fwrite\n";
- // 1.fwrite
- fwrite(msg, strlen(msg), 1, stdout);
- // 2.printf
- printf("hello printf\n");
- // 3.fprintf
- fprintf(stdout, "hello fprintf\n");
- return 0;
- }
C库常见IO接口:
- // 1.默认向显示器格式化打印
- int printf(const char *format, ...);
- // 2.向指定的文件中格式化输入
- int fprintf(FILE * stream, const char *format, ...);
- // 3.向指定的空间中格式化输入
- int sprintf(char *str, const char *format, ...);
- // 4.向指定的空间中格式化输入指定个数字符
- int snprintf(char *str, size_t size, const char *format, ...);
总结:
操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:
隆重介绍一个系统调用:
- #include
- #include
- #include
-
- int open(const char *pathname, int flags);
- int open(const char *pathname, int flags, mode_t mode);
pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,,就是一种位图结构,flags参数:
返回值:
- #include
-
- ssize_t write(int fd, const void *buf, size_t count);
参数介绍:
- #include
-
- int close(int fd);
关闭指定的文件描述符的文件。
测试代码:
- #include
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
- // fd:文件描述符
- // mufile:打开的文件名
- // O_WRONLY :写方式 | O_CREAT:没有该文件就创建 | O_APPEND : 追加写入
- int fd = open("myfile", O_WRONLY | O_CREAT | O_APPEND, 0666);
- if (fd == -1)
- {
- perror("open");
- }
-
- int count = 5;
- char *msge = "hello C++ and Linux\n";
-
- while (count--)
- {
- ssize_t n = write(fd, msge, strlen(msge));
- if (n == -1)
- {
- perror("write:");
- }
- }
-
- close(fd);
-
- return 0;
- }
测试结果:
- #include
-
- ssize_t read(int fd, void *buf, size_t count);
参数:
返回值:
测试代码:
- #include
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
- // fd:文件描述符
- // mufile:打开的文件名
- // ORDONLY:独方式打开
- int fd = open("myfile", O_RDONLY);
- if (fd == -1)
- {
- perror("open");
- }
-
- char buff[1024];
-
- // fd:读取文件的文件描述符
- // buff:存储读取数据的缓冲区
- // 1024:最大读取字节数
- ssize_t n = read(fd, buff, 1024);
- if (n == -1)
- {
- perror("write:");
- }
-
- printf(buff);
- close(fd);
-
- return 0;
- }
测试结果:
我们真正理解语言层面的文件操作吗?其实我们并不理解,因为这不是语言问题,这是系统问题。
是不是只有C/C++有文件操作呢?python,java,php,go ..... 他们都是有文件操作?他们的文件操作一样吗?他们都有文件操作,且根据语言的语法不同,文件操作也是不同的。有没有一种同意的视角,看待所有语言的文件操作呢?
在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数:
上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
而, open close read write 都属于系统提供的接口,称之为系统调用接口回忆一下我们讲操作系统概念时,画的一张图:
系统调用接口和库函数的关系,一目了然。
所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。
只要语言层支持了文件操作,那么语言层对下必然封装了系统调用。
文件=内容+属性。
当一个文件没有被操作时,文件一般会被放在磁盘上。
当我们对一个文件进程操作的时候,文件需要被放进内存,因为冯诺依曼体系的限定!
当我们对文件进程操作的时候,文件需要被load到内存,load的是属性还是内容?至少要有属性被load。
当我们对文件进程操作的时候,文件需要被提前放进内存,操作文件的又不是我们一个,所以OS内部移动同时存在大量被打开的文件。那么操作系统如何管理这些被打开的文件呢?创建对应的结构体进行抽象,和数据机构进行组织。
每一个被打开的文件,都要在OS内部对应文件对象的struct结构体,可以将所有的struct_file结构体用某种数据结构连接起来,在OS内部,对被打开的文件进行管理,就转换成对链表的增删查改。
文件可以分为两大类,磁盘文件(没有被打开),内存文件(被打开)。
文件被打开,是指文件被以进程为代表的用户让操作系统打开的。
所以之前的文件操作,都是进程与被打开文件之间的关系。在OS的角度,就是PCB与struct_file的关系。
那么进程是如何管理自己打开的文件的呢?
open返回值:
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
- // 打开一个文件
- int fd = open("testfile", O_WRONLY | O_CREAT, 0666);
-
- // 打印文件描述符
- printf("%d\n", fd);
-
- return 0;
- }
通过对open函数的学习,我们知道了文件描述符就是一个小整数。
这里为什么是3?我们多打开几个文件看看:
- #include
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
- // 打开一个文件
- int fd = open("testfile", O_WRONLY | O_CREAT, 0666);
- int fd1 = open("testfile1", O_WRONLY | O_CREAT, 0666);
- int fd2 = open("testfile2", O_WRONLY | O_CREAT, 0666);
- int fd3 = open("testfile3", O_WRONLY | O_CREAT, 0666);
-
- // 打印文件描述符
- printf("%d\n", fd);
- printf("%d\n", fd1);
- printf("%d\n", fd2);
- printf("%d\n", fd3);
-
- return 0;
- }
我们发现打印出的是连续的整数。但是没有还是从3开始的,那么会不会有0,1,2呢?
0 & 1 & 2 :
所以输入输出还可以采用如下方式:
- #include
- #include
- #include
- #include
- #include
- #include
- int main()
- {
- char buf[1024];
- // 0:标准输入的文件描述符——键盘文件
- ssize_t s = read(0, buf, sizeof(buf));
- if (s > 0)
- {
- buf[s] = 0;
- // 写入1号文件描述符的文件中——显示器文件
- // 写入2号文件描述符的文件中——显示器文件
- write(1, buf, strlen(buf));
- write(2, buf, strlen(buf));
- }
- return 0;
- }
而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件,于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要在进程PCB中拿着文件描述符,就可以找到对应的文件。
测试代码:
- #include
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
- close(0);
- int fd = open("testfile", O_WRONLY | O_CREAT, 0666);
- close(2);
- int fd1 = open("testfile1", O_WRONLY | O_CREAT, 0666);
- int fd2 = open("testfile2", O_WRONLY | O_CREAT, 0666);
-
- // 打印文件描述符
- printf("%d\n", fd);
- printf("%d\n", fd1);
- printf("%d\n", fd2);
-
- return 0;
- }
测试结果:
说明:
上述代码如果我们关闭的是1号文件描述符:
- #include
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
- close(0);
- int fd = open("testfile", O_WRONLY | O_CREAT, 0666);
- // 如果关闭1号文件描述符
- close(1);
- int fd1 = open("testfile1", O_WRONLY | O_CREAT, 0666);
- int fd2 = open("testfile2", O_WRONLY | O_CREAT, 0666);
-
- // 打印文件描述符
- printf("%d\n", fd);
- printf("%d\n", fd1);
- printf("%d\n", fd2);
-
- return 0;
- }
测试结果:
说明:
重定向的本质:
说明:
原本输入到显示器的数据输入到了其他文件,仅仅通过更改struct file*fdarray[ ]对应下标的存储的指针。
- #include
-
- int dup2(int oldfd, int newfd)
说明:
测试代码:
- #include
- #include
- #include
- int main()
- {
- int fd = open("./log", O_CREAT | O_RDWR, 0666);
- if (fd < 0)
- {
- perror("open");
- return 1;
- }
- close(1);
- // 将fd对应的文件,重定向到1号文件描述符
- dup2(fd, 1);
- for (;;)
- {
- char buf[1024] = {0};
- ssize_t read_size = read(0, buf, sizeof(buf) - 1);
- if (read_size < 0)
- {
- perror("read");
- break;
- }
- printf("%s", buf);
- fflush(stdout);
- }
- return 0;
- }
测试结果:
printf是C库当中的IO函数,一般往 stdout 中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了./log的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成输出重定向。
因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd。
测试代码:
- #include
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
- int fd = open("testfile", O_CREAT | O_WRONLY, 0666);
- int fd1 = open("testfile1", O_CREAT | O_WRONLY, 0666);
-
- printf("%d\n", stdin->_fileno);
- printf("%d\n", stdout->_fileno);
- printf("%d\n", stderr->_fileno);
- printf("%d\n", fd);
- printf("%d\n", fd1);
-
- return 0;
- }
测试结果:
看一段代码:
- #include
- #include
- #include
- #include
- #include
- #include
- int main()
- {
- const char *msg0 = "hello printf\n";
- const char *msg2 = "hello write\n";
-
- printf("%s", msg0);
- write(1, msg2, strlen(msg2));
-
- fork();
- return 0;
- }
运行结果:
看到这里一切正常,如果我们将输出到显示器的数据,重定向到其他文件中:
我们发现 printf 输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关!
综上: