linux系统调用之write源码解析(基于linux0.11)

write函数的部分逻辑和read相似。我们先看入口函数。

int sys_write(unsigned int fd,char * buf,int count)
{
	struct file * file;
	struct m_inode * inode;
	
	if (fd>=NR_OPEN || count <0 || !(file=current->filp[fd]))
		return -EINVAL;
	if (!count)
		return 0;
	inode=file->f_inode;
	if (inode->i_pipe)
		return (file->f_mode&2)?write_pipe(inode,buf,count):-EIO;
	if (S_ISCHR(inode->i_mode))
		return rw_char(WRITE,inode->i_zone[0],buf,count,&file->f_pos);
	if (S_ISBLK(inode->i_mode))
		return block_write(inode->i_zone[0],&file->f_pos,buf,count);
	if (S_ISREG(inode->i_mode))
		return file_write(inode,file,buf,count);
	printk("(Write)inode->i_mode=%06o\n\r",inode->i_mode);
	return -EINVAL;
}

这里我们只分析一般文件的写。接着我看file_write

int file_write(struct m_inode * inode, struct file * filp, char * buf, int count)
{
	off_t pos;
	int block,c;
	struct buffer_head * bh;
	char * p;
	int i=0;

/*
 * ok, append may not work when many processes are writing at the same time
 * but so what. That way leads to madness anyway.
 */
	// 如果设置了追加标记位,则更新当前位置指针到文件最后一个字节
	if (filp->f_flags & O_APPEND)
		pos = inode->i_size;
	else
		pos = filp->f_pos;
	// i为已经写入的长度,count为需要写入的长度
	while (i<count) {
		// 读取一个硬盘的数据块,如果没有则创建一个块,即标记硬盘中这个块已经被使用
		if (!(block = create_block(inode,pos/BLOCK_SIZE)))
			break;
		// 然后根据返回的块号把这个块内容读进来
		if (!(bh=bread(inode->i_dev,block)))
			break;
		c = pos % BLOCK_SIZE;
		p = c + bh->b_data; // 开始写入数据的位置
		bh->b_dirt = 1; // 标记数据需要回写硬盘
		c = BLOCK_SIZE-c; // 算出能写的长度
		if (c > count-i) c = count-i; // 比较能写的长度和还需要写的长度,取小的
		pos += c; // 更新偏移指针,c为准备写入的长度
		// 如果超过原来长度则需要更新i_size字段,标记inode需要回写
		if (pos > inode->i_size) {
			inode->i_size = pos;
			inode->i_dirt = 1;
		}
		i += c; // 更新已经写入的长度
		while (c-->0)
			*(p++) = get_fs_byte(buf++);
		brelse(bh);
	}
	inode->i_mtime = CURRENT_TIME;
	if (!(filp->f_flags & O_APPEND)) {
		filp->f_pos = pos;
		inode->i_ctime = CURRENT_TIME;
	}
	return (i?i:-1);
}
file_write的大概逻辑就是根据inode中记录的文件信息,根据需要写入的位置算出,硬盘的位置或者说块号。如果对应的块已经存在,则直接返回对应的块号,否则需要新建块。我们看bmap函数。
// 查找inode中第block块对应硬盘的块号
int bmap(struct m_inode * inode,int block)
{
	return _bmap(inode,block,0);
}
// 找到inode中块号为block的块对应哪个硬盘块号或如果没有该块则在硬盘中新建一个块
static int _bmap(struct m_inode * inode,int block,int create)
{
	struct buffer_head * bh;
	int i;

	if (block<0)
		panic("_bmap: block<0");
	// 文件的大小最大值,(7+512+512*512) * 硬盘每块的大小
	if (block >= 7+512+512*512)
		panic("_bmap: block>big");
	// 块号小于7则直接在i_zone数组的前面7个中找就行
	if (block<7) {
		// 如果是创建模式并且该索引为空则创建一个块
		if (create && !inode->i_zone[block])
			// 保存块号
			if (inode->i_zone[block]=new_block(inode->i_dev)) {
				inode->i_ctime=CURRENT_TIME;
				// 该inode需要回写硬盘
				inode->i_dirt=1;
			}
		// 返回硬盘中的块号
		return inode->i_zone[block];
	}
	block -= 7;
	if (block<512) {
		if (create && !inode->i_zone[7])
			if (inode->i_zone[7]=new_block(inode->i_dev)) {
				inode->i_dirt=1;
				inode->i_ctime=CURRENT_TIME;
			}
		if (!inode->i_zone[7])
			return 0;
		// 索引为7的块是间接块,需要把内容读进来才知道具体的硬盘块号
		if (!(bh = bread(inode->i_dev,inode->i_zone[7])))
			return 0;
		// 直接根据block取得对应的硬盘块号
		i = ((unsigned short *) (bh->b_data))[block];
		// 之前没有,新建一个块
		if (create && !i)
			if (i=new_block(inode->i_dev)) {
				((unsigned short *) (bh->b_data))[block]=i;
				bh->b_dirt=1;
			}
		brelse(bh);
		return i;
	}
	block -= 512;
	if (create && !inode->i_zone[8])
		if (inode->i_zone[8]=new_block(inode->i_dev)) {
			inode->i_dirt=1;
			inode->i_ctime=CURRENT_TIME;
		}
	if (!inode->i_zone[8])
		return 0;
	// 先取得一级索引对应的数据,数据中的每一项对应512个项
	if (!(bh=bread(inode->i_dev,inode->i_zone[8])))
		return 0;
	// 每一个索引对应512个项,所以除以512,即右移9位,取得二级索引
	i = ((unsigned short *)bh->b_data)[block>>9];
	if (create && !i)
		if (i=new_block(inode->i_dev)) {
			((unsigned short *) (bh->b_data))[block>>9]=i;
			bh->b_dirt=1;
		}
	brelse(bh);
	if (!i)
		return 0;
	// 取得二级索引对应的数据
	if (!(bh=bread(inode->i_dev,i)))
		return 0;
	// 算出偏移,最大偏移是511,所以&511
	i = ((unsigned short *)bh->b_data)[block&511];
	if (create && !i)
		if (i=new_block(inode->i_dev)) {
			((unsigned short *) (bh->b_data))[block&511]=i;
			bh->b_dirt=1;
		}
	brelse(bh);
	return i;
}

