马鞍山网站建设制作,网站搜索用户体验,东营公共资源交易网,烟台公司网站建设✅1主页#xff1a;#xff1a;我的代码爱吃辣 #x1f4c3;2知识讲解#xff1a;Linux——文件系统 ☂️3开发环境#xff1a;Centos7 #x1f4ac;4前言#xff1a;是不是只有C/C有文件操作呢#xff1f;python#xff0c;java1主页我的代码爱吃辣 2知识讲解Linux——文件系统 ☂️3开发环境Centos7 4前言是不是只有C/C有文件操作呢pythonjavaphpgo ..... 他们都是有文件操作他们的文件操作一样吗他们都有文件操作且根据语言的语法不同文件操作也是不同的。有没有一种同意的视角看待所有语言的文件操作呢 目录
一.回顾C文件IO相关操作
1.C语言文件写入
2.C语言文件读取 3.输出信息到显示器有哪些方法
二.系统文件IO
1.open 2.write
3.close 4.read
三.对比C库与系统调用
四.如何管理文件
1.操作系统如何管理文件
2.进程如何管理文件 ——文件描述符
3.文件描述符的分配规则 三.重定向
1.重定向原理
2.dup2 系统调用 四.理解FILE 一.回顾C文件IO相关操作
1.C语言文件写入
测试代码
#include stdio.h
#include string.h
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;
}测试结果 2.C语言文件读取
#include stdio.h
#include string.h
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;
} 3.输出信息到显示器有哪些方法
#include stdio.h
#include string.h
int main()
{const char *msg hello fwrite\n;// 1.fwritefwrite(msg, strlen(msg), 1, stdout);// 2.printfprintf(hello printf\n);// 3.fprintffprintf(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默认会打开三个输入输出流分别是stdin, stdout, stderr仔细观察发现这三个流的类型都是FILE*, fopen返回值类型文件指针
二.系统文件IO
操作文件除了上述C接口当然C也有接口其他语言也有我们还可以采用系统接口来进行文件访问先来直接以代码的形式实现和上面一模一样的代码
1.open
隆重介绍一个系统调用 #include sys/types.h
#include sys/stat.h
#include fcntl.hint 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: 追加写 返回值
成功新打开的文件描述符失败-1 2.write #include unistd.hssize_t write(int fd, const void *buf, size_t count);参数介绍
fd:要写入的文件描述符。buf要写入的字符串。count写入的个数。
3.close #include unistd.hint close(int fd); 关闭指定的文件描述符的文件。
测试代码
#include stdio.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint 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;
}测试结果 4.read #include unistd.hssize_t read(int fd, void *buf, size_t count);参数
fd读取文件的文件描述符buf:存储读取出的数据的缓冲区count最大读取个数
返回值
读取成功返回读取的字节数。读取失败返回-1. 测试代码
#include stdio.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint 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/C有文件操作呢pythonjavaphpgo ..... 他们都是有文件操作他们的文件操作一样吗他们都有文件操作且根据语言的语法不同文件操作也是不同的。有没有一种同意的视角看待所有语言的文件操作呢
在认识返回值之前先来认识一下两个概念: 系统调用 和 库函数 上面的 fopen fclose fread fwrite 都是C标准库当中的函数我们称之为库函数libc。 而 open close read write 都属于系统提供的接口称之为系统调用接口回忆一下我们讲操作系统概念时画的一张图 系统调用接口和库函数的关系一目了然。 所以可以认为f#系列的函数都是对系统调用的封装方便二次开发。
只要语言层支持了文件操作那么语言层对下必然封装了系统调用。
四.如何管理文件
1.操作系统如何管理文件
文件内容属性。
当一个文件没有被操作时文件一般会被放在磁盘上。
当我们对一个文件进程操作的时候文件需要被放进内存因为冯诺依曼体系的限定
当我们对文件进程操作的时候文件需要被load到内存load的是属性还是内容至少要有属性被load。
当我们对文件进程操作的时候文件需要被提前放进内存操作文件的又不是我们一个所以OS内部移动同时存在大量被打开的文件。那么操作系统如何管理这些被打开的文件呢创建对应的结构体进行抽象和数据机构进行组织。
每一个被打开的文件都要在OS内部对应文件对象的struct结构体可以将所有的struct_file结构体用某种数据结构连接起来在OS内部对被打开的文件进行管理就转换成对链表的增删查改。 2.进程如何管理文件 ——文件描述符 文件可以分为两大类磁盘文件没有被打开内存文件被打开。
文件被打开是指文件被以进程为代表的用户让操作系统打开的。
所以之前的文件操作都是进程与被打开文件之间的关系。在OS的角度就是PCB与struct_file的关系。
那么进程是如何管理自己打开的文件的呢
open返回值
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint main()
{// 打开一个文件int fd open(testfile, O_WRONLY | O_CREAT, 0666);// 打印文件描述符printf(%d\n, fd);return 0;
}通过对open函数的学习我们知道了文件描述符就是一个小整数。 这里为什么是3我们多打开几个文件看看
#include stdio.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint 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开始的那么会不会有012呢
0 1 2
Linux进程默认情况下会有3个缺省打开的文件描述符分别是标准输入0 标准输出1 标准错误2.0,1,2对应的物理设备一般是键盘显示器显示器 所以输入输出还可以采用如下方式
#include stdio.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include string.h
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中拿着文件描述符就可以找到对应的文件。 3.文件描述符的分配规则 测试代码
#include stdio.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint 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;
}
测试结果 说明
当我们关闭02号文件描述符02文件描述符空着新打开的文件描述符不再从3开始。fd: 0 或者 fd 2 可见文件描述符的分配规则在files_struct数组当中找到当前没有被使用的最小的一个下标作为新的文件描述符。 三.重定向
1.重定向原理 上述代码如果我们关闭的是1号文件描述符
#include stdio.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint 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[ ]对应下标的存储的指针。
2.dup2 系统调用
#include unistd.hint dup2(int oldfd, int newfd)
说明
oldfd需要重定向的文件描述符。newfd被重定向的文件描述符。 测试代码
#include stdio.h
#include unistd.h
#include fcntl.h
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的地址不再是显示器文件的地址所以输出的任何消息都会往文件中写入进而完成输出重定向。 四.理解FILE
因为IO相关函数与系统调用接口对应并且库函数封装系统调用所以本质上访问文件都是通过fd访问的。所以C库当中的FILE结构体内部必定封装了fd。
测试代码
#include stdio.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint 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 stdio.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
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有关 一般C库函数写入文件时是全缓冲的而写入显示器是行缓冲。printf fwrite 库函数会自带缓冲区进度条例子就可以说明当发生重定向到普通文件时数据的缓冲方式由行缓冲变成了全缓冲。而我们放在缓冲区中的数据就不会被立即刷新fork之后。但是进程退出之后会统一刷新写入文件当中。但是fork的时候父子数据会发生写时拷贝所以当你父进程准备刷新的时候子进程也就有了同样的一份数据随即产生两份数据。write 没有变化说明没有所谓的缓冲而是直接写入文件。 综上
printf fwrite 等库函数会自带缓冲区而 write 系统调用没有带缓冲区。另外我们这里所说的缓冲区都是用户级缓冲区。其实为了提升整机性能OS也会提供相关内核级缓冲区不过不再我们讨论范围之内。那这个缓冲区谁提供呢 printf fwrite 是库函数 write 是系统调用库函数在系统调用的“上层” 是对系统调用的“封装”但是 write 没有缓冲区而 printf fwrite 有足以说明该缓冲区是二次加上的又因为是C所以由C标准库提供。