关于块缓存和页缓存的问题
(块设备文件的address space, 文件中页序号)是可以导出
(address space->host->i_rdev ,页序号<<3+i) i=0,1,2,....7
从而对应上buffer_head的设备和块号,事实上也是这么做的。sys_read,读一个块设备文件中一页的话,虽然只是把这页加入了页缓存,但是当以后getblk的时候,除了搜索块缓存,在块缓存中没找到的话,还要将块扩展到一页,搜索块设备文件的页缓存,搜索成功后还要把块设备的页缓存所关联的buffer_head加入到块缓存hash表。
但是,普通文件的话,就有问题,sys_read普通文件中的一页,搜索页缓存,找到或者分配一页,分配的话还要把这页加入了页缓存,并读取文件上互相分离的8个块。这8个块有可能在块缓存中有对应的块缓存,也可能没有。即使读取完成后,这8个buffer_head对应了磁盘上的8个块,他们也不会加入到块缓存中。
我想知道如果我使用sys_read读取普通文件的一页,这时块缓存中有这页的一个块,而且这个块标记为Uptodata|Dirty。那么我读普通文件时,并没有进行过块缓存的搜索,而是只进行页缓存的搜索,搜索失败就直接分配空页,从磁盘上读取,这样就导致了磁盘上的一块在内存中有两个副本,而且这两个副本并不一致
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
to LZ,
也就是说普通文件(内容)没有buffer_head, 那你一开始帖子中说的问题还存在吗
读普通文件时好象不使用getblk(),getblk()只在读诸如块组描述符,inode本身的内容,超级块,inode位图什么的时使用。
读普通文件只搜索页缓存,不行就分配空页和buffer_head,然后直接submit()这几个buffer_head
getblk()用的buffer_head 读普通文件[内容----注意这里说的是内容]时被使用吗
我查看了2.4.32的代码,和你说的似乎一样(在下是初学者).
其实在2.4.0的框架上做稍许修改是能够解决这个问题的。但是2.4.32硬是把设备文件和普通文件的操作统一化,反而使问题朝着反向发展。不知我的理解是否太片面,望高手指点。
其实仔细想一想,还有一个特殊的情况,那就是环回设备。
mount -o loop -t ext2 initrd-2.4.7-10smp.img mnt
一个普通文件,也可以被安装为一个文件系统。
如果再对文件initrd-2.4.7-10smp.img进行读写操作,那么就和访问mnt/下面的普通文件会产生页缓冲内容的不一致/冲突。
这个(类)问题似乎无法解决。唯一的解决办法只能是互斥。
我这里只有2.4.32,和你的2.4.0看来不一样,我这里是这样的。
struct file_operations def_blk_fops = {
open: blkdev_open,
release: blkdev_close,
llseek: block_llseek,
read: generic_file_read,
write: generic_file_write,
mmap: generic_file_mmap,
fsync: block_fsync,
ioctl: blkdev_ioctl,
};
而且普通文件和块设备文件的inode->i_mapping->a_ops->readpage分别为
static int ext2_readpage(struct file *file, struct page *page)
{
return block_read_full_page(page,ext2_get_block);
}
static int blkdev_readpage(struct file * file, struct page * page)
{
return block_read_full_page(page, blkdev_get_block);
}
区别只在于ext2_get_block和blkdev_get_block
这两个都是个函数,主要的功能就是对于给定的(inode, 文件的逻辑块号),转换为(设备号,磁盘的物理块号)。我们暂时假定磁盘没有分区,没有引导块,buffer_head->block就是物理块号。
对于blkdev_readpage,它的参数实际只使用了 struct page * page。
调用时,这个page已经设置了正确的(块设备文件inode->i_mapping , index),加入了hash,lru,address space->clean,既已经加入了页缓存。
而block_read_full_page只保证的是:page有与其相关的8个buffer_head(假设一页8块),还没有buffer_head就分配,而且这8个buffer_head->b_this_page要正确的设置;
但是并不保证这8个buffer_head链入了块缓存的hash表,不会把他们加入块缓存。既这8个buffer_head所对应的页只存在于页缓存中。
但是,如果这个页是个读块设备文件得来的页,虽然目前它只在于页缓存,但是变化发生在读取目标块的时候, getblk()------->grow_buffers()函数中。
一旦搜索块缓存没找到目标块,那么就会grow_buffers,而grow_buffers调用grow_dev_page,这个grow_dev_page捣鬼,它把块号换算成相应页号,按照 (块设备的inode->i_mapping, 块号>>3)搜索了页缓存,如果在页缓存中找到了目标页,那么就把目标页的8个buffer_head加入了块缓存的hash表,既把这个缓存页的内容分成8块,并把他们加入了块缓存。
所以,从块设备inode读上来的缓存页,基本可以肯定,与它相关联的8个buffer_head,迟早会被加入到块 hash表中。因此,属于块设备inode的缓存页,这些缓存页,就是块缓存。
块设备inode的页缓存,与设备层的块缓存达到了统一,不会引发不一致的问题。
但是普通文件完全不同,普通文件inode的缓存页永远不会对应上块缓存。而且磁盘上的同一页甚至可能会有(普通文件inode->i_mapping , index) 和 (块设备inode->i_mapping , index)两个唯一标识这个页的值,既可能会在页缓存中有两个副本???????????
虽然这绝对不可能。
[ 本帖最后由 motalelf 于 2006-7-17 17:21 编辑 ]
不知道你是否注意到,设备文件的读写和普通文件的读写是有区别的。
设备文件的读写是通过block_read(是以块为单位,直接搜索块缓存),普通文件的读写是通过generic_file_read(是以页为单位).
普通文件可以mmap,例如ext2文件系统,inode->i_mapping->a_ops = &ext2_aops(ext2_read_inode中),
而设备文件不可以mmap.因为inode->i_mapping->a_ops=&empty_aops;(clean_inode中,所有的函数全为空),inode->i_fop = &def_blk_fops;(ext2_read_inode中,其中mmmap函数指针为空).
所以你的表述中,诸如“块设备文件的页缓存所占用的内存”之类的话,是值得商榷的。
当然我认为你提的问题很好,需要解决,也给出我的解决办法(v2.4.0)。
简单的说,之所以可以用( address space , index)来唯一标识页缓存中的一页,是因为文件系统的原则:任何一个inode所管辖的文件的块,不能与其他inode所管辖的块有所重叠。因此( address space , index)才变的唯一。
但是块设备文件违反了这个原则,块设备文件inode与其他任何一个普通文件inode所管辖的块都有重叠,Linux怎么解决的呢?
我QQ:248162486
[ 本帖最后由 motalelf 于 2006-7-17 12:54 编辑 ]
并不是楼上所说的那样
块缓存所占的内存空间,应该说是页缓存的真子集。这个真子集在大部分情况下,就是块设备文件的页缓存所占用的内存。
不管是ext2_readpage还是blkdev_readpage,都只是以(inode->i_mapping, index)为关键字,搜索了页缓存。搜索不到就进行读,直接读一页。并没有搜索块缓存的过程。
但是读的如果是块设备文件的一页,问题不大,搜索页缓存的过程,就等价于搜索块缓存的过程。。
但是读的如果是普通文件的一页的话,麻烦就来了,普通文件的页所关联的buffer_head,永远不会加入到块缓存的hash表中,无论任何时候。
这个问题非常好.我还没有发现v2.4.0解决了这个问题.
我想这个问题的大致解决办法是:把页缓存建立在块缓存之上.
1.对设备文件读写就直接在块缓存中寻找,找不到再把bh加入块缓存,并从设备上读入该块.
2.对普通文件进行内存映射或读写,先在页内存中查找,找不到就新建一新页,并加入页缓存,然后读入该页.
通常情况一页(4k)分成4块(1k),一块一个bh,这些块的内容是顺序的,但是块号通常是不连续的.对于每个块,在块缓存中查找,找到的话就把该块数据复制进新页对应位置,然后从块缓存释放旧bh,加入新bh;如果找不到,就把新bh加入块缓存,并从设备读入.