文件系统基本概念——高速缓冲 buffer.c学习笔记

一、Linux使用文件系统分几个部分

1.1 有关linux中高速缓冲区的管理程序。

        分页机制,可以产生分页中断。

1.2文件系统的底层通用函数。

        硬盘的读写分配释放,对节点管理iNode,内存与磁盘映射。

1.3对文件数据进行读写操作

        VFS虚拟文件系统把一个设备当成文件读取,硬件驱动和文件系统的关系,pipe块设备的读取。

1.4文件系统与其他程序的接口实现

        fopen,关闭文件等常见的文件调用方式。

二、文件系统的基本概念

磁盘中有目录的映射,我们把磁盘分成盘片,每一个盘片都有一个子文件系统。

引导块:用来引导设备,引导块可以分开,要保持格式。

超级块:是该文件子系统的描述符,记录该盘片的逻辑块位图的地址,通过设备号可以获取。

逻辑块位图:其每一位对应一个;逻辑块的使用情况,逻辑块没有占用的对应为1,。

i节点位图:跟逻辑块位图一样,每一块记录一个i节点的使用情况。

逻辑块:用来存储数据的数据存储单元。

i节点:目录与磁盘的桥接,文件的属性描述。

在这里插入图片描述

 下面是inode的结构体:

struct m_inode {
	unsigned short i_mode;   //文件的类型和属性
	unsigned short i_uid;    //宿主用户ID
	unsigned long i_size;    //该文件的大小
	unsigned long i_mtime;   //该文件的修改时间
	unsigned char i_gid;     //宿主的组ID
	unsigned char i_nlinks;  //链接数  硬链接
	unsigned short i_zone[9];//该文件映射在逻辑块的号的数组
}

文件类型根据首字母分类:

         c就代表文件类型
        -代表文件类型
        d 目录
        s 符号链接
        p pipe管道
        c 字符设备
        b 块设备
        l 链接文件

在i_zone[9]中,他是文件和磁盘的映射,前七个是直接块号,如果文件只占用了7个文件块,那么这个数组中的每一个逻辑块的号。

izone[8]一次间接块号,如果占用逻辑块较多,大于7小于512+7,那么占用一次间接块号。

izone[9]二次间接块号,如果占用块号太多,大于512+7小于512*512+7,则占用二次间接块号。

三、高速缓冲区

在这里插入图片描述

高速缓冲区管理要素
1)、用户操作磁盘时候是需要高速缓冲区做铺垫的,否则速度太慢了,磁盘与高速缓冲区的映射关系;
2)、应用程序与高速缓冲区的交互API;
3)、高速缓冲区与磁盘的交互API;
4)、应用程序不需要控制高速缓冲区什么时候读进、读出到磁盘否则太low了;
5)、高速缓冲区的管理系统(循环链表+哈希表+单链表)
例如:应用程序要写入到磁盘中、或者从磁盘中读取一些数据,我们要开辟一段高速缓冲区并映射到磁盘中,且还要判断高速缓冲区是否满了,要是满了就要释放掉一些内存。

 整个高速缓冲区被分为1024字节大小的缓冲块,正好与块设备上的磁盘逻辑块大小相同,高速缓冲采用hash表和包含所有缓冲块的链表进行操作管理。初始化缓冲块时从两端开始,分别同时设置缓冲头结构和划分出来的对应缓冲块。

 缓冲端高端被划为1024字节的缓冲块,低端则用于描述对应缓冲块的属性,可以用它将所有缓冲头连成链表,知道不可以分出缓冲块为止。所有缓冲块的buffer_head被连成一个双向循环链表,free_list指针指向最久未使用的缓冲块。他的b_prev_free就指向缓冲块链表的最后一个缓冲块,就是刚刚使用过的缓冲块。buffer_head的结构体如下:

