Linux文件系统(五)---三大缓冲区之buffer块缓冲区

在文件系统中,有三大缓冲为了提升效率:inode缓冲区、dentry缓冲区、块缓冲。

(内核:2.4.37)

二、块buffer缓冲区

0、整体来说,Linux 文件缓冲区分为page cache和buffer cache,每一个 page cache 包含若干 buffer cache。

》 内存管理系统和 VFS 只与 page cache 交互,内存管理系统负责维护每项 page cache 的分配和回收,同时在使用“内存映射”方式访问时负责建立映射。

》 VFS 负责 page cache 与用户空间的数据交换。

》 而具体文件系统则一般只与 buffer cache 交互,它们负责在存储设备和 buffer cache 之间交换数据,具体的文件系统直接操作的就是disk部分,而具体的怎么被包装被用户使用是VFS的责任(VFS将buffer cache包装成page给用户)。

》 每一个page有N个buffer cache,struct buffer_head结构体中一个字段b_this_page就是将一个page中的buffer cache连接起来的结构

看一下这个结构:/include/linux/mm.h,对于struct buffer_head下面再看。

看到下面167行代码就懂了~

typedef struct page {
156         struct list_head list;          /* ->mapping has some page lists. */
157         struct address_space *mapping;  /* The inode (or ...) we belong to. */
158         unsigned long index;            /* Our offset within mapping. */
159         struct page *next_hash;         /* Next page sharing our hash bucket in
160                                            the pagecache hash table. */
161         atomic_t count;                 /* Usage count, see below. */
162         unsigned long flags;            /* atomic flags, some possibly
163                                            updated asynchronously */
164         struct list_head lru;           /* Pageout list, eg. active_list;
165                                            protected by pagemap_lru_lock !! */
166         struct page **pprev_hash;       /* Complement to *next_hash. */
167         struct buffer_head * buffers;   /* Buffer maps us to a disk block. */
168
169         /*
170          * On machines where all RAM is mapped into kernel address space,
171          * we can simply calculate the virtual address. On machines with
172          * highmem some memory is mapped into kernel virtual memory
173          * dynamically, so we need a place to store that address.
174          * Note that this field could be 16 bits on x86 ... ;)
175          *
176          * Architectures with slow multiplication can define
177          * WANT_PAGE_VIRTUAL in asm/page.h
178          */
179 #if defined(CONFIG_HIGHMEM) || defined(WANT_PAGE_VIRTUAL)
180         void *virtual;                  /* Kernel virtual address (NULL if
181                                            not kmapped, ie. highmem) */
182 #endif /* CONFIG_HIGMEM || WANT_PAGE_VIRTUAL */
183 } mem_map_t;

关系图如下:

1、对于具体的Linux文件系统,会以block(磁盘块)的形式组织文件,为了减少对物理块设备的访问,在文件以块的形式调入内存后,使用块高速缓存进行管理。每个缓冲区由两部分组成,第一部分称为缓冲区首部,用数据结构buffer_head表示,第二部分是真正的存储的数据。由于缓冲区首部不与数据区域相连,数据区域独立存储。因而在缓冲区首部中,有一个指向数据的指针和一个缓冲区长度的字段。

Ps:内核同样有几种不同的链表来管理buffer cache,在fs/buffer.c中定义:

static struct buffer_head **hash_table;

static struct buffer_head *lru_list[NR_LIST];

static struct buffer_head * unused_list;

下面我们具体看看这个结构体struct buffer_head,这个版本中这个结构体在fs.h中,后面的一些版本,在buffer_head.h中。

