文件系统需要解答以下两个问题:
1 文件系统在内核中时如何实现的?如何呈现给用户一个树状的目录结构?如何处理用户的文件和目录操作请求?
2 磁盘是一种顺序的存储介质,一个树状的目录结构如何存到磁盘上?怎样设计文件系统的存储格式使访问磁盘的效率最高?各种文件和目录操作在磁盘上的实际效果是什么?
在Windows系统中一般我们使用的是NTFS文件系统(早期的windows中使用的时FAT32系统)。目前U盘的文件系统也大多时FAT32。但是Linux的文件系统是完全不一样的。Linux文件系统有ext2, ext3, ext4。ext3和ext4也只是在原来的文件系统上作了一些扩展。所以学习的角度来看一般都学习ext2文件系统。
0. 从设计文件系统来看
假设现在我们有一个名字为hello的文件需要存储到磁盘中,那么我们应该怎样去设计文件系统来存储这个文件呢?
用最简单的方式考虑,很自然的我们会想到使用一种类似键值对的形式进行存储,每个文件存储他的文件名,文件存储的起始位置以及文件的大小。乍一看好像这样确实能将我们的文件存到磁盘中。随之的问题又来了,存储了这个文件之后,假设我们现在想修改这个文件。由于该文件的随后的磁盘位置很可能已经被占用了,我们就不能把追加的内容直接写在原文件的后面。
从设计文件系统的角度来看,文件系统设计的目的就是方便我们将文件存储到磁盘,方便我们从磁盘中去取得文件。FAT32,NTFS,ext2等各种文件系统都是基于着一个目标的。
1. ext2文件系统
ext2文件系统中磁盘的按照下图的方式进行分割:
1.1 Boot Block(启动块)
所有的文件系统都有这个块(而且这个块的大小都是一样的(1KB),肯定要一样,不然怎么读取磁盘的内容),这个块存储着磁盘的分区信息和启动信息,任何文件都不能使用启动块。windows系统安装的时候有个MBR,其实就是这个块。
1.2 Super Block (超级块)
超级块描述整个分区的文件系统信息,例如:块大小、文件系统版本号,上次mount时间等等。
超级块在每个块组的开头都有一份拷贝
1.3 Group Description Table (GDT, 块组描述符表)
块组描述符由很多的块组描述符组成,整个分区分成多少个块组就对应有多少块组描述符。每个块组描述符存储一个块组的描述信息,例如这个块组从哪里开始是inode表,从哪里开始是数据块,空闲的inode和数据块还有多少个等等。
和超级块类似,块组描述表在每个块组的开头都有一个拷贝,这些信息非常的重要,一旦超级块意外损坏就会丢失整个分区的数据,一旦块组描述符意外损坏就会丢失整个块组的数据,因此他们都有多份拷贝。
通常,内核只用地第0个块组中的拷贝,当执行e2fsck检查文件系统一致性时,第0个块组中的超级块组中的超级块和块组描述符就会拷贝到其他块组,这样当第0个块组的开头意外损坏的时候就可以从用其他的拷贝来恢复,从而减少损失。
1.4 Block Bitmap (块位图)
用来标注那些块使用了,哪些快没有使用。在文件删除的时候就是在此操作的,文件删除的时候并不是去写掉数据块的内容,而是将块位图写为未用,这样下次有数据要写入的时候就可以使用了,而且这样“删除”的速度很快。这也就是磁盘数据恢复的原理。
1.5 inode Bitmap(inode位图)
用来标注inode的使用情况。
1.6 inode Table (inode表)
由若干个inode组成,文件属性都是在inode里面。
1.7 数据块
保存文件的数据。前面说到一个数据块的大小是固定的,那么如果有一个非常大的文件想要存储到磁盘中该怎么解决呢?这就涉及到了数据的寻址,ext2文件系统inode中的数据指针共有15个,前面的12个位直接寻址指针,后面3个分别为一级,二级和三级间接寻址:
那按照这样的放射能不能存大文件呢?可以计算假设块大小为1KB,那么最大的文件大小可以达到16GB。
这里就有一个问题:在磁盘的分区的时候如何分配合适的块大小来满足我们的使用?也就是说当我们系统需要存的都是很小的文件,那么我们就需要更多的文件描述符,块大小就可以设置较小;当我们系统存储大多数都是大文件的时候我们就可以将块设置较大。
数据块根据不同的文件类型有以下几种情况:
- 1 对于常规文件,文件的数据存储在数据块中。
- 2 于目录,该目录下的所有文件名和目录名存储在数据块中,注意文件名保存在它所在目录的数据块中,除文件名之外, ls -l 命令看到的其它信息都保存在该文件的inode中。
注意: 目录也是一种文件,是一种特殊类型的文件。
- 3 对于符号链接,如果目标路径名较短则直接保存在inode中以便更快地查找,如果目标路径名较长则分配一个数据块来保存。
- 4 设备文件、FIFO和socket等特殊文件没有数据块,设备文件的主设备号和次设备号保存在inode中。
2 文件和目录访问相关API函数
2.1 stat, fstat, lstat, fstatat文件相关信息
NAME
stat, fstat, lstat, fstatat - get file status
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
#include <fcntl.h> /* Definition of AT_* constants */
#include <sys/stat.h>
int fstatat(int dirfd, const char *pathname, struct stat *buf,
int flags);
使用时一般带入文件路径或者文件描述就可以获取到该文件的属性,获得的结果以结构体stat的形式给出。
系统内核中给struct stat 结构体定义了:
struct stat
{
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
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; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
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 */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
mode_t 文件类型
S_IFSOCK 0140000 socket
S_IFLNK 0120000 symbolic link
S_IFREG 0100000 regular file
S_IFBLK 0060000 block device
S_IFDIR 0040000 directory
S_IFCHR 0020000 character device
S_IFIFO 0010000 FIFO
使用下述函数来判断文件类型,实际上就是判断某位上的值是否为1.
S_ISREG(m) is it a regular file?
S_ISDIR(m) directory?
S_ISCHR(m) character device?
S_ISBLK(m) block device?
S_ISFIFO(m) FIFO (named pipe)?
S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.)
S_ISSOCK(m) socket? (Not in POSIX.1-1996.)
- example: 使用stat函数查看文件属性
/*************************************************************************
> File Name: stat.c
> Author: Robin
> Mail: chou_robin@163.com
> Created Time: 2016年06月01日 星期三 14时56分55秒
************************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
struct stat sb;
if(argc != 2)
{
fprintf(stderr, "Usage: %s <pathname>\n",argv[0]);
exit(1);
}
if(stat(argv[1], &sb) == -1)
{
perror("stat");
exit(1);
}
printf("File type: ");
switch(sb.st_mode & S_IFMT)
{
case S_IFBLK: printf("block device\n"); break;
case S_IFCHR: printf("character device\n"); break;
case S_IFDIR: printf("dirctory\n"); break;
case S_IFIFO: printf("FIFO/pipe\n"); break;
case S_IFLNK: printf("symlink\n"); break;
case S_IFREG: printf("regular file\n"); break;
case S_IFSOCK: printf("socket\n"); break;
default: printf("unkonwn\n"); break;
}
printf("I-node number:%ld\n", (long) sb.st_ino);
printf("Mode: %lo (octal)\n", (unsigned long) sb.st_mode);
printf("Link count: %ld\n", (long) sb.st_nlink);
printf("Ownership: UID=%ld GID=%ld\n",(long) sb.st_uid, (long) sb.st_gid);
printf("Preferred I/O block size: %ld bytes\n",(long) sb.st_blksize);
printf("File size: %lld bytes\n",(long long) sb.st_size);
printf("Blocks allocated: %lld\n",(long long) sb.st_blocks);
printf("Last status change: %s", ctime(&sb.st_ctime));
printf("Last file access: %s", ctime(&sb.st_atime));
printf("Last file modification: %s", ctime(&sb.st_mtime));
exit(EXIT_SUCCESS);
}
2.2 access, faccessat 检查用户对文件的操作
NAME
access, faccessat - check user's permissions for a file
SYNOPSIS
#include <unistd.h>
int access(const char *pathname, int mode);
#include <fcntl.h> /* Definition of AT_* constants */
#include <unistd.h>
int faccessat(int dirfd, const char *pathname, int mode, int flags);
mode | 说明 |
---|---|
R_OK | 测试读权限 |
W_OK | 测试写权限 |
X_OK | 测试执行权限 |
F_OK | 文件是否存在 |
example
/*************************************************************************
> File Name: access.c
> Author: Robin
> Mail: chou_robin@163.com
> Created Time: 2016年06月01日 星期三 15时52分50秒
************************************************************************/
#include<stdio.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char *argv[])
{
if(argc != 2)
{
fprintf(stderr,"args num: %d error\n", argc);
exit(1);
}
int rt;
if((rt = access(argv[1], R_OK)) == -1)
{
fprintf(stderr,"Error: ", perror);
exit(1);
}
printf("Read:%d\n",rt);
return 0;
}