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的主要内容,如果未能解决你的问题,请参考以下文章

Linux文件系统与基础IO

Linux-基础IO

Linux:基础IO

Linux:基础IO

Linux:基础IO

Linux基础IO