前面两篇blog是分析缓存区的,而这里开始分析文件系统底层操作函数;首先开刀的是bitmap.c程序,这个程序主要负责逻辑块的申请和释放以及i节点的申请和释放;如果大家看了一系列的blog会想《Linux完全注释》不是有吗,我这个blog就没有什么价值了。其实不是这样的(至少我个人不是这么认为的),首先是我看了理解后的笔记,然后是《Linux完全注释》非常散,仅仅是注释,没有对整个操作系统的联系来分析。就好比如告诉你一篇文章中的每个字的意思,但你却未必知道整篇文章讲的是什么意思。
总结的来说其实Bitmap.c程序就是操作文件系统中的两个位图:i节点位图和逻辑块位图;拿逻辑块位图来说吧,申请一个新的逻辑块时,首先是在8块逻辑块位图中查找一个空闲的比特位,找到以后转换成逻辑块号,然后就通过逻辑块号找到真正的逻辑块,通过函数把该逻辑块映射到缓存区中,当然最后要对缓存区做些属性设置;释放一个逻辑块时也一样,首先做些检查,然后释放缓存块,最后也是设置下逻辑块位图(当然也是根据逻辑块号转换成8块逻辑块位图中的某个比特位)。
1、看下本程序涉及到的一些汇编;
stosl指令:相当于将EAX中的值保存到ES:EDI指向的地址中, 若设置了EFLAGS中的方向位置位(即在STOSL指令前使用STD指令)则EDI自减4,否则(使用CLD指令)EDI自增4。一般和rep一起用循环的把eax中的值放到edi中(当然cx表示循环的次数)。本程序中在clear_block() 清除块上所有位数时使用;
btsl指令:bts %1, %2 把基地址%2 偏移地址为%1所指的比特位值保存到cf进位标志中,然后设置该比特位为1;指令setb用于根据进位标志cf设置al,cf=1,al=1;cf=0,al=0;在set_bit(nr,addr)指定某块逻辑块上的某个位进行置1,并把该位原来的值保存到进位标志cf上,以便接下来的检查;
btrl指令:btr %1, %2把基地址%2 偏移地址1所指的比特位保存到cf进位标志中,然后设置该比特位为0。setnb 根据进位标志cf设置al,cf=1,al=0;cf=0,al=1;这个指令刚好和上个相反;在clear_bit(nr,addr)指定某块逻辑块上的某个位进行清零,并把该位原来的值放到cf中,以便接下来的检查;
在一块逻辑块上查找一个第一个比特位为0的比特位(其实就是在一个逻辑块中查找空闲位)
#define find_first_zero(addr) ({ \
int __res; \
__asm__("cld\n" \
"1:\tlodsl\n\t" \//将esi指向的内存内容存放到ax中
"notl %%eax\n\t" \//反转eax中的比特位
"bsfl %%eax,%%edx\n\t" \//顺序扫描eax把第一个为1的存放到edx中
"je 2f\n\t" \//如果没有为1的比特位,则跳转到2标号处
"addl %%edx,%%ecx\n\t" \//把偏移地址号加入到ecx处
"jmp 3f\n" \//跳转到3处
"2:\taddl $32,%%ecx\n\t" \//没有找到,则到下个字节开始查找
"cmpl $8192,%%ecx\n\t" \//比较是否超出一个块大小
"jl 1b\n" \
"3:" \
:"=c" (__res):"c" (0),"S" (addr):"ax","dx","si"); \
__res;})
2、释放掉设备上数据区的逻辑块block:首先根据设备号和逻辑块号查找到对应的缓存头结构;根据引用次数分两种情况来处理,如果引用次数大于1,就递减引用就可以;如果是多次引用,则修改缓存块的属性值;最后再得到逻辑块号,设置逻辑块位图就可以了;
//释放设备dev上数据区中的逻辑块block
//复位指定逻辑块block对应的逻辑块位图比特位
//dev设备号,block逻辑块号
void free_block(int dev, int block)
{
struct super_block * sb;
struct buffer_head * bh;
if (!(sb = get_super(dev)))//得到超级块
panic("trying to free block on nonexistent device");
if (block < sb->s_firstdatazone || block >= sb->s_nzones)//确定逻辑块号范围
panic("trying to free block not in datazone");
//首先查找下要释放的逻辑块是否映射到了缓存区,如果有就需要释放掉缓存区的数据
bh = get_hash_table(dev,block);//查找到逻辑块映射的缓存区块
if (bh) {
if (bh->b_count != 1) {//因为上面能够查找到,所以引用一定大于等于1
printk("trying to free block (%04x:%d), count=%d\n",
dev,block,bh->b_count);//brelse(bh),递减掉一个引用就可以
return;
}
bh->b_dirt=0;
bh->b_uptodate=0;
brelse(bh);//上面是释放映射的缓存块,如果没有映射到缓存块就不需要操作
}
//逻辑块位图比特位1代表数据区中逻辑块的第一块(逻辑块位图0不用)
block -= sb->s_firstdatazone - 1 ;//开始的block是该块在整个文件系统中的块号,操作后表示该块在数据区的块号
if (clear_bit(block&8191,sb->s_zmap[block/8192]->b_data)) {//block&8191 获取到该块在某一块位图中的偏移量;
//sb->s_zmap[block/8192]->b_data获取到block所在位图块
printk("block (%04x:%d) ",dev,block+sb->s_firstdatazone-1);
panic("free_block: bit already cleared");
}
sb->s_zmap[block/8192]->b_dirt = 1;//该块已经修改
}
//向设备申请一个逻辑块
int new_block(int dev)
{
struct buffer_head * bh;
struct super_block * sb;
int i,j;
if (!(sb = get_super(dev)))//得到超级块
panic("trying to get new block from nonexistant device");
//8块逻辑块位图,每块8192个比特位
j = 8192;
for (i=0 ; i<8 ; i++)
if (bh=sb->s_zmap[i])
if ((j=find_first_zero(bh->b_data))<8192)//在一个逻辑块中查找一个空闲的比特位
break;
if (i>=8 || !bh || j>=8192)
return 0;//没找到比特位为0的
if (set_bit(j,bh->b_data))//在相应逻辑块位图中的相应位置1
panic("new_block: bit already set");
bh->b_dirt = 1;
j += i*8192 + sb->s_firstdatazone-1;//把j转换成逻辑块号
if (j >= sb->s_nzones)//逻辑块号大于总的逻辑块总数,出错返回
return 0;
if (!(bh=getblk(dev,j)))//根据设备号,和逻辑块号获取一个缓存块
panic("new_block: cannot get block");
if (bh->b_count != 1)
panic("new block: count is != 1");
clear_block(bh->b_data);
bh->b_uptodate = 1;
bh->b_dirt = 1;
brelse(bh);
return j;//返回逻辑块号
}
4、释放i节点,这个比较简单,主要是判断下i节点的一些属性;
//释放指定的i节点
void free_inode(struct m_inode * inode)
{
struct super_block * sb;
struct buffer_head * bh;
if (!inode)
return;
if (!inode->i_dev) {//设备号为0,表示没有使用
memset(inode,0,sizeof(*inode));//对inode清零
return;
}
if (inode->i_count>1) {//如果i节点还有其他程序引用,则不能释放
printk("trying to free inode with count=%d\n",inode->i_count);
panic("free_inode");
}
if (inode->i_nlinks)//不为0 表示还有其他文件目录项在引用
panic("trying to free inode with links");
if (!(sb = get_super(inode->i_dev)))//获取超级块,判断设备是否存在
panic("trying to free inode on nonexistent device");
if (inode->i_num < 1 || inode->i_num > sb->s_ninodes)//i节点号范围
panic("trying to free inode 0 or nonexistant inode");
//得到i节点所在的缓存块中
if (!(bh=sb->s_imap[inode->i_num>>13]))// 整除8192得到该i节点在哪块缓存块中
panic("nonexistent imap in superblock");
//清除i节点所在的i节点位图中比特位
if (clear_bit(inode->i_num&8191,bh->b_data))
printk("free_inode: bit already cleared.\n\r");
bh->b_dirt = 1;//设置表示该缓存区修改过
memset(inode,0,sizeof(*inode));
}
//为设备dev建设一个新i节点,初始化并返回该新i节点的指针
struct m_inode * new_inode(int dev)
{
struct m_inode * inode;
struct super_block * sb;
struct buffer_head * bh;
int i,j;
if (!(inode=get_empty_inode()))//得到一个空闲的i节点,会把它的引用设置为1
return NULL;
if (!(sb = get_super(dev)))
panic("new_inode with unknown device");
j = 8192;
for (i=0 ; i<8 ; i++)
if (bh=sb->s_imap[i])
if ((j=find_first_zero(bh->b_data))<8192)
break;
if (!bh || j >= 8192 || j+i*8192 > sb->s_ninodes) {
iput(inode);//释放i节点内存
return NULL;
}
if (set_bit(j,bh->b_data))
panic("new_inode: bit already set");
bh->b_dirt = 1;
inode->i_count=1;
inode->i_nlinks=1;
inode->i_dev=dev;
inode->i_uid=current->euid;
inode->i_gid=current->egid;
inode->i_dirt=1;
inode->i_num = j + i*8192;
inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
return inode;//返回i节点指针
}
如果有什么不正确之处,欢迎大家指正,一起努力,共同学习!!