linux0.11源码分析-文件节点的iput于iget

642 阅读10分钟

inode

inode用来代表一个文件,这里的文件很宽泛,可以是我们熟知的普通文件,可以是目录,可以是管道等。磁盘的inode表里就存着inode信息。

磁盘上的inode结构是

struct d_inode {
	unsigned short i_mode;
	unsigned short i_uid;
	unsigned long i_size;
	unsigned long i_time;
	unsigned char i_gid;
	unsigned char i_nlinks;
	unsigned short i_zone[9];  //直接块(0-6)、间接(7)或双重间接(8)逻辑块号
};

在内存中的inode结构是

struct m_inode {
        //与d_inode一致
	unsigned short i_mode;
	unsigned short i_uid;
	unsigned long i_size;
	unsigned long i_mtime;
	unsigned char i_gid;
	unsigned char i_nlinks;
	unsigned short i_zone[9]; //直接块(0-6)、间接(7)或双重间接(8)逻辑块号
    
        /* these are in memory also */
	struct task_struct * i_wait;
	unsigned long i_atime;
	unsigned long i_ctime;
	unsigned short i_dev;
	unsigned short i_num;
	unsigned short i_count;
	unsigned char i_lock;
	unsigned char i_dirt;
	unsigned char i_pipe;
	unsigned char i_mount;
	unsigned char i_seek;
	unsigned char i_update;
};

iput

inode如果是一个管道,则唤醒等待该管道的进程,然后把inode的引用数减去1,如果为0表示没有引用了,可以释放管道占用的内存了。如果是块设备先把缓冲区、inode、设备三个之间数据同步。

fs/inode.c

void iput(struct m_inode * inode)
{

	if (!inode)
		return;
        
       //如果inode被锁,就等待 
	wait_on_inode(inode); 
    	
	if (!inode->i_count) //inode还在使用
		panic("iput: trying to free free inode");
        
    	// 如果是管道i节点,则唤醒等待该管道的进程,引用次数减1,如果还有引用则返回。
    	// 否则释放管道占用的内存页面,并复位该节点的引用计数值、已修改标志和管道标志,
    	// 并返回。对于管道节点,inode->i_size存放这内存也地址。
	if (inode->i_pipe) {
		wake_up(&inode->i_wait);
		if (--inode->i_count)
			return;
		free_page(inode->i_size);
		inode->i_count=0;
		inode->i_dirt=0;
		inode->i_pipe=0;
		return;
	}
    
    // 如果i节点对应的设备号 = 0,则将此节点的引用计数递减1,返回。例如用于管道操作
    // 的i节点,其i节点的设备号为0.
	if (!inode->i_dev) {
		inode->i_count--;
		return;
	}
    
    // 如果是块设备文件的i节点,此时逻辑块字段0(i_zone[0])中是设备号,则刷新该设备。
    // 并等待i节点解锁。
	if (S_ISBLK(inode->i_mode)) {
		sync_dev(inode->i_zone[0]);
		wait_on_inode(inode);
	}
    
    // 如果i节点的引用计数大于1,则计数递减1后就直接返回(因为该i节点还有人在用,不能
    // 释放),否则就说明i节点的引用计数值为1。如果i节点的链接数为0,则说明i节点对应文件
    // 被删除。于是释放该i节点的所有逻辑块,并释放该i节点。函数free_inode()用于实际释
    // 放i节点操作,即复位i节点对应的i节点位图bit位,清空i节点结构内容。
repeat:
	if (inode->i_count>1) {
		inode->i_count--;  //如果i节点的引用计数大于1,则计数递减1后就直接返回,因为该i节点还有人在用,不能释放
		return;
	}
    
        // i节点的引用计数值为1
	if (!inode->i_nlinks) { // 如果i节点的链接数为0,则说明i节点对应文件被删除。于是释放该i节点的所有逻辑块,并释放该i节点。
		truncate(inode);
		free_inode(inode);
		return;
	}
    
    // 如果该i节点已做过修改,则回写更新该i节点,并等待该i节点解锁。由于这里在写i节点
    // 时需要等待睡眠,此时其他进程有可能修改i节点,因此在进程被唤醒后需要再次重复进行
    // 上述判断过程(repeat)。
	if (inode->i_dirt) {
		write_inode(inode); //把inode信息所对应的块读取到缓冲区,使用inode信息更新缓冲区中的数据,然后缓冲区等待回写到磁盘 
		wait_on_inode(inode);
		goto repeat;
	}
    // 程序若能执行到此,则说明该i节点的引用计数值i_count是1、链接数不为零,并且内容
    // 没有被修改过。因此此时只要把i节点引用计数递减1,返回。此时该i节点的i_count=0,
    // 表示已释放。
	inode->i_count--;
	return;
}

