1:基本信息
代码:linux-0.11
super.c : 含有处理超级快的代码
超级块是设备的映射,超级块的操作关系到设备文件系统操作
下面会解析:
- 对超级块进行操作(创建,读取,释放)
get_super
,put_super
,read_super
- 文件系统加载/卸载: mount/umount
sys_mount
,sys_umount
- 根文件系统的加载(/)
mount_root
2:相关宏和数组
/*
* linux/fs/super.c
*
* (C) 1991 Linus Torvalds
*/
/* set_bit uses setb, as gas doesn't recognize setc */
//测试地址addr的偏移bitnr的值,并返回
#define set_bit(bitnr,addr) ({ \
register int __res __asm__("ax"); \
__asm__("bt %2,%3;setb %%al":"=a" (__res):"a" (0),"r" (bitnr),"m" (*(addr))); \
__res;
//系统当前所有设备文件系统超级快的数组
struct super_block super_block[NR_SUPER];
/* this is initialized in init/main.c 在main函数中被初始化,代表根文件系统的设备号*/
int ROOT_DEV = 0;
3:get_super
从全局的已挂载的文件系统设备
在超级块表中找到,返回超级快的指针,否则返回空指针
#define NR_SUPER 8 //超级快的个数为8
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) //遍历数组
if (s->s_dev == dev) {//设备号相等
wait_on_super(s);//等待超级快解锁(如果被锁的话)
if (s->s_dev == dev)//防止等待期间被其他进程使用,再次验证是否相等
return s;//最终返回超级块的指针
s = 0+super_block;//被其他进程修改,s在指向数组起始处,再从头寻找
} else
s++;
return NULL; //遍历完数组也没有找到,则返回NULL
}
4:put_super
释放指定dev的超级块
1:锁定块并清空其设备号
2:清空其i节点位图和逻辑块位图所占的高速缓冲区
3:解锁该块并唤醒等待该资源(系统super_block数组的空槽,这种情况是当数组8个都被占用时发生)的进程队列
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) {//文件系统被安装的i节点 等于0表示没有被安装。直接返回
printk("Mounted disk changed - tssk, tssk\n\r");
return;
}
lock_super(sb);//锁定这个块
sb->s_dev = 0;//把设备号清空
for(i=0;i<I_MAP_SLOTS;i++) //释放超级快中所有i节点位图
brelse(sb->s_imap[i]);
for(i=0;i<Z_MAP_SLOTS;i++) //释放超级快中所有逻辑块位图
brelse(sb->s_zmap[i]);
free_super(sb);//释放超级块
return;
}
5:read_super
读取指定设备的超级块,相对于前面两个函数是对内存中的超级块的操作,这个函数是对设备中的超级快进行的操作
- 如果指定的设备dev上的文件系统超级快已经在超级块表中,则直接返回该超级块项指针
- 否则就从设备dev上读取超级块到缓冲区,并复制到超级块表中。返回超级块指针
static struct super_block * read_super(int dev)
{
struct super_block * s;
struct buffer_head * bh;
int i,block;
if (!dev) //参数有效性判断
return NULL;
check_disk_change(dev); //检查是否换过盘片(即是否是软盘设备,要经常插拔的),若换过,高速缓冲区关于该设备的缓冲块均失效,需要做失效处理,即释放原来加载的文件系统
if (s = get_super(dev)) //在超级块表中查找dev的超级块,若找到,直接返回
return s;
//在超级块中数组中找到一个空项(即s->s_dev = 0的超级块), 若数组已经占满则返回NULL
for (s = 0+super_block ;; s++) {
if (s >= NR_SUPER+super_block) //数组已经占满
return NULL;
if (!s->s_dev) //找到空闲
break;
}
//初始化超级块在内存中一些动态配置项
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))) { //读取逻辑块号位1(超级快在的位置)的块到高速缓冲区, 失败就
s->s_dev=0; //释放超级快数组中的项
free_super(s);//释放超级快
return NULL; //返回空指针
}
//读取成功把对应的高速缓冲区块的内容 复制到 超级快数组相应项结构中
*((struct d_super_block *) s) =*((struct d_super_block *) bh->b_data);
brelse(bh);//然后把高速缓冲区释放掉
//检测读取超级块的文件系统魔数字段,如果不对,就说明不是正确的文件系统.就会执行,释放超级快数组中的项,解锁项,返回NULL指针
//对于此版本linux,只支持minix文件系统1.0 s_magic = 0x137f
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;
//i节点位图保存在设备2号块开始的逻辑块中,共有s_imap_blocks块
block=2; //此时block指向i节点位图块的起始位置
for (i=0 ; i < s->s_imap_blocks ; i++)
if (s->s_imap[i]=bread(dev,block)) //在高速缓冲区中,分配设备文件系统的i节点位图
block++;
else
break;
//此时block指向逻辑块位图块的起始位置
for (i=0 ; i < s->s_zmap_blocks ; i++)
if (s->s_zmap[i]=bread(dev,block)) //在高速缓冲区中,分配设备文件系统的逻辑块位图
block++;
else
break;
//此时block应该指向逻辑块位图块的最后的位置
if (block != 2+s->s_imap_blocks+s->s_zmap_blocks) {//如果block的位置没有正确移动到逻辑块位图块的最后的位置,说明文件系统文图信息有问题,超级块初始化失败。
//因此只能释放前面申请并占用的资源
for(i=0;i<I_MAP_SLOTS;i++)
brelse(s->s_imap[i]);//释放i节点位图占用的高速缓冲区
for(i=0;i<Z_MAP_SLOTS;i++)
brelse(s->s_zmap[i]);//释放逻辑块位图占用的高速缓冲区
s->s_dev=0;//释放超级块数组项
free_super(s);//解锁该超级块
return NULL;//返回空指针
}
s->s_imap[0]->b_data[0] |= 1; //i节点位图的第0号节点,不能使用,直接赋值1
s->s_zmap[0]->b_data[0] |= 1; //逻辑块位图的第0号节点,不能使用,直接赋值1
//之所以不用0,是查找i节点的机制导致的。如果第n号节点,没被使用则返回节点号n,如果被使用则返回0
//同样如果把这套机制用到n=0上,没被使用则返回节点号0,如果被使用则返回0。就无法区分0是节点号还是被使用的状态
free_super(s);//解锁该超级块
return s;//返回超级块指针
}
6 sys_umount
系统调用的函数,卸载指针设备文件系统
参数:dev_name 文件系统所在设备的设备文件名
卸载成功返回0 失败返回错误码
- 首先根据参数给出块的设备文件名 获得设备号
- 然后复位文件系统超级快中相应的字段,释放超级快和位图占用的缓冲块
- 最后对该设备执行高速缓冲与设备上数据同步操作
int sys_umount(char * dev_name)
{
struct m_inode * inode;
struct super_block * sb;
int dev;
if (!(inode=namei(dev_name))) //根据设备名,找到对应的i节点,
return -ENOENT;
dev = inode->i_zone[0];//取得设备号,设备文件定义的设备号存放在其i节点的i_zone[0]中
if (!S_ISBLK(inode->i_mode)) {//由于文件系统需要放在块设备上,如果不是块设备文件
iput(inode);//放回放申请的i节点
return -ENOTBLK;//返回错误码
}
//上面操作只是为了得到设备号dev,因此i节点已经没有用,就放回该设备文件的i节点
iput(inode);
//下面检查要卸载的文件系统是否满足条件
if (dev==ROOT_DEV) //如果要卸载根文件系统,则不能被卸载,返回忙出错号
return -EBUSY;
if (!(sb=get_super(dev)) || !(sb->s_imount))// 如果(超级块表中没有该设备文件的超级块 || 该设备文件上的系统没有被安装,安装的在s_imount节点上)
return -ENOENT;//返回出错号
if (!sb->s_imount->i_mount)// 如果该文件系统挂接节点s_imount上的挂接标志i_mount为NULL(一个节点的i_mount表示是否有文件系统在本节点挂接的标志,例如文件系统挂接在a节点上,那么a节点的i_mount会大于等于1)
printk("Mounted inode has i_mount=0\n");// 显示警告信息
//查看是否有进程使用该设备上的文件
for (inode=inode_table+0 ; inode<inode_table+NR_INODE ; inode++) //inode结构体遍历
if (inode->i_dev==dev && inode->i_count)//找到了这个节点,引用值不为0(正在被使用),则返回忙错误,不允许卸载
return -EBUSY;
//到这里,说明可以正常卸载
sb->s_imount->i_mount=0; //复位被安装到的节点上的安装标志
iput(sb->s_imount);//释放该i节点
sb->s_imount = NULL;//把超级快中被安装节点字段置空
iput(sb->s_isup);//放回设备文件系统的根i节点
sb->s_isup = NULL;//超级块中被安装系统根i节点指针置空
put_super(dev);//释放该设备文件系统的上的超级块 以及 位图占用的高速缓冲区
sync_dev(dev); //对设备执行高速缓冲区 与 设备上数据同步操作
return 0;
}
7 sys_mount
安装文件系统(系统调用)
参数:dev_name 是设备文件名;dir_name 是安装到的目录名,rw_flag :被安装系统的可读可写标志
将被加载的地方必须是一个目录名,并且对于的i节点没有被其他程序占用
成功:返回0
失败:返回错误号
int sys_mount(char * dev_name, char * dir_name, int rw_flag)
{
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)) {//检查是否为块设备,不是的话就释放i节点,并返回错误码
iput(dev_i);
return -EPERM;
}
iput(dev_i);//成功得到设备号后,i节点就可以释放
if (!(dir_i=namei(dir_name)))//根据安装到的目录名名找到对应的i节点
return -ENOENT;
if (dir_i->i_count != 1 || dir_i->i_num == ROOT_INO) {//如果(i节点引用计数不为1 || i节点号是跟文件系统的节点号1(文件系统只能安装在目录名上))
iput(dir_i);//则放回i节点
return -EBUSY;//返回错误码
}
if (!S_ISDIR(dir_i->i_mode)) {//判断安装点是否是一个目录名
iput(dir_i);
return -EPERM;
}
//到这里全部检查完毕
if (!(sb=read_super(dev))) {//开始读取超级快的信息,失败则放回安装点dir_i,并返回错误码
iput(dir_i);
return -EBUSY;
}
//得到文件系统超级快之后,先检查超级块和文件系统安装节点。
if (sb->s_imount) {//如果该文件系统已经被安装了(安装节点已经存在),释放i节点,返回错误码
iput(dir_i);
return -EBUSY;
}
if (dir_i->i_mount) {//如果要安装的节点已经安装了文件系统(安装标志已经置位),释放i节点,返回错误码
iput(dir_i);
return -EPERM;
}
//到这里说明检查都正常
sb->s_imount=dir_i; //设置被安装文件系统的超级块的被安装到的节点(s_imount) 指向 安装到的目录节点
dir_i->i_mount=1;//置位安装位置节点的安装标志
dir_i->i_dirt=1;//置位安装位置节点的修改标志位 /* NOTE! we don't iput(dir_i) */ 注意!这里没有iput(dir_i)
return 0; /* we do that in umount */ //会在umount函数中来执行iput(dir_i)的操作
}
8 mount_root 安装根根据系统
该函数属于系统系统化操作的一部分。
- 函数首先初始化文件表数组file_table[]和超级块表(数组)
- 然后读取跟文件系统超级快,并取得文件系统根i节点
- 最后统计并显示出根文件系统上的可用资源(空闲块数和空闲i节点数据)
该函数在系统开机初始化设置 (sys_setup()被调用 blk_drv/hd.c 157行)
void mount_root(void)
{
int i,free;
struct super_block * p;
struct m_inode * mi;
if (32 != sizeof (struct d_inode))//若磁盘i节点结构体不是32字节,则出错停机。该判断用于防止修改代码时出现不一致情况
panic("bad i-node size");
//初始化文件表数组(共64项,系统同时只能打开64个文件)和超级快
for(i=0;i<NR_FILE;i++)//初始化文件表
file_table[i].f_count=0; //这里将文件结构体中引用计数设置为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++) {//初始化超级块表
p->s_dev = 0;//设备字段初始化为0,表示空闲
p->s_lock = 0;
p->s_wait = NULL;
}
//做好初始化工作之后,开始安装跟文件系统
if (!(p=read_super(ROOT_DEV)))//从根设备上读取文件的超级快
panic("Unable to mount root");
if (!(mi=iget(ROOT_DEV,ROOT_INO)))//并取得文件系统的根i节点(1号节点)在内存i节点表中的指针
panic("Unable to read root i-node");
//对超级块和根i节点进行设置,把i节点的引用次数递增3次。因为紧接下一条的语句也引用了i节点。
//另外,iget()函数中i节点应用计数已被置1,然后该超级块的被安装文件系统i节点和被安装到i节点字段为该i节点
//再设置当前进程的当前工作目录和根目录i节点。
//此时当前进程是1号进程 (init进程)
mi->i_count += 3 ; /* NOTE! it is logically used 4 times, not 1 逻辑上被引用了4次*/
p->s_isup = p->s_imount = mi; // + 1
current->pwd = mi; // +1
current->root = mi;// +1
//对根文件系统上的资源做统计工作。统计设备上空闲块数和空闲i节点数。
free=0;
i=p->s_nzones;//i等于超级快中表面的逻辑块数总数
while (-- i >= 0)//根据逻辑块位图中相应的bit位占用情况统计出空闲块
if (!set_bit(i&8191,p->s_zmap[i>>13]->b_data))//宏set_bit只是测试bit位,而非设置。i&8191用来计算在当前位图块对应的bit偏移。i>>13是对8192取余,来得到位于第几个位图块上
free++; //每检测到一个空闲,free就加1
printk("%d/%d free blocks\n\r",free,p->s_nzones);//显示,空闲逻辑块数 与总逻辑块数
free=0;
i=p->s_ninodes+1;//i等于超级快中设备i节点总数 + 1 (加1 是把0节点也统计上去)
while (-- i >= 0)
if (!set_bit(i&8191,p->s_imap[i>>13]->b_data))
free++;//每检测到一个空闲,free就加1
printk("%d/%d free inodes\n\r",free,p->s_ninodes);;//显示,空闲i节点数 与 总i节点数
}
8.1 函数流程图
该函数在系统执行初始化程序main.cz中,在进程0创建第一个子进程(进程1)后被调用,而且系统仅在这里调用它一次。
具体位置初始化函数init()->setup() 。 其中setup位于/kernel/blk_drv/hd.c 第71行开始