最后一战果然过瘾.代码量够多,新机制够复杂度,都管饱.做这一课就像从高山上往下走,坡急且路险,还不知自己的方位,琢磨不透系统的架构.待到下了山,回头一看豁然开朗,原来方才自己所下的山是这般模样.在这里面最重要的道具就是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接口再度封装成适合用户使用的形式

ucore lab8 文件系统 学习笔记的更多相关文章

  1. Hadoop学习笔记【分布式文件系统学习笔记】

    分布式文件系统介绍 分布式文件系统:Hadoop Distributed File System,简称HDFS. 一.HDFS简介 Hadoop分布式文件系统(HDFS)被设计成适合运行在通用硬件(c ...

  2. ucore lab1 bootloader学习笔记

    ---恢复内容开始--- 开机流程回忆 以Intel 80386为例,计算机加电后,CPU从物理地址0xFFFFFFF0(由初始化的CS:EIP确定,此时CS和IP的值分别是0xF000和0xFFF0 ...

  3. Linux文件系统学习笔记-1

       在Linux中, 一切皆文件,不论是目录,设备,套接字等都可以看成文件,而且每一个文件对应一个inode号,这是一一对应的关系. [root@oracle ~]# ls -il 总用量 2624 ...

  4. FAT文件系统规范v1.03学习笔记---4.文件和目录数据区之长目录项

    1.前言 本文主要是对Microsoft Extensible Firmware Initiative FAT32 File System Specification中文翻译版的学习笔记. 每个FAT ...

  5. FAT文件系统规范v1.03学习笔记---3.根目录区之FAT目录项结构

    1.前言 本文主要是对Microsoft Extensible Firmware Initiative FAT32 File System Specification中文翻译版的学习笔记. 每个FAT ...

  6. FAT文件系统规范v1.03学习笔记---1.保留区之 Fat32 FSInfo扇区结构和备份启动扇区

    1.前言 本文主要是对Microsoft Extensible Firmware Initiative FAT32 File System Specification中文翻译版的学习笔记. 每个FAT ...

  7. FAT文件系统规范v1.03学习笔记---2.FAT区之FAT数据结构(Fat Data Structure)

    1.前言 本文主要是对Microsoft Extensible Firmware Initiative FAT32 File System Specification中文翻译版的学习笔记. 每个FAT ...

  8. FAT文件系统规范v1.03学习笔记---1.保留区之启动扇区与BPB

    1.前言 本文主要是对Microsoft Extensible Firmware Initiative FAT32 File System Specification中文翻译版的学习笔记. 每个FAT ...

  9. Linux学习笔记-文件系统和基本命令

    目录 分区设备文件名 分区 挂载 文件目录 文件处理命令 目录处理命令 硬件设备文件名 IDE硬盘 /dev/hd[a-d] USB硬盘 /dev/sd[a-p] 光驱 /dev/cdrom或者/de ...

随机推荐

  1. 分布式锁redis

    1. 首先看这篇文章中  https://mp.weixin.qq.com/s/s-ozSjM5WmSUopxttSWYeQ 为什么redis能实现锁功能呢,看下图,redis命令窗口中,setnx  ...

  2. 什么是 spring 的内部 bean?

    只有将 bean 用作另一个 bean 的属性时,才能将 bean 声明为内部 bean. 为了定义 bean,Spring 的基于 XML 的配置元数据在 <property> 或 &l ...

  3. read,readline,readlines的特点与区别

    1.read 读取全部文件 with open("test.text", "r",encoding='utf8') as f: print(f.read()) ...

  4. Tcp三次握手四次挥手个人学习

    最近想跳槽,学习了tcp中的三次握手与四次挥手,特意记录下,加深记忆 SYN 代表请求创建连接 FIN 表示请求关闭连接 ACK 代表确认接受,不管是三次握手还是四次分手,在回应的时候都会加上ACK= ...

  5. Asp.Net Core之Identity应用(上篇)

    一.前言 在前面的篇章介绍中,简单介绍了IdentityServer4持久化存储机制相关配置和操作数据,实现了数据迁移,但是未对用户实现持久化操作说明.在总结中我们也提到了, 因为IdentitySe ...

  6. 前端每日实战:144# 视频演示如何用 D3 和 GSAP 创作一个集体舞动画

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/gdVObN 可交互视频 此视频是可 ...

  7. 用Exception类捕获所有异常的技术是怎么用的?

    3.用Exception类捕获所有异常  马克-to-win:注意,一个事实是:Exception类是所有其他异常类的父类,所以Exception类能捕获所有的异常.马克-to-win:问题是用Exc ...

  8. SecureCRT显示连接失败的原因

    问题描述:连接后像192.168.111.140那样的红色图标 原因:没有开启对应的虚拟机 解决办法:打开对应的虚拟机

  9. java中如何求出2008年的第1星期星期一是几号?

    题目8: 2008年的第1星期星期一是几号? import java.util.*; public class Test {     public static void main(String[] ...

  10. es5语法下,javascript如何判断函数是new还是()调用

    es5语法没有支持类class,但是可以通关函数来申明一个类,如下: function Person(name){ this.name=name; } var john=new Person('joh ...