246 struct buffer_head {
247         /* First cache line: */
248         struct buffer_head *b_next;     /* Hash queue list */
249         unsigned long b_blocknr;        /* block number */
250         unsigned short b_size;          /* block size */
251         unsigned short b_list;          /* List that this buffer appears */
252         kdev_t b_dev;                   /* device (B_FREE = free) */
253
254         atomic_t b_count;               /* users using this block */
255         kdev_t b_rdev;                  /* Real device */
256         unsigned long b_state;          /* buffer state bitmap (see above) */
257         unsigned long b_flushtime;      /* Time when (dirty) buffer should be written */
258
259         struct buffer_head *b_next_free;/* lru/free list linkage */
260         struct buffer_head *b_prev_free;/* doubly linked list of buffers */
261         struct buffer_head *b_this_page;/* circular list of buffers in one page */
262         struct buffer_head *b_reqnext;  /* request queue */
263
264         struct buffer_head **b_pprev;   /* doubly linked list of hash-queue */
265         char * b_data;                  /* pointer to data block */
266         struct page *b_page;            /* the page this bh is mapped to */
267         void (*b_end_io)(struct buffer_head *bh, int uptodate); /* I/O completion */
268         void *b_private;                /* reserved for b_end_io */
269
270         unsigned long b_rsector;        /* Real buffer location on disk */
271         wait_queue_head_t b_wait;
272
273         struct list_head     b_inode_buffers;   /* doubly linked list of inode dirty buffers */
274 };

解释一些上面的字段:

b_next:用于链接到块缓冲区的hash表

b_blocknr:本block的块号

b_size:block的大小

b_list:表示当前的这个buffer在那个链表中

b_dev:虚拟设备标识

b_count:引用计数(几个人在使用这个buffer)

b_rdev:真实设备标识

b_state:状态位图,如下:

212 /* bh state bits */
213 enum bh_state_bits {
214         BH_Uptodate,    /* 如果缓冲区包含有效数据则置1 */
215         BH_Dirty,       /* 如果buffer脏(存在数据被修改情况),那么置1 */
216         BH_Lock,        /* 如果缓冲区被锁定,那么就置1 */
217         BH_Req,         /* 如果缓冲区无效就置0 */
218         BH_Mapped,      /* 如果缓冲区有一个磁盘映射就置1 */
219         BH_New,         /* 如果缓冲区是新的,而且没有被写出去,那么置1 */
220         BH_Async,       /* 如果缓冲区是进行end_buffer_io_async I/O 同步则置1 */
221         BH_Wait_IO,     /* 如果要将这个buffer写回,那么置1 */
222         BH_Launder,     /* 如果需要重置这个buffer,那么置1 */
223         BH_Attached,    /* 1 if b_inode_buffers is linked into a list */
224         BH_JBD,         /* 如果和 journal_head 关联置1 */
225         BH_Sync,        /* 如果buffer是同步读取置1 */
226         BH_Delay,       /* 如果buffer空间是延迟分配置1 */
227
228         BH_PrivateStart,/* not a state bit, but the first bit available
229                          * for private allocation by other entities
230                          */
231 };
232

b_flushtime:脏buffer需要被写入的时间

b_next_free:指向lru链表中next元素

b_prev_free:指向链表上一个元素

b_this_page:连接到同一个page中的那个链表

b_reqnext:请求队列

b_pprev:hash队列双向链表

data:指向数据块的指针

b_page:这个buffer映射的页面

b_end_io:IO结束时候执行函数

b_private:保留

b_rsector:缓冲区在磁盘上的实际位置

b_inode_buffers:inode脏缓冲区循环链表

3、关于VFS怎么去管理几个buffer cache的链表,如下:

hash表:用于管理包含有效数据的buffer,在定位buffer的时候很快捷。哈希索引值由数据块号以及其所在的设备标识号计算(散列)得到。

关于这段hash代码如下:

<span style="font-size:14px;">539 /* After several hours of tedious analysis, the following hash
540  * function won.  Do not mess with it... -DaveM
541  */
542 #define _hashfn(dev,block)      543         ((((dev)<<(bh_hash_shift - 6)) ^ ((dev)<<(bh_hash_shift - 9))) ^ 544          (((block)<<(bh_hash_shift - 6)) ^ ((block) >> 13) ^ 545           ((block) << (bh_hash_shift - 12))))</span>

下面简单的看一下流程:

