vlambda博客
学习文章列表

操作系统-文件系统概要

文件系统的功能规划

操作系统-文件系统概要

对于运行的进程来说,内存就像一个纸箱子,仅仅是一个暂存数据的地方,而且空间有限。如果我们想要进程结束之后,数据依然能够保存下来,就不能只存在内存里,而是应该保存在外部存储中。就像图书馆这种地方,不仅空间大,而且能够永久保存,在操作系统中我们称之为文件系统。


我们最常用的外部存储就是硬盘,数据是以文件的形式保存在硬盘上的。为了管理这些文件,我们在规划文件系统的时候,需要考虑以下几点:


第一点,文件系统要有严格的组织形式,使得文件能够以块为单位进行存储。这就像在图书馆里,我们会设置一排排书架,然后再把书架分成一个个小格子,有的项目存放的资料非常多,一个格子放不下,就需要多个格子来存放。


第二点,文件系统中也要有索引区,方便查找一个文件分成的多个块都放在了什么位置。这就好比,图书馆的书太多了,为了方便查找,我们需要专门设置一排书架,这里面会写清楚整个档案库有哪些资料,资料在哪个架子的哪个格子上。这样找资料时候就不要跑遍整个档案库,在这个书架上找到后,直奔目标书架就可以了。

操作系统-文件系统概要


第三点,如果文件系统中有的文件是热点文件,近期经常被读取和写入,文件系统应该有缓存层。这就相当于图书馆里面的热门图书区,这里面的书都是畅销书或者是常常被借还的图书。因为借还的次数比较多,那就没必要每次有人还了之后,还放回遥远的货架,我们可以专门开辟一个区域,放着这些借还频次高的图书。这样借还的效率就会提高。


第四点,文件应该用文件夹的形式组织起来,方便管理和查询。这就像在图书馆里面,你可以给这些资料分门别类,比如分成计算机类、文学类、历史类等等。这样你也容易管理,项目组借阅的时候只要在某个类别中去找就可以了。


在文件系统中,每个文件都有一个名字,这样我们访问一个文件,希望通过它的名字就可以找到。文件名就是一个普通的文本。当然文件名会经常冲突,不同用户取相同的名字的情况还是会经常出现的。


要想把很多的文件有序地组织起来,我们就需要把他们成为目录或者文件夹。这样,一个文件夹里可以包含文件夹,也可以包含文件,这样就形成了一种树形结构。而我们可以将不同的用户放在不同的用户目录下,就可以一定程度上避免了命名的冲突问题。

操作系统-文件系统概要


如图所示,不同的用户的文件放在不同的目录下,虽然很多文件都叫“文件 1”,只要在不同的目录下,就不会有问题。


第五点,Linux内核要在自己的内存里面维护一套数据结构,来保存哪些文件被哪些进程打开和使用。这就好比,图书馆里会有个图书管理系统,记录哪些书被借阅,被谁借阅了,借阅了多久,什么时候归还。



操作系统-文件系统概要

文件系统相关系统调用

操作系统-文件系统概要

如何使用系统调用操作文件呢?我们先来看一个完整的例子:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>

int main(int argc, char *argv[]){

int fd = -1; int ret = 1; int buffer = 1024; int num = 0;

if((fd=open("./test", O_RDWR|O_CREAT|O_TRUNC))==-1) { printf("Open Error\n"); exit(1); }

ret = write(fd, &buffer, sizeof(int)); if( ret < 0) { printf("write Error\n"); exit(1); } printf("write %d byte(s)\n",ret);

lseek(fd, 0L, SEEK_SET); ret= read(fd, &num, sizeof(int)); if(ret==-1) { printf("read Error\n"); exit(1); } printf("read %d byte(s),the number is %d\n", ret, num);

close(fd);

return 0;}

当使用系统调用open打开一个文件时,操作系统会创建一些数据结构来表示这个被打开的文件。为了能够找到这些数据结构,在进程中,我们会为这个打开的文件分配一个文件描述符fd(File Descriptor)。


