ucore实验八

编译与执行过程

# 直接从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的文件系统架构主要由四部分组成:

  1. 通用文件系统访问接口层:该层提供了一个从用户空间到文件系统的标准访问接口。这一层访问接口让应用程序能够通过一个简单的接口获得ucore内核的文件系统服务。
  2. 文件系统抽象层:向上提供一个一致的接口给内核其他部分(文件系统相关的系统调用实现模块和其他内核功能模块)访问;向下提供一个同样的抽象函数指针列表和数据结构屏蔽不同文件系统的实现细节。
  3. Simple FS文件系统层:一个基于索引方式的简单文件系统实例。向上通过各种具体函数实现以对应文件系统抽象层提出的抽象函数。向下访问外设接口。
  4. 外设接口层:向上提供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实现了一些辅助的函数:

  1. 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 调用)
  2. 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 也要格外小心。
  3. sfs_dirent_read_nolock:将目录的第 slot 个 entry 读取到指定的内存空间。他通过上面提到的函数来完成。
  4. sfs_dirent_write_nolock:用指定的 entry 来替换某个目录下的第 slot 个entry。他通过调用 sfs_bmap_load_nolock保证,当第 slot 个entry 不存在时(slot == inode->blocks),SFS 会分配一个新的entry,即在目录尾添加了一个 entry。
  5. 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_openfilesfs_closesfs_readsfs_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 }

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值