struct buffer_head {
	char * b_data;			/* pointer to data block (1024 bytes) */   //数据  1024比特
	unsigned long b_blocknr;	/* block number */     //数据逻辑块号   1k
	unsigned short b_dev;		/* device (0 = free) */   //块设备号  1k
	unsigned char b_uptodate;                             //更新的标志位
	unsigned char b_dirt;		/* 0-clean,1-dirty */     //是否占用
	unsigned char b_count;		/* users using this block */    //
	unsigned char b_lock;		/* 0 - ok, 1 -locked */      //是否锁定
	struct task_struct * b_wait;       //等待该高速缓冲区释放进程结构体指针  
	struct buffer_head * b_prev;       //
	struct buffer_head * b_next;
	struct buffer_head * b_prev_free;
	struct buffer_head * b_next_free;
};

b_dirt是脏标志,说明缓冲块中内容已被修改而块设备上的对应数据块内容不同,字段b_uptodate是数据更新标志,说明缓冲块中数据是否有效。当数据被写入缓冲块但没有写入设备时b_dirt=1,b_uptodate=0。特殊情况:在新申请的一个设备缓冲块时b_dirt与b_uptodate都为1,表示缓冲块中数据虽然与块设备上的不同,但是数据有效。

其他字段中 包括块设备号、缓冲数据的逻辑块号,这两个字段唯一确认了缓冲块中数据对应的块设备和数据块。内核程序在使用高速缓冲区的缓冲块时,指定设备号(dev)和要访问设备数据的逻辑块号(block),通过调用缓冲块读取函数bread、bread_page、breada进行操作,这几个函数都依赖函数getblk,他用于所有缓冲块中寻找最为空闲的缓冲块。释放缓冲块利用brelse函数。他们之间的关系可以如下图表示:

为了能够快速且有效的在缓冲区中寻找判断出请求的数据块是否已经被读入到缓冲区中,使用了 307个buffer_head指针项的hash数组表结构。hash表所使用的散列函数由设备号和逻辑块号^所得。

 这里用流程图表示getblk()函数的执行过程:

         由以上处理我们可以看到,getblk()返回的缓冲区可能是一个新的空闲块,可能正好是含有我们需要数据的缓冲块,他已经存在于高速缓冲区中。因此对于读取数据块操作(bread),此时要判断缓冲块的更新标志,看看所含数据是否有效,有效则将数据块返回给所申请的程序。否则需要调用设备的底层读写函数(ll_rw_block),并同时让自己进入睡眠状态,等待数据被读入缓冲块。醒来后再判断数据是否有效,有效了则将此数据返给申请的程序,否则说明对设备的读操作失败,没有取到数据,于是释放该块并返回NULL。以下就是bread的流程。

从设备中读取指定的数据块到该缓冲块中并返回缓冲块指针!!!!

 当程序不再需要一个缓冲块中的数据时,就调用brelse函数,释放该缓冲块并唤醒因等待该缓冲块而进入睡眠的进程。空闲缓冲块链表中的缓冲块并不是都是空闲的,只有当被写盘刷新、解锁并没有其他进程引用时(引用计数=0)才能挪用他。

综上所述:高速缓冲区在提高块设备的访问效率和增加数据共享方面起着重要的作用。除了驱动程序外,内核其他上层程序对块设备的读写操作需要经过高速缓冲区间接访问。他们主要联系是通过高速缓冲区管理程序中的bread函数和块设备底层接口函数ll_rw_block实现。上层访问块设备就通过bread函数向缓冲区管理程序申请,如果所需的数据在高速缓冲区中,管理程序会将数据直接返回给程序。所需数据不在高速缓冲区时管理程序会通过ll_rw_block函数向块设备驱动程序申请,同时让程序对应的进程睡眠,等待块设备驱动程序将数据放入高速缓冲区之后,管理程序才会返回上层程序。

          对于更新和同步操作,其主要作用是让内存中的一些缓冲块内容与磁盘等块设备上的信息保持一致。sync_inodes函数的主要作用是把i节点表inode_table中的i节点信息与磁盘上的一致起来。但需要经过系统高速缓冲区这一中间环节,实际上,任何同步操作都被分成了两个阶段:

1、数据结构信息与高速缓冲区中的缓冲块同步问题,有驱动程序独立负责;

2、高速缓冲区中数据块与磁盘对应块的同步问题,由这里的缓冲管理程序负责。

sync_inodes函数不会直接与磁盘打交道,他只能前进到缓冲区这一步,只负责与缓冲区中的信息同步。剩下的需要缓冲管理程序负责。为了让sync_inodes知道那些i节点与磁盘上的不同,就必须先让缓冲区中内容与磁盘上的内容一致。这样sync_inodes通过与当前磁盘在缓冲区中的最新数据比较才能知道哪些磁盘inode需要修改和更新。最后在进行第二次高速缓冲区与磁盘设备的同步操作,做到内存中的数据与块设备中的数据真正的同步。

四、高速缓冲区代码实现

3.1getblk

 取高速缓冲中指定的缓冲块
// 检查指定(设备号和块号)的缓冲区是否已经在高速缓冲中。如果指定块已经在
// 高速缓冲中,则返回对应缓冲区头指针退出;如果不在,就需要在高速缓冲中设置一个
// 对应设备号和块好的新项。返回相应的缓冲区头指针。
struct buffer_head * getblk(int dev,int block)
{
	struct buffer_head * tmp, * bh;

repeat:
    // 搜索hash表,如果指定块已经在高速缓冲中,则返回对应缓冲区头指针,退出。
	if ((bh = get_hash_table(dev,block)))
		return bh;
    // 扫描空闲数据块链表,寻找空闲缓冲区。
    // 首先让tmp指向空闲链表的第一个空闲缓冲区头
	tmp = free_list;
	do {
        // 如果该缓冲区正被使用(引用计数不等于0),则继续扫描下一项。对于
        // b_count = 0的块,即高速缓冲中当前没有引用的块不一定就是干净的
        // (b_dirt=0)或没有锁定的(b_lock=0)。因此,我们还是需要继续下面的判断
        // 和选择。例如当一个任务该写过一块内容后就释放了,于是该块b_count()=0
        // 但b_lock不等于0;当一个任务执行breada()预读几个块时,只要ll_rw_block()
        // 命令发出后,它就会递减b_count; 但此时实际上硬盘访问操作可能还在进行,
        // 因此此时b_lock=1, 但b_count=0.
		if (tmp->b_count)
			continue;
        // 如果缓冲头指针bh为空,或者tmp所指缓冲头的标志(修改、锁定)权重小于bh
        // 头标志的权重,则让bh指向tmp缓冲块头。如果该tmp缓冲块头表明缓冲块既
        // 没有修改也没有锁定标志置位,则说明已为指定设备上的块取得对应的高速
        // 缓冲块,则退出循环。否则我们就继续执行本循环,看看能否找到一个BANDNESS()
        // 最小的缓冲块。
		if (!bh || BADNESS(tmp)<BADNESS(bh)) {
			bh = tmp;
			if (!BADNESS(tmp))
				break;
		}
/* and repeat until we find something good */
	} while ((tmp = tmp->b_next_free) != free_list);
    // 如果循环检查发现所有缓冲块都正在被使用(所有缓冲块的头部引用计数都>0)中,
    // 则睡眠等待有空闲缓冲块可用。当有空闲缓冲块可用时本进程会呗明确的唤醒。
    // 然后我们跳转到函数开始处重新查找空闲缓冲块。
	if (!bh) {
		sleep_on(&buffer_wait);
		goto repeat;
	}
    // 执行到这里,说明我们已经找到了一个比较合适的空闲缓冲块了。于是先等待该缓冲区
    // 解锁。如果在我们睡眠阶段该缓冲区又被其他任务使用的话,只好重复上述寻找过程。
	wait_on_buffer(bh);
	if (bh->b_count)
		goto repeat;
    // 如果该缓冲区已被修改,则将数据写盘,并再次等待缓冲区解锁。同样地,若该缓冲区
    // 又被其他任务使用的话,只好再重复上述寻找过程。
	while (bh->b_dirt) {
		sync_dev(bh->b_dev);
		wait_on_buffer(bh);
		if (bh->b_count)
			goto repeat;
	}
/* NOTE!! While we slept waiting for this block, somebody else might */
/* already have added "this" block to the cache. check it */
    // 在高速缓冲hash表中检查指定设备和块的缓冲块是否乘我们睡眠之际已经被加入
    // 进去。如果是的话,就再次重复上述寻找过程。
	if (find_buffer(dev,block))
		goto repeat;
/* OK, FINALLY we know that this buffer is the only one of it's kind, */
/* and that it's unused (b_count=0), unlocked (b_lock=0), and clean */
    // 于是让我们占用此缓冲块。置引用计数为1,复位修改标志和有效(更新)标志。
	bh->b_count=1;
	bh->b_dirt=0;
	bh->b_uptodate=0;
    // 从hash队列和空闲队列块链表中移出该缓冲区头,让该缓冲区用于指定设备和
    // 其上的指定块。然后根据此新的设备号和块号重新插入空闲链表和hash队列新
    // 位置处。并最终返回缓冲头指针。
	remove_from_queues(bh);
	bh->b_dev=dev;
	bh->b_blocknr=block;
	insert_into_queues(bh);
	return bh;
}