read_inode

根据inode节点号计算块号,当然这个块可能不止这一个inode信息,然后把指定块号的数据读取到缓冲区中。

static void read_inode(struct m_inode * inode)
{
	struct super_block * sb;
	struct buffer_head * bh;
	int block;

    // 首先锁定该i节点,并取该节点所在设备的超级块。
	lock_inode(inode);
	if (!(sb=get_super(inode->i_dev)))
		panic("trying to read inode without dev");
        
    // 该i节点所在的设备逻辑块号=(启动块+超级块)+i节点位图占用的块数+逻辑块位图占用的块数
    // +(i节点号-1)/每块含有的i节点数。虽然i节点号从0开始编号,但第i个0号i节点不用,并且
    // 磁盘上也不保存对应的0号i节点结构。因此存放i节点的盘块的第i块上保存的是i节点号是1--16
    // 的i节点结构而不是0--15的。因此在上面计算i节点号对应的i节点结构所在盘块时需要减1,即:
    // B=(i节点号-1)/每块含有i节点结构数。例如,节点号16的i节点结构应该在B=(16-1)/16 = 0的
    // 块上。这里我们从设备上读取该i节点所在的逻辑块,并复制指定i节点内容到inode指针所指位置处。
	block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks +(inode->i_num-1)/INODES_PER_BLOCK;
	if (!(bh=bread(inode->i_dev,block)))
		panic("unable to read i-node block");
        
	*(struct d_inode *)inode =((struct d_inode *)bh->b_data)[(inode->i_num-1)%INODES_PER_BLOCK];
    
    // 最后释放读入的缓冲块,并解锁该i节点。
	brelse(bh);
	unlock_inode(inode);
}

write_inode

先根据inode节点号就可以计算出块号,把这个设备的块号读取到缓冲区中,然后把缓冲区中相应的的inode信息更新,修改b_dirt置为1,等待回写到磁盘。唤醒等待在缓冲区上的进程

static void write_inode(struct m_inode * inode)
{
	struct super_block * sb;
	struct buffer_head * bh;
	int block;

    // 首先锁定该i节点,如果该i节点没有被修改或者该i节点的设备号等于零,则解锁该i节点,并退出。
    // 对于没有被修改过的i节点,其内容与缓冲区中或设备中的相同。然后获取该i节点的超级块。
	lock_inode(inode);
	if (!inode->i_dirt || !inode->i_dev) {
		unlock_inode(inode);
		return;
	}
	if (!(sb=get_super(inode->i_dev)))
		panic("trying to write inode without device");
        
    // 该i节点所在的设备逻辑块号=(启动块+超级块)+i节点位图占用的块数+逻辑块位图占用的块数
    // +(i节点号-1)/每块含有的i节点数。我们从设备上读取i节点所在的逻辑块,并将该i节点信息复制
    // 到逻辑块对应i节点的项位置处。
	block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks + (inode->i_num-1)/INODES_PER_BLOCK;
        
	if (!(bh=bread(inode->i_dev,block)))
		panic("unable to read i-node block");
       
       //找到数据块中inode所属的位置,写到高速缓存等待回写到硬盘 
	((struct d_inode *)bh->b_data)[(inode->i_num-1)%INODES_PER_BLOCK] =*(struct d_inode *)inode;
            
    // 然后置缓冲区已修改标志,而i节点内容已经与缓冲区中的一致,因此修改标志置零。然后释放该
    // 含有i节点的缓冲区,并解锁该i节点。
	bh->b_dirt=1;  // 等待回写硬盘
	inode->i_dirt=0;
	brelse(bh); //把bh的引用数减去1,唤醒等待在缓冲区上的进程
	unlock_inode(inode);
}
// 每个块有几个inode,硬盘里是d_inode,内存是m_inode结构
#define INODES_PER_BLOCK ((BLOCK_SIZE)/(sizeof (struct d_inode)))

