Linux页高速缓存与文件读写

了解到了页高速缓存之后,页高速缓存在内核中是怎样具体与文件读写挂钩的呢,在本文中就对这个进行探究,基于:Linux-4.4.4内核源码。

一、read分析

可参考博文:linux内核分析:read过程分析

https://blog.csdn.net/u013837209/article/details/54923508

当我们读取一个文件时,首先会检查数据是否已经缓存,如果没有就会预读取,将数据加载入缓存页。

do_generic_file_read

    --> page = find_get_page(mapping, index);

在函数do_generic_file_read()函数中,该函数定义在mm/filemap.c文件中,在这里面进行了读缓存的操作。do_generic_file_read函数中有如下内容进行了缓存页的查找,这与页高速缓存查找页的接口一致。

find_page:
		page = find_get_page(mapping, index); //在radix 树中查找相应的page
		if (!page) { 
			//如果没有找到page,内存中没有该数据,先进行预读
			page_cache_sync_readahead(mapping,
					ra, filp,
					index, last_index - index);
			page = find_get_page(mapping, index);//在radix 树种再次查找相应的page
			if (unlikely(page == NULL))
				goto no_cached_page;
		}
		if (PageReadahead(page)) {
	//发现找到的page已经是预读的情况了,再继续异步预读,此处是基于经验的优化
			page_cache_async_readahead(mapping,
					ra, filp, page,
					index, last_index - index);
		}
		if (!PageUptodate(page)) { //数据内容不是最新,则需要更新数据内容
			if (inode->i_blkbits == PAGE_CACHE_SHIFT ||
					!mapping->a_ops->is_partially_uptodate)
				goto page_not_up_to_date;
			if (!trylock_page(page))
				goto page_not_up_to_date;
			/* Did it get truncated before we got the lock? */
			if (!page->mapping)
				goto page_not_up_to_date_locked;
			if (!mapping->a_ops->is_partially_uptodate(page,
							offset, iter->count))
				goto page_not_up_to_date_locked;
			unlock_page(page);
		}

至此读操作就简单的写到这里,如果要详细点了解,可以去给出的参考链接中查看。

二、write分析

write如何写缓存的,首先是定义在fs/ext4/file.c文件下的ext4_file_write_iter()函数,这里还未涉及到缓存,进入到这个函数里面,首先函数里面进行了O_DIRECT标志的判断。这个O_DIRECT就是不使用缓存的形式,O_DIRECT判断结束之后,下面就是对使用缓存的写,如下图1所示。

Linux页高速缓存与文件读写_第1张图片

图1 文件写操作1

这里面调用了__generic_file_write_iter()函数,进入到这个函数中,看里面的实现,__generic_file_write_iter()函数定义在mm/filemap.c中。其中也是对IOCB_DIRECT进行了判断,判断结束之后,再调用了generic_perform_write()函数,如下图2所示。

Linux页高速缓存与文件读写_第2张图片

图2 文件写操作2

进入到这个函数里面,generic_perform_write()函数定义在mm/filemap.c文件中,在这个函数里面看到了对address_space的操作。

generic_perform_write()是文件写入的主要部分,整个部分是由一个do{}while循环完成的。首先是通过write_begin函数进行一些预处理工作,具体指向ext4_write_begin函数。ext4_write_begin()函数定义在fs/ext4/inode.c文件中。

 

status = a_ops->write_begin(file, mapping, pos, bytes, flags,

                            &page, &fsdata);

 

ext4_write_begin首先用grab_cache_page_write_begin()获取页高速缓存,调用__block_write_begin,在其中用get_block获取物理块号,同时启用journal机制。

当write_begin已经完成的时候,将用户态数据拷贝到内核态的page上。

 

copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes);

 

通过write_end完成具体写入,ext4_write_end()函数定义在fs/ext4/inode.c文件中,它调用了block_write_end()函数,block_write_end()函数定义在fs/buffer.c文件中,其中调用__block_commit_write()函数提交写入的数据,该函数同样定义在fs/buffer.c文件中,事实上该函数提交只是对buff的状态做了处理,并没有其他大的操作。

Linux页高速缓存与文件读写_第3张图片

图3 ext4_write_end()函数内

在ext4_write_end()函数中之后更新了inode_size如下图4所示,通过调用ext4_update_inode_size来更新了size。

图4 更新inode_size

进入到函数ext4_update_inode_size()函数中,该函数定义在fs/ext4/ext4.h中,是一个内联函数,其中的内容如下图5所示。

Linux页高速缓存与文件读写_第4张图片

图5 ext4_update_inode_size函数

其中又调用了ext4_update_i_disksize()函数,其中的内容如下图6所示。

Linux页高速缓存与文件读写_第5张图片

图6 ext4_update_i_disksize()函数

然后有回到ext4_write_end()函数中,在下面又做了一些判断工作,如下图7所示,同时又调用了ext4_mark_inode_dirty()函数,将inode标记为dirty。

Linux页高速缓存与文件读写_第6张图片

图7 ext4_write_end中的判断工作

之后再ext4_write_end()函数中又做了一些truncate操作,然后将inode添加到孤儿链表中,如下图8所示。

图8 ext4_orphan_add函数

 

关于orphan inode机制可以参考如下博文:

名称:文件系统orphan inode机制分析

http://oenhan.com/fs-orphan-inode-analysis

 

最后在ext4_write_end()函数中又做了下图9所示的操作,至此ext4_write_end()完成,同时写缓存也完成了。

Linux页高速缓存与文件读写_第7张图片

图9 ext4_write_end()函数的剩余操作

然后代码又回到generic_perform_write()函数中,事实上前面都没有做刷新磁盘的操作。

在generic_perform_write()函数中,有balance_dirty_pages_ratelimited()函数,顾名思义,平衡脏页比率,在此处进行脏页的刷新。

 

balance_dirty_pages_ratelimited(mapping);

 

如何把脏页写回磁盘,linux-2.6版本可参考博客:

名称:Linux缓存写回机制

http://oenhan.com/linux-cache-writeback

3.2版本可参考:

https://blog.csdn.net/SweeNeil/article/details/84993387

你可能感兴趣的:(Linux内核开发,Linux页高速缓存,写缓存与读缓存)