Linux0.11内核源码解析-bitmap.c

目录

bitmap.c在内核中的作用

什么是位图操作?

代码介绍

clear_block(addr)

set_bit & clear_bit(nr, addr)

find_first_zero(addr)查找第一位为0

free_block(int dev, int block)释放block

int new_block(int dev)分配一个新的数据块

free_inode(struct m_inode * inode)清空节点的内容

struct m_inode * new_inode(int dev)创建节点


bitmap.c在内核中的作用

linux0.11内核bitmap.c主要实现了位图操作相关的函数。

什么是位图操作?

位图是一种用于描述二进制状态的数据结构,常用于表示数据是否存在、已使用等情况。在操作系统中,位图常用于管理内存分配、文件系统的索引节点等。

具体来说,bitmap.c实现了以下功能:

  1. 初始化位图:将位图中所有位都置为指定的状态(0或1)。
  2. 设置位图中指定位的状态:将指定位的状态设置为指定的值(0或1)。
  3. 在位图中查找指定范围内第一个可用的位:从指定位置开始,查找第一个值为指定值(0或1)的位,并返回该位的索引值。
  4. 在位图中查找指定范围内连续的可用位:从指定位置开始,查找指定数量的值为指定值(0或1)的连续位,并返回该连续位的起始索引值。
  5. 在位图中设置指定范围内的位值:将指定范围内的位全部设置为指定的值(0或1)。

这些功能都是在操作系统内存管理和文件系统管理过程中非常重要的,因此位图操作也是操作系统内核开发中的重要知识点。

代码介绍

clear_block(addr)

功能:指定地址开始的连续块清零

#define clear_block(addr) \
__asm__ __volatile__ ("cld\n\t" \
	"rep\n\t" \
	"stosl" \
	::"a" (0),"c" (BLOCK_SIZE/4),"D" ((long) (addr)))

指令介绍:

cld指令用于清除方向标志位

rep指令用于重复执行stosl指令

stosl指令将eax寄存器的值保存到es:edi,并将DF寄存器指定的方向标志位进行移位

在这段代码中,eax寄存器被赋值0,表示将要写入的值为0,ecx寄存器被赋值BLOCK_SIZE/4,表示要清零的字节数,edi寄存器被赋值addr,表示要清零的起始地址

set_bit & clear_bit(nr, addr)

功能:实现了位图中设置或清除某一位的操作,其中set_bit宏用于将位图中的某一位设置为1,clear_bit宏用于将位图中的某一位清零。

#define set_bit(nr,addr) ({\
register int res ; \
__asm__ __volatile__("btsl %2,%3\n\tsetb %%al": \
"=a" (res):"0" (0),"r" (nr),"m" (*(addr))); \
res;})


#define clear_bit(nr,addr) ({\
register int res ; \
__asm__ __volatile__("btrl %2,%3\n\tsetnb %%al": \
"=a" (res):"0" (0),"r" (nr),"m" (*(addr))); \
res;})

btsl (bit test and set long) :将某个位清零,具体操作是将寄存器中指定位设为1,保存原bit值到CF标志位

tsetb (set byte): 指令将CF标志位中的值写入到AL寄存器

btrl (bit test and reset long) 指令来实现:将某个位清零,具体操作是将寄存器中指定位设为0,保存原bit值到CF标志位

 setnb (set non-byte) :指令将CF标志位取反并写入到AL寄存器

find_first_zero(addr)查找第一位为0

这段代码是一个内联汇编,作用是在给定的地址上查找第一个为0的位,并返回该位的索引。具体实现方式是:

  1. 初始化一个结果变量__res和ecx寄存器的值都为0。
  2. 使用lodsl指令将地址addr所指向的4个字节数据加载到eax寄存器中。
  3. 对eax寄存器的值取反,并使用bsfl指令查找第一个被置为1的位,并将其索引存储到edx寄存器中。如果eax寄存器的值为0,则跳转到步骤2。
  4. 将找到的位的索引加上ecx寄存器的值(已查询过的位数),得到该位在addr中的绝对索引,并存储到ecx寄存器中。
  5. 如果没有查找完所有位,则跳转到步骤2继续查找,否则返回结果。