然后再看一下创建新块的逻辑。

/*
	新建一个数据块,首先利用超级块的块位图信息找到一个可用的数据块,
	然后读进来,清0,等待回写,返回块号
*/
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++)
		//s_zmap[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;
	// 置第j个数据块已使用标记
	if (set_bit(j,bh->b_data))
		panic("new_block: bit already set");
	// 该位图对应的buffer需要回写
	bh->b_dirt = 1;
	// 位图存在多个块中,i为第i个块,每个块对应的位图管理着8192个数据块
	j += i*8192 + sb->s_firstdatazone-1; // 算出块号
	// 超过了最大块号
	if (j >= sb->s_nzones)
		return 0;
	// 拿到一个buffer,然后清0,等待回写
	if (!(bh=getblk(dev,j)))
		panic("new_block: cannot get block");
	if (bh->b_count != 1)
		panic("new block: count is != 1");
	// 清0防止脏数据
	clear_block(bh->b_data);
	// 内容是最新的
	bh->b_uptodate = 1;
	// 需要回写硬盘,因为新建的内容在硬盘还没有
	bh->b_dirt = 1;
	brelse(bh);
	return j;
}

创建新块就是在文件系统的超级块结构中,根据当前块的使用情况,申请一个新的块,并标记这个块已经使用。然后把超级块的信息回写到硬盘,并且返回新建的块号。
我们回到file_write函数,处理完块的逻辑后,就需要把块的内容读进来,因为是新块,所以内容都是0。其中bread函数的逻辑可以参考read函数分析那篇文章。内容读进来后,存在buffer中,我们就可以把用户的数据写进去了,然后标记这个buffer是脏的,等待回写到硬盘。所以我们看到,我们写文件的时候,数据不是直接到硬盘的,只是在缓存里,系统会有线程定期更新缓存到硬盘。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值