文件描述符,就是用来区分一个进程打开的多个文件的。它的作用域就是当前进程,出了当前进程这个文件描述符就没有意义了。open返回的fd必须记录好,我们对这个文件的所有操作都要靠这个fd,包括最后关闭文件。


在Open函数中,有一些参数:

  • O_CREAT表示当文件不存在,创建一个新文件。

  • O_RDWR表示以读写方式打开。

  • O_TRUNC表示打开文件后,将文件的长度截断为0.


接下来,write用于写入数据。第一个参数就是文件描述符,第二个参数表示要写入的数据存放位置,第三个参数表示希望写入的字节数,返回值表示成功写入到文件的字节数。


最终,close将关闭一个文件。


对于命令行来讲,通过ls可以得到文件的属性,使用代码怎么办呢?


我们有下面三个函数,可以返回与打开的文件描述符相关的文件状态信息。这个信息将会写到类型为struct stat的buf结构中。

int stat(const char *pathname, struct stat *statbuf);int fstat(int fd, struct stat *statbuf);int lstat(const char *pathname, struct stat *statbuf);

struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* Inode number */ mode_t st_mode; /* File type and mode */ nlink_t st_nlink; /* Number of hard links */ uid_t st_uid; /* User ID of owner */ gid_t st_gid; /* Group ID of owner */ dev_t st_rdev; /* Device ID (if special file) */ off_t st_size; /* Total size, in bytes */ blksize_t st_blksize; /* Block size for filesystem I/O */ blkcnt_t st_blocks; /* Number of 512B blocks allocated */ struct timespec st_atim; /* Time of last access */ struct timespec st_mtim; /* Time of last modification */ struct timespec st_ctim; /* Time of last status change */};

函数stat和lstat返回的是通过文件名查到的状态信息。这两个方法区别在于,stat没有处理符号链接(软链接)的能力。如果一个文件是符号链接,stat会直接返回它所指向的文件的属性,而lstat返回的就是这个符号链接的内容,fstat则是通过文件描述符获取文件对应的属性。


接下来我们来看,如何使用系统调用列出一个文件夹下面的文件以及文件的属性。

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>#include <sys/types.h>#include <sys/stat.h>#include <dirent.h>

int main(int argc, char *argv[]){ struct stat sb; DIR *dirp; struct dirent *direntp; char filename[128]; if ((dirp = opendir("/root")) == NULL) { printf("Open Directory Error%s\n"); exit(1); } while ((direntp = readdir(dirp)) != NULL){ sprintf(filename, "/root/%s", direntp->d_name); if (lstat(filename, &sb) == -1) { printf("lstat Error%s\n"); exit(1); }

printf("name : %s, mode : %d, size : %d, user id : %d\n", direntp->d_name, sb.st_mode, sb.st_size, sb.st_uid);

} closedir(dirp);

return 0}

opendir函数打开一个目录名所对应的DIR目录流。并返回执行DIR目录流的指针。流定位在DIR目录流的第一个条目。


readdir函数从DIR目录流中读取一个项目,返回的是一个指针,指向dirent结构体,且流会自动指向下一个目录条目。如果已经流到最后一个条目,则返回NULL。


closedir()关闭参数dir所指的目录流。



操作系统-文件系统概要

总结

通过本章内容,我们对于文件系统的主要功能有了一个总体的映像,我们通过下面的这张图梳理一下。

  • 在文件系统上,需要维护文件严格的格式,可以通过mkfs.ext4命令来格式化为严格的格式。

  • 每一个硬盘上保存的文件都要有一个索引,来维护这个文件上的数据块都保存在哪里。

  • 为了能够更快的读取文件,内存里会分配一块空间作为缓存,让一些数据块放在缓存里面。

  • 在内核中,要有一整套的数据结构来表示打开的文件。

  • 在用户态,每个打开的文件都有一个文件描述符,可以通过各种文件相关的系统调用,操作这个文件描述符。