3.2get_hash_table

 利用hash表在高速缓冲区中寻找指定的缓冲块。若找到则对该缓冲块上锁
// 返回块头指针
struct buffer_head * get_hash_table(int dev, int block)
{
	struct buffer_head * bh;

	for (;;) {
        // 在高速缓冲中寻找给定设备和指定块的缓冲区块,如果没有找到则返回NULL。
		if (!(bh=find_buffer(dev,block)))
			return NULL;
        // 对该缓冲块增加引用计数,并等待该缓冲块解锁。由于经过了睡眠状态,
        // 因此有必要在验证该缓冲块的正确性,并返回缓冲块头指针。
		bh->b_count++;
		wait_on_buffer(bh);
		if (bh->b_dev == dev && bh->b_blocknr == block)
			return bh;
        // 如果在睡眠时该缓冲块所属的设备号或块设备号发生了改变,则撤消对它的
        // 引用计数,重新寻找。
		bh->b_count--;
	}
}

3.3find_buffer

 利用hash表在高速缓冲区中寻找给定设备和指定块号的缓冲区块。
// 如果找到则返回缓冲区块的指针,否则返回NULL。
static struct buffer_head * find_buffer(int dev, int block)
{		
	struct buffer_head * tmp;

    // 搜索hash表,寻找指定设备号和块号的缓冲块。
	for (tmp = hash(dev,block) ; tmp != NULL ; tmp = tmp->b_next)
		if (tmp->b_dev==dev && tmp->b_blocknr==block)
			return tmp;
	return NULL;
}

3.4hash

// 下面两行代码是hash(散列)函数定义和Hash表项的计算宏
// hash表的主要作用是减少查找比较元素所花费的时间。通过在元素的存储位置与关
// 键字之间建立一个对应关系(hash函数),我们就可以直接通过函数计算立刻查询到指定
// 的元素。建立hash函数的指导条件主要是尽量确保散列在任何数组项的概率基本相等。
// 建立函数的方法有多种,这里Linux-0.11主要采用了关键字除留余数法。因为我们
// 寻找的缓冲块有两个条件,即设备号dev和缓冲块号block,因此设计的hash函数肯定
// 需要包含这两个关键值。这两个关键字的异或操作只是计算关键值的一种方法。再对
// 关键值进行MOD运算就可以保证函数所计算得到的值都处于函数数组项范围内。
#define _hashfn(dev,block) (((unsigned)(dev^block))%NR_HASH)
#define hash(dev,block) hash_table[_hashfn(dev,block)]

3.5wait_on_buffer

 等待指定缓冲块解锁
