最后一战果然过瘾.代码量够多,新机制够复杂度,都管饱.做这一课就像从高山上往下走,坡急且路险,还不知自己的方位,琢磨不透系统的架构.待到下了山,回头一看豁然开朗,原来方才自己所下的山是这般模样.在这里面最重要的道具就是gdb的调用栈查看器了,没了它我肯定得迷失在深山里.
打过了难关就是舒坦,成就感满满,跟打游戏一样还想继续,下一次打哪个BOSS呢?一般就CSAPP吧.
概览
先放定义:
通用文件系统访问接口层(UFSAI):该层提供了一个从用户空间到文件系统的标准访问接口。这一层访问接口让应用程序能够通过一个简单的接口获得ucore内核的文件系统服务。
文件系统抽象层(VFS):向上提供一个一致的接口给内核其他部分(文件系统相关的系统调用实现模块和其他内核功能模块)访问。向下提供一个同样的抽象函数指针列表和数据结构屏蔽不同文件系统的实现细节。
Simple FS文件系统层(SFS):一个基于索引方式的简单文件系统实例。向上通过各种具体函数实现以对应文件系统抽象层提出的抽象函数。向下访问外设接口
外设接口层(DEV):向上提供device访问接口屏蔽不同硬件细节。向下实现访问各种具体设备驱动的接口,比如disk设备接口/串口设备接口/键盘设备接口等。
通用文件系统访问接口
主要向用户提供了开关读写的接口
[SYS_open] sys_open,
[SYS_close] sys_close,
[SYS_read] sys_read,
[SYS_write] sys_write,
[SYS_getdirentry] sys_getdirentry,
[SYS_seek] sys_seek,
[SYS_fstat] sys_fstat,
[SYS_fsync] sys_fsync,
[SYS_getcwd] sys_getcwd,
[SYS_dup] sys_dup,
VFS层
struct file: 进程访问的单个文件信息
struct file {
enum {
FD_NONE, FD_INIT, FD_OPENED, FD_CLOSED,
} status; //访问文件的执行状态
bool readable; //文件是否可读
bool writable; //文件是否可写
int fd; //文件在filemap中的索引值
off_t pos; //访问文件的当前位置
struct inode *node; //该文件对应的内存inode指针
int open_count; //打开此文件的次数
};
struct file_struct: 某个进程访问的当前工作目录和打开的文件集合
struct files_struct {
struct inode *pwd; //进程当前执行目录的内存inode指针
struct file *fd_array; //进程打开文件的数组
atomic_t files_count; //访问此文件的线程个数
semaphore_t files_sem; //确保对进程控制块中fs_struct的互斥访问
};
struct inode: 内存里的索引节点,封装了不同文件系统的索引节点
struct inode {
union {
//包含不同文件系统特定inode信息的union成员变量
struct device __device_info; //设备文件系统内存inode信息
struct sfs_inode __sfs_inode_info; //SFS文件系统内存inode信息
} in_info;
enum {
inode_type_device_info = 0x1234,
inode_type_sfs_inode_info,
} in_type; //此inode所属文件系统类型
atomic_t ref_count; //此inode的引用计数
atomic_t open_count; //打开此inode对应文件的个数
struct fs *in_fs; //抽象的文件系统,包含访问文件系统的函数指针
const struct inode_ops *in_ops; //抽象的inode操作,包含访问inode的函数指针
};
struct inode_ops: 封装了不同索引节点的操作函数列表(开关读写),vop为virtual operation简写
struct inode_ops {
unsigned long vop_magic;
int (*vop_open)(struct inode *node, uint32_t open_flags);
int (*vop_close)(struct inode *node);
int (*vop_read)(struct inode *node, struct iobuf *iob);
int (*vop_write)(struct inode *node, struct iobuf *iob);
int (*vop_getdirentry)(struct inode *node, struct iobuf *iob);
int (*vop_create)(struct inode *node, const char *name, bool excl, struct inode **
node_store);
int (*vop_lookup)(struct inode *node, char *path, struct inode **node_store);
......
};
SFS层
需要关注的文件类型有:
常规文件: 字节序列
目录: entry序列,包括文件名和关联的inode
设备文件: 物理设备映射成的文件
硬链接: 参考linux的硬链接
SFS的文件系统布局
每块4K
第0块 superblock: 识别魔数,总块数,未使用块数,SFS名
第1块 root-dir的inode
之后的部分: freemap及其他
索引节点:
inode,即index node.注意这里说的是SFS的索引节点,不是VFS抽象出的索引节点
struct sfs_disk_inode,代表了一个实际位于磁盘上的文件
struct sfs_disk_inode {
uint32_t size; //如果inode表示常规文件,则size是文件大小
uint16_t type; //inode的文件类型
uint16_t nlinks; //此inode的硬链接数
uint32_t blocks; //此inode的数据块数的个数
uint32_t direct[SFS_NDIRECT]; //此inode的直接数据块索引值(有SFS_NDIRECT
个)
uint32_t indirect; //此inode的一级间接数据块索引值
};
SFS_NDIRECT=12,即<=12 * 4k的数据块索引直接存在direct数组里,indirect=0;超过12 * 4k时direct=某一数据块索引值,该数据块全部用来存放剩余的数据块索引.因此UCORE最大支持(12*4k+(4k/4)*4k=48k+4m)的单个文件
对于普通文件,数据块中存放的就是文件的内容;
对于目录,数据块内包含了文件名和文件对应索引节点编号
/* file entry (on disk) */
struct sfs_disk_entry {
uint32_t ino; //索引节点所占数据块索引值
char name[SFS_MAX_FNAME_LEN + 1]; //文件名
};
为了方便,UCORE中直接用block(数据块)在磁盘上的编号作为inode编号
注意sfs_disk_inode是inode在磁盘上的存在形式,读入内存后为了方便判断否改写、互斥操作、回收和快速地定位等,我们为每个disk inode封装了一个sfs_inode
/* inode for sfs */
struct sfs_inode {
struct sfs_disk_inode *din; /* on-disk inode */
uint32_t ino; /* inode number */
uint32_t flags; /* inode flags */
bool dirty; /* true if inode modified */
int reclaim_count; /* kill inode if it hits zero */
semaphore_t sem; /* semaphore for din */
list_entry_t inode_link; /* entry for linked-list in sfs_fs*/
list_entry_t hash_link; /* entry for hash linked-list in sfs_fs
*/
};
同时还有一些辅助函数:
sfs_bmap_load_nolock: 将指定block载入内存,或创建一个新block并载入内存
sfs_bmap_truncate_nolock :释放掉指定inode的最后一个entry
sfs_dirent_read_nolock: 读取指定inode的第k个entry
sfs_dirent_search_nolock: 在指定目录下查找指定名字的inode的编号
设备文件IO层
关键数据结构
struct device: 表示一个设备及对应的开关读写操作,
struct device {
size_t d_blocks;
//设备占用的数据块个数
size_t d_blocksize;
//数据块的大小
int (*d_open)(struct device *dev, uint32_t open_flags);
//打开设备的函数指针
int (*d_close)(struct device *dev); //关闭设备的函数指针
int (*d_io)(struct device *dev, struct iobuf *iob, bool write); //读写设备的函数指针
int (*d_ioctl)(struct device *dev, int op, void *data); //用ioctl方式控制设备的函数指
针
};
在万物皆文件思想下,设备也是一种文件,所以需要关联的inode,strcut vfs_dev_t负责将device和inode关联
// device info entry in vdev_list
typedef struct {
const char *devname;
struct inode *devnode;
struct fs *fs;
bool mountable;
list_entry_t vdev_link;
} vfs_dev_t;
同时,所有设备通过vdev_list串连
文件系统初始化
vfs初始化:
vfs_init->vfs_devlist_init->list_init(&vdev_list)
dev初始化:
dev_init->dev_init_stdin
->dev_init_stdout
->dev_init_disk0
以stdin为例进行研究:
dev_init_stdin->dev_create_inode->inode_init 将node.ops与dev_node_ops绑定
->stdin_device_init 将device结构体的各个函数与stdin对应函数关联
->vfs_add_dev
//封装后的设备操作
static const struct inode_ops dev_node_ops = {
.vop_magic = VOP_MAGIC,
.vop_open = dev_open,
.vop_close = dev_close,
.vop_read = dev_read,
.vop_write = dev_write,
.vop_fstat = dev_fstat,
.vop_ioctl = dev_ioctl,
.vop_gettype = dev_gettype,
.vop_tryseek = dev_tryseek,
.vop_lookup = dev_lookup,
};
此时调用node的开关读写函数指向了dev的开关读写函数,进而调用node对应的device的开关读写函数.意味着整个dev层封装完成
//封装后的SFS文件操作
static const struct inode_ops sfs_node_fileops = {
.vop_magic = VOP_MAGIC,
.vop_open = sfs_openfile,
.vop_close = sfs_close,
.vop_read = sfs_read,
.vop_write = sfs_write,
.vop_fstat = sfs_fstat,
.vop_fsync = sfs_fsync,
.vop_reclaim = sfs_reclaim,
.vop_gettype = sfs_gettype,
.vop_tryseek = sfs_tryseek,
.vop_truncate = sfs_truncfile,
};
sfs初始化:
sfs_init->sfs_mount->vfs_mount->sfs_do_mount
在sfs_do_mount中完成对整个disk0文件系统的解析,并将disk0的数据读写操作封装成SFS的文件开关读写操作
读写操作分析
stdout的读写:
sys_write->sysfile_write->file_write->dev_write->stdout_io
stdin的读写:
sys_read->sysfile_read->file_read->dev_read_stdin_io
disk0_io的读写:
触发场景众多,这里只截取部分
sfs_init->sfs_mount->vfs_mount->sfs_do_mount->sfs_init_freemap->sfs_init_read->disk0_io
sfs_load_inode->sfs_rbuf->sfs_rwblock_nolock->disk0_io
sfs_dirent_read_nolock->sfs_rbuf->sfs_rbuf->sfs_rwblock_nolock->disk0_io
sfs_bmap_load_nolock->sfs_bmap_get_sub_nolock->sfs_rbuf->sfs_rwblock_nolock->disk0_io
可以发现stdin和stdout作为流设备是没有文件系统的,故没有SFS这一层
而disk0作为有文件系统的块设备,需要借助SFS才能操作
自下而上的总结
DEV层完成了对stdin,stdout,disk0的封装,使得可以分别通过对应的device结构体对他们进行操作
SFS层将disk0内的数据解析成了一套文件系统,在文件的开关读写和块设备读写间建立了桥梁
VFS层将各个stdio,stdout和SFS进行封装,提供了统一的读写接口VOP_XXX
UFSAI层将VFS接口再度封装成适合用户使用的形式