编译与执行过程
# 直接从mksfs.c编译成bin/mksfs
224 + cc tools/mksfs.c
225 gcc -Itools/ -g -Wall -O2 -D_FILE_OFFSET_BITS=64 -c tools/mksfs.c -o obj/mksfs/tools/mksfs.o
226 gcc -g -Wall -O2 -D_FILE_OFFSET_BITS=64 obj/mksfs/tools/mksfs.o -o bin/mksfs
# 创建bin/ucore.img
227 dd if=/dev/zero of=bin/ucore.img count=10000
228 10000+0 records in
229 10000+0 records out
230 5120000 bytes (5.1 MB, 4.9 MiB) copied, 0.0214048 s, 239 MB/s
# 将bin/bootblock打入bin/ucore.img
231 dd if=bin/bootblock of=bin/ucore.img conv=notrunc
232 1+0 records in
233 1+0 records out
234 512 bytes copied, 0.00015277 s, 3.4 MB/s
# 将bin/kernel打入bin/ucore.img
235 dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
236 708+1 records in
237 708+1 records out
238 362528 bytes (363 kB, 354 KiB) copied, 0.00136994 s, 265 MB/s
# 创建对换区
239 dd if=/dev/zero of=bin/swap.img bs=1024k count=128
240 128+0 records in
241 128+0 records out
242 134217728 bytes (134 MB, 128 MiB) copied, 0.767973 s, 175 MB/s
# 创建disk0目录,编译用户态可执行程序并他们拷贝到disk0目录
243 mkdir -p disk0
cp obj/__user_divzero.out disk0//divzero
cp obj/__user_badarg.out disk0//badarg
cp obj/__user_forktree.out disk0//forktree
cp obj/__user_faultread.out disk0//faultread
cp obj/__user_pgdir.out disk0//pgdir
cp obj/__user_exit.out disk0//exit
cp obj/__user_priority.out disk0//priority
cp obj/__user_sleep.out disk0//sleep
cp obj/__user_hello.out disk0//hello
cp obj/__user_waitkill.out disk0//waitkill
cp obj/__user_softint.out disk0//softint
cp obj/__user_spin.out disk0//spin
cp obj/__user_ls.out disk0//ls
cp obj/__user_yield.out disk0//yield
cp obj/__user_badsegment.out disk0//badsegment
cp obj/__user_testbss.out disk0//testbss
cp obj/__user_faultreadkernel.out disk0//faultreadkernel
cp obj/__user_sh.out disk0//sh
cp obj/__user_forktest.out disk0//forktest
cp obj/__user_sfs_filetest1.out disk0//sfs_filetest1
cp obj/__user_matrix.out disk0//matrix
cp obj/__user_sleepkill.out disk0//sleepkill
# 创建空的bin/sfs.img
329 dd if=/dev/zero of=bin/sfs.img bs=1024k count=128
330 128+0 records in
331 128+0 records out
332 134217728 bytes (134 MB, 128 MiB) copied, 0.384861 s, 349 MB/s
# 运行bin/mksfs,将空的bin/sfs.img变成可用的带sfs的硬盘,里面还有用户态可执行程序。
333 create bin/sfs.img (disk0) successfully.
makefile对mksfs的操作
# -D
# _FILE_OFFSET_BITS=64
# -m32
# off_t
# ino_t
# 注意这里去掉了-m32选项,生成的应该是默认的64位程序,该程序与ucore无关,
# 是生成ucore的SFSIMG硬盘的工具而已。
49 ## for mksfs program, -D_FILE_OFFSET_BITS=64 can guarantee sizeof(off_t)==8, sizeof(ino_t) ==8
50 ## for 64 bit gcc, to build 32-bit mksfs, you can use below line
51 ## HOSTCFLAGS := -g -Wall -m32 -O2 -D_FILE_OFFSET_BITS=64
52 HOSTCFLAGS := -g -Wall -O2 -D_FILE_OFFSET_BITS=64
262 # -------------------------------------------------------------------
263 # create sfs.img
264 SFSIMG := $(call totarget,sfs.img)
265 SFSBINS :=
266 SFSROOT := disk0
267
# 将可执行用户态程序拷贝到disk0目录下
268 define fscopy
269 __fs_bin__ := $(2)$(SLASH)$(patsubst $(USER_PREFIX)%,%,$(basename $(notdir $(1))))
270 SFSBINS += $$(__fs_bin__)
271 $$(__fs_bin__): $(1) | $$$$(dir $@)
272 @$(COPY) $$< $$@
273 endef
274
275 $(foreach p,$(USER_BINS),$(eval $(call fscopy,$(p),$(SFSROOT)$(SLASH))))
276
# 创建disk0目录
277 $(SFSROOT):
278 if [ ! -d "$(SFSROOT)" ]; then mkdir $(SFSROOT); fi
279
280 $(SFSROOT):
281 $(V)$(MKDIR) $@
282
283 $(SFSIMG): $(SFSROOT) $(SFSBINS) | $(call totarget,mksfs)
# 先创建一个空的bin/sfs.img
284 $(V)dd if=/dev/zero of=$@ bs=1024k count=128
# 再运行bin/mksfs将bin/sfs.img做成可用的带文件系统和可执行用户程序的硬盘
285 @$(call totarget,mksfs) $@ $(SFSROOT)
286
287 $(call create_target,sfs.img)
# 对qemu的使用增加了SFSIMG这块硬盘
315 QEMUOPTS = -hda $(UCOREIMG) -drive file=$(SWAPIMG),media=disk,cache=writeback -drive file=$(SFSIMG),media=disk,cache=writeback
......
320 qemu: $(UCOREIMG) $(SWAPIMG) $(SFSIMG)
321 $(V)$(QEMU) -no-reboot -parallel stdio $(QEMUOPTS) -serial null
mksfs.c源码分析
数据结构
146 struct cache_block {
147 uint32_t ino;
148 struct cache_block *hash_next;
149 void *cache;
150 };
151
152 struct cache_inode {
153 struct inode {
154 uint32_t size;
155 uint16_t type;
156 uint16_t nlinks;
157 uint32_t blocks;
158 uint32_t direct[SFS_NDIRECT];
159 uint32_t indirect;
160 uint32_t db_indirect;
161 } inode;
162 ino_t real;
163 uint32_t ino;
164 uint32_t nblks;
165 struct cache_block *l1, *l2;
166 struct cache_inode *hash_next;
167 };
168
169 struct sfs_fs {
# 超级块
170 struct {
171 uint32_t magic;
172 uint32_t blocks;
173 uint32_t unused_blocks;
174 char info[SFS_MAX_INFO_LEN + 1];
175 } super;
176 struct subpath {
177 struct subpath *next, *prev;
178 char *subname;
179 } __sp_nil, *sp_root, *sp_end;
180 int imgfd;
181 uint32_t ninos, next_ino;
182 struct cache_inode *root;
183 struct cache_inode *inodes[HASH_LIST_SIZE];
184 struct cache_block *blocks[HASH_LIST_SIZE];
185 };
main()
598 int
599 main(int argc, char **argv) {
600 static_check();
601 if (argc != 3) {
602 bug("usage: <input *.img> <input dirname>\n");
603 }
604 const char *imgname = argv[1], *home = argv[2];
605 if (create_img(open_img(imgname), home) != 0) {
606 bug("create img failed.\n");
607 }
608 printf("create %s (%s) successfully.\n", imgname, home);
609 return 0;
610 }
open_img()
370 struct sfs_fs *
371 open_img(const char *imgname) {
# 检查文件名称
372 const char *expect = ".img", *ext = imgname + strlen(imgname) - strlen(expect);
373 if (ext <= imgname || strcmp(ext, expect) != 0) {
374 bug("invalid .img file name '%s'.\n", imgname);
375 }
376 int imgfd;
# 打开sfs.img,注意此时的sfs.img是个空文件,里面全是0
# 这个open是linux的,不是ucore的
377 if ((imgfd = open(imgname, O_WRONLY)) < 0) {
378 bug("open '%s' failed.\n", imgname);
379 }
# 创建sfs_fs并将其初始化
380 return create_sfs(imgfd);
381 }
create_sfs()
# 文件系统布局:
(注意,通常以“扇区”512B为单位,ucore直接以“block”4K为单位)
|superblock|rootdir inode| freemap |inods/file data/Dir data blocks...|
1个block 1个block 位图区域
是个inode 1位代表1块
242 struct sfs_fs *
243 create_sfs(int imgfd) {
# 每个block是4096B
# ninos:sfs.img共有多少个blocks
# next_ino:sfs.img中superblock、rootdir-inode和位图总共占用多少个blocks
244 uint32_t ninos, next_ino;
# 注意,这里的struct stat并不是ucore里面的,而是linux原生的,可以用man查看
# 获取sys.img文件的文件状态
245 struct stat *stat = safe_fstat(imgfd);
# stat->st_size是以字节为单位的文件大小
# SFS_BLKSIZE 4096
# SFS_MAX_NBLKS (1024UL * 512)
# ninos就是sfs.img有多少个4096B,不能超过512K个,而512K * 4096B = 2GB
# ninos涉及的总量以2GB为上限
246 if ((ninos = stat->st_size / SFS_BLKSIZE) > SFS_MAX_NBLKS) {
# 如果大于2GB,则让总量就是2GB
247 ninos = SFS_MAX_NBLKS;
248 warn("img file is too big (%llu bytes, only use %u blocks).\n",
249 (unsigned long long)stat->st_size, ninos);
250 }
# SFS_BLKN_FREEMAP 2
# SFS_BLKN_FREEMAP代表superblock和rootdir-inode占用的这两个blocks
# CHAR_BIT 8
# SFS_BLKBITS (SFS_BLKSIZE * CHAR_BIT)
# 在位图中,1位代表1个block,故1B代表8个blocks,4096B就是“4096*8=32768”个blocks,
# 如果位图占用n个blocks,那么位图就代表“n*4096*8”个blocks。
# SFS_BLKBITS代表1个block全部作为位图可以对应32768个blocks。
# “(ninos + SFS_BLKBITS - 1) / SFS_BLKBITS” 是用于
# 计算“位图”要占用多少个blocks
251 if ((next_ino = SFS_BLKN_FREEMAP + (ninos + SFS_BLKBITS - 1) / SFS_BLKBITS) >= ninos) {
252 bug("img file is too small (%llu bytes, %u blocks, bitmap use at least %u blocks).\n",
253 (unsigned long long)stat->st_size, ninos, next_ino - 2);
254 }
# 走到这里,就说明已经计算好了ninos和next_ino
# 即总块数和已经有明确用处的块数
255
256 struct sfs_fs *sfs = safe_malloc(sizeof(struct sfs_fs));
# 初始化超级块
# super.blocks记录sfs.img硬盘中所有的blocks数量
# super.unused_blocks的初始值为ninos - next_ino
257 sfs->super.magic = SFS_MAGIC;
258 sfs->super.blocks = ninos, sfs->super.unused_blocks = ninos - next_ino;
259 snprintf(sfs->super.info, SFS_MAX_INFO_LEN, "simple file system");
260
# sfs->ninos记录sfs.img的总块数
# sfs->next_ino记录sfs.img已经用了的块数
# imgfd是sfs.img的文件描述符
261 sfs->ninos = ninos, sfs->next_ino = next_ino, sfs->imgfd = imgfd;
262 sfs->sp_root = sfs->sp_end = &(sfs->__sp_nil);
263 sfs->sp_end->prev = sfs->sp_end->next = NULL;
264
265 int i;
266 for (i = 0; i < HASH_LIST_SIZE; i ++) {
# 初始化作为inode的blocks和存放数据的blocks
267 sfs->inodes[i] = NULL;
268 sfs->blocks[i] = NULL;
269 }
270
# 创建并初始化rootdir-inode,
# 注意,创建的是rootdir的inode,要和存放跟目录的数据的block-node做区分!!!
# 在ucore里面为了简单,每个inode占用一个block
271 sfs->root = alloc_cache_inode(sfs, 0, SFS_BLKN_ROOT, SFS_TYPE_DIR);
272 return sfs;
273 }
safe_fstat()
104 struct stat *
105 safe_fstat(int fd) {
# 这个struct stat是linux原生的,具体内部成员见图片
106 static struct stat __stat;
# 这个fstat()并不是ucore自带的,而是linux原生的,可用man fstat查看,内容大致如下,
# stat() and fstatat() retrieve information about the file pointed to by pathname
# ......
# fstat() is identical to stat(), except that the file about which information
# is to be retrieved is specified by the file descriptor fd.
107 if (fstat(fd, &__stat) != 0) {
108 bug("fstat %d failed.\n", fd);
109 }
110 return &__stat;
111 }
alloc_cache_inode()
# sfs->root = alloc_cache_inode(sfs, 0, SFS_BLKN_ROOT, SFS_TYPE_DIR);
220 static struct cache_inode *
221 alloc_cache_inode(struct sfs_fs *sfs, ino_t real, uint32_t ino, uint16_t type) {
222 struct cache_inode *ci = safe_malloc(sizeof(struct cache_inode));
223 ci->ino = (ino != 0) ? ino : sfs_alloc_ino(sfs);
224 ci->real = real, ci->nblks = 0, ci->l1 = ci->l2 = NULL;
225 struct inode *inode = &(ci->inode);
226 memset(inode, 0, sizeof(struct inode));
227 inode->type = type;
228 struct cache_inode **head = sfs->inodes + hash64(real);
229 ci->hash_next = *head, *head = ci;
230 return ci;
231 }
create_img()
563 int
564 create_img(struct sfs_fs *sfs, const char *home) {
565 int curfd, homefd;
# curfd应该是当前的bin目录
566 if ((curfd = open(".", O_RDONLY)) < 0) {
567 bug("get current fd failed.\n");
568 }
# homefd应该是disk0目录
569 if ((homefd = open(home, O_RDONLY | O_NOFOLLOW)) < 0) {
570 bug("open home directory '%s' failed.\n", home);
571 }
# chdir() changes the current working directory of the calling
# process to the directory specified in path.fchdir() is identical
# to chdir(); the only difference is that the directory is given as
# an open file descriptor.
# 将mksfs的工作目录从bin改为disk0
572 safe_fchdir(homefd);
573 open_dir(sfs, sfs->root, sfs->root);
574 safe_fchdir(curfd);
575 close(curfd), close(homefd);
576 close_sfs(sfs);
577 return 0;
578 }
总结
这部分代码就看到这里,不细研究了。ino号是按照blocks顺序排列的,从0开始;写block的时候都是一写就一整个block,一个目录entry就占用一整个block。
ucore 文件系统总体介绍
操作系统中负责管理和存储可长期保存数据的软件功能模块称为文件系统。在本次试验中,主要侧重文件系统的设计实现和对文件系统执行流程的分析与理解。
ucore的文件系统模型源于Havard的OS161的文件系统和Linux文件系统。但其实这二者都是源于传统的UNIX文件系统设计。UNIX提出了四个文件系统抽象概念:文件(file)、目录项(dentry)、索引节点(inode)和安装点(mount point)。
- 文件:UNIX文件中的内容可理解为是一有序字节buffer,文件都有一个方便应用程序识别的文件名称(也称文件路径名)。典型的文件操作有读、写、创建和删除等。
- 目录项:目录项不是目录,而是目录的组成部分。在UNIX中目录被看作一种特定的文件,而目录项是文件路径中的一部分。如一个文件路径名是“/test/testfile”,则包含的目录项为:根目录“/”,目录“test”和文件“testfile”,这三个都是目录项。一般而言,目录项包含目录项的名字(文件名或目录名)和目录项的索引节点(见下面的描述)位置。
- 索引节点:UNIX将文件的相关元数据信息(如访问控制权限、大小、拥有者、创建时间、数据内容等等信息)存储在一个单独的数据结构中,该结构被称为索引节点。
- 安装点:在UNIX中,文件系统被安装在一个特定的文件路径位置,这个位置就是安装点。所有的已安装文件系统都作为根文件系统树中的叶子出现在系统中。
上述抽象概念形成了UNIX文件系统的逻辑数据结构,并需要通过一个具体文件系统的架构设计与实现把上述信息映射并储存到磁盘介质上。一个具体的文件系统需要在磁盘布局中实现上述抽象概念。比如文件元数据信息存储在磁盘块中的索引节点上。当文件被载入内存时,内核需要使用磁盘块中的索引点来构造内存中的索引节点。
ucore模仿了UNIX的文件系统设计,ucore的文件系统架构主要由四部分组成:
- 通用文件系统访问接口层:该层提供了一个从用户空间到文件系统的标准访问接口。这一层访问接口让应用程序能够通过一个简单的接口获得ucore内核的文件系统服务。
- 文件系统抽象层:向上提供一个一致的接口给内核其他部分(文件系统相关的系统调用实现模块和其他内核功能模块)访问;向下提供一个同样的抽象函数指针列表和数据结构屏蔽不同文件系统的实现细节。
- Simple FS文件系统层:一个基于索引方式的简单文件系统实例。向上通过各种具体函数实现以对应文件系统抽象层提出的抽象函数。向下访问外设接口。
- 外设接口层:向上提供device访问接口屏蔽不同硬件细节。向下实现访问各种具体设备驱动的接口,比如disk设备接口/串口设备接口/键盘设备接口等。
对照上面的层次我们再大致介绍一下文件系统的访问处理过程,加深对文件系统的总体理解。假如应用程序操作文件(打开/创建/删除/读写),首先需要通过文件系统的通用文件系统访问接口层给用户空间提供的访问接口进入文件系统内部,接着由文件系统抽象层把访问请求转发给某一具体文件系统(比如SFS文件系统),具体文件系统(Simple FS文件系统层)把应用程序的访问请求转化为对磁盘上的block的处理请求,并通过外设接口层交给磁盘驱动例程来完成具体的磁盘操作。结合用户态写文件函数write的整个执行过程,我们可以比较清楚地看出ucore文件系统架构的层次和依赖关系。
Simple FS 文件系统
关键数据结构
struct fs
# 该结构体用于描述通用的文件系统,是各种文件系统的通用抽象。
12 /*
13 * Abstract filesystem. (Or device accessible as a file.)
14 *
15 * Information:
16 * fs_info : filesystem-specific data (sfs_fs)
17 * fs_type : filesystem type
18 * Operations:
19 *
20 * fs_sync - Flush all dirty buffers to disk.
21 * fs_get_root - Return root inode of filesystem.
22 * fs_unmount - Attempt unmount of filesystem.
23 * fs_cleanup - Cleanup of filesystem.???
24 *
25 *
26 * fs_get_root should increment the refcount of the inode returned.
27 * It should not ever return NULL.
28 *
29 * If fs_unmount returns an error, the filesystem stays mounted, and
30 * consequently the struct fs instance should remain valid. On success,
31 * however, the filesystem object and all storage associated with the
32 * filesystem should have been discarded/released.
33 *
34 */
35 struct fs {
36 union {
# ucore当前支持的文件系统,可见就这一个
37 struct sfs_fs __sfs_info;
38 } fs_info; // filesystem-specific data
39 enum {
# ucore当前支持的文件系统类型,可见就这一个
40 fs_type_sfs_info,
41 } fs_type; // filesystem type
42 int (*fs_sync)(struct fs *fs); // Flush all dirty buffers to disk
43 struct inode *(*fs_get_root)(struct fs *fs); // Return root inode of filesystem.
44 int (*fs_unmount)(struct fs *fs); // Attempt unmount of filesystem.
45 void (*fs_cleanup)(struct fs *fs); // Cleanup of filesystem.???
46 };
struct sfs_fs
# 相比struct fs作为通用文件系统的抽象,这个是sfs文件系统自己的专属描述
82 /* filesystem for sfs */
83 struct sfs_fs {
84 struct sfs_super super; /* on-disk superblock */
85 struct device *dev; /* device mounted on */
86 struct bitmap *freemap; /* blocks in use are mared 0 */
87 bool super_dirty; /* true if super/freemap modified */
88 void *sfs_buffer; /* buffer for non-block aligned io */
89 semaphore_t fs_sem; /* semaphore for fs */
90 semaphore_t io_sem; /* semaphore for io */
91 semaphore_t mutex_sem; /* semaphore for link/unlink and rename */
92 list_entry_t inode_list; /* inode linked-list */
# 从磁盘中取出的inode都会被挂到这个哈希链上,从而常驻内存,以便后续查找。
# 每次找的时候,都是先从这个哈希链上找,如果没有,再从磁盘读
93 list_entry_t *hash_list; /* inode hash linked-list */
94 };
struct sfs_super
37 /*
38 * On-disk superblock
39 */
40 struct sfs_super {
41 uint32_t magic; /* magic number, should be SFS_MAGIC */
42 uint32_t blocks; /* # of blocks in fs */
43 uint32_t unused_blocks; /* # of unused blocks in fs */
44 char info[SFS_MAX_INFO_LEN + 1]; /* infomation for sfs */
45 };
可以看到,包含一个成员变量魔数magic,其值为0x2f8dbe2a,内核通过它来检查磁盘镜像是否是合法的 sfs.img
;成员变量blocks记录了SFS中所有block的数量,即 img 的大小;成员变量unused_block记录了SFS中还没有被使用的block的数量;成员变量info包含了字符串"simple file system"。
struct bitmap
11 struct bitmap {
12 uint32_t nbits;
13 uint32_t nwords;
14 WORD_TYPE *map;
15 };
struct sfs_disk_inode 磁盘索引节点
SFS中的磁盘索引节点代表了一个实际位于磁盘上的文件。首先我们看看在硬盘上的索引节点的内容:
# 和mksfs.c里面的 struct cache_node里面的struct inode是完全对应的。
# 这样就和disk0硬盘里面自己的数据结构对应上了。
47 /* inode (on disk) */
48 struct sfs_disk_inode {
# 如果inode表示常规文件,则size是文件大小
49 uint32_t size; /* size of the file (in bytes) */
# inode的文件类型
50 uint16_t type; /* one of SYS_TYPE_* above */
# 此inode的硬链接数
51 uint16_t nlinks; /* # of hard links to this file */
# 此inode的数据块数的个数
52 uint32_t blocks; /* # of blocks */
# 此inode的直接数据块索引值(有SFS_NDIRECT个)
53 uint32_t direct[SFS_NDIRECT]; /* direct blocks */
# 此inode的一级间接数据块索引值
54 uint32_t indirect; /* indirect blocks */
55 // uint32_t db_indirect; /* double indirect blocks */
56 // unused
57 };
通过上表可以看出,如果inode表示的是文件,则成员变量direct[]
直接指向了保存文件内容数据的数据块索引值。indirect
间接指向了保存文件内容数据的数据块,indirect
指向的是间接数据块(indirect block
),此数据块实际存放的全部是数据块索引,这些数据块索引指向的数据块才被用来存放文件内容数据。
默认的,ucore 里 SFS_NDIRECT 是 12,即直接索引的数据页大小为 12 * 4k = 48k;当使用一级间接数据块索引时,ucore 支持最大的文件大小为 12 * 4k + 1024 * 4k = 48k + 4m。数据索引表内,0 表示一个无效的索引,inode 里 blocks 表示该文件或者目录占用的磁盘的 block 的个数。indiret
为 0 时,表示不使用一级索引块。(因为 block 0 用来保存 super block,它不可能被其他任何文件或目录使用,所以这么设计也是合理的)。
struct sfs_disk_entry
对于普通文件,索引值指向的 block 中保存的是文件中的数据。而对于目录,索引值指向的数据保存的是目录下所有的文件名以及对应的索引节点所在的索引块(磁盘块)所形成的数组。数据结构如下:
/* file entry (on disk) */
struct sfs_disk_entry {
uint32_t ino; 索引节点所占数据块索引值
char name[SFS_MAX_FNAME_LEN + 1]; 文件名
};
操作系统中,每个文件系统下的 inode 都应该分配唯一的 inode 编号。SFS 下,为了实现的简便(偷懒),每个 inode 直接用他所在的磁盘 block 的编号作为 inode 编号。比如,root block 的 inode 编号为 1;每个 sfs_disk_entry 数据结构中,name 表示目录下文件或文件夹的名称,ino 表示磁盘 block 编号,通过读取该 block 的数据,能够得到相应的文件或文件夹的 inode。ino 为0时,表示一个无效的 entry。此外,和 inode 相似,每个 sfs_dirent_entry 也占用一个 block。
struct sfs_inode 位于内存,专属sfs文件系统的索引节点
68 /* inode for sfs */
69 struct sfs_inode {
70 struct sfs_disk_inode *din; /* on-disk inode */
71 uint32_t ino; /* inode number */
72 bool dirty; /* true if inode modified */
73 int reclaim_count; /* kill inode if it hits zero */
74 semaphore_t sem; /* semaphore for din */
75 list_entry_t inode_link; /* entry for linked-list in sfs_fs */
76 list_entry_t hash_link; /* entry for hash linked-list in sfs_fs */
77 };
可以看到SFS中的内存inode包含了SFS的硬盘inode信息,而且还增加了其他一些信息,这属于是便于进行是判断否改写、互斥操作、回收和快速地定位等作用。需要注意,一个内存inode是在打开一个文件后才创建的,如果关机则相关信息都会消失,而硬盘inode的内容是保存在硬盘中的,只是在进程需要时才被读入到内存中,用于访问文件或目录的具体内容数据。
为了方便实现上面提到的多级数据的访问以及目录中 entry 的操作,对 inode SFS实现了一些辅助的函数:
- sfs_bmap_load_nolock:将对应 sfs_inode 的第 index 个索引指向的 block 的索引值取出存到相应的指针指向的单元(ino_store)。该函数只接受 index <= inode->blocks 的参数。当 index == inode->blocks 时,该函数理解为需要为 inode 增长一个 block。并标记 inode 为 dirty(所有对 inode 数据的修改都要做这样的操作,这样,当 inode 不再使用的时候,sfs 能够保证 inode 数据能够被写回到磁盘)。sfs_bmap_load_nolock 调用的 sfs_bmap_get_nolock 来完成相应的操作,阅读 sfs_bmap_get_nolock,了解他是如何工作的。(sfs_bmap_get_nolock 只由 sfs_bmap_load_nolock 调用)
- sfs_bmap_truncate_nolock:将多级数据索引表的最后一个 entry 释放掉。他可以认为是 sfs_bmap_load_nolock 中,index == inode->blocks 的逆操作。当一个文件或目录被删除时,sfs 会循环调用该函数直到 inode->blocks 减为 0,释放所有的数据页。函数通过 sfs_bmap_free_nolock 来实现,他应该是 sfs_bmap_get_nolock 的逆操作。和 sfs_bmap_get_nolock 一样,调用 sfs_bmap_free_nolock 也要格外小心。
- sfs_dirent_read_nolock:将目录的第 slot 个 entry 读取到指定的内存空间。他通过上面提到的函数来完成。
- sfs_dirent_write_nolock:用指定的 entry 来替换某个目录下的第 slot 个entry。他通过调用 sfs_bmap_load_nolock保证,当第 slot 个entry 不存在时(slot == inode->blocks),SFS 会分配一个新的entry,即在目录尾添加了一个 entry。
- sfs_dirent_search_nolock:是常用的查找函数。他在目录下查找 name,并且返回相应的搜索结果(文件或文件夹)的 inode 的编号(也是磁盘编号),和相应的 entry 在该目录的 index 编号以及目录下的数据页是否有空闲的 entry。(SFS 实现里文件的数据页是连续的,不存在任何空洞;而对于目录,数据页不是连续的,当某个 entry 删除的时候,SFS 通过设置 entry->ino 为0将该 entry 所在的 block 标记为 free,在需要添加新 entry 的时候,SFS 优先使用这些 free 的 entry,其次才会去在数据页尾追加新的 entry。
注意,这些后缀为 nolock 的函数,只能在已经获得相应 inode 的semaphore才能调用。
struct inode_ops sfs_node_fileops——Inode的文件操作函数
# 通过vop_init()--->inode_init()--->sfs_get_ops()
# 最终被挂到struct inode的in_ops上,以便被inode使用。
973 /// The sfs specific FILE operations correspond to the abstract operations on a inode.
974 static const struct inode_ops sfs_node_fileops = {
975 .vop_magic = VOP_MAGIC,
976 .vop_open = sfs_openfile,
977 .vop_close = sfs_close,
978 .vop_read = sfs_read,
979 .vop_write = sfs_write,
980 .vop_fstat = sfs_fstat,
981 .vop_fsync = sfs_fsync,
982 .vop_reclaim = sfs_reclaim,
983 .vop_gettype = sfs_gettype,
984 .vop_tryseek = sfs_tryseek,
985 .vop_truncate = sfs_truncfile,
986 };
上述sfs_openfile
、sfs_close
、sfs_read
和sfs_write
分别对应用户进程发出的open、close、read、write操作。其中sfs_openfile不用做什么事;sfs_close需要把对文件的修改内容写回到硬盘上,这样确保硬盘上的文件内容数据是最新的;sfs_read和sfs_write函数都调用了一个函数sfs_io,并最终通过访问硬盘驱动来完成对文件内容数据的读写。
struct inode_ops sfs_node_dirops——Inode的目录操作函数
# 通过vop_init()--->inode_init()--->sfs_get_ops()
# 最终被挂到struct inode的in_ops上,以便被inode使用。
960 // The sfs specific DIR operations correspond to the abstract operations on a inode.
961 static const struct inode_ops sfs_node_dirops = {
962 .vop_magic = VOP_MAGIC,
963 .vop_open = sfs_opendir,
964 .vop_close = sfs_close,
965 .vop_fstat = sfs_fstat,
966 .vop_fsync = sfs_fsync,
967 .vop_namefile = sfs_namefile,
968 .vop_getdirentry = sfs_getdirentry,
969 .vop_reclaim = sfs_reclaim,
970 .vop_gettype = sfs_gettype,
971 .vop_lookup = sfs_lookup,
972 };
对于目录操作而言,由于目录也是一种文件,所以sfs_opendir、sys_close对应户进程发出的open、close函数。相对于sfs_open,sfs_opendir只是完成一些open函数传递的参数判断,没做其他更多的事情。目录的close操作与文件的close操作完全一致。由于目录的内容数据与文件的内容数据不同,所以读出目录的内容数据的函数是sfs_getdirentry,其主要工作是获取目录下的文件inode信息。
sfs_init()
# 在disk0目录上挂载sfs文件系统
6 /*
7 * sfs_init - mount sfs on disk0
8 *
9 * CALL GRAPH:
10 * kern_init-->fs_init-->sfs_init
11 */
12 void
13 sfs_init(void) {
14 int ret;
15 if ((ret = sfs_mount("disk0")) != 0) {
16 panic("failed: sfs: sfs_mount: %e.\n", ret);
17 }
18 }
sfs_mount()
254 int
255 sfs_mount(const char *devname) {
256 return vfs_mount(devname, sfs_do_mount);
257 }
vfs_mount()
# devname: disk0
# mountfunc: sfs_do_mount
217 /*
218 * vfs_mount - Mount a filesystem. Once we've found the device, call MOUNTFUNC to
219 * set up the filesystem and hand back a struct fs.
220 *
221 * The DATA argument is passed through unchanged to MOUNTFUNC.
222 */
223 int
224 vfs_mount(const char *devname, int (*mountfunc)(struct device *dev, struct fs **fs_store)) {
225 int ret;
226 lock_vdev_list();
227 vfs_dev_t *vdev;
# 搜索vdev_list,找到当初dev_init()的时候已经初始化好
# 的disk0磁盘设备inode的vfs_dev_t外壳。
228 if ((ret = find_mount(devname, &vdev)) != 0) {
229 goto out;
230 }
# 当初dev_init()初始化的时候,vdev->fs被初始化为NULL
231 if (vdev->fs != NULL) {
232 ret = -E_BUSY;
233 goto out;
234 }
235 assert(vdev->devname != NULL && vdev->mountable);
236
# 指向disk0的inode的__device_info,拿到disk0的设备信息
237 struct device *dev = vop_info(vdev->devnode, device);
# 调用sfs_do_mount(),将disk0挂载到当前系统
238 if ((ret = mountfunc(dev, &(vdev->fs))) == 0) {
239 assert(vdev->fs != NULL);
240 cprintf("vfs: mount %s.\n", vdev->devname);
241 }
242
243 out:
244 unlock_vdev_list();
245 return ret;
246 }
sfs_do_mount()
142 /*
143 * sfs_do_mount - mount sfs file system.
144 *
145 * @dev: the block device contains sfs file system
146 * @fs_store: the fs struct in memroy
147 */
148 static int
149 sfs_do_mount(struct device *dev, struct fs **fs_store) {
150 static_assert(SFS_BLKSIZE >= sizeof(struct sfs_super));
151 static_assert(SFS_BLKSIZE >= sizeof(struct sfs_disk_inode));
152 static_assert(SFS_BLKSIZE >= sizeof(struct sfs_disk_entry));
153
154 if (dev->d_blocksize != SFS_BLKSIZE) {
155 return -E_NA_DEV;
156 }
157
158 /* allocate fs structure */
# 并将文件系统类型设置为fs_type_sfs_info
159 struct fs *fs;
160 if ((fs = alloc_fs(sfs)) == NULL) {
161 return -E_NO_MEM;
162 }
# 指向fs中的sfs文件系统结构体
163 struct sfs_fs *sfs = fsop_info(fs, sfs);
# 让sfs指向当前的设备
164 sfs->dev = dev;
165
166 int ret = -E_NO_MEM;
167
168 void *sfs_buffer;
# 给sfs上的缓存开辟空间
169 if ((sfs->sfs_buffer = sfs_buffer = kmalloc(SFS_BLKSIZE)) == NULL) {
170 goto failed_cleanup_fs;
171 }
172
173 /* load and check superblock */
# 最终调用disk0_io(),从disk0中读取数据到sfs_buffer(也是sfs->sfs_buffer),
# 读的是disk0的第0块,即超级块,读了一整个block
# 这里就是将超级块读进来
174 if ((ret = sfs_init_read(dev, SFS_BLKN_SUPER, sfs_buffer)) != 0) {
175 goto failed_cleanup_sfs_buffer;
176 }
177
178 ret = -E_INVAL;
179
# 校验超级块
180 struct sfs_super *super = sfs_buffer;
181 if (super->magic != SFS_MAGIC) {
182 cprintf("sfs: wrong magic in superblock. (%08x should be %08x).\n",
183 super->magic, SFS_MAGIC);
184 goto failed_cleanup_sfs_buffer;
185 }
186 if (super->blocks > dev->d_blocks) {
187 cprintf("sfs: fs has %u blocks, device has %u blocks.\n",
188 super->blocks, dev->d_blocks);
189 goto failed_cleanup_sfs_buffer;
190 }
191 super->info[SFS_MAX_INFO_LEN] = '\0';
# 将超级块挂到sfs上,注意,此时超级块使用的内存仍然是sfs->sfs_buffer
192 sfs->super = *super;
193
194 ret = -E_NO_MEM;
195
196 uint32_t i;
197
198 /* alloc and initialize hash list */
199 list_entry_t *hash_list;
200 if ((sfs->hash_list = hash_list = kmalloc(sizeof(list_entry_t) * SFS_HLIST_SIZE)) == NULL) {
201 goto failed_cleanup_sfs_buffer;
202 }
203 for (i = 0; i < SFS_HLIST_SIZE; i ++) {
204 list_init(hash_list + i);
205 }
206
207 /* load and check freemap */
208 struct bitmap *freemap;
209 uint32_t freemap_size_nbits = sfs_freemap_bits(super);
# 创建用于描述位图的结构体,里面会新开辟空间
210 if ((sfs->freemap = freemap = bitmap_create(freemap_size_nbits)) == NULL) {
211 goto failed_cleanup_hash_list;
212 }
213 uint32_t freemap_size_nblks = sfs_freemap_blocks(super);
# 将disk0磁盘中的位图数据读到freemap->map中。
214 if ((ret = sfs_init_freemap(dev, freemap, SFS_BLKN_FREEMAP, freemap_size_nblks, sfs_buffer)) != 0) {
215 goto failed_cleanup_freemap;
216 }
217
# 根据位图,统计当前没有使用的块数
218 uint32_t blocks = sfs->super.blocks, unused_blocks = 0;
219 for (i = 0; i < freemap_size_nbits; i ++) {
220 if (bitmap_test(freemap, i)) {
221 unused_blocks ++;
222 }
223 }
224 assert(unused_blocks == sfs->super.unused_blocks);
225
226 /* and other fields */
227 sfs->super_dirty = 0;
228 sem_init(&(sfs->fs_sem), 1);
229 sem_init(&(sfs->io_sem), 1);
230 sem_init(&(sfs->mutex_sem), 1);
231 list_init(&(sfs->inode_list));
232 cprintf("sfs: mount: '%s' (%d/%d/%d)\n", sfs->super.info,
233 blocks - unused_blocks, unused_blocks, blocks);
234
235 /* link addr of sync/get_root/unmount/cleanup funciton fs's function pointers*/
# 将sfs自己专属的访问操作实现挂到通用文件系统结构体上,从而让
# fs这个抽象(通用)的结构可以使用sfs文件系统。
236 fs->fs_sync = sfs_sync;
237 fs->fs_get_root = sfs_get_root;
238 fs->fs_unmount = sfs_unmount;
239 fs->fs_cleanup = sfs_cleanup;
# 将这个通用的、现在已经被指定为sfs文件系统的fs挂到vdev->fs上
240 *fs_store = fs;
# 走到这里,就相当于将sfs文件系统挂载到了ucore,使用fs这个通用结构来访问。
# 挂载的结果:开辟好fs结构体,里面有了对应sfs文件系统的结构体sfs,初始化好这些
# 结构体,从硬盘读取到超级块并将其挂到sfs上,从硬盘读取位图并将其挂到sfs上,初始化
# 好信号量和链表,初始化好sfs文件系统专用的几个函数指针到fs结构体上。
241 return 0;
242
243 failed_cleanup_freemap:
244 bitmap_destroy(freemap);
245 failed_cleanup_hash_list:
246 kfree(hash_list);
247 failed_cleanup_sfs_buffer:
248 kfree(sfs_buffer);
249 failed_cleanup_fs:
250 kfree(fs);
251 return ret;
252 }
sfs_load_inode()
150 /*
151 * sfs_load_inode - If the inode isn't existed, load inode related ino disk block data into a new created inode.
152 * If the inode is in memory alreadily, then do nothing
153 */
154 int
155 sfs_load_inode(struct sfs_fs *sfs, struct inode **node_store, uint32_t ino) {
156 lock_sfs_fs(sfs);
157 struct inode *node;
# 查当前已经挂在sfs_fs文件系统结构体上的inode是不是有正要找的,如果没有,再创建并初始化
158 if ((node = lookup_sfs_nolock(sfs, ino)) != NULL) {
159 goto out_unlock;
160 }
161
162 int ret = -E_NO_MEM;
# 创建和磁盘上的inode完全对应的结构体,以便将磁盘上的inode读到内存
163 struct sfs_disk_inode *din;
164 if ((din = kmalloc(sizeof(struct sfs_disk_inode))) == NULL) {
165 goto failed_unlock;
166 }
167
168 assert(sfs_block_inuse(sfs, ino));
# 将磁盘上对应的inode完整读入内存
169 if ((ret = sfs_rbuf(sfs, din, sizeof(struct sfs_disk_inode), ino, 0)) != 0) {
170 goto failed_cleanup_din;
171 }
172
173 assert(din->nlinks != 0);
# 磁盘上的inode结构体不便于直接在内存使用,所以还要再专门创建一个可以在内存使用的
# inode结构体,并将磁盘上的inode结构体挂上去。
174 if ((ret = sfs_create_inode(sfs, din, ino, &node)) != 0) {
175 goto failed_cleanup_din;
176 }
177 sfs_set_links(sfs, vop_info(node, sfs_inode));
178
179 out_unlock:
180 unlock_sfs_fs(sfs);
181 *node_store = node;
182 return 0;
183
184 failed_cleanup_din:
185 kfree(din);
186 failed_unlock:
187 unlock_sfs_fs(sfs);
188 return ret;
189 }
sfs_create_inode()
111 /*
112 * sfs_create_inode - alloc a inode in memroy, and init din/ino/dirty/reclian_count/sem fields in sfs_inode in inode
113 */
114 static int
115 sfs_create_inode(struct sfs_fs *sfs, struct sfs_disk_inode *din, uint32_t ino, struct inode **node_store) {
116 struct inode *node;
117 if ((node = alloc_inode(sfs_inode)) != NULL) {
# 通过vop_init()宏将node进行初步初始化,将sfs文件系统的inode处理函数都挂上去
# 这些处理函数根据“文件”或“目录”的不同而不同,
# 详细参见:
# struct inode_ops sfs_node_dirops
# struct inode_ops sfs_node_fileops
118 vop_init(node, sfs_get_ops(din->type), info2fs(sfs, sfs));
# 初始化struct inode上面的struct sfs_inode结构体
119 struct sfs_inode *sin = vop_info(node, sfs_inode);
120 sin->din = din, sin->ino = ino, sin->dirty = 0, sin->reclaim_count = 1;
121 sem_init(&(sin->sem), 1);
# 让调用者得到新创建的struct inode
122 *node_store = node;
123 return 0;
124 }
125 return -E_NO_MEM;
126 }
文件系统抽象层 - VFS
文件系统抽象层是把不同文件系统的对外共性接口提取出来,形成一个函数指针数组,这样,通用文件系统访问接口层只需访问文件系统抽象层,而不需关心具体文件系统的实现细节和接口。
file & dir接口
file&dir接口层定义了进程在内核中直接访问的文件相关信息,这定义在file数据结构中,具体描述如下:
14 struct file {
15 enum {
16 FD_NONE, FD_INIT, FD_OPENED, FD_CLOSED,
17 } status; //访问文件的执行状态
18 bool readable; //文件是否可读
19 bool writable; //文件是否可写
20 int fd; //文件在filemap中的索引值,这就是文件描述符
21 off_t pos; //访问文件的当前位置
22 struct inode *node; //该文件对应的内存inode指针,抽象的struct file对应的struct inode
23 int open_count; //打开此文件的次数
24 };
而在kern/process/proc.h中的proc_struct结构中描述了进程访问文件的数据接口fs_struct,其数据结构定义如下:
struct fs_struct {
struct inode *pwd; //进程当前执行目录的内存inode指针
struct file *filemap; //进程打开文件的数组
atomic_t fs_count; //访问此文件的线程个数??
semaphore_t fs_sem; //确保对进程控制块中fs_struct的互斥访问
};
当创建一个进程后,该进程的fs_struct将会被初始化或复制父进程的fs_struct。当用户进程打开一个文件时,将从filemap数组中取得一个空闲file项,然后会把此file的成员变量node指针指向一个代表此文件的inode的起始地址。
struct inode 接口
index node是位于内存的索引节点,它是VFS结构中的重要数据结构,因为它实际负责把不同文件系统的特定索引节点信息(甚至不能算是一个索引节点)统一封装起来,避免了进程直接访问具体文件系统。其定义如下:
13 /*
14 * A struct inode is an abstract representation of a file.
15 *
16 * It is an interface that allows the kernel's filesystem-independent
17 * code to interact usefully with multiple sets of filesystem code.
18 */
19
20 /*
21 * Abstract low-level file.
22 *
23 * Note: in_info is Filesystem-specific data, in_type is the inode type
24 *
25 * open_count is managed using VOP_INCOPEN and VOP_DECOPEN by
26 * vfs_open() and vfs_close(). Code above the VFS layer should not
27 * need to worry about it.
28 */
29 struct inode {
30 union { //包含不同文件系统特定inode信息的union成员变量
31 struct device __device_info; //设备文件系统内存inode信息
32 struct sfs_inode __sfs_inode_info; //SFS文件系统内存inode信息
33 } in_info;
34 enum {
35 inode_type_device_info = 0x1234,
36 inode_type_sfs_inode_info,
37 } in_type; //此inode所属文件系统类型
38 int ref_count; //此inode的引用计数
39 int open_count; //打开此inode对应文件的个数
40 struct fs *in_fs; //抽象的文件系统,包含访问文件系统的函数指针
41 const struct inode_ops *in_ops; //抽象的inode操作,包含访问inode的函数指针
42 };
设备层文件 IO 层
在本实验中,为了统一地访问设备,我们可以把一个设备看成一个文件,通过访问文件的接口来访问设备。目前实现了stdin设备文件文件、stdout设备文件、disk0设备。stdin设备就是键盘,stdout设备就是CONSOLE(串口、并口和文本显示器),而disk0设备是承载SFS文件系统的磁盘设备。下面我们逐一分析ucore是如何让用户把设备看成文件来访问。
关键数据结构
struct device
为了表示一个设备,需要有对应的数据结构,ucore为此定义了struct device,其描述如下:
9 /*
10 * Filesystem-namespace-accessible device.
11 * d_io is for both reads and writes; the iobuf will indicates the direction.
12 */
13 struct device {
14 size_t d_blocks; // 设备占用的数据块个数
15 size_t d_blocksize; // 数据块的大小
//打开设备的函数指针
16 int (*d_open)(struct device *dev, uint32_t open_flags);
//关闭设备的函数指针
17 int (*d_close)(struct device *dev);
//读写设备的函数指针
18 int (*d_io)(struct device *dev, struct iobuf *iob, bool write);
//用ioctl方式控制设备的函数指针
19 int (*d_ioctl)(struct device *dev, int op, void *data);
20 };
这个数据结构能够支持对块设备(比如磁盘)、字符设备(比如键盘、串口)的表示,完成对设备的基本操作。
设备链表vdev_list
ucore虚拟文件系统为了把这些设备链接在一起,还定义了一个设备链表,即双向链表vdev_list
,这样通过访问此链表,可以找到ucore能够访问的所有设备文件。
struct vfs_dev_t
inode上面本身就挂着device,但是由于inode还可以表示其他类型的文件,所以为了后面能够用vdev_list
将所有的设备inode都链起来,这里专门定义了vfs_dev_t
这个结构体,该结构体会挂上作为设备inode的inode,并最终链在vdev_list
上。
// 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;
利用vfs_dev_t
数据结构,就可以让文件系统通过一个链接vfs_dev_t
结构的双向链表找到作为设备文件的inode,一个inode节点的成员变量in_type的值是0x1234,则此 inode的成员变量in_info将成为一个device结构,这样inode就是一个设备文件的inode。
stdin 设备文件
这里的stdin设备文件实际上就是指的键盘。这个设备文件是一个只读设备,如果写这个设备,就会出错。
dev_init_stdin()初始化
113 void
114 dev_init_stdin(void) {
115 struct inode *node;
# 创建并设置好stdin使用的inode
116 if ((node = dev_create_inode()) == NULL) {
117 panic("stdin: dev_create_node.\n");
118 }
# 初始化stdin设备inode中的device结构体
119 stdin_device_init(vop_info(node, device));
120
121 int ret;
# 将“stdin”设备加到vdev_list设备链表
122 if ((ret = vfs_add_dev("stdin", node, 0)) != 0) {
123 panic("stdin: vfs_add_dev: %e.\n", ret);
124 }
125 }
相对于stdout的初始化过程,stdin的初始化相对复杂一些,多了一个stdin_buffer缓冲区,描述缓冲区读写位置的变量p_rpos、p_wpos以及用于等待缓冲区的等待队列wait_queue。在stdin_device_init函数的初始化中,也完成了对p_rpos、p_wpos和wait_queue的初始化。
dev_create_inode()
158 /* dev_create_inode - Create inode for a vfs-level device. */
159 struct inode *
160 dev_create_inode(void) {
161 struct inode *node;
# 创建了一个inode_type_device_info类型的inode
162 if ((node = alloc_inode(device)) != NULL) {
# 将该node的操作接口设置为dev_node_ops,但是对应的文件系统为NULL。
163 vop_init(node, &dev_node_ops, NULL);
164 }
165 return node;
166 }
#define alloc_inode(type) __alloc_inode(__in_type(type))
#define __in_type(type) inode_type_##type##_info
/* *
* __alloc_inode - alloc a inode structure and initialize in_type
* */
struct inode *
__alloc_inode(int type) {
# type 是 inode_type_device_info
# inode_type_device_info = 0x1234 是inode中的枚举
struct inode *node;
if ((node = kmalloc(sizeof(struct inode))) != NULL) {
# 此inode所属文件系统
node->in_type = type;
}
return node;
}
#define vop_init(node, ops, fs) inode_init(node, ops, fs)
/* *
* inode_init - initialize a inode structure
* invoked by vop_init
* */
void
inode_init(struct inode *node, const struct inode_ops *ops, struct fs *fs) {
node->ref_count = 0;
node->open_count = 0;
node->in_ops = ops, node->in_fs = fs;
# 让node下的引用计数加一
vop_ref_inc(node);
}
stdin_device_init()
stdin设备文件的初始化过程主要由stdin_device_init()
完成了主要的初始化工作,具体实现如下:
#define vop_info(node, type) __vop_info(node, type)
#define __vop_info(node, type) \
({ \
struct inode *__node = (node); \
assert(__node != NULL && check_inode_type(__node, type)); \
&(__node->in_info.__##type##_info); \
})
# 入参:node->in_info.__device_info,
# 即:struct device __device_info
# stdin_device_init(vop_info(node, device));
# 就是初始化node下面的__device_info
100 static void
101 stdin_device_init(struct device *dev) {
# 占用的数据块个数为0
102 dev->d_blocks = 0;
# 数据块大小为1
103 dev->d_blocksize = 1;
# 设置函数指针,用以操作stdin设备文件
104 dev->d_open = stdin_open;
105 dev->d_close = stdin_close;
106 dev->d_io = stdin_io;
107 dev->d_ioctl = stdin_ioctl;
108
109 p_rpos = p_wpos = 0;
110 wait_queue_init(wait_queue);
111 }
vfs_do_add()
# vfs_add_dev("stdin", node, 0)
133 /*
134 * vfs_do_add - Add a new device to the VFS layer's device table.
135 *
136 * If "mountable" is set, the device will be treated as one that expects
137 * to have a filesystem mounted on it, and a raw device will be created
138 * for direct access.
139 */
# 这里传入的mountable是0,所以没有要被挂载的文件系统,而且入参fs是NULL。
140 static int
141 vfs_do_add(const char *devname, struct inode *devnode, struct fs *fs, bool mountable) {
142 assert(devname != NULL);
143 assert((devnode == NULL && !mountable) || (devnode != NULL && check_inode_type(devnode, device)));
144 if (strlen(devname) > FS_MAX_DNAME_LEN) {
145 return -E_TOO_BIG;
146 }
147
148 int ret = -E_NO_MEM;
149 char *s_devname;
# 拷贝设备名称
150 if ((s_devname = strdup(devname)) == NULL) {
151 return ret;
152 }
153
# 该结构体会将作为设备文件的inode最终挂到设备链表vdev_list上。
154 vfs_dev_t *vdev;
155 if ((vdev = kmalloc(sizeof(vfs_dev_t))) == NULL) {
156 goto failed_cleanup_name;
157 }
158
159 ret = -E_EXISTS;
160 lock_vdev_list();
161 if (!check_devname_conflict(s_devname)) {
162 unlock_vdev_list();
163 goto failed_cleanup_vdev;
164 }
165 vdev->devname = s_devname;
166 vdev->devnode = devnode;
167 vdev->mountable = mountable;
168 vdev->fs = fs;
169
170 list_add(&vdev_list, &(vdev->vdev_link));
171 unlock_vdev_list();
172 return 0;
173
174 failed_cleanup_vdev:
175 kfree(vdev);
176 failed_cleanup_name:
177 kfree(s_devname);
178 return ret;
179 }
stdin_io()
函数负责完成设备的读操作工作,具体实现如下:
83 static int
84 stdin_io(struct device *dev, struct iobuf *iob, bool write) {
85 if (!write) {
86 int ret;
87 if ((ret = dev_stdin_read(iob->io_base, iob->io_resid)) > 0) {
88 iob->io_resid -= ret;
89 }
90 return ret;
91 }
92 return -E_INVAL;
93 }
可以看到,如果是写操作,则stdin_io函数直接报错返回。所以这也进一步说明了此设备文件是只读文件。如果是读操作,则此函数进一步调用dev_stdin_read
函数完成对键盘设备的读入操作。dev_stdin_read
函数的实现相对复杂一些,主要的流程如下:
39 static int
40 dev_stdin_read(char *buf, size_t len) {
41 int ret = 0;
42 bool intr_flag;
43 local_intr_save(intr_flag);
44 {
45 for (; ret < len; ret ++, p_rpos ++) {
46 try_again:
47 if (p_rpos < p_wpos) {
48 *buf ++ = stdin_buffer[p_rpos % STDIN_BUFSIZE];
49 }
50 else {
51 wait_t __wait, *wait = &__wait;
52 wait_current_set(wait_queue, wait, WT_KBD);
53 local_intr_restore(intr_flag);
54
55 schedule();
56
57 local_intr_save(intr_flag);
58 wait_current_del(wait_queue, wait);
59 if (wait->wakeup_flags == WT_KBD) {
60 goto try_again;
61 }
62 break;
63 }
64 }
65 }
66 local_intr_restore(intr_flag);
67 return ret;
68 }
在上述函数中可以看出,如果p_rpos < p_wpos,则表示有键盘输入的新字符在stdin_buffer
中,于是就从stdin_buffer
中取出新字符放到iobuf
指向的缓冲区中;如果p_rpos >=p_wpos,则表明没有新字符,这样调用read用户态库函数的用户进程就需要采用等待队列的睡眠操作进入睡眠状态,等待键盘输入字符的产生。
键盘输入字符后,如何唤醒等待键盘输入的用户进程呢?回顾lab1中的外设中断处理,可以了解到,当用户敲击键盘时,会产生键盘中断,在trap_dispatch函数中,当识别出中断是键盘中断(中断号为IRQ_OFFSET + IRQ_KBD
)时,会调用dev_stdin_write
函数,来把字符写入到stdin_buffer
中,且会通过等待队列的唤醒操作唤醒正在等待键盘输入的用户进程。
case IRQ_OFFSET + IRQ_KBD:
// There are user level shell in LAB8, so we need change COM/KBD interrupt processing.
# 通过串口获取输入字符
c = cons_getc();
{
extern void dev_stdin_write(char c);
# 将获取到的字符写到缓冲并唤醒相应进程
dev_stdin_write(c);
}
break;
21 void
22 dev_stdin_write(char c) {
23 bool intr_flag;
24 if (c != '\0') {
25 local_intr_save(intr_flag);
26 {
27 stdin_buffer[p_wpos % STDIN_BUFSIZE] = c;
28 if (p_wpos - p_rpos < STDIN_BUFSIZE) {
29 p_wpos ++;
30 }
31 if (!wait_queue_empty(wait_queue)) {
32 wakeup_queue(wait_queue, WT_KBD, 1);
33 }
34 }
35 local_intr_restore(intr_flag);
36 }
37 }
stdout设备文件
既然stdout设备是设备文件系统的文件,自然有自己的inode结构。在系统初始化时,即只需如下处理过程
kern_init-->fs_init-->dev_init-->dev_init_stdout --> dev_create_inode
--> stdout_device_init
--> vfs_add_dev
在dev_init_stdout中完成了对stdout设备文件的初始化。即首先创建了一个inode,然后通过stdout_device_init完成对inode中的成员变量inode->__device_info进行初始化:这里的stdout设备文件实际上就是指的console外设(它其实是串口、并口和CGA的组合型外设)。这个设备文件是一个只写设备,如果读这个设备,就会出错。接下来我们看看stdout设备的相关处理过程。
dev_init_stdout()初始化
51 void
52 dev_init_stdout(void) {
53 struct inode *node;
# 与stdin一样,创建一个作为设备inode的inode
54 if ((node = dev_create_inode()) == NULL) {
55 panic("stdout: dev_create_node.\n");
56 }
57 stdout_device_init(vop_info(node, device));
58
59 int ret;
# 与stdin一样,将作为设备inode的inode加入设备链表
60 if ((ret = vfs_add_dev("stdout", node, 0)) != 0) {
61 panic("stdout: vfs_add_dev: %e.\n", ret);
62 }
63 }
stdout_device_init()
stdout设备文件的初始化过程主要由stdout_device_init
完成,其具体实现如下:
# 初始化stdout的设备结构体
41 static void
42 stdout_device_init(struct device *dev) {
43 dev->d_blocks = 0;
44 dev->d_blocksize = 1;
45 dev->d_open = stdout_open;
46 dev->d_close = stdout_close;
47 dev->d_io = stdout_io;
48 dev->d_ioctl = stdout_ioctl;
49 }
可以看到,stdout_open
函数完成设备文件打开工作。
disk0磁盘
dev_init_disk0()初始化
131 void
132 dev_init_disk0(void) {
133 struct inode *node;
# 创建disk0磁盘的设备inode
134 if ((node = dev_create_inode()) == NULL) {
135 panic("disk0: dev_create_node.\n");
136 }
137 disk0_device_init(vop_info(node, device));
138
139 int ret;
# 将disk0磁盘作为设备挂到vdev_list链上,注意这里与stdin/stdout不同,
# 第三个参数mountable是1。
140 if ((ret = vfs_add_dev("disk0", node, 1)) != 0) {
141 panic("disk0: vfs_add_dev: %e.\n", ret);
142 }
143 }
disk0_device_init()
111 static void
112 disk0_device_init(struct device *dev) {
113 static_assert(DISK0_BLKSIZE % SECTSIZE == 0);
114 if (!ide_device_valid(DISK0_DEV_NO)) {
115 panic("disk0 device isn't available.\n");
116 }
# 初始化块个数和块大小
117 dev->d_blocks = ide_device_size(DISK0_DEV_NO) / DISK0_BLK_NSECT;
118 dev->d_blocksize = DISK0_BLKSIZE;
# 初始化操作
119 dev->d_open = disk0_open;
120 dev->d_close = disk0_close;
121 dev->d_io = disk0_io;
122 dev->d_ioctl = disk0_ioctl;
# 初始化信号量
123 sem_init(&(disk0_sem), 1);
124
125 static_assert(DISK0_BUFSIZE % DISK0_BLKSIZE == 0);
# 为disk0的磁盘缓存开辟内存空间
126 if ((disk0_buffer = kmalloc(DISK0_BUFSIZE)) == NULL) {
127 panic("disk0 alloc buffer failed.\n");
128 }
129 }
实验执行流程概述
关键数据结构
struct files_struct
22 /*
23 * process's file related informaction
24 */
# 该结构体是挂在PCB上的,代表一系列“struct file”的抽象
25 struct files_struct {
26 struct inode *pwd; // inode of present working directory
27 struct file *fd_array; // opened files array,这个其实是个数组,代表当前进程打开的文件
28 int files_count; // the number of opened files
29 semaphore_t files_sem; // lock protect sem
30 };
struct file
14 struct file {
15 enum {
16 FD_NONE, FD_INIT, FD_OPENED, FD_CLOSED,
17 } status;
18 bool readable;
19 bool writable;
20 int fd;
21 off_t pos;
22 struct inode *node;
23 int open_count;
24 };
proc_init()
# 新增如下代码
# 主要目的就是让现在的PCB支持操作文件,让他带上负责管理一系列文件的结构体。
905 if ((idleproc->filesp = files_create()) == NULL) {
906 panic("create filesp (idleproc) failed.\n");
907 }
908 files_count_inc(idleproc->filesp);
files_create()
32 //Called when a new proc init
33 struct files_struct *
34 files_create(void) {
35 //cprintf("[files_create]\n");
36 static_assert((int)FILES_STRUCT_NENTRY > 128);
37 struct files_struct *filesp;
38 if ((filesp = kmalloc(sizeof(struct files_struct) + FILES_STRUCT_BUFSIZE)) != NULL) {
39 filesp->pwd = NULL;
# filesp指向的内存空间是sizeof(struct files_struct) + FILES_STRUCT_BUFSIZE
# 所以(filesp + 1)正好指向FILES_STRUCT_BUFSIZE区域,
# 这里其实就是“struct file”数组所在位置,代表当前进程已经打开的文件相关信息。
40 filesp->fd_array = (void *)(filesp + 1);
41 filesp->files_count = 0;
42 sem_init(&(filesp->files_sem), 1);
# 初始化“struct file”数组,将里面的元素逐个初始化。
43 fd_array_init(filesp->fd_array);
44 }
45 return filesp;
46 }
files_count_inc()
48 static inline int
49 files_count_inc(struct files_struct *filesp) {
50 filesp->files_count += 1;
51 return filesp->files_count;
52 }
fs_init()
11 void
12 fs_init(void) {
13 vfs_init();
14 dev_init();
15 sfs_init();
16 }
init_main()
开始调度后,首先被调度的就是init_main
,里面执行了文件系统相关的代码。
857 static int
858 init_main(void *arg) {
859 int ret;
# 将disk0的根目录读到内存并挂链(挂到文件系统的链上)
# 让bootfs_node这个struct inode指向disk0磁盘的根目录的inode
860 if ((ret = vfs_set_bootfs("disk0:")) != 0) {
861 panic("set boot fs failed: %e.\n", ret);
862 }
863
864 size_t nr_free_pages_store = nr_free_pages();
865 size_t kernel_allocated_store = kallocated();
866
# 这个user_main会将init_main里面文件系统相关的东西全部拷贝到自己身上,
# 比如全部的文件描述符,详细内容见do_fork()里面的alloc_proc和copy_files()
867 int pid = kernel_thread(user_main, NULL, 0);
868 if (pid <= 0) {
869 panic("create user_main failed.\n");
870 }
871 extern void check_sync(void);
872 check_sync(); // check philosopher sync problem
873
874 // There is a schedule() in do_wait(),and will execute.
875 while (do_wait(0, NULL) == 0) {
876 cprintf(">>>>WQ DETECT: init_main start at schedule().\n");
877 schedule();
878 }
879
880 fs_cleanup();
881
882 cprintf("all user-mode processes have quit.\n");
883 assert(initproc->cptr == NULL && initproc->yptr == NULL && initproc->optr == NULL);
884 assert(nr_process == 2);
885 assert(list_next(&proc_list) == &(initproc->list_link));
886 assert(list_prev(&proc_list) == &(initproc->list_link));
887
888 cprintf("init check memory pass.\n");
889 return 0;
890 }
vfs_chdir()
86 /*
87 * vfs_chdir - Set current directory, as a pathname. Use vfs_lookup to translate
88 * it to a inode.
89 */
90 int
91 vfs_chdir(char *path) {
92 int ret;
93 struct inode *node;
# 注意这里对node取了地址,说明里面要给node赋值让其有指向。
# path 是 "disk0:"
94 if ((ret = vfs_lookup(path, &node)) == 0) {
# 此时node已经指向disk0磁盘根目录的struct inode
# 将disk0磁盘根目录的struct inode设置为“当前进程”的目录,
# 即current->filesp->pwd现在就指向disk0磁盘根目录的struct inode
95 ret = vfs_set_curdir(node);
96 vop_ref_dec(node);
97 }
98 return ret;
99 }
vfs_lookup()
68 /*
69 * vfs_lookup - get the inode according to the path filename
70 */
71 int
72 vfs_lookup(char *path, struct inode **node_store) {
73 int ret;
74 struct inode *node;
# path 是 "disk0:"
# 注意这里对path取址,说明里面会改变这里的path的指向。
75 if ((ret = get_device(path, &path, &node)) != 0) {
76 return ret;
77 }
# 走到这里,path已经成了"\0",而node已经指向disk0的根目录节点
78 if (*path != '\0') {
# 此时不会进入该判断
79 ret = vop_lookup(node, path, node_store);
80 vop_ref_dec(node);
81 return ret;
82 }
# 得到根目录的inode
83 *node_store = node;
84 return 0;
85 }
get_device()
# path 是 "disk0:"
13 static int
14 get_device(char *path, char **subpath, struct inode **node_store) {
15 int i, slash = -1, colon = -1;
16 for (i = 0; path[i] != '\0'; i ++) {
17 if (path[i] == ':') { colon = i; break; }
18 if (path[i] == '/') { slash = i; break; }
19 }
# path 是 "disk0:",不会进入该判断
20 if (colon < 0 && slash != 0) {
21 /* *
22 * No colon before a slash, so no device name specified, and the slash isn't leading
23 * or is also absent, so this is a relative path or just a bare filename. Start from
24 * the current directory, and use the whole thing as the subpath.
25 * */
26 *subpath = path;
27 return vfs_get_curdir(node_store);
28 }
# 会进入该判断
29 if (colon > 0) {
30 /* device:path - get root of device's filesystem */
31 path[colon] = '\0';
32
33 /* device:/path - skip slash, treat as device:path */
34 while (path[++ colon] == '/');
# 注意,这里的*subpath并不会改变path的指向,*subpath只会改变调用get_device()
# 时的实参的指向。
35 *subpath = path + colon;
# 此时的path已经是"disk0"
# vfs_get_root()会遍历vdev_list链表,用字符串匹配的方式通过设备名称"disk0"
# 找到该设备在vdev_list链表中的节点,由于该节点挂着fs结构体,所以可通过该结构体
# 上挂着的“fs->fs_get_root = sfs_get_root”函数找到disk0磁盘上的根目录的inode,
# node_store便指向该根目录的inode。
36 return vfs_get_root(path, node_store);
37 }
......
65 return 0;
66 }
user_main()
与之前不同,user_main
现在执行的不再是exit.c
,而是sh.c
,会启动一个类似linux的bash
,这就和linux非常像了,真正开始有操作系统的感觉了。
do_execve()
# 相比之前,这里有了些许改变
654 int
655 do_execve(const char *name, int argc, const char **argv) {
656 static_assert(EXEC_MAX_ARG_LEN >= FS_MAX_FPATH_LEN);
657 struct mm_struct *mm = current->mm;
658 if (!(argc >= 1 && argc <= EXEC_MAX_ARG_NUM)) {
659 return -E_INVAL;
660 }
661
662 char local_name[PROC_NAME_LEN + 1];
663 memset(local_name, 0, sizeof(local_name));
664
665 char *kargv[EXEC_MAX_ARG_NUM];
666 const char *path;
667
668 int ret = -E_INVAL;
669
670 lock_mm(mm);
671 if (name == NULL) {
672 snprintf(local_name, sizeof(local_name), "<null> %d", current->pid);
673 }
674 else {
675 if (!copy_string(mm, local_name, name, sizeof(local_name))) {
676 unlock_mm(mm);
677 return ret;
678 }
679 }
680 if ((ret = copy_kargv(mm, argc, kargv, argv)) != 0) {
681 unlock_mm(mm);
682 return ret;
683 }
684 path = argv[0];
685 unlock_mm(mm);
# 由于要加载新的可执行程序,所以该进程原来打开的文件要全部关闭
686 files_closeall(current->filesp);
687
688 /* sysfile_open will check the first argument path, thus we have to use a user-space pointer, and argv[0] may be incorrect */
689 int fd;
# 为了加载新的可执行程序,这里要用只读的方式打开该可执行程序文件,
# 返回的fd是从current->filesp数组上找的一个空闲file在filesp中的数组索引,
# 这个fd也是记录在这个file结构体里面的。通过这个fd就可以找到当前进程的
# current->filesp上的file,这个file对应实际的要被打开的文件,里面挂了该文件
# 的inode
690 if ((ret = fd = sysfile_open(path, O_RDONLY)) < 0) {
691 goto execve_exit;
692 }
693 if (mm != NULL) {
694 lcr3(boot_cr3);
695 if (mm_count_dec(mm) == 0) {
696 exit_mmap(mm);
697 put_pgdir(mm);
698 mm_destroy(mm);
699 }
700 current->mm = NULL;
701 }
702 ret= -E_NO_MEM;;
# 这里与之前完全不同,这里要将磁盘上该文件的内容加载到内存了
703 if ((ret = load_icode(fd, argc, kargv)) != 0) {
704 goto execve_exit;
705 }
706 put_kargv(argc, kargv);
707 set_proc_name(current, local_name);
708 return 0;
709
710 execve_exit:
711 put_kargv(argc, kargv);
712 do_exit(ret);
713 panic("already exit: %e.\n", ret);
714 }