// 如果指定的缓冲块bh已经上锁就让进程不可中断地睡眠在该缓冲块的等待队列b_wait中。
// 在缓冲块解锁时,其等待队列上的所有进程将被唤醒。虽然是在关闭中断(cli)之后
// 去睡眠的,但这样做并不会影响在其他进程上下文中影响中断。因为每个进程都在自己的
// TSS段中保存了标志寄存器EFLAGS的值,所以在进程切换时CPU中当前EFLAGS的值也随之
// 改变。使用sleep_on进入睡眠状态的进程需要用wake_up明确地唤醒。
static inline void wait_on_buffer(struct buffer_head * bh)
{
	cli();                          // 关中断
	while (bh->b_lock)              // 如果已被上锁则进程进入睡眠,等待其解锁
		sleep_on(&bh->b_wait);
	sti();                          // 开中断
}

3.6sync_dev

 对指定设备进行高速缓冲数据与设备上数据的同步操作
// 该函数首先搜索高速缓冲区所有缓冲块。对于指定设备dev的缓冲块,若其数据已经
// 被修改过就写入盘中(同步操作)。然后把内存中i节点表数据写入 高速缓冲中。之后
// 再对指定设备dev执行一次与上述相同的写盘操作。
int sync_dev(int dev)
{
	int i;
	struct buffer_head * bh;

    // 首先对参数指定的设备执行数据同步操作,让设备上的数据与高速缓冲区中的数据
    // 同步。方法是扫描高速缓冲区中所有缓冲块,对指定设备dev的缓冲块,先检测其
    // 是否已被上锁,若已被上锁就睡眠等待其解锁。然后再判断一次该缓冲块是否还是
    // 指定设备的缓冲块并且已修改过(b_dirt标志置位),若是就对其执行写盘操作。
    // 因为在我们睡眠期间该缓冲块有可能已被释放或者被挪作他用,所以在继续执行前
    // 需要再次判断一下该缓冲块是否还是指定设备的缓冲块。
	bh = start_buffer;
	for (i=0 ; i<NR_BUFFERS ; i++,bh++) {
		if (bh->b_dev != dev)               // 不是设备dev的缓冲块则继续
			continue;
		wait_on_buffer(bh);                     // 等待缓冲区解锁
		if (bh->b_dev == dev && bh->b_dirt)
			ll_rw_block(WRITE,bh);
	}
    // 再将i节点数据吸入高速缓冲。让i姐电表inode_table中的inode与缓冲中的信息同步。
	sync_inodes();
    // 然后在高速缓冲中的数据更新之后,再把他们与设备中的数据同步。这里采用两遍同步
    // 操作是为了提高内核执行效率。第一遍缓冲区同步操作可以让内核中许多"脏快"变干净,
    // 使得i节点的同步操作能够高效执行。本次缓冲区同步操作则把那些由于i节点同步操作
    // 而又变脏的缓冲块与设备中数据同步。
	bh = start_buffer;
	for (i=0 ; i<NR_BUFFERS ; i++,bh++) {
		if (bh->b_dev != dev)
			continue;
		wait_on_buffer(bh);
		if (bh->b_dev == dev && bh->b_dirt)
			ll_rw_block(WRITE,bh);
	}
	return 0;
}

3.7ll_rw_block

void ll_rw_block(int rw, struct buffer_head * bh)
{
	unsigned int major;

	if ((major=MAJOR(bh->b_dev)) >= NR_BLK_DEV ||
	!(blk_dev[major].request_fn)) {
		printk("Trying to read nonexistent block-device\n\r");
		return;
	}
	make_request(major,rw,bh);
}

3.8sync_inodes

 同步所有i节点