// 每个块块包含的目录项数
#define DIR_ENTRIES_PER_BLOCK ((BLOCK_SIZE)/(sizeof (struct dir_entry)))

当块大小是1024字节时,0号块保留,1号块是超级块

sync_dev

先把缓冲区中的数据同步到指定设备,腾出缓冲区空间,给后面inode信息同步提供空间,然后把inode_table表中的inode信息同步到设备中,再把因为同步inode信息时变脏的缓冲区再次回写到设备中。 这样缓冲区是最新的数据,inode信息也是最新的了。

int sync_dev(int dev)
{
	int i;
	struct buffer_head * bh;
    
	bh = start_buffer;
	for (i=0 ; i<NR_BUFFERS ; i++,bh++) {
		if (bh->b_dev != dev)  // 不是设备dev的缓冲块则继续
			continue;
		wait_on_buffer(bh);  
		if (bh->b_dev == dev && bh->b_dirt)
			ll_rw_block(WRITE,bh); //把缓冲区数据写到磁盘
	}
    
 
	sync_inodes();  // 把inode_table表中的inode信息同步到磁盘
	
        // 把因为同步inode的缓冲区回写到设备中
	bh = start_buffer;
	for (i=0 ; i<NR_BUFFERS ; i++,bh++) {
		if (bh->b_dev != dev)
			continue;
		wait_on_buffer(bh);
		if (bh->b_dev == dev && bh->b_dirt)
			ll_rw_block(WRITE,bh);
	}
	return 0;
}

sync_inodes

inode_table中把非管道类型的inode信息同步到磁盘,inode_table数组大小是32,linux0.11只能打开32个文件。 这32个inode是所有进程共享的。

void sync_inodes(void)
{
	int i;
	struct m_inode * inode;
	inode = 0+inode_table;   // inode 表
	for(i=0 ; i<NR_INODE ; i++,inode++) {  // NR_INODE = 32
		wait_on_inode(inode);
		if (inode->i_dirt && !inode->i_pipe)
			write_inode(inode);
	}
}

iget

fs/inode.c

根据指定节点号获取一个inode。 先从inode表中获取一个空闲的inode,这个空闲的inode不一定用的上,然后遍历inode表,从表里找到一个指定设备号与节点号的inode,如果找到,因为我们马上就要用这个inode,所以这个inode的引用数又要加1

struct m_inode * iget(int dev,int nr)
{
	struct m_inode * inode, * empty;
    
	if (!dev)
		panic("iget with dev==0");
        
	empty = get_empty_inode(); // 从inode表里找到一个空闲的inode
    
