📄 buffefffr.c
字号:
/* passed * linux/fs/buffer.c * * (C) 1991 Linus Torvalds */#include <set_seg.h>
/* * 'buffer.c'用于实现缓冲区高速缓存功能。通过不让中断过程改变缓冲区,而是让调用者
* 来执行,避免了竞争条件(当然除改变数据以外)。注意!由于中断可以唤醒一个调用者,
* 因此就需要开关中断指令(cli-sti)序列来检测等待调用返回。但需要非常地快(希望是这样)。 *//* * 注意!这里有一个程序应不属于这里:检测软盘是否更换。但我想这里是
* 放置该程序最好的地方了,因为它需要使已更换软盘缓冲失效。 */
// 标准参数头文件。以宏的形式定义变量参数列表。主要说明了-个
// 类型(va_list)和三个宏(va_start, va_arg 和va_end),用于
// vsprintf、vprintf、vfprintf 函数。#include <stdarg.h>// 内核配置头文件。定义键盘语言和硬盘类型(HD_TYPE)可选项。#include <linux/config.h>
// 调度程序头文件,定义了任务结构task_struct、初始任务0 的数据,
// 还有一些有关描述符参数设置和获取的嵌入式汇编函数宏语句。#include <linux/sched.h>
// 内核头文件。含有一些内核常用函数的原形定义。#include <linux/kernel.h>
// 系统头文件。定义了设置或修改描述符/中断门等的嵌入式汇编宏。#include <asm/system.h>
// io 头文件。定义硬件端口输入/输出宏汇编语句。#include <asm/io.h>extern int end; //由连接程序ld 生成的位于程序末端的变量。extern void put_super(int);
extern void invalidate_inodes(int);
struct buffer_head * start_buffer = (struct buffer_head *)(&end);struct buffer_head * hash_table[NR_HASH] = {0}; // NR_HASH = 307 项。static struct buffer_head * free_list = 0;static struct task_struct * buffer_wait = NULL;int NR_BUFFERS = 0;
//// 等待指定缓冲区解锁。static _inline void wait_on_buffer(struct buffer_head * bh){ cli(); // 关中断。 while (bh->b_lock) // 如果已被上锁,则进程进入睡眠,等待其解锁。 sleep_on(&bh->b_wait); sti(); // 开中断。}
//// 系统调用。同步设备和内存高速缓冲中数据。int sys_sync(void)//passed{ int i; struct buffer_head * bh; sync_inodes(); /* 将i 节点写入高速缓冲 */
// 扫描所有高速缓冲区,对于已被修改的缓冲块产生写盘请求,将缓冲中数据与设备中同步。 bh = start_buffer; for (i=0 ; i<NR_BUFFERS ; i++,bh++) { wait_on_buffer(bh); // 等待缓冲区解锁(如果已上锁的话)。 if (bh->b_dirt) ll_rw_block(WRITE,bh); // 产生写设备块请求。 } return 0;}
//// 对指定设备进行高速缓冲数据与设备上数据的同步操作。int sync_dev(int dev){ int i; struct buffer_head * bh; 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); } sync_inodes(); // 将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;}
//// 使指定设备在高速缓冲区中的数据无效。
// 扫描高速缓冲中的所有缓冲块,对于指定设备的缓冲区,复位其有效(更新)标志和已修改标志。void _inline invalidate_buffers(int dev){ int i; struct buffer_head * bh; 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_uptodate = bh->b_dirt = 0; }}/* * 该子程序检查一个软盘是否已经被更换,如果已经更换就使高速缓冲中与该软驱 * 对应的所有缓冲区无效。该子程序相对来说较慢,所以我们要尽量少使用它。
* 所以仅在执行'mount'或'open'时才调用它。我想这是将速度和实用性相结合的
* 最好方法。若在操作过程当中更换软盘,会导致数据的丢失,这是咎由自取 :-) * * 注意!尽管目前该子程序仅用于软盘,以后任何可移动介质的块设备都将使用该
* 程序,mount/open 操作是不需要知道是否是软盘或其它什么特殊介质的。 */
//// 检查磁盘是否更换,如果已更换就使对应高速缓冲区无效。void check_disk_change(int dev){ int i;
// 是软盘设备吗?如果不是则退出。 if (MAJOR(dev) != 2) return;
// 测试对应软盘是否已更换,如果没有则退出。 if (!floppy_change(dev & 0x03)) return;
// 软盘已经更换,所以释放对应设备的i 节点位图和逻辑块位图所占的高速缓冲区;并使该设备的
// i 节点和数据块信息所占的高速缓冲区无效。 for (i=0 ; i<NR_SUPER ; i++) if (super_block[i].s_dev == dev) put_super(super_block[i].s_dev); invalidate_inodes(dev); invalidate_buffers(dev);}
// hash 函数和hash 表项的计算宏定义。#define _hashfn(dev,block) (((unsigned)(dev^block))%NR_HASH)#define hash(dev,block) hash_table[_hashfn(dev,block)]
//// 从hash 队列和空闲缓冲队列中移走指定的缓冲块。static _inline void remove_from_queues(struct buffer_head * bh){/* 从hash 队列中移除缓冲块 */ 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;/* 从空闲缓冲区表中移除缓冲块 */ 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;}
//// 将指定缓冲区插入空闲链表尾并放入hash 队列中。static _inline void insert_into_queues(struct buffer_head * bh){/* 放在空闲链表末尾处 */ 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;/* 如果该缓冲块对应一个设备,则将其插入新hash 队列中 */ 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;}
//// 在高速缓冲中寻找给定设备和指定块的缓冲区块。
// 如果找到则返回缓冲区块的指针,否则返回NULL。static struct buffer_head * find_buffer(int dev, int block){ struct buffer_head * tmp; for (tmp = hash(dev,block) ; tmp != NULL ; tmp = tmp->b_next) if (tmp->b_dev==dev && tmp->b_blocknr==block) return tmp; return NULL;}/* * 代码为什么会是这样子的?我听见你问... 原因是竞争条件。由于我们没有对
* 缓冲区上锁(除非我们正在读取它们中的数据),那么当我们(进程)睡眠时
* 缓冲区可能会发生一些问题(例如一个读错误将导致该缓冲区出错)。目前
* 这种情况实际上是不会发生的,但处理的代码已经准备好了。 */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--; }}/* * OK,下面是getblk 函数,该函数的逻辑并不是很清晰,同样也是因为要考虑
* 竞争条件问题。其中大部分代码很少用到,(例如重复操作语句),因此它应该 * 比看上去的样子有效得多。 * * 算法已经作了改变:希望能更好,而且一个难以琢磨的错误已经去除。 */
// 下面宏定义用于同时判断缓冲区的修改标志和锁定标志,并且定义修改标志的权重要比锁定标志大。#define BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock)
//// 取高速缓冲中指定的缓冲区。
// 检查所指定的缓冲区是否已经在高速缓冲中,如果不在,就需要在高速缓冲中建立一个对应的新项。
// 返回相应缓冲区头指针。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -