两种文件写操作的页缓存数据刷出操作和函数调用路径分析

    xiaoxiao2021-03-25  197

    一、内存映射文件的写操作(MAP_SHARED模式): 1、写内存时按以下流程标记页为脏:pte_mkdirty(pte),swap_out->……->try_to_swap_out时set_page_dirty(page) 2、文件映射内存同步到磁盘(调用sys_msync) (1)sys_msync->msync_interval:调用filemap_sync、filemap_fdatasync、ext2_sync_file。 (2)filemap_sync扫描指定范围的内存页表项,对于页表项标记为脏的,将相关页标记为脏; (3)filemap_fdatasync将脏页的缓冲区加入脏缓冲区链表(inode->i_dirty_data_buffers和lru_list[BUF_DIRTY]链表)。 (4)ext2_sync_file->ext2_fsync_inode:先后调用3个函数,fsync_inode_buffers、fsync_inode_data_buffers、ext2_sync_inode,见一.2.(5)的描述。 (5)ext2_sync_file->ext2_fsync_inode:调用fsync_inode_buffers、fsync_inode_data_buffers、ext2_sync_inode。 fsync_inode_buffers对inode->i_dirty_buffers(元数据,如文件中间指针块)链表中的脏缓冲区调用ll_rw_block刷出到磁盘; fsync_inode_data_buffers对inode->i_dirty_data_buffers(实际文件数据)链表中的脏缓冲区调用ll_rw_block刷出到磁盘; ext2_sync_inode调用ext2_update_inode把inode节点所在块写回磁盘。 (6)1个文件元数据脏块的产生场景举例:ext2_get_block->ext2_alloc_branch->buffer_insert_inode_queue 注意: buffer_insert_inode_queue是把文件元数据脏块加入inode->i_dirty_buffers链表。 buffer_insert_inode_data_queue是把文件本身的脏数据块加入inode->i_dirty_data_buffers链表。 二、普通文件写操作sys_write->generic_file_write: (1)__grab_cache_page->__find_lock_page (2)prepare_write、__copy_from_user、commit_write。 (3)最后脏数据块由后台内核线程bdflush或kupdate等异步写回磁盘。 struct address_space_operations ext2_aops = {     readpage: ext2_readpage,     writepage: ext2_writepage,     sync_page: block_sync_page,     prepare_write: ext2_prepare_write,     commit_write: generic_commit_write,     bmap: ext2_bmap,     direct_IO: ext2_direct_IO, }; 三、sys_fsync刷新文件脏页到磁盘: (1)sys_fsync主要有两个步骤: 首先调用filemap_fdatasync(对脏页的块缓冲区打BH_Dirty标记),然后调用ext2_sync_file(刷出脏缓冲区到磁盘)。 (2)filemap_fdatasync先后调用三个函数:lock_page(page),ClearPageDirty(page),write_page(page) write_page->ext2_writepage->block_write_full_page:调用两个函数, prepare_write(建立页块映射)、commit_write(块缓冲区标记为脏,加入相应链表)。 prepare_write->ext2_prepare_write commit_write->generic_commit_write (3)ext2_sync_file见一.2.(5)的描述。 四、综述 (1)3个层面的操作互斥 以上几种方式凡是在文件级别进行操作时一般通过inode->i_sem信号量进行互斥; 对页缓存操作时通过自旋锁pagecache_lock进行互斥; 对页操作时,给页上锁lock_page(page) 给块缓冲区调整lru_list时,通过自旋锁lru_list_lock互斥。 (2)重点关注下几个方式操作同一块缓冲区时的互斥同步问题 后台内核线程kupdate、bdflush刷出脏块,用户进程调用sys_fsync或sys_msync刷出脏块。主要涉及块缓存的状态一致性、所在链表的一致性。 kupdate->……->sync_old_buffers->write_some_buffers->write_locked_buffers->submit_bh->……->do_rw_disk->……->ide_output_data bdflush->write_some_buffers->write_locked_buffers->submit_bh->……->do_rw_disk->……->ide_output_data 根据kupdate和bdflush的路径,可知它们都调用了write_some_buffers,该函数是自身同步互斥的,保证了数据一致性。 sys_msync和sys_fsync也有相似点,它们都调用了ext2_sync_file进行文件脏块缓冲区的刷出。 最后两边的互斥同步落在write_some_buffers和ll_rw_block之间。代码贴在下面,这里边的关键在于atomic_set_buffer_clean这个原子操作。 它保证了两个函数不会同时操作同一个块缓冲区。 ---------------------------------write_some_buffers #define NRSYNC (32) static int write_some_buffers(kdev_t dev) {     struct buffer_head *next;     struct buffer_head *array[NRSYNC];     unsigned int count;     int nr;     next = lru_list[BUF_DIRTY];     nr = nr_buffers_type[BUF_DIRTY];     count = 0;     while (next && --nr >= 0) {         struct buffer_head * bh = next;         next = bh->b_next_free;         if (dev && bh->b_dev != dev)             continue;         if (test_and_set_bit(BH_Lock, &bh->b_state))             continue;         if (atomic_set_buffer_clean(bh)) {             __refile_buffer(bh);             get_bh(bh);             array[count++] = bh;             if (count < NRSYNC)                 continue;             spin_unlock(&lru_list_lock);             write_locked_buffers(array, count);             return -EAGAIN;         }         unlock_buffer(bh);         __refile_buffer(bh);     }     spin_unlock(&lru_list_lock);     if (count)         write_locked_buffers(array, count);     return 0; } ---------------------------------ll_rw_block void ll_rw_block(int rw, int nr, struct buffer_head * bhs[]) {     unsigned int major;     int correct_size;     int i;     if (!nr)         return;     major = MAJOR(bhs[0]->b_dev);     /* Determine correct block size for this device. */     correct_size = get_hardsect_size(bhs[0]->b_dev);     /* Verify requested block sizes. */     for (i = 0; i < nr; i++) {         struct buffer_head *bh = bhs[i];         if (bh->b_size % correct_size) {             printk(KERN_NOTICE "ll_rw_block: device %s: "                    "only %d-char blocks implemented (%u)\n",                    kdevname(bhs[0]->b_dev),                    correct_size, bh->b_size);             goto sorry;         }     }     if ((rw & WRITE) && is_read_only(bhs[0]->b_dev)) {         printk(KERN_NOTICE "Can't write to read-only device %s\n",                kdevname(bhs[0]->b_dev));         goto sorry;     }     for (i = 0; i < nr; i++) {         struct buffer_head *bh = bhs[i];         /* Only one thread can actually submit the I/O. */         if (test_and_set_bit(BH_Lock, &bh->b_state))             continue;         /* We have the buffer lock */         atomic_inc(&bh->b_count);         bh->b_end_io = end_buffer_io_sync;         switch(rw) {         case WRITE:             if (!atomic_set_buffer_clean(bh))                 /* Hmmph! Nothing to write */                 goto end_io;             __mark_buffer_clean(bh);             break;         case READA:         case READ:             if (buffer_uptodate(bh))                 /* Hmmph! Already have it */                 goto end_io;             break;         default:             BUG();     end_io:             bh->b_end_io(bh, test_bit(BH_Uptodate, &bh->b_state));             continue;         }         submit_bh(rw, bh);     }     return; sorry:     /* Make sure we don't get infinite dirty retries.. */     for (i = 0; i < nr; i++)         mark_buffer_clean(bhs[i]); } (3)关于修改缓冲区数据和异步磁盘IO操作的数据一致性问题: 在request_queue中的bh写到磁盘IO端口的同时generic_file_write修改bh数据内容的问题,其实不存在数据不一致的问题,因为 generic_file_write会设置缓冲区的BH_Dirty标志,因此该缓冲区以后至少还会再写到磁盘一次。 相关论坛帖子: ---------------------------------2.4内核中文件写操作和缓冲区刷新到磁盘之间的竞态存在吗? 既然写文件是异步的,是否有可能一个缓冲区刷新到磁盘的过程中,另一个文件写操作正在改变缓冲区的内容?怎么避免generic_file_write和bdflush、kupdate之间的这种竞态关系? ---------------------------------重温2.4内核的文件写操作和缓冲区时,关于竞态问题的疑惑。 读2.4.18内核的generic_file_write函数,发现里边写page内的块缓冲区时没有使用任何关于块缓冲区buffer层面的同步互斥措施,也没检查缓冲区上锁情况。写文件是异步的,也就是说,将来的某个不确定的时机这个dirty的buffer将被submit_bh到硬盘的request_queue中。假设这样一个情景,即page中的buffer(不妨称之为bufferA)写脏后,在某个时刻提交给request_queue,然后某个时刻do_rw_disk将其中数据拷贝到硬盘接口的IO端口中。正在拷贝的过程中,某个进程调用了文件写操作,也操作这个bufferA中的数据,那么就可能造成数据不一致的情况。两个路径如下: //1、用户数据拷贝到buffer中时没有检查buffer是不是正写到IO generic_file_write->__copy_from_user(kaddr+offset, buf, bytes) //2、缓冲区写到硬盘接口IO(比如可以假设是由kupdate启动的) kupdate->……->sync_old_buffers->write_some_buffers->write_locked_buffers->submit_bh->……->do_rw_disk->……->ide_output_data bdflush->write_some_buffers->write_locked_buffers->submit_bh->……->do_rw_disk->……->ide_output_data 是否有可能一个缓冲区刷新到磁盘的过程(路径2执行)中,另一个文件写操作(路径1也执行)正在改变缓冲区的内容?内核中似乎没有避免generic_file_write和bdflush、kupdate之间的这种竞态关系的操作,难道说这种情景是绝对发生不了的?
    转载请注明原文地址: https://ju.6miu.com/read-2002.html

    最新回复(0)