
文件系统-超级块操作
文件系统的超级块操作主要是在super.c这个源文件中
这个文件的作用主要有:
- 对设备的超级块进行操作(获取,读取,释放)
- 因为超级块是设备文件系统的映射,所以超级块的操作关系到设备文件系统的操作,包含文件系统的加载,卸载,挂载等
- 根文件系统的加载
get_super
//// 取指定设备的超级块。返回该超级块结构指针
struct super_block * get_super(int dev)
{
struct super_block * s;
if (!dev)
return NULL;
s = 0+super_block; // s 指向超级块数组开始处。搜索整个超级块数组,寻找指定设备的超级块
while (s < NR_SUPER+super_block)
// 如果当前搜索项是指定设备的超级块,则首先等待该超级块解锁(若已经被其它进程上锁的话)。
// 在等待期间,该超级块有可能被其它设备使用,因此此时需再判断一次是否是指定设备的超级块,
// 如果是则返回该超级块的指针。否则就重新对超级块数组再搜索一遍,因此 s 重又指向超级块数组
// 开始处
if (s->s_dev == dev) {
wait_on_super(s);
if (s->s_dev == dev)
return s;
s = 0+super_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(i=0;i<I_MAP_SLOTS;i++)
brelse(sb->s_imap[i]);
for(i=0;i<Z_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 = 0+super_block ;; s++) { // 否则,首先在超级块数组中找出一个空项(也即其 s_dev=0 的项)。如果数组已经占满则返回空指针
if (s >= NR_SUPER+super_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_dev=0;
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 (i=0;i<I_MAP_SLOTS;i++) // 初始化
s->s_imap[i] = NULL;
for (i=0;i<Z_MAP_SLOTS;i++)
s->s_zmap[i] = NULL;
block=2; //指向i节点位图
//根据得到的超级块信息分配该设备的i节点位图
for (i=0 ; i < s->s_imap_blocks ; i++) // 读取
if (s->s_imap[i]=bread(dev,block)) // 表示inode
block++;
else
break;
//根据得到的超级块信息分配该设备的逻辑块位图
for (i=0 ; i < s->s_zmap_blocks ; i++)
if (s->s_zmap[i]=bread(dev,block)) // 表示逻辑块
block++;
else
break;
//出错处理,还原一切
if (block != 2+s->s_imap_blocks+s->s_zmap_blocks) {
for(i=0;i<I_MAP_SLOTS;i++)
brelse(s->s_imap[i]);
for(i=0;i<Z_MAP_SLOTS;i++)
brelse(s->s_zmap[i]);
s->s_dev=0;
free_super(s);
return NULL;
}
最上面是去判断是不是支持的文件系统,不是的话就直接返回NULL
然后接下来将设备上的每块i节点位图和逻辑块位图置空,等待接下来的分配
让block=2; //指向i节点位图,然后从第一块i节点位图到最后一块逻辑块位图依次分配高速缓冲块
如果block != 2+s->s_imap_blocks+s->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_i=namei(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_i=namei(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 (!(sb=read_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_imount=dir_i; // 被安装设备挂载到目录设备节点
dir_i->i_mount=1; // 已挂载,已修改
dir_i->i_dirt=1; /* NOTE! we don't 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 (!(inode=namei(dev_name)))
return -ENOENT;
dev = inode->i_zone[0];
if (!S_ISBLK(inode->i_mode)) {
iput(inode);
return -ENOTBLK;
}
iput(inode);
if (dev==ROOT_DEV)
return -EBUSY;
if (!(sb=get_super(dev)) || !(sb->s_imount))
return -ENOENT;
if (!sb->s_imount->i_mount)
printk("Mounted inode has i_mount=0\n");
for (inode=inode_table+0 ; inode<inode_table+NR_INODE ; inode++)
if (inode->i_dev==dev && inode->i_count)
return -EBUSY;
sb->s_imount->i_mount=0;
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(i=0;i<NR_FILE;i++) // 初始化文件表数组(共 64 项,也即系统同时只能打开 64 个文件),将所有文件结构中的引用计数设置为0
file_table[i].f_count=0;
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
获取根设备超级块和inode
if (!(p=read_super(ROOT_DEV))) // 读根设备上超级块
panic("Unable to mount root");
if (!(mi=iget(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_count+3后实际数值是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节点数
free=0;
i=p->s_nzones;
while (-- i >= 0)
if (!set_bit(i&8191,p->s_zmap[i>>13]->b_data))
free++;
printk("%d/%d free blocks\n\r",free,p->s_nzones);
free=0;
i=p->s_ninodes+1;
while (-- i >= 0)
if (!set_bit(i&8191,p->s_imap[i>>13]->b_data))
free++;
printk("%d/%d free inodes\n\r",free,p->s_ninodes);
