首页
学习
活动
专区
圈层
工具
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

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

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

代码语言:javascript
复制
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

代码语言:javascript
复制
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;
}

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

代码语言:javascript
复制
/*
    新建一个数据块,首先利用超级块的块位图信息找到一个可用的数据块,
    然后读进来,清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是脏的,等待回写到硬盘。所以我们看到,我们写文件的时候,数据不是直接到硬盘的,只是在缓存里,系统会有线程定期更新缓存到硬盘。

下一篇
举报
领券