该内联汇编指令返回查找到的第一个为0的位的索引(从0开始计数)。注意,这个代码的实现针对的是32位体系结构,且只能处理长度不超过32位的数据,因为它仅使用了eax寄存器来存储待查找的位。

#define find_first_zero(addr) ({ \
int __res; \
__asm__ __volatile__ ("cld\n" \
	"1:\tlodsl\n\t" \
	"notl %%eax\n\t" \
	"bsfl %%eax,%%edx\n\t" \
	"je 2f\n\t" \
	"addl %%edx,%%ecx\n\t" \
	"jmp 3f\n" \
	"2:\taddl $32,%%ecx\n\t" \
	"cmpl $8192,%%ecx\n\t" \
	"jl 1b\n" \
	"3:" \
	:"=c" (__res):"c" (0),"S" (addr)); \
__res;})

lodsl:从DS:[ESI]指向的内存地址读取32位数据,存储在EAX寄存器中,并将ESI自动加上4(因为读取了4个字节)

notl:32位整数按位取反

bsfl:查找一个32位整数中从低位开始第一个为0的位。bsfl指令有两个操作数,其中第一个操作数指定要查找的源操作数,第二个操作数指定结果的目标寄存器

free_block(int dev, int block)释放block

该函数的作用是释放指定设备上的一个数据块,具体来说,该函数接受两个参数,一个是设备编号dev,一个是要释放的数据块编号block。函数的主要实现流程如下:

  1. 首先通过get_super函数获取指定设备的超级块结构体指针,如果获取失败则调用panic函数输出错误信息并终止程序运行。

  2. 检查要释放的数据块是否在设备的数据区内,如果不是则调用panic函数输出错误信息并终止程序运行。

  3. 接下来通过get_hash_table函数获取指定设备和指定块号的缓冲块结构体指针bh。

  4. 如果获取到了缓冲块结构体指针bh,则检查该缓冲块的引用计数b_count是否为1,如果不是则输出错误信息并直接返回。

  5. 将缓冲块的b_dirt和b_uptodate标志位都设为0,表示该缓冲块的数据已经过时,需要重新读取,然后调用brelse函数释放该缓冲块。

  6. 接下来将要释放的数据块编号block转换为相对于数据区第一个数据块的偏移量,即block -= sb->s_firstdatazone - 1。

  7. 然后根据block所在的位图块号和位图块内的偏移量,找到对应的位图项,并将其所对应的位设置为0,表示该数据块现在是空闲的。如果该位已经是0,说明该数据块已经被释放过了,此时输出错误信息并调用panic函数终止程序运行。

  8. 最后将位图块的b_dirt标志位置为1,表示该位图块的内容已经被修改,需要更新到磁盘上。

总的来说,该函数的作用是释放指定设备上的一个数据块,并更新对应的位图块的内容,主要用于文件系统的管理,例如在删除文件时需要释放该文件所占用的数据块等。

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) {
			printk("trying to free block (%04x:%d), count=%d\n",
				dev,block,bh->b_count);
			return;
		}
		bh->b_dirt=0;
		bh->b_uptodate=0;
		brelse(bh);
	}
	block -= sb->s_firstdatazone - 1 ;
	if (clear_bit(block&8191,sb->s_zmap[block/8192]->b_data)) {
		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)分配一个新的数据块

该函数的作用是为指定设备分配一个新的数据块,并返回该数据块的块号。

