Linux文件写过程分析一(写过程中的锁)

来源:互联网 发布:数据脱敏的常用方法 编辑:程序博客网 时间:2024/05/23 00:02

【转】tracymacding的博客 http://tracymacding.blog.163.com/blog/static/212869299201301381838154/

Linux文件写过程分析一(写过程中的锁) 

1. 前言
本篇博客主要关注Linux文件的写过程在内核的处理流程,写过程的处理比较复杂,有许多细节问题值得深思,这里我们主要关注写过程中内核对文件以及页面的加锁情况处理,因为并行处理和锁一直以来是内核中最为复杂的细节之一。

2. 写流程粗解
Linux内核的一次写流程主要涉及到的函数流程大致如下描述:
sys_write(位于fs/read_write.c)
        ------>vfs_write(位于fs/read_write.c)
                ------>do_sync_write(因具体文件系统而异,对于ext2,位于fs/ext2/file.c)
                        ------>generic_file_aio_write(位于mm/filemap.c)
                                ------>__generic_file_aio_write(位于mm/filemap.c)
                                        ------>generic_file_buffered_write(位于mm/filemap.c)
                                                ------>generic_perform_write(位于mm/filemap.c)
                                                        ------>ext2_write_begin(因具体文件系统而异,对于ext2,位于fs/ext2/inode.c)
                                                                ------>block_write_begin(位于fs/buffer.c)
                                                                        ------>__block_write_begin(位于fs/buffer.c)
                                                                                ------>block_prepare_write(位于fs/buffer.c)
                                                        ------>iov_iter_copy_from_user_atomic(位于mm/filemap.c)
                                                        ------>ext2_write_end(因具体文件系统而异,对于ext2,位于fs/ext2/inode.c)
                                                                ------>generic_write_end(位于fs/buffer.c)
                                                                        ------>block_write_end(位于fs/buffer.c)
                                                                                ------>__block_commit_write(位于fs/buffer.c)

3. 写过程中加锁处理
        Linux对当前需要的文件数据和元数据在内存作缓存,且对文件数据按照页面为单位进行缓存。每个缓存页面在内存中仅存在一份,而系统中可能存在多个任务流(包含进程,中断,内核线程等)可能同时访问同一个页面,如果不采取保护措施,则可能会导致文件数据的不一致。因此,内核在写的执行流程中不可避免地要进行加锁等处理。本章节中我们就来仔细分析思考写过程中的锁

3.1 字节偏移锁
        首先,在写入之前,我们必须判断:当前要写入的位置没有被别人占用。作此判断是为了不会导致文件的同一个区域会被多个进程进行有冲突的读写操作。具体的原理与实现可参考另外一篇文章。该过程由vfs_write()调用rw_verify_area()完成。

3.2 文件加锁
      接下来,在真正写入数据之前,会对文件加锁。这里所说的对文件加锁,其实是对代表文件的索引节点inode结构加锁,为什么需要对inode加锁呢?我想是因为在接下来的写入的过程中可能会对inode中的某些成员变量进行修改,通过锁来保证这种修改可能会造成的不一致。该过程由generic_file_aio_write调用mutex_lock(&inode->i_mutex)完成。锁定以后,便调用函数__generic_file_aio_write()真正开始数据的写入。

3.3 页面加锁
       因Linux按照页面为单位缓存文件数据,因此,文件的写入过程也必然是按照页面进行。其实Linux内核的写入正是对每个处于待写入偏移内的页面进行三部曲,分别是write_begin,copy,write_end。该三部曲在函数generic_perform_write中进行。
        在write_begin函数中首先会调用grab_cache_page_write_begin()来寻找特定缓存页面并对页面加锁。这时才真正对当前要写入的页面加锁了。如果无法锁定成功,则会将当前进程睡眠。

3.4 缓冲区加锁 
        在write_begin之初对页面加锁,但接下来有可能会对页面中的缓冲区即buffer_head加锁,这是因为在写页面之前可能该页面上的部分缓冲区数据与磁盘上处于不一致状态(BH_Uptodata),此时需要从磁盘上读出该缓冲区对应的磁盘块,因此,在读之前需要对页面中不一致的缓冲区加锁,即设置缓冲区页面的BH_Locked标志位,然后一直等待在该标志位直到正确读出后该标志位被清除(解锁)。

3.5 缓冲区解锁
        页面中不一致状态的缓冲区被正确读出处于一致状态以后,加在缓冲区上的锁可被解除,此时可继续接下来的处理流程,即开始将待写入的数据拷贝至页面相应的位置处。

3.6 页面解锁
        在数据拷贝完成后,会进入页面处理三部曲中的write_end流程,generic_write_end()是通用的页面完成处理函数,首先会调用block_write_end()来对页面中刚刚写入的缓冲区标记为脏(BH_Dirty)。接下来调用unlock_page()将3.3中锁定的页面解锁(即清除PG_Locked标志位)。如果是文件大小被改变,则还需要修改索引节点中的文件大小(inode 中的i_size)成员变量以反映这种修改。我想这就是要在3.2中对inode加锁的原因。

3.7 文件解锁
        当上述写入过程完成以后,需要对加在文件上的锁进行解除。具体来说,在函数generic_file_aio_write()中调用mutex_unlock(&inode->i_mutex)。

 

原创粉丝点击