临海网站制作,合肥企业建网站,网站换域名做301,金山区网站制作前言 这个专栏其实是博主在复习操作系统和计算机网络时候的笔记#xff0c;所以如果是博主比较熟悉的知识点#xff0c;博主可能就直接跳过了#xff0c;但是所有重要的知识点#xff0c;在这个专栏里面都会提到#xff01;而且我也一定会保证这个专栏知识点的完整性…前言 这个专栏其实是博主在复习操作系统和计算机网络时候的笔记所以如果是博主比较熟悉的知识点博主可能就直接跳过了但是所有重要的知识点在这个专栏里面都会提到而且我也一定会保证这个专栏知识点的完整性大家可以放心订阅# 基础IO
1. 文件描述符预备工作
Linux系统下一切皆文件
1.1 复习C文件接口相关细节
#include stdio.h
#include stdlib.h
// 复习C语言文件接口
int main()
{FILE *fp fopen(log.txt, w);if (fp NULL){perror(fopen);return 1;}// 文件操作fclose(fp);return 0;
}这个文件在哪里创建
我们实验发现程序在哪里被执行log.txt就会在哪里被创建log.txt是相对路径。 就是在工作目录下创建。
1.2 用C语言相关文件接口模拟实现一个cat命令
// 模拟实现一个cat命令
int main(int argc, char *argv[])
{if (argc ! 2){printf(args error!\n);return 1;}FILE *fp fopen(argv[1], r); // 打开这个文件if (fp NULL){perror(fopen);return 2;}// 读取文件里面的内容char line[64];while (fgets(line, sizeof(line), fp) ! NULL) // 按照行读取{fprintf(stdout, %s, line);}fclose(fp);return 0;
}三个自动打开的文件描述符很熟悉了不再赘述。
1.3 学习系统调用 open 如果打开成功 — 返回文件描述符如果打开失败返回-1。
O_WRONLY只负责写如果没有这个文件是打不开的
我们带上O_CREAT就能创建了
但是我们发现 创建出来的这个文件的权限怎么是个奇怪的东西呢所以不像我们C接口创建出来的那么整齐
所以光光创建是不够的
一般涉及到文件的创建的时候我们会传递第三个参数表示权限。 如果这个文件已经有了
我们就使用两个参数的open就行了 不需要三个参数的带上O_RDONLY选项 — read only
关闭文件fclose
int close(int fd);现在想要往里面写东西了。
用write函数 如果我们往已经有东西的文件里面再写入一个短一点的字符串。 所以这个是不会帮我们清空文件的。
想要系统帮我们清空还要带上一个选项O_TRUNC。
int fd open(test.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);这样才会帮我们清空。
那如果我想要往文件中追加呢
把O_TRUNC换成O_APPEND。
现在我们来认识一下 读文件的接口 read
read 的返回值我们到进程通信再说 现在我们先不关心
read是不会给我们加 \0 的 1.4 文件描述符这个int和FILE*的关系
FILE是一个结构体是C语言提供的。
C中文件相关库函数内部一定会调用系统调用! 那么在系统角度认FILE还是认 fd ? 系统只认fd
FILE结构体里面必定封装了fd
// 文件描述符和FILE*
int main()
{printf(stdin: %d\n, stdin-_fileno);printf(stdout: %d\n, stdout-_fileno);printf(stderr: %d\n, stderr-_fileno);return 0;
}yufcALiCentos7:~/Src/Review/operatingSys/Unit4$ ./test
stdin: 0
stdout: 1
stderr: 2
yufcALiCentos7:~/Src/Review/operatingSys/Unit4$ 同样先描述再组织在内核中OS内部要为了管理每一个被打开的文件构建struct file{}。 1.5 struct file{}
用双链表组织起来。
struct file
{struct file* next;struct file* prev;// 后面的字段 ...// 包含了一个被打开的文件的几乎所有的内容, 不仅仅包含属性
};所以本质是存在一个数组的
struct file* array[32]
所以fd的本质就是一个数组下标。 2. 正式开始学习文件描述符
fd的分配规则是最小的没有被占用的文件描述符。
然后012是被打开的这个很熟所以下一个打开的文件就是3。
2.1 输入重定向和输出重定向
如果我把1文件描述符关了然后打开一个文件那么这个新打开的文件的fd就是1这个很好理解。
所以原本要打印到stdout的东西会打印到新打开的文件中去。
// 输出重定向
int main()
{close(1);int fd open(log.txt, O_WRONLY | O_CREAT | O_TRUNC);assert(fd 0);printf(fd: %d\n, fd);printf(hello world!\n);fflush(stdout); // 不加这个是没有输出的close(fd);return 0;
}这里fflush(stdout)其实没有刷新屏幕其实刷新的是log.txt因为里面文件描述符是1而现在1不是显示器而是log.txt。 至于这个代码里面为什么如果不加fflush(stdout);会没有输出 因为重定向到文件里面磁盘文件是全缓冲的第三节复习缓冲区的时候会讲所以printf之后在缓冲区里面所以没有输出然后按道理来说程序结束会自动刷新但是你都close了肯定就刷新不了了。 所以要不不加close不用fflush也能有结果 加了close那么fflush也要加不然结果被close清理掉了 输入重定向也是一个道理。 当然重定向不是这样实现的!我们这种方式仅仅只是利用了文件描述符的特点而已。有没有一种方式可以让我们的不用关闭别人的也能完成重定向呢肯定是有的
2.2 dup2 记住最终想要输出到哪里哪里的fd就是第一个参数。
如果oldfd不是有效的文件描述符则调用失败并且newfd未关闭如果oldfd是有效的文件描述符而newfd的值与oldfd相同则dup2()不执行任何操作并且 返回newfd。 至于为什么我们上面那种先close的重定向方法最后close之后就不能成功重定向而左边代码的方法 没问题。 这其实是dup2的一个特性涉及到缓冲区的概念。
2.3 如何理解一切皆文件VFS 3. 缓冲区
缓冲区在哪里我们写一个代码看看。
这个代码分别调用了C语言的输出函数和系统的输出函数打印一句话。
// 缓冲区
int main()
{// C语言printf(hello printf\n);fprintf(stdout, hello fprintf\n);const char *s hello fputs\n;fputs(s, stdout);// 系统调用const char *ss hello write\n;write(1, ss, strlen(ss)); // 写到fd1的文件上-stdoutfork();return 0;
}我们先把现象解释一下:
首先
write 只打印了一次 其他的打印了两次
为什么我们下节课再讲讲清楚之后我们可以也可以回答一些尚未解答的现象了
为什么会这样我们现在来解释
关于缓冲区的认识: 一般而言行缓冲的设备文件 – 显示器 一般而言全换从的设备文件 – 磁盘文件 所有设备永远都倾向于全缓冲 缓冲区满了才刷新-需要更少的IO操作-更少次的外设访问-提高效率
当和外部设备进行IO的时候数据量的大小不是主要矛盾 和外设预备IO的过程才是最耗费时间的
其他刷新策略是结合具体情况做的妥协
为什么fork()之后拷贝一份 如果向显示器打印刷新策略是行刷新那么最后执行 fork的时候一定一定是函数执行完了 数据已经刷新了 如果对应的程序做了重定向本质是向磁盘文件打印 — 隐性的刷新策略变成了全缓冲此时代码的已经没有意 义了所以fork的时候函数执行完了但是数据没有刷新现在数据在当前进程的C标准库中 这部分数据属不属于父进程的数据?肯定是的fork之后父子各自执行自己的退出。进程退出是需要刷新缓冲区的 那么现在的一个问题刷新这个动作算不算“写”算的从缓冲区刷新出去相当于写到显示器里 此时会有写时拷贝 所以C的接口会出现两份的数据
4. 文件系统和inode
4.1 背景知识1 int main()
{// Cprintf(hello printf\n);fprintf(stdout, hello fprintf\n);perror(hello perror); // stderr// 系统调用const char* s1 hello write(stdout)\n;const char* s2 hello write(stderr)\n;write(1, s1, strlen(s1));write(2, s2, strlen(s2));// Cstd::cout hello cout std::endl;std::cerr hello cerr std::endl;return 0;
}这个代码直接运行肯定是这样的。 但是如果重定向一下呢 12都是显示器文件但是他们 两个是不同的显示器文件! 我们可以认为同一个显示器文 件被打开了两次!
一般而言如果程序运行有可能 有问题的话建议使用stderr来打 印! 如果是常规打印建议用stdout 打印。
然后区分之后我们可以这么运行可以把正确的和错误的分开打印到文件里面去。 可以理解成把fd为2的放到err.txt里面去。
另外cat还有一个用法
cat log.txt back.txt这个表示把log.txt的内容交给catcat准备向显示器打印但是此时再次重定向到back.txt上所以最终就是log.txt的内容完成一次拷贝到back.txt上 4.2 背景知识2 学习文件系统要掌握的背景知识 我们以前学习的都是被打开的文件那们有没有没有被打开的文件?当然存在在磁盘里 2. 我们学习磁盘级别的文件我们侧重点在哪里呢 单个文件的角度 — 这个文件在哪里这个文件多大?这个文件的其他属性是什么? 站在系统的角度 一共有多少个文件各自属性在哪里?如何快速找到我还可以存储多少 个文件?如何快速找到制定的文件 如何进行对磁盘文件进行分门别类的存储又来支持更好的存取 所以我们先要了解磁盘 磁盘具体构造寻址方式可以看看以前的课件/ppt。
一个重要概念虽然磁盘的基本单位是扇区(512字节) 但是操作系统(文件系统)和磁盘进行IO的基本单位是4kb
为什么?
太小了有可能会导致多次的IO进而导致效率降低如果OS使用和磁盘一样的大小万一磁盘基本大小变了的话OS的源代码要不要改呢? 所以硬件和软件(OS)进行解耦。
4.3 文件系统构造 4.4 如果文件特别大怎么办
一个block放不下怎么办
在data block中不是所有的datablock只能存文件数据也可以存其他块的块号
我们通过索引找到一个块之后可以通过这个块继续找到下面的块这样就解决了要存大文件的问题
找到一个文件的步骤inode 编号 - 分区特定的bg - inode - 属性 - 内容
现在的问题是inode编号是怎么得到的
在Linux文件属性中国呢是没有文件名这个东西的。 在一个目录下可以保存很多文件但是这些文件名是不能重复的目录是文件吗是 - 所以目录也有自己的inode也有自己的datablock 一个文件的文件名是存在datablock里面存的
datablock里面存了文件名和inode编号的映射关系
下面我们要回答三个问题
创建文件系统做了什么删除文件系统做了什么?查看文件系统做了什么?
为什么删除总比拷贝快很多因为删除的时候不用把内容这部分东西真的删掉只需要把位图标记改了就行了。
所以删了的东西能恢复吗肯定是可以的只是我们不会而已 我们只要找到原来的inode找到磁盘的位置删除日志只要它还没被覆盖就一定能找到。 一道面试题: 为什么还有空间但是一直不能创建文件呢可能就是因为inode申请不下来文件无法创建。 如果创建出来也只有个文件名没有inode这个时候一写就会失败一写就会失败的无法写入。