    // 接着扫描i节点表。寻找参数指定节点号nr的i节点。并递增该节点的引用次数。如果当
    // 前扫描i节点的设备号不等于指定的设备号或者节点号不等于指定的节点号,则继续扫描。
	inode = inode_table;
	while (inode < NR_INODE+inode_table) {
		if (inode->i_dev != dev || inode->i_num != nr) {
			inode++;
			continue;
		}
        // 如果找到指定设备号dev和节点号nr的i节点,则等待该节点解锁。在等待该节点解
        // 锁过程中,i节点表可能会发生变化。所以再次进行上述相同判断。如果发生了变化,
        // 则再次重新扫描整个i节点表。
		wait_on_inode(inode);
		if (inode->i_dev != dev || inode->i_num != nr) {
			inode = inode_table;
			continue;
		}
        
        // 到这里表示找到相应的i节点。于是将该i节点引用计数增1.然后再做进一步检查,看它
        // 是否是另一个文件系统的安装点。若是则寻找被安装文件系统根节点并返回。如果
        // 该i节点的确是其他文件系统的安装点,则在超级块表中搜寻安装在此i节点的超级块。
        // 如果没有找到,则显示出错信息,并放回本函数开始时获取的空闲节点empty,
        // 返回该i节点指针。
		inode->i_count++;
		if (inode->i_mount) {
			int i;

			for (i = 0 ; i<NR_SUPER ; i++)
				if (super_block[i].s_imount==inode)
					break;
			if (i >= NR_SUPER) {
				printk("Mounted inode hasn't got sb\n");
				if (empty)
					iput(empty);
				return inode;
			}
            // 执行到这里表示已经找到安装到inode节点的文件系统超级块。于是将该i节点写盘
            // 放回,并从安装在次i节点上的文件系统超级块中取设备号,并令i节点号为ROOT_INO,
            // 即为1.然后重新扫描整个i节点表,以获取该被安装文件系统的根i节点信息。
			iput(inode);
			dev = super_block[i].s_dev;
			nr = ROOT_INO;
			inode = inode_table;
			continue;
		}
        // 最终我们找到了相应的i节点。因此可以放弃本函数开始处临时申请的空闲的i节点,返回
        // 找到的i节点指针。
		if (empty)
			iput(empty);
		return inode;
	}
    
    // 如果我们在i节点表中没有找到指定的i节点,则利用前面申请的空闲i节点empty在i节点表中
    // 建立该i节点。并从相应设备上读取该i节点信息,返回该i节点指针。
	if (!empty)
		return (NULL);
	inode=empty;
	inode->i_dev = dev;
	inode->i_num = nr;
	read_inode(inode);
	return inode;
}

get_empty_inode

inode_table中找到一个引用数i_count等于0,并且i_dirt等于0和没有上锁的inode,如果没找到就会一直找。

struct m_inode * get_empty_inode(void)
{
	struct m_inode * inode;
	static struct m_inode * last_inode = inode_table; //全局的inode表
    
	int i;

	do {
		inode = NULL;
		for (i = NR_INODE; i ; i--) {
			if (++last_inode >= inode_table + NR_INODE) 
				last_inode = inode_table; //又要从头开始来了
			if (!last_inode->i_count) {
				inode = last_inode;
				if (!inode->i_dirt && !inode->i_lock)
					break; // 找到空闲inode了,退出for循环
			}
		}
        
        
        	// 如果没有找到空闲i节点(inode=NULL),则将i节点表打印出来供调试使用,并停机。
		if (!inode) {
			for (i=0 ; i<NR_INODE ; i++)
				printk("%04x: %6d\t",inode_table[i].i_dev,
					inode_table[i].i_num);
			panic("No free inodes in mem");
		}
        
		wait_on_inode(inode); //如果被锁,就等待
        
        	//睡眠期间很可能被其他进程修改,先同步信息
		while (inode->i_dirt) {
			write_inode(inode);
			wait_on_inode(inode);
		}
        
        //睡眠期间很可能被其他进程占用,那么这个inode就不选是空闲节点了,重新寻找
	} while (inode->i_count);
    
        // 终于从inode表里找到一个空闲的indoe,初始化内存区域为0
	memset(inode,0,sizeof(*inode));
	inode->i_count = 1; //初始引用数为1 
	return inode;
}