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是脏的,等待回写到硬盘。所以我们看到,我们写文件的时候,数据不是直接到硬盘的,只是在缓存里,系统会有线程定期更新缓存到硬盘。