当前位置: 首页 > 科技观察

让我们从上一本书继续学习如何加载根文件系统

时间:2023-03-20 14:00:40 科技观察

本书。我们在上一本书中说过,我们已经将硬盘的基本信息存储到了hd_info[]中。硬盘的分区信息存放在hd[]中。并且我留下了一个读取硬盘数据的bread函数没有讲到,待主流程结束后再展开这些函数的细节。我知道这就是你关心的。这些都是在setup方法中完成的,也就是0进程fork出来的进程1执行的第一个方法。今天我们说一下setup方法中的最后一个函数mount_root。intsys_setup(void*BIOS){...mount_root();}mount_root直译为加载根目录。多说几句就是加载根文件系统。有了它,操作系统就可以从根开始找到硬盘中存储的所有文件,所以它是文件系统的基石,非常重要。让我们打开看看。voidmount_root(void){inti,free;结构超级块*p;结构m_inode*mi;for(i=0;i<64;i++)file_table[i].f_count=0;for(p=&super_block[0];p<&super_block[8];p++){p->s_dev=0;p->s_lock=0;p->s_wait=NULL;}p=read_super(0);mi=iget(0,1);mi->i_count+=3;p->s_isup=p->s_imount=mi;当前->密码=mi;当前->根=mi;免费=0;i=p->s_nzones;while(--i>=0)if(!set_bit(i&8191,p->s_zmap[i>>13]->b_data))free++;免费=0;i=p->s_ninodes+1;while(--i>=0)if(!set_bit(i&8191,p->s_imap[i>>13]->b_data))free++;}非常简单。整体来说就是将硬盘中的数据以文件系统的格式进行解释,加载到内存中设计好的数据结构中,从而使操作系统以文件系统的形式访问硬盘通过内存中的数据一个一个地放入文件中。其实搞清楚两件事就可以了:第一,硬盘中的文件系统是什么格式的?二、文件系统在内存中使用的数据结构是什么?让我们一一进行。硬盘中文件系统的格式是什么?首先,硬盘中的文件系统无非就是硬盘中的一堆数据,我们按照一定的格式来解析。Linux-0.11中的文件系统是MINIX文件系统,长这样。每个块结构的大小为1024字节,也就是1KB,硬盘中的数据就是按照这个结构在硬盘中合理排列的。但是为什么硬盘里会有这样的信息呢?这是一个先有鸡还是先有蛋的问题。可以先写一个操作系统,然后用某种类型的文件系统格式化硬盘,这样就可以得到一个带有文件系统的硬盘。有了这个硬盘,你的操作系统就可以顺利启动了。总之,想办法把数据写入这个硬盘。好了,现在让我们简单了解一下MINIX文件系统的格式。bootblock就是我们系列开头提到的bootarea。当然,并不是所有的硬盘都有引导区,但是我们还是要保留这个位置,以保持格式的统一。超级块用于描述整个文件系统的整体信息。我们通过查看它的字段就可以看到它的字段,包括inode的个数,block的个数,第一个block所在的位置。有了它,整个硬盘的布局就一目了然了。inodebitmap和blockbitmap是bitmap的基本操作和作用,表示后面inode和block的使用,类似于我们前面提到的内存占用位图mem_map[]。更进一步,inode存储了每个文件或目录的元信息和索引信息。元信息是文件类型、文件大小、修改时间等,索引信息是大小为9的i_zone[9]块数组,表示该文件或文件的具体数据占用了哪些块目录。其中,块数组中,0~6表示直接索引,7表示第一个间接索引,8表示第二个间接索引。当文件比较小时,比如只占用2块就够了,那么只需要zone[0]和zone[1]两个直接索引。之后,它们都是存储特定文件或目录的实际信息的块。如果是普通文件类型的inode指向的block,那么就直接包含文件的二进制信息。如果是目录类型的inode指向的块,则存储该目录下文件和目录的inode索引、文件名或目录名等信息。好了,就简单的说完文件系统格式的描述吧。MINIX文件系统已过时。文件系统在内存中使用的数据结构是什么?赶紧回过头来看看我们的代码,如何把存储在硬盘中的数据以这样的格式加载出来,让我们的操作系统能够控制。划痕。structfile{unsignedshortf_mode;无符号短f_flags;无符号短f_count;结构m_inode*f_inode;off_tf_pos;};voidmount_root(void){for(i=0;i<64;i++)file_table[i].f_count=0;...}清除64个file_tables中的f_count。这个file_table表示进程使用的文件。进程每次使用一个文件,都需要在这里记录,包括文件类型,文件inode索引信息等,而这个f_count表示引用次数。此时还没有被引用,所以设置为零。.而这个file_table的索引就是我们通常所说的文件描述符。例如,有以下命令。echo"hello">0表示向0号文件描述符输出hello,0号文件描述符是哪个文件?就是file_table[0]代表的文件。这个文件在哪里?注意文件结构中有一个f_inode字段,通过f_inode可以找到它的inode信息。inode信息包含了一个文件所需要的所有信息,包括文件的大小、文件的类型、文件的位置等。硬盘块号,这个所在的硬盘块号,就是文件所在的位置。继续阅读。结构super_blocksuper_block[8];voidmount_root(void){...结构super_block*p;for(p=&super_block[0];p<&super_block[8];p++){p->s_dev=0;p->s_lock=0;p->s_wait=NULL;}...}是再次清空一个数组super_block。这个super_block存在的意义在于,当操作系统和一个设备以文件的形式进行读写访问时,需要将设备的superblock信息放在这里。这样,通过这个超级块,就可以控制这个设备的整个文件系统。果然,接下来的操作就是将硬盘的超级块信息读入内存。voidmount_root(void){...p=read_super(0);...}read_super是读取硬盘中的superblock。接下来读取根inode信息。structm_inode*mi;voidmount_root(void){...mi=iget(0,1);...}然后将inode设置为当前进程(即进程1)的当前工作目录和根目录。voidmount_root(void){...current->pwd=mi;当前->根=mi;...}然后记录块位图信息。voidmount_root(void){...i=p->s_nzones;while(--i>=0)set_bit(i&8191,p->s_zmap[i>>13]->b_data);...}最后记录inode位图信息。voidmount_root(void){...i=p->s_ninodes+1;while(--i>=0)set_bit(i&8191,p->s_imap[i>>13]->b_data);}完成了。其实整体就是把硬盘中的文件系统的信息全部搬到内存中。上图可以说是非常直观了。有了内存中的这些结构,我们就可以沿着根inode找到所有的文件。至此,加载根文件系统的mount_root函数就全部结束了。同时,让我们回到全局视图,发现setup函数也结束了。voidmain(void){...move_to_user_mode();如果(!fork()){初始化();}for(;;)pause();}voidinit(void){setup((void*)&drive_info);...}intsys_setup(void*BIOS){...mount_root();}setup的主要工作就是我们今天说的,加载根文件系统。让我们继续讨论init函数。voidinit(void){setup((void*)&drive_info);(void)open("/dev/tty0",O_RDWR,0);(无效)重复(0);(void)dup(0);}看到这里,相信你也明白了。之前折腾了很多setup函数,加载根文件系统,沿着根inode可以找到所有的文件,这样下一行open函数就可以很方便的通过文件路径。在这里,我们已经打开了一个/dev/tty0文件,那么我们接下来的重点就是/dev/tty0是什么?