当我们在一个具有的文件系统中,当我们需要读取一块数据的时候,需要调用bread函数(面包?ヾ(?`Д′?),应该是buffer read的缩写吧。。。)。

如下:

1181 /**
1182  *      bread() - reads a specified block and returns the bh
1183  *      @block: number of block        块号
1184  *      @size: size (in bytes) to read 需要读取的size
1185  *
1186  *      Reads a specified block, and returns buffer head that
1187  *      contains it. It returns NULL if the block was unreadable.  返回一个包含这个block的buffer
1188  */
1189 struct buffer_head * bread(kdev_t dev, int block, int size)
1190 {
1191         struct buffer_head * bh;
1192
1193         bh = getblk(dev, block, size);    /* 找到这个buffer */
1194         if (buffer_uptodate(bh))  /* 判断是否存在有效数据,如果存在那么直接返回即可 */
1195                 return bh;
1196         set_bit(BH_Sync, &bh->b_state); /* 如果不存在有效数据,将这个buffer设置成同步状态 */
1197         ll_rw_block(READ, 1, &bh); /* 如果没有,那么需要从磁盘中读取这个block到buffer中,这个是一个底层的读取操作 */
1198         wait_on_buffer(bh);  /* 等待buffer的锁打开 */
1199         if (buffer_uptodate(bh)) /* 理论上这个时候应该是存在有效数据的了,直接返回就可以 */
1200                 return bh;
1201         brelse(bh);
1202         return NULL;
1203 }

对于上面函数的分析,基本上分成两个步骤,

第一:通过dev号+block号找到相应的buffer,使用函数getblk,如下:

1013 struct buffer_head * getblk(kdev_t dev, int block, int size)
1014 {
1015         for (;;) {
1016                 struct buffer_head * bh;
1017
1018                 bh = get_hash_table(dev, block, size);   /* 关键函数,得到hash表中的buffer */
1019                 if (bh) {
1020                         touch_buffer(bh);
1021                         return bh;     /* 返回这个buffer */
1022                 }
1023                 /* 如果没有找到对应的buffer,那么试着去增加一个buffer,就是使用下面的grow_buffers函数 */
1024                 if (!grow_buffers(dev, block, size))
1025                         free_more_memory();
1026         }
1027 }

简单看一下这个查找buffer函数:get_hash_table

628 struct buffer_head * get_hash_table(kdev_t dev, int block, int size)
629 {
630         struct buffer_head *bh, **p = &hash(dev, block);   /* 首先通过hash值得到对应的位置,这个函数h很easy */
631 /* 其实就是 #define hash(dev,block) hash_table[(_hashfn(HASHDEV(dev),block) & bh_hash_mask)]。hashfn函数上面已经说过了,就是通过hash值得到buffer*/
632         read_lock(&hash_table_lock);
633
634         for (;;) {           /* 下面就是判断这个得到的buffer数组中有没有我们需要的buffer */
635                 bh = *p;
636                 if (!bh)
637                         break;
638                 p = &bh->b_next;
639                 if (bh->b_blocknr != block)
640                         continue;
641                 if (bh->b_size != size)
642                         continue;
643                 if (bh->b_dev != dev)
644                         continue;
645                 get_bh(bh);       /* 如果有那么直接执行这个函数,这个函数很easy,其实就是增加一个使用计数器而已: atomic_inc(&(bh)->b_count);*/
646                 break;
647         }
648
649         read_unlock(&hash_table_lock);
650         return bh;
651 }

如果没找到对应的buffer,那么使用grow_buffers函数增加一个新的buffer,看函数:

2596 /*
2597  * Try to increase the number of buffers available: the size argument
2598  * is used to determine what kind of buffers we want.
2599  */
2600 static int grow_buffers(kdev_t dev, unsigned long block, int size)
2601 {
2602         struct page * page;
2603         struct block_device *bdev;
2604         unsigned long index;
2605         int sizebits;
2606
2607         /* Size must be multiple of hard sectorsize */
2608         if (size & (get_hardsect_size(dev)-1))
2609                 BUG();
2610         /* Size must be within 512 bytes and PAGE_SIZE */
2611         if (size < 512 || size > PAGE_SIZE)
2612                 BUG();
2613
2614         sizebits = -1;
2615         do {
2616                 sizebits++;
2617         } while ((size << sizebits) < PAGE_SIZE);
2618
2619         index = block >> sizebits;
2620         block = index << sizebits;
2621
2622         bdev = bdget(kdev_t_to_nr(dev));
2623         if (!bdev) {
2624                 printk("No block device for %s\n", kdevname(dev));
2625                 BUG();
2626         }
2627
2628         /* Create a page with the proper size buffers.. */
2629         page = grow_dev_page(bdev, index, size);
2630
2631         /* This is "wrong" - talk to Al Viro */
2632         atomic_dec(&bdev->bd_count);
2633         if (!page)
2634                 return 0;
2635
2636         /* Hash in the buffers on the hash list */
2637         hash_page_buffers(page, dev, block, size);
2638         UnlockPage(page);
2639         page_cache_release(page);
2640
2641         /* We hashed up this page, so increment buffermem */
2642         atomic_inc(&buffermem_pages);
2643         return 1;
2644 }
2645 

这个函数就是增加一个新的buffer,首先由grow_dev_page创建一个缓冲区包含这个block,然后将这个buffer链接到这个全局的hash缓冲区中使用函数hash_page_buffers。具体的代码很简单,不单看了。

第二:如果没有找到需要的buffer,那么执行底层读取函数ll_rw_block将数据从磁盘去读进来,这个函数在/source/drivers/block/ll_rw_blk.c中,具体的代码不看了。

OK,至此,寻找一个我们需要的buffer就结束了。

LRU链表

对于每一种不同缓冲区都会使用一个LRU来管理未使用的有效缓冲区

Ps:缓冲区类型如下:

1152 #define BUF_CLEAN       0
1153 #define BUF_LOCKED      1       /* Buffers scheduled for write */
1154 #define BUF_DIRTY       2       /* Dirty buffers, not yet scheduled for write */

分别是:未使用的干净的缓冲区;正在等待写入的缓冲区;脏缓冲区,还没有被写回磁盘。

这个三种链表怎么得到的呢,看代码也知道是吻合的,看LRU的声明:static struct buffer_head *lru_list[NR_LIST];

再看:#define NR_LIST         3,OK

当我们需要寻找一块buffer的时候,如果发现buffer在缓冲区中,且在LRU链表中,那么从LRU表中删除。

结合上面的一个hash链表,基本过程就是:

首先呢在hash表中寻找,如果找到,那就OK,如果没有找到,那么需要分配新的buffer,如果分到,那么加载数据进来,继续...如果没有足够的空间分配,那么, 需要将LRU中一个取出(LRU链首元素),先看是否置了“脏”位,如已置,则将它的内容写回磁盘。然后清空内容,将它分配给新的数据块。

在缓冲区使用完了后,将它的b_count域减1,如果b_count变为0,则将它放在某个LRU链尾,表示该缓冲区已可以重新利用。

unused_list 用于辅助就不多说了~~~

时间: 2024-10-24 12:28:01

Linux文件系统(五)---三大缓冲区之buffer块缓冲区的相关文章

Linux文件系统(四)---三大缓冲区之inode缓冲区 (内存inode映像 )

在文件系统中,有三大缓冲为了提升效率:inode缓冲区.dentry缓冲区.块缓冲. (内核:2.4.37) 一.inode缓冲区 为了加快对索引节点的索引,引入inode缓冲区,下面我们看Linux/fs/inode.c代码.inode缓冲区代码 1.一些数据结构: 之前已经说过,有多个链表用于管理inode节点: <span style="font-size:14px;">59 static LIST_HEAD(inode_in_use); 60 static LIST

十天学Linux内核之第五天---有关Linux文件系统实现的问题

有时间睡懒觉了,却还是五点多醒了,不过一直躺倒九点多才算起来,昨晚一直在弄飞凌的嵌入式开发板,有些问题没解决,自己电脑系统的问题,虽然Win10发布了,,但我还是好喜欢XP呀,好想回家用用家里的XP来玩玩这块板子,不知不觉也第五天了,感觉代码都有些模糊,连自己都不是很清楚了,担心现在分享起来比较困惑,各路大神多加批评呀,觉得渣渣的尽量指正出来,拉出来批评,今天还是来总结一下有关Linux文件系统的问题吧~ Linux的使用和用户空间程序的编程和文件系统有着密切的关系,文件系统的概念大家应该都有些

总结五:Linux文件系统之层次结构及目录管理命令

总结五:Linux文件系统之层次结构及目录管理命令 1.FHS:(Filesystem Hierarchy Standard)--文件系统层次结构标准 /bin,/sbin:系统自身启动和运行时可能会用到的核心二进制命令 /lib:共享库文件和内核模块 /lib64:共享库文件和内核模块 /etc:配置文件 /usr:universal shared read-only /usr/bin,/usr/sbin:系统运行中,用户为完成某些操作可能用到的命令 /usr/lib,/usr/lib64:库

Linux 学习总结(五)-linux 文件系统及相关命令

http://www.lxway.com/505469826.htm以下所写有许多参考以上链接文章 一 linux文件系统概要 linux系统结构有别用于windos,他是树状结构的文件系统,在linux下我们称一切皆文件,我们将一个目录,可以成称为目录文件.linux只有一个单独的顶级目录结构.所有一切都从root开始,用'/'代表,并且延伸到子目录.DOS/Windows有不同的分区同时目录都存于分区上.linux则通过'加载'的方式把所有分区都放置在root下制定的目录里.windows下

Linux内核设计基础(六)之块I/O层

块设备是指能随机访问固定大小数据片的设备,如硬盘:字符设备(如串口和键盘)是按照字符流的方式有序访问.区别在于是否可以随机访问数据--也就是能否在访问设备时随意地从一个位置跳转到另一个位置.我们可以感觉到块设备的控制要比字符设备复杂多,实际上内核在块设备上下了大工夫--块I/O层. 基础概念 块设备中最小的可寻址单元是扇区. 文件系统的最小寻址单元是块. 所谓的缓冲区是块在内存中的表示. 对于一个缓冲区(块),内核需要知道它的控制信息,这时需要一个结构进行描述--缓冲区头. I/O调度机制 首先

linux文件系统及bash基础特性

linux文件系统 一.根文件系统 linux被识别的第一个被称为根之间关联的文件系统叫做根文件系统(rootfs),其他分区要想被读到,需要挂载到根目录的某个挂载点(根的子目录)上.根文件系统至关重要,整个文件系统统一访问入口. Linux引导启动时,默认使用的文件系统是根文件系统.其中一般都包括这样一些子目录: /boot 系统引导文件存放目录 /bin 供所有用户使用的命令(不能关联到独立分支)不能关联到独立分区,os启动即会用到的程序. /sbin 管理员使用特权命令.root-only

Linux核心设计依据(六)该块I/O一层

块设备是能随机存取装置固定大小的数据表设备.如硬盘:字符设备(如串口和键盘)它是按照字符流进入有序进行.不同之处在于是否足够的随机存取数据--这时候,你可以随心所欲地从一个位置跳到访问设备和位置.复杂多,实际上内核在块设备上下了大工夫--块I/O层. 基础概念 块设备中最小的可寻址单元是扇区. 文件系统的最小寻址单元是块. 所谓的缓冲区是块在内存中的表示. 对于一个缓冲区(块),内核须要知道它的控制信息.这时须要一个结构进行描写叙述--缓冲区头. I/O调度机制 首先,这里的I/O调度指的是磁盘

Linux文件系统详解(文件系统层次、分类、存储结构、存储介质、文件节点inode)

从操作系统的角度详解Linux文件系统层次.文件系统分类.文件系统的存储结构.不同存储介质的区别(RAM.ROM.Flash).存储节点inode. 本文参考:http://blog.chinaunix.net/uid-8698570-id-1763151.html http://www.iteye.com/topic/816268 http://soft.chinabyte.com/os/142/12315142.shtml http://www.ibm.com/developerworks/

linux 文件系统及磁盘管理

一个新的磁盘盘需要经过分区.格式化(创建文件系统),挂载之后才能正常使用.关于磁盘的分区表有两种格式:MBR(Master Boot Record)和GPT(GUID Partition Table)     MBR:      也称为主引导扇区,是计算机开机后访问硬盘时必须读取的首个扇区,也就是0柱面0磁头1扇区,MBR由bootload(446字节).分区表(64字节).MBR结束标志(2字节)共512字节组成.最大支持2TB的分区.MBR格式的分区表支持3个主分区和1个扩展分区. GPT: