Linux文件系统与基础IO
Posted 燕麦冲冲冲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux文件系统与基础IO相关的知识,希望对你有一定的参考价值。
文件的宏观理解
1⃣️文件在哪里呢?
“狭义”上在磁盘,“广义”上一切皆文件。
主要研究“狭义”,磁盘为外设,那么对文件的所有操作,本质都是对外设的输入输出,简称IO
2⃣️文件是什么?
空文件占不占用磁盘空间?依旧占用。
文件 = 属性 + 内容
所有的文件操作,无外乎就是两种操作,对属性和对内容。
3⃣️从系统角度看内存
文件操作代码-》可执行程序-〉从磁盘加载到内存-》变成进程
对文件的操作,本质都是进程对文件的操作!
C语言提供的对文件操作接口是用户层的,真正起作用的是系统调用接口
一些c文件接口的使用注意事项⚠️
FILE* fp = fopen(“./log.txt”, “a”);
if(fp == NULL)
perror(“fopen”);
const char* msg = “hello world\\n”;
fwrite(msg, strlen(msg), 1, fp);
fclose(fp);
1⃣️使用fwrite的时候,需要把字符串的’\\0’写入文件吗?
不加,因为‘\\0’是C的规定,用于标识字符串结束,而与文件无关。
2⃣️使用fopen的时候,反复使用a模式在文件中追加了几次后,再使用w模式写入,此时文件是啥样的?
以w模式写入,会直接清空掉以前所有的内容,再重新写入。
3⃣️filename前不加./时也会在当前路径下创建,当前路径是怎么找到的?
每个进程,都有一个内置的属性cwd,标识该进程认为自己所处的路径。
如何理解一切皆文件📁
1⃣️任何C程序,都默认会打开三个“文件”📁, 分别叫做标准输入(stdin),标准输出(stdout),标准错误(stderr)。分别为键盘文件,显示器文件、显示器文件📁
明明这三个都是硬件,那么为什么还被叫做文件?
Linux下一切皆文件。
2⃣️一切皆文件如何实现的?
所有的外设硬件,无外乎就是read和write
(1)不同的硬件,对应的读写方式肯定是不一样的。
(2)OS需要管理大量的文件——先描述,再组织
OS管理一个个file结构体对象形成的链表,每个对象代表一种文件,其文件的读写方式由函数指针指向。
3⃣️为什么C程序会默认打开stdin、stdout、stderr?仅仅是C吗?
(1)便于语言进行上手使用。
这三个文件对应着键盘和显示器文件,要调用如scanf、printf、perrpor等库函数必须需要打开这些文件,这些库函数的底层也一定会涉及到这几个硬件文件。
(2)几乎任何语言都是这样的。
open等系统调用接口的使用示例
open
返回值:file descriptor 文件描述符
对比C语言的返回值:FILE* 文件指针
参数:第一个为文件的路径,第二个为打开文件的方式,第三个为创建新文件时为其赋予的权限,若文件已存在就不传。
第二个参数是利用宏定义的整型参数,每种权限在其二进制的某一位上为1,判断是否满足某一权限,就拿这个权限&该参数。
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<sys/stat.h>
4 #include<fcntl.h>
5 #include<unistd.h>
6 #include<string.h>
7 int main()
8
9 int fd = open("./log.txt", O_WRONLY|O_CREAT, 0644);
10 if(fd < 0)
11
12 perror("open");
13 return 1;
14
15 const char* msg = "hello system call!\\n";
16 write(fd, msg, strlen(msg));
17 close(fd);
18 return 0;
19
为什么每种语言需要封装一个自己的接口来调用系统接口呢?
兼容自身语法特性,系统调用使用成本高,而且不具备可移植性。
上述代码是在Linux系统下使用的,Windows肯定不可兼容。而库函数可以自动根据平台,选择自己底层对应的文件接口。
文件描述符详解
其本质是数组的下标,且默认打开的三个文件就占了0、1、2,那么之后创建的文件的描述符会排在后面,除非提前关闭默认的。
(1)用户层看到的fd,本质是系统中维护进程和文件对应关系的数组的下标。
(2)对进程来讲,对所有的文件进行操作,统一使用一套接口(一组函数指针),那么就做到了一切皆文件📁
(3)所谓默认打开的三个文件,其实是有底层系统支持的。默认一个进程在运行的时候,就打开了0,1,2
(4)系统中,分配文件描述符的规则:最小的、未被使用的进行分配。
输出重定向的原理
将本来需要打印到屏幕的,写入到文件里。
通过关闭fd为1的stdout文件,再创建一个新文件,此时新文件的fd为1,再调用printf函数,理论上可以向新文件内写入,但是执行后,却看不到文件中有任何内容。
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<sys/stat.h>
4 #include<fcntl.h>
5 #include<unistd.h>
6 #include<string.h>
7 int main()
8
9 close(1);
10 int fd = open("log.txt", O_CREAT|O_WRONLY, 0644);
11 printf("log.txt's fd is %d\\n", fd);
12 printf("hello world!\\n");
13 close(fd);
14 return 0;
15
穿插知识点:C语言FILE*与fd的耦合原理
FILE是一个结构体,其中有个成员变量int _fileno,即为C语言的文件描述符。
printf不带\\n时,输出的内容会暂存于缓冲区内,这个缓冲区在哪里呢?
FILE结构体内还有缓冲区相关内容。
总结:struct FILE 内部包含1、底层对应的文件描述符。2、应用层C语言提供的缓冲区数据。
所以解决方案是在执行close(fd)前,利用fflush(stdout)进行刷新C语言FILE结构体内的缓冲区即可。原因是打印的信息会暂存于缓冲区,如果还未等刷新就关闭文件,缓冲区内的数据就没机会刷新了。
输出重定向也只会更改下标为1指向的stdout显示器文件。
为什么上述代码明明带了\\n却未提前刷新?
因为显示器文件刷新策略是行刷新,但普通文件的刷新策略是要将缓冲区写满才刷新,即为全缓冲刷新策略。
重要知识点再复习
重定向基本原理
通过演示重定向未能成功,证明用户层缓冲区和内核层缓冲区的刷新机制不同。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6
7 int main()
8
9 close(1);
10 int fd = open("log.txt", O_CREAT|O_WRONLY, 0644);
11 if(fd < 0)
12 perror("open");
13 return 1;
14
15 fprintf(stdout, "hello world! fd:%d\\n", fd);
16 close(fd);
17 return 0;
18
文件系统的大致框架
为什么存在不同的缓冲区?
如果用户层直接把数据拷贝至内核缓冲区效率较低,而且使得用户与内核得以区别开来。
系统接口和C接口的混用
下述示例中利用三个c接口向显示器上打印三个字符串,再利用系统接口向显示器上打印一个字符串,最后fork创建子进程。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<string.h>
7 int main()
8
9 printf("I am printf\\n");
10 fprintf(stdout, "I am fprintf\\n");
11 fputs("I am fputs\\n", stdout);
12 const char str[] = "I am write\\n";
13 write(1, str, strlen(str));
14 fork();
15 return 0;
16
编译运行程序
把该可执行程序从定向到新文件中
出现两次的是c接口,一次是系统接口。
显示器文件写入策略——行缓冲刷新。——第一种情况的解释
普通文件写入策略——全缓冲刷新。——第二种情况的解释
执行完C语言的三个接口后,结果会暂存于C语言缓冲区内。fork结束后有两个进程,此时程序即将退出,会刷新C语言的缓冲区的那三行到普通文件中,刷新缓冲区本质是对内存进行写入(设置为无效区域),子进程进行写时拷贝,于是出现了两次C接口执行内容,一次系统接口的执行内容也证明了系统缓冲区的存在。
利用dup2接口实现直接重定向
之前关闭显示器文件实现的是模拟重定向,操作繁琐。
原理是修改一号文件描述符对应的指针指向。
int dup2(int oldfd, int newfd); //重定向目标+新位置
new是old的一份拷贝。
(1)输出重定向——把要打印到显示器的内容写入文件
7 int main()
8
9 int fd = open("log.txt", O_CREAT|O_WRONLY, 0644);
10 if(fd < 0)
11
12 perror("open");
13 return 1;
14
15 dup2(fd, 1);
16 const char str[] = "hello world\\n";
17 write(1, str, strlen(str));
18 close(fd);
19 return 0;
20
(2)输入重定向——把文件的内容打印到显示器
8 int main()
9
10 int fd = open("log.txt", O_RDONLY);
11 if(fd < 0)
12
13 perror("open");
14 return 1;
15
16 dup2(fd, 0);
17 char buffer[1024];
18 ssize_t num = read(0, buffer, sizeof(buffer) - 1);
19 if(num > 0)
20
21 buffer[num] = '\\0';
22 printf("echo: %s\\n", buffer);
23
24 return 0;
25
程序替换的时候,会不会影响重定向对应的数据结构
不会。程序替换时只涉及到磁盘与内存的交互,还有子进程的页表映射关系的修改,并不会与文件相关的数据结构有关。
文件:打开的+未打开的
打开的:属性与操作方法的表现就是struct file
未打开的:在磁盘上,未被加载到进程。
两者类似于进程与程序的关系。
重要知识点再复习
关于Linux的两点
1、什么是Linux及其特点
一款操作系统,但为企业级,特点开源,因开源而健壮稳定成本低,进而被企业所选择。
2、Linux怎么来的
计算机的发明投入大量成本用于实验,要回本就要将计算机商业化,最早主要是在硅谷进行。于是计算机被许多人使用,但直接操作硬件麻烦,于是操作系统随之而来。unix被一个大学生模仿并开源,社区大佬共同维护,诞生了Linux。
Linux怎么用
命令行操作(多用命令,不用死记)
界面操作(较少使用)
Linux权限管理
分为两类
人:拥有者、所属组、其他
事物属性:rwx
更改权限:(1)chmod u/g/o +/- rwx (2)chmod 777
目录的权限:进入一个目录需要x权限。
粘滞位:目录可以让多用户rwx,所以为了防止他人误删文件,需要设置粘滞位。
Linux软件包管理器yum
yum install
yum remove
编译源代码可能因移植性问题导致报错。
但需要注意更新yum源。
Linux编辑器vim使用
多分析自己那一块效率低下,去学习和使用快捷键
Linux调试器gdb使用
虽然以后用得少而且考得简单,但是现阶段需要多用。
Linux项目自动化构建工具make/Makefile
make是一条命令
Makefile是一个文件,格式为:依赖关系+依赖方法
可维护文件之间的编译结构。
磁盘上的文件系统
机械硬盘的物理结构是一个个的圆盘,而固态硬盘却不是,为了OS便于管理,将这些物理结构抽象成一个数组,对应各个内存区域。由于内存大小很大,可以分成多个区域,为一小块区域设计管理文件的方案,并直接将管理方案复用到其他区域,这就是磁盘上的文件系统。
管理方案
内存分区其实还会被进一步划分成一块块的,叫做块组
boot block是启动块,要是被刮花了就启动不了了,所以十分重要。
super block包含所有块组的文件系统信息,并不是每一个块组都有,是做了冗余备份。
group descriptor table是组内信息,所有块组都有。
一个文件对应一个inode(包括目录)。
inode是一个文件的所有属性集合,但不包括文件名,inode也是数据,需要占据空间。
真正标识文件的不是文件名而是inode。
inode是可以和特定的数据块产生关联。
如何定位文件?
通过路径和目录定位。
目录也是文件,有inode,也有数据块。
目录的inode对应的数据块存放:目录下存放的文件名及其inode的映射关系
inode bitmap的意义是标识inode的使用情况,以便创建新文件的时候快速索引到未使用位置处。
block bitmap标识data blocks的使用情况。
touch一个空文件后,ext*文件系统做了哪些工作?
通过inode bitmap索引到未使用的区域,创建一个inode并填入文件信息,将文件名与其inode到映射关系存储到该文件的目录的数据块中。
如果向文件中写入,通过目录中的数据块找到文件的inode,为该inode分配数据块(通过block bitmap索引),写入数据。
这也能说明了OS为什么不允许同一目录下存在文件名相同的文件。
Linux下属性和内容是分离的,属性由inode保存,内容由data blocks保存
软硬链接🔗
软链接的建立
显示出inode
软链接就是一个普通文件,有自己独立的inode,类似于一个桌面上的快捷方式。保存的是指向所链接文件的路径。
硬链接的建立
硬链接拥有与链接文件相同的inode,没有自己独立的inode,类似C++的引用&(别名)。
本质是在该目录的数据块中创建了硬链接名与inode的映射关系,并没有创建新文件。
第三列数字代表的是硬链接数,可以发现普通文件也至少会有一个,是当前目录的数据块中存放了一组文件名与inode的映射关系。
为什么目录硬链接数默认就是2呢?
简单来说就是目录自己和该目录内的路径是一个东西,类似原理还有上一路径的…
这样设置是为了方便路径的快速转换或者设置相对路径。
文件的三个时间
文件被修改的频率不高,而文件被访问频率较高,所以为了提高效率,单纯访问文件其实并不会修改access时间,如cat
touch文件名,可以刷新所有时间。
使用价值
通过时间之间的比对,在编译代码的过程中判断哪些代码不需要再被重复编译了。
以上是关于Linux文件系统与基础IO的主要内容,如果未能解决你的问题,请参考以下文章