马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
本帖最后由 moc 于 2018-11-2 21:44 编辑
1. 什么是文件系统
文件系统是操作系统用于明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构;即在存储设备上组织文件的方法。
操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。
文件系统由三部分组成:
① 文件系统的接口; ② 对对象操纵和管理的软件集合; ③ 对象及属性。
从系统角度来看,文件系统是对文件存储设备的空间进行组织和分配,负责文件存储并对存入的文件进行保护和检索的系统。具体地说,它负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。,控制文件的存取,当用户不再使用时撤销文件等。
2. ext2文件系统
一个磁盘可以划分成多个分区,每个分区必须先用格式化工具(例如某种mkfs命令)格式化成某种格式的文件系统,然后才能存储文件,格式化的过程会在磁盘上写一些管理存储布局的信息。下图是一个磁盘分区格式化成ext2文件系统后的存储布局。
1. 块(Block)
块(Block)是文件系统中存储的最小单位,一个块究竟多大是在格式化时确定的,例如mke2fs的-b选项可以设定块大小为1024、2048或4096字节。
2. 启动块(Boot Block)
启动块(Boot Block)的大小是确定的,就是1KB,启动块是由PC标准规定的,用来存储磁盘分区信息和启动信息,任何文件系统都不能使用启动块。
3. 块组(Block Group)
启动块之后才是ext2文件系统的开始,EXT2会将整个分区空间分为一个个BlockGroup(块组),这些块组的大小都是一致的,每个磁盘空间中至少有一个块组。大致就相当于EXT2这个“管家”会把我们的家先分成一个个大小一致的“房间”。每个块组又由超级块等部分组成,如上图所示。
4. 超级块(Super Block)
超级块(Super Block)描述整个分区的文件系统信息,例如块大小、文件系统版本号、上次mount(挂载)的时间等等。超级块在每个块组的开头都有一份拷贝。
5. 块组描述符表(GDT,Group Descriptor Table)
块组描述符表(GDT,Group Descriptor Table)由很多块组描述符组成,整个分区分成多少个块组就对应有多少个块组描述符。
每个块组描述符(Group Descriptor)存储一个块组的描述信息,例如在这个块组中从哪里开始是inode表,从哪里开始是数据块,空闲的inode和数据块还有多少个等等。
和超级块类似,块组描述符表在每个块组的开头也都有一份拷贝,这些信息是非常重要的,一旦超级块意外损坏就会丢失整个分区的数据,一旦块组描述符意外损坏就会丢失整个块组的数据,因此它们都有多份拷贝。通常内核只用到第0个块组中的拷贝,当执行e2fsck检查文件系统一致性时,第0个块组中的超级块和块组描述符表就会拷贝到其它块组,这样当第0个块组的开头意外损坏时就可以用其它拷贝来恢复,从而减少损失。
6. 块位图(Block Bitmap)
块位图(Block Bitmap)一个块组中的块是这样利用的:数据块存储所有文件的数据,比如某个分区的块大小是1024字节,某个文件是2049字节,那么就需要三个数据块来存,即使第三个块只存了一个字节也需要占用一个整块;
超级块、块组描述符表、块位图、inode位图、inode表这几部分存储该块组的描述信息。
那么如何知道哪些块已经用来存储文件数据或其它描述信息,哪些块仍然空闲可用呢?块位图就是用来描述整个块组中哪些块已用哪些块空闲的,它本身占一个块,其中的每个bit代表本块组中的一个块,这个bit为1表示该块已用,这个bit为0表示该块空闲可用。
7. inode位图(inode Bitmap)
inode位图(inode Bitmap):和块位图类似,本身占一个块,其中每个bit表示一个inode是否空闲可用。
8. inode表(inode Table)
inode表(inode Table):我们知道,一个文件除了数据需要存储之外,一些描述信息也需要存储,例如文件类型(常规、目录、符号链接等),权限,文件大小,创建/修改/访问时间等,也就是ls -l命令看到的那些信息,这些信息存在inode中而不是数据块中。每个文件都有一个inode,一个块组中的所有inode组成了inode表。
9.数据块(Data Block)
数据块(Data Block)根据不同的文件类型有以下几种情况:
① 对于常规文件,文件的数据存储在数据块中。
② 对于目录,该目录下的所有文件名和目录名存储在数据块中,注意文件名保存在它所在目录的数据块中,除文件名之外,ls -l命令看到的其它信息都保存在该文件的inode中。
注意这个概念:目录也是一种文件,是一种特殊类型的文件。
③ 对于符号链接,如果目标路径名较短则直接保存在inode中以便更快地查找,如果目标路径名较长则分配一个数据块来保存。
④ 设备文件、FIFO和socket等特殊文件没有数据块,设备文件的主设备号和次设备号保存在inode中。
目录中记录项文件类型:
编码 | 文件类型
| 0 | Unknown
| 1 | Regular file
| 2 | Directory
| 3 | Character device
| 4 | Block device
| 5 | Named pipe
| 6 | Socket
| 7 | Symbolic link |
补充: 数据块寻址
索引项Blocks[13]指向两级的间接寻址块,最多可表示(b/4)2+b/4+12个数据块,对于1K的块大小最大可表示64.26MB的文件。索引项Blocks[14]指向三级的间接寻址块,最多可表示(b/4)3+(b/4)2+b/4+12个数据块,对于1K的块大小最大可表示16.06GB的文件。
3.文件操作函数
1. stat 函数
原型: int stat(const char *path, struct stat *buf);
作用: 获取文件属性(从inode上获取)
特点: 能够穿透(跟踪)符号链接
文件属性结构体:
struct stat {
dev_t st_dev; // 文件设配编号
ino_t st_ino; // 节点
mode_t st_mode; // 文件类型和存取的权限
nlink_t st_nlink; // 连到该文件的硬链接数目,刚建立的文件值为1
uid_t st_uid; // 用户ID
gid_t st_gid; // 组ID
dev_t st_rdev; // (设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; // 文件字节数(文件大小)
blksize_t st_blksize; // 块大小(文件系统I/O 缓冲区大小)
blkcnt_t st_blocks; // 块数
time_t st_atime; // 最后一次访问时间
time_t st_mtime; // 最后一次修改时间
time_t st_ctime; // 最后一次改变时间(指修改属性即inode内容)
2. lstat 函数
原型: int lstat(const char *path, struct stat *buf);
作用: 与stat一样
特点: 不能够穿透(跟踪)符号链接
3. access 函数
原型: int access(const char *pathname, int mode);
作用: 测试指定文件是否拥有某种权限
4. access 函数
原型: int chmod(const char *path, mode_t mode);
作用: 改变文件权限,mode必须是一个8进制数
5. truncate 函数
原型: int truncate(const char *path, off_t length);
作用: 将参数path指定的文件大小改变为参数length的大小;如果原来文件大小比参数length大,则超过部分删除。
6. link 函数、unlink 函数
原型: int link(const char *oldpath, const char *newpath);
作用: link为创建一个硬链接;
unlink为删除一个文件的目录项并减少它的链接数,若成功返回0,否则返回-1,错误原因存于errno;如果想通过调用这个函数成功删除文件,你就必须拥有这个文件的所属目录的写和执行权限
使用: ① 如果是符号链接,删除符号链接;
② 如果是硬链接,硬链接数减1,当减为0时,释放数据块和inode;
③ 3. 如果文件硬链接数为0,但进程已打开该文件,并持有该文件描述符,则等进程结束后再删除该文件。利用该特性创建临时文件,先open或create创建一个文件,马上unlink该文件。
7. rename 函数
原型: int rename(const char *oldpath, const char *newpath);
作用: 文件重命名
4. 目录操作函数
1. chdir 函数
原型: int chdir(const char *path);
作用: 修改当前进程的工作路径
2. getcwd 函数
原型: char *getcwd(char *buf, size_t size);
作用: 获取当前进程的工作目录
3. mkdir 函数
原型: int mkdir(const char *pathname, mode_t mode);
作用: 创建目录;创建的目录需要有执行权限,否则无法进入目录。
4.rmdir 函数
原型: int rmdir(const char *pathname);
作用: 删除一个空目录。
5 .opendir 函数
原型: DIR *opendir(const char *name);
作用: 打开一个目录。
返回值:DIR结构指针,该结构是一个内部结构,保存所打开的目录信息,作用类似于FILE结构,函数出错时返回 NULL。
6. readdir 函数
原型: struct dirent *readdir(DIR *dirp);
作用: 读目录。
返回值:返回一条记录项struct dirent
{
ino_t d_ino; // 此目录进入点的inode
ff_t d_off; // 目录文件开头至此目录进入点的位移
signed short int d_reclen; //d_name的长度,不包括
unsigned char d_type; // d_name所指的文件类型
har d_name[256]; // 文件名
}
文件类型d_type:DT_BLK 块设备
DT_CHR 字符设备
DT_DIR 目录
DT_LNK 软链接
DT_FIFO 管道
DT_REG 普通文件
DT_SOCK 套接字
DT_UNKNOWN 未知
7.closedir 函数
作用: 关闭目录。
5. VFS虚拟文件系统
inux支持各种各样的文件系统格式,如ext2、ext3、reiserfs、FAT、NTFS、iso9660等等,不同的磁盘分区、光盘或其它存储设备都有不同的文件系统格式,然而这些文件系统都可以mount到某个目录下,使我们看到一个统一的目录树,各种文件系统上的目录和文件我们用ls命令看起来是一样的,读写操作用起来也都是一样的,这是怎么做到的呢?Linux内核在各种不同的文件系统格式之上做了一个抽象层,使得文件、目录、读写访问等概念成为抽象层的概念,因此各种文件系统看起来用起来都一样,这个抽象层称为虚拟文件系统(VFS,Virtual Filesystem)。
dup/dup2函数
up和dup2都可用来复制一个现存的文件描述符,使两个文件描述符指向同一个file结构体。如果两个文件描述符指向同一个file结构体,File Status Flag和读写位置只保存一份在file结构体中,并且file结构体的引用计数是2。如果两次open同一文件得到两个文件描述符,则每个描述符对应一个不同的file结构体,可以有不同的File Status Flag和读写位置。请注意区分这两种情况。
dup/dup2实现重定向: #include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
int fd, save_fd;
char msg[] = "This is a test\n";
fd = open("somefile", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
if(fd<0) {
perror("open");
exit(1);
}
save_fd = dup(STDOUT_FILENO);
dup2(fd, STDOUT_FILENO);
close(fd);
write(STDOUT_FILENO, msg, strlen(msg));
dup2(save_fd, STDOUT_FILENO);
write(STDOUT_FILENO, msg, strlen(msg));
close(save_fd);
return 0;
}
|