文件描述符
0、1和2
系统在开启后,会默认打开三个流,stdin、stdout、stderror,后面打开文件会从文件描述符表中挑选一个最小的空闲描述符来使
用
文件描述符表
在task_struct中,有一个指针,指向一个file_struct,其中最重要的部分就是一个指针数组,每个指针都是指向打开文件的指针,
所谓文件描述符fd,其实就是这个指针在这个数组当中的下标。所以当打开一个文件描述符但是不关闭时,会导致文件描述符泄露
重定向
- 基于上面对文件描述符和分配方式的介绍,不难发现,其实操作系统就是根据文件描述符表中每个指针对文件进行操作,所以更改
文件描述符表的内容,就可以实现重定向
cpp
close(1);
int fd = open("log.txt", O_RDWR|O_APPEND);
printf("hello world\n");
这时,上述代码不再往屏幕上打印,发现打印到log.txt当中,因为系统默认标准输出的文件描述符是1,而我们用log.txt的文件指
针替换了标准输出的位置,操作系统继续默认往文件描述符表的一号位置操作,就实现了往log.txt输出内容的重定向操作
- 系统调用dup2
cpp
int dup2(int oldfd, int newfd);
oldfd表示原来的文件描述符,newfd表示目的要替换的文件描述符
FILE* 文件流指针
其实就是struct_IO_FILE的重定义,指向了一个struct_IO_FILE结构体,其中包含了file_no,和文件描述符是对应的,换言之,FILE* 是对文件描述符的封装
Linux下一切皆文件
这句话其实在源自于Linux对驱动程序的处理原则。驱动程序+硬件,才让一个硬件能够使用。对于普通文件,单开一个文件后,在内
核中会创建一个struct file,保存着文件的inode元信息,其中保存着一个struct file_operation指针,指向一个struct file_operation结构体,其中包含许多函数指针,他们对于硬件来说,就指向硬件的驱动程序,将对硬件的控制权交给函数处理,所以对于上
层来说,对于硬件、软件的操作,其实都是对于文件的操作,几乎Linux的所有资源,都能够通过操作文件时使用的系统调用read、write来使用
缓冲区
- 当需要对硬件操作时,需要cpu从用户态切换到内核态,完成上下文切换,频繁的使用系统调用对硬件进行操作,会降低cpu的运行
效率,所以需要缓冲区集中的对数据进行操作,根据输入输出的不同分为输入缓冲区和输出缓冲区,他们都是内存中的一块区域
缓冲区类型
- 全缓冲区:只有打满时才进行io操作,对磁盘文件的操作一般使用全缓冲区
cpp
close(1);
int fd = open("log.txt", O_TRUNC | O_CREAT | O_RDWR);
dup2(fd, 1);
printf("hello world\n");
可以发现并没有实际的写入文件,因为原来的对标准输出的操作转换为了对磁盘的操作,采取全缓冲的方式。显然我们并没有写满缓
冲区
-
行缓冲区:涉及到终端的操作,比如像屏幕上打印,输入字符串,都是采取的行缓冲区,遇到换行符时刷新缓冲区
-
无缓冲区:不缓冲,直接进行输入输出,比如stderr,为了尽快显示错误信息,操作直接输出不缓冲的方式
刷新时机
- 进程结束
- 系统调用fflush
- 换行符
用户级缓冲区和内核级缓冲区
- 看下面的代码
cpp
const char* msg1 = "hello fwrite\n";
const char* msg2 = "hello write\n";
printf("hello printf\n");
fwrite(msg1, strlen(msg1), 1, stdout);
write(1, msg2, strlen(msg2));
fork();
将上面的代码重定向到文件后,可以发现,除了write,每句都打印了两遍
- printf和fwrite是标准库对系统调用的封装,即最后还是调用了write,但是他加上了自己的缓冲区,这种语言级的缓冲区不是系
统的缓冲区。向屏幕输出时,采用行缓冲;但是重定向到文件之后,采取全缓冲,显然我们没有打满缓冲区,所以fork之后的子进程
同样获得了缓冲区,两者结束时进行刷新,所以printf和fwrite写入了两次 - 对于write,是系统调用,使用的是系统的缓冲区,采取行缓冲,在fork之前已经刷新完毕,所以只有一次write,并且write在printf和fwrite之前
ext文件系统
ext2是较早的版本,ext3、ext4均是在ext2的改进,核心设计并没有发生改变。
cpp
//MBR Partition1 Partition2 Partition3...
// / \
// Boot Block ext2 File System
// / \
// Block Group1 Block Group2...
// / \
// Super Block GDT Block Bitmap inode Bitmap inode table block table
Boot Block
是一个分区中任何一个文件系统都没有权利修改的分区,存放着系统启动相关信息,一旦破坏,基本上就无法正常启动使用相关分区
或者文件系统的操作系如同了
Block Gruop 块组
ext2文件系统有多个块组组成
Super Block 超级块
是整个文件系统相关信息的描述,包括block大小、inode大小、已使用和未使用的inode数目、已使用和未使用的block数目、最后一
次读写时间等等
可以说,一旦超级块被破坏,这个文件系统就报废了。
所以,文件系统会在第一个块组的开头放置一个超级块的备份(至于其他分区开头是否备份没有硬性要求),以保证在硬盘保存超级
块的区域受损后文件系统依然能够正常使用
GDB 块组描述符表
group desctiption table,描述这个块组的信息,比如inode数目,block bitmap、inode bitmap、inode table、block table的起
止位置等等
块组描述符表的数目,与文件系统中块组的数目相同,每个块组的起始位置也会保存一份GDB的拷贝
两个bitmap和table
bitmap用来表示相应的table中的相应存储位置是否被占用,真正的内容存储在table中,bitmap起到辅助检索的作用
创建一个文件的过程
目录和文件的区别
在linux下,两者没有本质区别,目录其实也是存储在文件系统中的文件,或者换句话说,linux中没有目录只有文件。目录在block
中存储了
其中包含的所有文件和目录的名称,在inode中保存了文件名和inode的映射关系,这样就可以通过文件名获得inode,从而获得访问
目标文件
所需要的信息了
- 首先查找到空闲的block和inode,存储数据到block,存储文件信息到inode
- 更新所在目录的inode信息,更新文件名和inode的映射关系
软硬连接
硬链接
硬链接其实建立的是相同的inode和文件的映射关系,每建立一个硬链接,硬连接数就+1,当文件删除时,先将硬连接数-1
如果减为零,就将文件删除。所以硬链接可以用于文件的备份
软连接
软连接是通过名字引用另一个文件,软连接文件和被链接文件是两个文件,类似于桌面快捷方式
linux中两个进程打开同一个文件
- 文件描述符:对于同一个文件,两个进程产生的文件描述符是两个文件描述符,因为这本质上是指向file_operation结构体的指针在文件描述符表当中的数组下标
- 一个进程删除文件时,另一个进程并不会立刻读写失败,删除文件是操作的目录项,而另一个进程已经拥有了这个文件的inode信息,能够找到磁盘上的目标文件,所以不会立刻读写失败
- 一个进程修改文件时,另一个进程是可以立刻感知的,因为修改文件是对磁盘文件本身进行的操作