函数的主要实现流程如下:

  1. 首先通过get_super函数获取指定设备的超级块结构体指针sb,如果获取失败则调用panic函数输出错误信息并终止程序运行。

  2. 然后从位图块中找到一个未被占用的数据块,找到之后将位图块对应的位设置为1,并将该位图块的b_dirt标志位置为1,表示该位图块的内容已经被修改,需要更新到磁盘上。

  3. 根据位图块的编号i和位图块内的偏移量j,计算出新分配的数据块的块号j,并检查该块号是否超出了设备的数据区范围。如果超出范围则返回0,表示分配失败。

  4. 调用getblk函数获取新分配的数据块对应的缓冲块结构体指针bh。

  5. 检查缓冲块的引用计数b_count是否为1,如果不是则调用panic函数输出错误信息并终止程序运行。

  6. 调用clear_block函数将该数据块的内容清零,并将缓冲块的b_uptodate和b_dirt标志位置为1,表示该数据块的内容已经被修改,需要写回磁盘。

  7. 最后调用brelse函数释放该缓冲块,并返回新分配的数据块的块号。

总的来说,该函数的作用是为指定设备分配一个新的数据块,并将其清空,准备用于存储文件数据等信息。该函数主要用于文件系统的管理,例如在创建新文件时需要为其分配空间等。

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");
	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;
	if (set_bit(j,bh->b_data))
		panic("new_block: bit already set");
	bh->b_dirt = 1;
	j += i*8192 + sb->s_firstdatazone-1;
	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;
}

free_inode(struct m_inode * inode)清空节点的内容

该函数的作用是释放一个指定的inode节点,并清空该节点的内容。

函数的主要实现流程如下:

  1. 首先检查参数inode是否为空,如果为空则直接返回,不做处理。

  2. 如果inode的设备编号为0,表示该inode是个空节点,需要将其清空。

  3. 如果inode的引用计数i_count大于1,表示该节点正在被其他进程使用,无法立即释放,此时调用panic函数输出错误信息并终止程序运行。

  4. 如果inode的链接数i_nlinks不为0,表示该节点还有链接指向它,无法立即释放,此时调用panic函数输出错误信息并终止程序运行。

  5. 根据inode的设备编号i_dev,获取对应设备的超级块结构体指针sb,如果获取失败则调用panic函数输出错误信息并终止程序运行。

  6. 检查inode的节点编号i_num是否在有效范围内,如果不在有效范围内则调用panic函数输出错误信息并终止程序运行。

  7. 通过节点编号i_num和位图块大小计算出inode所在的位图块,获取对应的缓冲块结构体指针bh。

  8. 将位图块对应的位清零,并将该位图块的b_dirt标志位置为1,表示该位图块的内容已经被修改,需要更新到磁盘上。

  9. 最后清空inode节点的内容,并返回。

总的来说,该函数的作用是释放一个指定的inode节点,并将其在位图块中对应的位清零,表示该节点已经被释放。该函数主要用于文件系统的管理,例如在删除文件时需要释放其占用的inode节点等。

void free_inode(struct m_inode * inode)
{
	struct super_block * sb;
	struct buffer_head * bh;

	if (!inode)
		return;
	if (!inode->i_dev) {
		memset(inode,0,sizeof(*inode));
		return;
	}
	if (inode->i_count>1) {
		printk("trying to free inode with count=%d\n",inode->i_count);
		panic("free_inode");
	}
	if (inode->i_nlinks)
		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)
		panic("trying to free inode 0 or nonexistant inode");
	if (!(bh=sb->s_imap[inode->i_num>>13]))
		panic("nonexistent imap in superblock");
	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));
}

struct m_inode * new_inode(int dev)创建节点

这段代码是用于在指定设备上创建新的inode节点。它在当前空闲的inode位图中找到未使用的位,并将其设置为已使用。然后,它更新有关inode的元数据,如节点编号、设备号、链接数、时间戳等,并返回一个指向该节点的指针。如果没有找到可用的inode位,该函数返回NULL。

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()))
		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);
		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;
}

你可能感兴趣的:(linux0.11内核源码,c语言,开发语言)