Lab-5实验报告
Lab-5实验报告
20373915-朱文涛
思考题
/proc是一种伪文件系统。存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。Windows通过提供相关的系统调用改变内核运行状态和查看进程信息。
好处:对系统调用进行了更多的抽象,并将其整合到了文件操作上,降低操作的复杂度。
缺点:需要在内存中实现,占用内存空间。
当外部设备产生中断信号或者更新数据时,此时Cache中之前旧的数据可能刚完成缓存,那么完成缓存的这一部分无法完成更新,则会发生错误的数据和行为。
对于串口设备来说,读写频繁,信号多,在相同的时间内发生错误的概论远高于IDE磁盘。
struct inode_operations {
int (*create) (struct inode *,struct dentry *,int);
struct dentry * (*lookup) (struct inode *,struct dentry *);
int (*link) (struct dentry *,struct inode *,struct dentry *);
int (*unlink) (struct inode *,struct dentry *);
int (*symlink) (struct inode *,struct dentry *,const char *);
...
};
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
...
};
struct inode {
...
unsigned long i_ino;
atomic_t i_count;
kdev_t i_dev;
umode_t i_mode;
nlink_t i_nlink;
uid_t i_uid;
gid_t i_gid;
kdev_t i_rdev;
loff_t i_size;
time_t i_atime;
time_t i_mtime;
time_t i_ctime;
...
struct inode_operations *i_op;
struct file_operations *i_fop;
struct super_block *i_sb;
...
};
Linux系统下FCB储存了文件的所有信息,但索引结点下的FCB 进行了改进,除了
struct File {
u_char f_name[MAXNAMELEN]; // filename
u_int f_size; // file size in bytes
u_int f_type; // file type
u_int f_direct[NDIRECT];
u_int f_indirect;
struct File *f_dir;
// the pointer to the dir where this file is in, valid only in memory.
u_char f_pad[BY2FILE - MAXNAMELEN - 4 - 4 - NDIRECT * 4 - 4 - 4];
};
// file descriptor + file
struct Filefd {
struct Fd f_fd;
u_int f_fileid;
struct File f_file;
};
// file descriptor
struct Fd {
u_int fd_dev_id;
u_int fd_offset;
u_int fd_omode;
};
struct Dev {
int dev_id;
char *dev_name;
int (*dev_read)(struct Fd *, void *, u_int, u_int);
int (*dev_write)(struct Fd *, const void *, u_int, u_int);
int (*dev_close)(struct Fd *);
int (*dev_stat)(struct Fd *, struct Stat *);
int (*dev_seek)(struct Fd *, u_int);
};
我们实验所编写的MOS文件控制块不是叫的FCB,而是File,对文件进行操作的函数却全都实现在了Dev结构体的接口里。此外我们对文件的操作是依靠进程间通信来完成的,而Linux是直接系统调用完成的。总的来说我们的MOS文件系统性能变低了,但用户空间实现使得可靠性变高了。
我们现看include/fs.h中的相关定义:
#define BY2BLK BY2PG
#define BY2PG 4096
#define BY2FILE 256
#define FILE2BLK (BY2BLK/sizeof(struct File))
可知一个磁盘块中最多能存储多少个文件控制块个数为4096/256 = 16个。
我们在观察File结构体定义:
struct File {
u_char f_name[MAXNAMELEN]; // filename
u_int f_size; // file size in bytes
u_int f_type; // file type
u_int f_direct[NDIRECT];
u_int f_indirect;
struct File *f_dir;
u_char f_pad[BY2FILE - MAXNAMELEN - 4 - 4 - NDIRECT * 4 - 4 - 4];
};
首先f_direct[NDIRECT]包含10个文件指针,同时f_indirect指向一个间接磁盘块,用来存储指向文件内容的磁盘块的指针,一个指针4B,一个磁盘块内容4096B,故可以存放1024个指针,而由于实验平台不使用间接磁盘块的前十个指针(方便计算),故我们一个文件夹可以有1024个文件。
那么一个文件最大也就是:1024 * 4kB = 4MB。
DISKMAX = 0x40000000,因此支持的最大磁盘大小为1GB。
我们的文件系统是放在用户态而不是内核态,而用户态不能访问用户空间以上的内核间,因此文件系统不能正常工作。(panic)
include/fs:
········
struct Fsreq_open {
char req_path[MAXPATHLEN];
u_int req_omode;
};
···········
struct Fsreq_remove {
u_char req_path[MAXPATHLEN];
};
这是为了文件系统实现用户进程实现各种请求而定义的特定请求结构体,例如Fsreq_open用于处理打开文件请求。fs/fs.h中各种定义看起来也不难理解,不在此赘述。
结构体Filefd的第一个成员就是结构体Fd的变量。因此每个Fd对应一个Filefd,而Filefd其他成员变量在内存上紧跟着Fd,因此转换可行。
我们的操作系统在父进程 fork 了子进程后,两个进程会共享文件描述符表,也同时会共用文件描述符的偏移指针,示例如下:
文件内容如下:
BUAA OS is my shit love!
代码如下:
int r,
fdnum, n;
char buf[200];
fdnum = open("/newmotd", O_RDWR);
if ((r = fork()) == 0) {
n = read(fdnum, buf, 4);
writef("[child] buffer is \'%s\'\n", buf);
} else {
n = read(fdnum, buf, 4);
writef("[father] buffer is \'%s\'\n", buf);
}
输出如下:
[father] buffer is 'BUAA'
[child] buffer is ' OS '
Fd用于记录文件的基本信息,需要单独占用一页:
// file descriptor
struct Fd {
u_int fd_dev_id; // 外设的id
u_int fd_offset; // 读写的偏移量
u_int fd_omode; // 允许用户进程对文件的操作权限,包括只读、只写、读写
};
Filefd用于记录文件的详细信息,Fd也存储在其中:
// file descriptor + file
struct Filefd {
struct Fd f_fd; // 文件描述符
u_int f_fileid; // 文件的id
struct File f_file; // 对应文件的文件控制块
};
Open是打开文件行为的抽象:
struct Open {
struct File *o_file; // 指向该文件的文件控制块的指针
u_int o_fileid; // 打开文件的id
int o_mode; // 允许用户进程对文件的操作权限
struct Filefd *o_ff; // 指向该文件描述符的指针
};
-
同步消息,用黑三角箭头搭配黑实线表示:
同步意义:消息的发送者把进程控制传递给消息的接收者,然后
-
异步消息,用开三角箭头和黑色实线表示:
异步的意义:消息的发送者将消息发送给消息的接受者后,
-
返回消息,用开三角箭头搭配黑色虚线表示:
返回消息和同步消息结合使用,因为异步消息不进行等待,所以不需要知道返回值。
-
创建消息,用开三角箭头搭配黑色虚线表示,其下面特别注明 <<create>>
创建消息用来创建一个实例。
-
摧毁消息,用黑三角箭头搭配黑实线表示,其下面特别注明 <<destroy>>
摧毁消息用来摧毁一个实例,生命线上会出现一个 X 表示结束。
-
Lost and Found Message,这类消息的特点是它可能没有发送者或者接收者,用一个黑色实心的
点和黑色实心三角箭头黑实线表示:
对于文件系统,通过特定调用号使得文件系统知道请求者有何种需求,然后文件系统进入相应处理函数中处理,将结果通过ipc_send传回用户进程。
serve进程随着系统运行开始时开始执行,每次循环都调用了ipc_recv,该进程会进入NOT_RUNNABLE状态,随时相应用户进程发出的文件请求,不会一直占用CPU,直到结束系统杀死进程。此进程为用户态进程,不会导致内核进程陷入panic。
实验难点
文件系统层次关系梳理
文件系统可以分成四个层次:
文件系统用户接口、文件系统抽象层、文件系统具体实现、文件系统设备接口。
文件系统的逐级调用关系可以用下图表示:
多级目录与多级索引
多级目录用来管理文件间的层次关系,多级索引用来管理单个文件的数据。两者并不是一个概念。
在我们的实验中,目录文件的内容是文件控制块struct File,目录文件的每一个数据块由16个文件目录项组成,找到相应的文件名,找到对应文件的FCB,进而找到该文件的数据块。
多级索引用来管理文件的数据块,我们的实验中使用二级索引机制。在FCB中可以有一级索引数组f_direct[NDIRECT],和一个二级索引f_indirect,二级索引找到的磁盘块为索引块,这个磁盘块可以存储1024的FCB。
目录文件综合体现了这一点,以下图来说明:
文件系统的IPC机制
文件系统通过IPC进行用户进程与文件系统进程的通信,很好地体现了OS多进程的处理过程。
文件系统的通信过程如下所示:
-
用户在通过用户接口最终调用相应的fsipc_xxxx()函数,
-
最终调用fsipc()将消息发送给文件系统进程。
-
文件系统进程中serve()函数调用ipc_recv接收用户传递来的信息,
-
根据req类型调用相应的serve_xxxx()函数,
-
对应的serve_xxxx()处理完毕后通过ipc_send()将信息发送给用户进程。
-
用户进程中最终又通过ipc_recv()接收文件系统进程发送回来的消息,最终完成一次完整的ipc通信过程。
设备驱动
设备驱动的难点其一在于要找到不同的设备寄存器对应的虚拟地址,其二是要对设备寄存器进行正确顺序的读写。
设备寄存器的虚拟地址分为三个部分:
/* BASE:物理地址到虚拟地址的转换,本次实验中我们映射外设使用的是不经过cache的kseg1地址段,这个地址是0xa0000000
PHY:设备的物理基地址,不同的设备有不同的物理地址,在ide磁盘中,这个地址是0x13000000
OFFSET:设备不同的寄存器对应的物理地址偏移不同,具体情况需要查阅手册 */
虚拟地址通过BASE + PHY + OFFSET计算得到。
其次需要对设备寄存器进行合理顺序的读写,以写磁盘为例:
-
将写入数据写入扇区数据缓冲区
-
写入disk编号
-
写入写的偏移
-
写入“磁盘写”的标记,使磁盘开始写
-
读出状态,判断写入操作是否成功
读磁盘与写磁盘过程类似。
体会与感想
本次lab花费20h。一部分原因主要是提示太少;一部分原因是MOS的代码结构比较混乱,让我阅读起来力不从心。对于文件系统接口部分内容,MOOC说的过于简洁了,导致我在实现的时候根本无从下手,于是又花了好长时间去了解文件接口以及IPC相关的机制,确实挺累的。
不过通过这段时间的学习,我对C语言的了解更加充分、更加透彻,对操作系统的工作原理有了具体的理解,而不是最开始简单的几行文字。
残留疑点
-
-
代码中多次出现
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)