// 把内存i节点表中所有i节点与设备上i节点作同步操作
void sync_inodes(void)
{
	int i;
	struct m_inode * inode;

    // 首先让内存i节点类型的指针指向i节点表首项,然后扫描整个i节点表中的节点。针对
    // 其中每个i节点,先等待该i节点解锁可用(若目前正被上锁的话),然后判断该i节点
    // 是否已被修改并且不是管道节点。若是这种情况则将该i节点写入高速缓冲区中。
    // 缓冲区管理程序buffer.c会在适当时机将他们写入盘中。
	inode = 0+inode_table;
	for(i=0 ; i<NR_INODE ; i++,inode++) {
		wait_on_inode(inode);
		if (inode->i_dirt && !inode->i_pipe)
			write_inode(inode);
	}
}

3.9remove_from_queues

 从hash队列和空闲缓冲区队列中移走缓冲块。
// hash队列是双向链表结构,空闲缓冲块队列是双向循环链表结构。
static inline void remove_from_queues(struct buffer_head * bh)
{
/* remove from hash-queue */
	if (bh->b_next)
		bh->b_next->b_prev = bh->b_prev;
	if (bh->b_prev)
		bh->b_prev->b_next = bh->b_next;
    // 如果该缓冲区是该队列的头一个块,则让hash表的对应项指向本队列中的下一个
    // 缓冲区。
	if (hash(bh->b_dev,bh->b_blocknr) == bh)
		hash(bh->b_dev,bh->b_blocknr) = bh->b_next;
/* remove from free list */
	if (!(bh->b_prev_free) || !(bh->b_next_free))
		panic("Free block list corrupted");
	bh->b_prev_free->b_next_free = bh->b_next_free;
	bh->b_next_free->b_prev_free = bh->b_prev_free;
    // 如果空闲链表头指向本缓冲区,则让其指向下一缓冲区。
	if (free_list == bh)
		free_list = bh->b_next_free;
}

3.10insert_into_queues

 将缓冲块插入空闲链表尾部,同时放入hash队列中。
static inline void insert_into_queues(struct buffer_head * bh)
{
/* put at end of free list */
	bh->b_next_free = free_list;
	bh->b_prev_free = free_list->b_prev_free;
	free_list->b_prev_free->b_next_free = bh;
	free_list->b_prev_free = bh;
/* put the buffer in new hash-queue if it has a device */
    // 请注意当hash表某项第1次插入项时,hash()计算值肯定为Null,因此此时得到
    // 的bh->b_next肯定是NULL,所以应该在bh->b_next不为NULL时才能给b_prev赋
    // bh值。
	bh->b_prev = NULL;
	bh->b_next = NULL;
	if (!bh->b_dev)
		return;
	bh->b_next = hash(bh->b_dev,bh->b_blocknr);
	hash(bh->b_dev,bh->b_blocknr) = bh;
	bh->b_next->b_prev = bh;                // 此句前应添加"if (bh->b_next)"判断
}

3.11bread

 从设备上读取数据块。
// 该函数根据指定的设备号 dev 和数据块号 block,首先在高速缓冲区中申请一块
// 缓冲块。如果该缓冲块中已经包含有有效的数据就直接返回该缓冲块指针,否则
// 就从设备中读取指定的数据块到该缓冲块中并返回缓冲块指针。
struct buffer_head * bread(int dev,int block)
{
	struct buffer_head * bh;

    // 在高速缓冲区中申请一块缓冲块。如果返回值是NULL,则表示内核出错,停机。
    // 然后我们判断其中说是否已有可用数据。如果该缓冲块中数据是有效的(已更新)
    // 可以直接使用,则返回。
	if (!(bh=getblk(dev,block)))
		panic("bread: getblk returned NULL\n");
	if (bh->b_uptodate)
		return bh;
    // 否则我们就调用底层快设备读写ll_rw_block函数,产生读设备块请求。然后
    // 等待指定数据块被读入,并等待缓冲区解锁。在睡眠醒来之后,如果该缓冲区已
    // 更新,则返回缓冲区头指针,退出。否则表明读设备操作失败,于是释放该缓
    // 冲区,返回NULL,退出。
	ll_rw_block(READ,bh);
	wait_on_buffer(bh);
	if (bh->b_uptodate)
		return bh;
	brelse(bh);
	return NULL;
}

