Linux内核-文件系统-超级块操作

📅 2026/8/2 3:35:38
Linux内核-文件系统-超级块操作
文件系统的超级块操作主要是在super.c这个源文件中这个文件的作用主要有对设备的超级块进行操作获取读取释放因为超级块是设备文件系统的映射所以超级块的操作关系到设备文件系统的操作包含文件系统的加载卸载挂载等根文件系统的加载get_super//// 取指定设备的超级块。返回该超级块结构指针 struct super_block * get_super(int dev) { struct super_block * s; if (!dev) return NULL; s 0super_block; // s 指向超级块数组开始处。搜索整个超级块数组寻找指定设备的超级块 while (s NR_SUPERsuper_block) // 如果当前搜索项是指定设备的超级块则首先等待该超级块解锁若已经被其它进程上锁的话。 // 在等待期间该超级块有可能被其它设备使用因此此时需再判断一次是否是指定设备的超级块 // 如果是则返回该超级块的指针。否则就重新对超级块数组再搜索一遍因此 s 重又指向超级块数组 // 开始处 if (s-s_dev dev) { wait_on_super(s); if (s-s_dev dev) return s; s 0super_block; } else s; return NULL; }这个函数和前面几次文章的函数都差不多获取指定设备的超级块put_super//// 释放指定设备的超级块 void put_super(int dev) { struct super_block * sb; struct m_inode * inode; int i; if (dev ROOT_DEV) { printk(root diskette changed: prepare for armageddon\n\r); return; } if (!(sb get_super(dev))) return; if (sb-s_imount) { printk(Mounted disk changed - tssk, tssk\n\r); return; } lock_super(sb); sb-s_dev 0; for(i0;iI_MAP_SLOTS;i) brelse(sb-s_imap[i]); for(i0;iZ_MAP_SLOTS;i) brelse(sb-s_zmap[i]); free_super(sb); return; }put_super用于释放掉指定的超级块在这里需要注意如果设备是根目录那不能被释放以及s_imount如果被挂载到了某个inode也是不能被释放的对于释放操作主要是将s_dev置0让系统不能找到这个超级块然后将inode位图和逻辑块位图清空读取超级块到内存read_super//// 从设备上读取超级块到内存中。 // 如果该设备的超级块已经在高速缓冲中并且有效则直接返回该超级块的指针 static struct super_block * read_super(int dev)查看软驱是否有更改check_disk_change(dev);check_disk_change(dev);这行代码的核心作用是检测指定设备主要是软盘上的介质是否被更换过。如果被更换了就使内核中该设备相关的所有旧缓存数据失效以确保接下来读取到的是新插入介质上的正确超级块。由于这段代码出自 Linux 0.11 内核那个时代主要依赖软盘作为移动存储设备且系统不支持热插拔通知机制所以这个函数是保证数据一致性的关键“防御性”检查。他的底层机制大致如下void check_disk_change(int dev) { int i; if (MAJOR(dev) ! 2) // 只对软盘设备主设备号 2进行检查 return; if (!floppy_change(dev)) // 调用软盘驱动检测磁盘是否真的被抽换过 return; // 如果检测到介质已更换立即执行清理操作 // 1. 从超级块数组中清除该设备的旧超级块 for (i 0; i NR_SUPER; i) if (super_block[i].s_dev dev) put_super(dev); // 2. 使内存中该设备的所有 i 节点inode失效 invalidate_inodes(dev); // 3. 使高速缓冲区buffer cache中该设备的所有数据块失效 invalidate_buffers(dev); }如果在高速缓冲直接拿取if (s get_super(dev)) // 如果该设备的超级块已经在高速缓冲中则直接返回 return s; for (s 0super_block ;; s) { // 否则首先在超级块数组中找出一个空项(也即其 s_dev0 的项)。如果数组已经占满则返回空指针 if (s NR_SUPERsuper_block) return NULL; if (!s-s_dev) // 当前dev为空 break; }如果get_super能够拿到那就说明已经在高速缓冲中了直接返回如果没有的话就需要在超级块数组中找到一个空闲的项存放这个设备的超级块设置动态配置项// 找到超级块空项后就将该超级块用于指定设备对该超级块进行部分初始化 //这里设置的都是一些超级块内存中的一些动态配置项 s-s_dev dev; s-s_isup NULL; s-s_imount NULL; s-s_time 0; s-s_rd_only 0; s-s_dirt 0; lock_super(s);在得到一个空闲的超级块数组项后就去初始化一些超级块存储在内存中的动态配置项读取超级块固定配置项到高速缓冲区if (!(bh bread(dev,1))) { // 从设备上读取超级块信息到 bh 指向的缓冲区中 s-s_dev0; free_super(s); return NULL; } // 将设备上读取的超级块信息复制到内存超级块结构中。并释放存放读取信息的高速缓冲块 *((struct d_super_block *) s) *((struct d_super_block *) bh-b_data); brelse(bh);超级块结构体有一部分是存储在磁盘上的固定配置项包含了节点数逻辑块数位图占据逻辑块数等等使用bread读取这部分内容到高速缓冲区这里的1表示的就是超级块的逻辑块号如果成功拿取到超级块的固定配置项就赋值拷贝到内存s这里也是直接结构体赋值是不规范的最后释放掉使用的这块高速缓冲区根据固定配置项设置位图信息//s_magic是文件系统ID号如果不支持则释放掉这个超级块 if (s-s_magic ! SUPER_MAGIC) { // 说明设备上不是正确的文件系统 s-s_dev 0; free_super(s); return NULL; } // 下面开始读取设备上 i 节点位图和逻辑块位图数据 for (i0;iI_MAP_SLOTS;i) // 初始化 s-s_imap[i] NULL; for (i0;iZ_MAP_SLOTS;i) s-s_zmap[i] NULL; block2; //指向i节点位图 //根据得到的超级块信息分配该设备的i节点位图 for (i0 ; i s-s_imap_blocks ; i) // 读取 if (s-s_imap[i]bread(dev,block)) // 表示inode block; else break; //根据得到的超级块信息分配该设备的逻辑块位图 for (i0 ; i s-s_zmap_blocks ; i) if (s-s_zmap[i]bread(dev,block)) // 表示逻辑块 block; else break; //出错处理还原一切 if (block ! 2s-s_imap_blockss-s_zmap_blocks) { for(i0;iI_MAP_SLOTS;i) brelse(s-s_imap[i]); for(i0;iZ_MAP_SLOTS;i) brelse(s-s_zmap[i]); s-s_dev0; free_super(s); return NULL; }最上面是去判断是不是支持的文件系统不是的话就直接返回NULL然后接下来将设备上的每块i节点位图和逻辑块位图置空等待接下来的分配让block2; //指向i节点位图然后从第一块i节点位图到最后一块逻辑块位图依次分配高速缓冲块如果block ! 2s-s_imap_blockss-s_zmap_blocks就代表出现问题直接将一切还原将位图第一位置1// 一切成功。对于申请空闲 i 节点的函数来讲如果设备上所有的 i 节点已经全被使用则查找 // 函数会返回 0 值。因此 0 号 i 节点是不能用的所以这里将位图中的最低位设置为 1以防止文件 // 系统分配 0 号 i 节点。同样的道理也将逻辑块位图的最低位设置为 1 s-s_imap[0]-b_data[0] | 1; s-s_zmap[0]-b_data[0] | 1; // 解锁该超级块并返回超级块指针 free_super(s); return s;位图的第一个bit位是固定不使用的所以强制让他们一直被占用挂载文件系统sys_mount/** * brief 安装文件系统 * * param dev_name 设备文件名 * param dir_name 安装到的目录名 * param rw_flag 被安装文件的读写标志 * return int 0-成功other-错误号 */ int sys_mount(char * dev_name, char * dir_name, int rw_flag)这个函数是mount的系统调用将dev_name的设备挂载到dir_name目录上获取设备和目录i节点struct m_inode * dev_i, * dir_i; struct super_block * sb; int dev; if (!(dev_inamei(dev_name))) // 首先根据设备文件名找到对应的 i 节点并取其中的设备号 return -ENOENT; dev dev_i-i_zone[0]; // 通过i节点获取设备号 if (!S_ISBLK(dev_i-i_mode)) { iput(dev_i); return -EPERM; } iput(dev_i); // 释放刚申请的 i 节点 dev_i if (!(dir_inamei(dir_name))) return -ENOENT; if (dir_i-i_count ! 1 || dir_i-i_num ROOT_INO) { iput(dir_i); return -EBUSY; } if (!S_ISDIR(dir_i-i_mode)) { iput(dir_i); return -EPERM; }1.首先是通过namei函数根据设备文件名和目录名去获取得到设备和目录对应的inode在获取得到设备inode后要去判断它是不是一个块设备S_ISBLK并且去获取得到设备号,块设备inode中的i_zone第一个直接块号存储的设备号dev dev_i-i_zone[0];在完成判断之后就可以释放掉设备的inode节点了,所以才会看到不管是不是块设备都有iput(dev_i);2.然后也会去查看当前要被挂载的文件目录是否没有被其他占用dir_i-i_count并且不是根目录ROOT_INO,以及是否是目录S_ISDIR获取设备超级块if (!(sbread_super(dev))) { // 通过设备号读取设备的超级块 iput(dir_i); return -EBUSY; } if (sb-s_imount) { iput(dir_i); return -EBUSY; } if (dir_i-i_mount) { iput(dir_i); return -EPERM; }这里根据设备号去获取得到设备超级块因为超级块存储着设备文件系统的相关信息通过s_imount i_mount去查看设备和目录是否已经被挂载,如果已经被挂载就退出设置挂载信息sb-s_imountdir_i; // 被安装设备挂载到目录设备节点 dir_i-i_mount1; // 已挂载已修改 dir_i-i_dirt1; /* NOTE! we dont iput(dir_i) */ return 0; /* we do that in umount 在umount 中释放 */在最后将设备的挂载节点指向目录节点,然后将它们各自的挂载标志位置1卸载文件系统sys_umount卸载文件系统sys_umount这里跟上面的挂载操作的思路类似这里是遍历寻找然后间朝状态位最后置位对应信息位,在此不赘述/** * brief 卸载文件系统调用函数 * * param dev_name 设备文件名 * return int 0-成功other-错误号 */ int sys_umount(char * dev_name) { struct m_inode * inode; struct super_block * sb; int dev; if (!(inodenamei(dev_name))) return -ENOENT; dev inode-i_zone[0]; if (!S_ISBLK(inode-i_mode)) { iput(inode); return -ENOTBLK; } iput(inode); if (devROOT_DEV) return -EBUSY; if (!(sbget_super(dev)) || !(sb-s_imount)) return -ENOENT; if (!sb-s_imount-i_mount) printk(Mounted inode has i_mount0\n); for (inodeinode_table0 ; inodeinode_tableNR_INODE ; inode) if (inode-i_devdev inode-i_count) return -EBUSY; sb-s_imount-i_mount0; iput(sb-s_imount); sb-s_imount NULL; iput(sb-s_isup); sb-s_isup NULL; put_super(dev); sync_dev(dev); return 0; }挂载根文件系统mount_root// 加载根文件系统 统开机初始化设置时(sys_setup())调用的 void mount_root(void)根文件系统挂载是在开机时的sys_setup调用的sys_setup函数内部主要是获取BIOS信息硬盘信息加载主存最后就是调用mount_root挂载初始化文件表和超级块数组int i,free; struct super_block * p; struct m_inode * mi; if (32 ! sizeof (struct d_inode)) panic(bad i-node size); for(i0;iNR_FILE;i) // 初始化文件表数组共 64 项也即系统同时只能打开 64 个文件将所有文件结构中的引用计数设置为0 file_table[i].f_count0; if (MAJOR(ROOT_DEV) 2) { // 如果根文件系统所在设备是软盘的话就提示“插入根文件系统盘并按回车键”并等待按键 printk(Insert root floppy and press ENTER); wait_for_keypress(); } for(p super_block[0] ; p super_block[NR_SUPER] ; p) { // 初始化超级块数组共 8 项 p-s_dev 0; p-s_lock 0; p-s_wait NULL; }在函数开始首先判断d_inode大小是否是32,如果不是就报错然后去初始化文件表数组将每一项的文件描述符都清零// 文件表数组 系统同时只能打开64个文件 struct file file_table[NR_FILE];并且对于软盘设备需要用户敲击键盘启动最后就是初始化超级块数组设置s_dev,s_lock,s_wait为0获取根设备超级块和inodeif (!(pread_super(ROOT_DEV))) // 读根设备上超级块 panic(Unable to mount root); if (!(miiget(ROOT_DEV,ROOT_INO))) //从设备上读取文件系统的根 i 节点 panic(Unable to read root i-node); mi-i_count 3 ; /* NOTE! it is logically used 4 times, not 1 */ p-s_isup p-s_imount mi; // 设置i节点挂载 current-pwd mi; // 设置当前工作目录和根目录i节点 current-root mi;接下来就是获取设备的超级块和inode这里的i_count3后实际数值是4,那么是哪4个指针在使用它呢mount_root()的后续代码中这个mi根目录 inode被赋值给了以下位置p-s_isup p-s_imount mi; // ① 超级块的 s_isup② 超级块的 s_imount current-pwd mi; // ③ 当前进程的工作目录 current-root mi; // ④ 当前进程的根目录s_isup文件系统超级块中指向“根目录”的指针用于路径解析的起点。s_imount超级块中指向“被挂载的 inode”的指针虽然根文件系统挂载在自身但这个指针必须有效。current-pwd当前进程的当前工作目录。current-root当前进程的根目录用于chroot环境根文件系统下就是根目录本身。iget(ROOT_DEV, ROOT_INO)在返回 inode 指针时会将该 inode 的i_count设置为1表示“有人正在使用这个 inode 结构体”。统计设备空闲块数和节点数// 统计该设备上空闲块数。 // 首先令 i 等于超级块中的设备逻辑块总数 // 然后根据位图统计出空闲总块 // 接着统计设备上空闲i节点数 free0; ip-s_nzones; while (-- i 0) if (!set_bit(i8191,p-s_zmap[i13]-b_data)) free; printk(%d/%d free blocks\n\r,free,p-s_nzones); free0; ip-s_ninodes1; while (-- i 0) if (!set_bit(i8191,p-s_imap[i13]-b_data)) free; printk(%d/%d free inodes\n\r,free,p-s_ninodes);