3.12brelse

// 释放指定缓冲块。
// 等待该缓冲块解锁。然后引用计数递减1,并明确地唤醒等待空闲缓冲块的进程。
void brelse(struct buffer_head * buf)
{
	if (!buf)
		return;
	wait_on_buffer(buf);
	if (!(buf->b_count--))
		panic("Trying to free free buffer");
	wake_up(&buffer_wait);
}

3.13buffer_init

高速缓冲区的初始化程序
(空闲缓冲区双向链表的创建和hash链表的创建)

// 缓冲区初始化函数
// 参数buffer_end是缓冲区内存末端。对于具有16MB内存的系统,缓冲区末端被设置为4MB.
// 对于有8MB内存的系统,缓冲区末端被设置为2MB。该函数从缓冲区开始位置start_buffer
// 处和缓冲区末端buffer_end处分别同时设置(初始化)缓冲块头结构和对应的数据块。直到
// 缓冲区中所有内存被分配完毕。
void buffer_init(long buffer_end)
{
	struct buffer_head * h = start_buffer;
	void * b;
	int i;

    // 首先根据参数提供的缓冲区高端位置确定实际缓冲区高端位置b。如果缓冲区高端等于1Mb,
    // 则因为从640KB - 1MB被显示内存和BIOS占用,所以实际可用缓冲区内存高端位置应该是
    // 640KB。否则缓冲区内存高端一定大于1MB。
	if (buffer_end == 1<<20)
		b = (void *) (640*1024);
	else
		b = (void *) buffer_end;
    // 这段代码用于初始化缓冲区,建立空闲缓冲区块循环链表,并获取系统中缓冲块数目。
    // 操作的过程是从缓冲区高端开始划分1KB大小的缓冲块,与此同时在缓冲区低端建立
    // 描述该缓冲区块的结构buffer_head,并将这些buffer_head组成双向链表。
    // h是指向缓冲头结构的指针,而h+1是指向内存地址连续的下一个缓冲头地址,也可以说
    // 是指向h缓冲头的末端外。为了保证有足够长度的内存来存储一个缓冲头结构,需要b所
    // 指向的内存块地址 >= h 缓冲头的末端,即要求 >= h+1.
	while ( (b -= BLOCK_SIZE) >= ((void *) (h+1)) ) {
		h->b_dev = 0;                       // 使用该缓冲块的设备号
		h->b_dirt = 0;                      // 脏标志,即缓冲块修改标志
		h->b_count = 0;                     // 缓冲块引用计数
		h->b_lock = 0;                      // 缓冲块锁定标志
		h->b_uptodate = 0;                  // 缓冲块更新标志(或称数据有效标志)
		h->b_wait = NULL;                   // 指向等待该缓冲块解锁的进程
		h->b_next = NULL;                   // 指向具有相同hash值的下一个缓冲头
		h->b_prev = NULL;                   // 指向具有相同hash值的前一个缓冲头
		h->b_data = (char *) b;             // 指向对应缓冲块数据块(1024字节)
		h->b_prev_free = h-1;               // 指向链表中前一项
		h->b_next_free = h+1;               // 指向连表中后一项
		h++;                                // h指向下一新缓冲头位置
		NR_BUFFERS++;                       // 缓冲区块数累加
		if (b == (void *) 0x100000)         // 若b递减到等于1MB,则跳过384KB
			b = (void *) 0xA0000;           // 让b指向地址0xA0000(640KB)处
	}
	h--;                                    // 让h指向最后一个有效缓冲块头
	free_list = start_buffer;               // 让空闲链表头指向头一个缓冲快
	free_list->b_prev_free = h;             // 链表头的b_prev_free指向前一项(即最后一项)。
	h->b_next_free = free_list;             // h的下一项指针指向第一项,形成一个环链
    // 最后初始化hash表,置表中所有指针为NULL。
	for (i=0;i<NR_HASH;i++)
		hash_table[i]=NULL;
}	

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西邮小菜机

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值