blk_dev
blk_dev
先定义了系统可以使用的一些块设备,数量也就是7个。该数组使用主设备号
作为索引,在代码中就会看到blk_dev[major]
这样找到 blk_dev_struct
结构的,实际内容将在各种块设备驱动程序初始化时填入。
kernel/blk_drv/ll_rw_blk.c
struct blk_dev_struct blk_dev[NR_BLK_DEV] = {
{ NULL, NULL }, /* no_dev */
{ NULL, NULL }, /* dev mem 虚拟盘 */
{ NULL, NULL }, /* dev fd 软盘*/
{ NULL, NULL }, /* dev hd 磁盘*/
{ NULL, NULL }, /* dev ttyx */
{ NULL, NULL }, /* dev tty */
{ NULL, NULL } /* dev lp lp打印机设备*/
};
ll_rw_block
在读写磁盘时,先使用open方法,触发中断,中断程序选择使用sys_open方法,往下就会走到ll_rw_block方法。验证设备号后就交给make_request方法。
kernel/blk_drv/ll_rw_blk.c
void ll_rw_block(int rw, struct buffer_head * bh)
{
unsigned int major;
//如果主设备号大于等于7或者没有挂载请求的说明是有问题的
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);
}
include/linux/fs.h
设备号=主设备号*256 +次设备号(也即dev_no = (major<<8) + minor), 其中主设备号:1-内存,2-磁盘,3-硬盘,4-ttyx,5-tty,6-并行口,7-非命名管道。
#define MAJOR(a) (((unsigned)(a))>>8) //设备号的主版本
#define MINOR(a) ((a)&0xff)
kernel/blk_drv/blk.h
#define NR_BLK_DEV 7 //支持的块设备的数量
struct blk_dev_struct {
void (*request_fn)(void); //执行的请求,磁盘就是 do_hd_request,虚拟盘就是do_rd_request,软盘就是do_fd_request
struct request * current_request;
};
struct request {
int dev; /* -1 if no request */
int cmd; /* READ or WRITE */
int errors;
unsigned long sector;
unsigned long nr_sectors;
char * buffer;
struct task_struct * waiting;
struct buffer_head * bh;
struct request * next;
};
make_request
WRITEA/READA 是一种特殊情况 - 它们并并非必要,所以如果缓冲区已经上锁,我们就不管它而退出,否则的话就执行一般的读/写操作。这里'READ'和'WRITE'后面的'A'字符代表英文单词Ahead,表示提前预读/写数据块的意思。对于命令是READA/WRITEA 的情况,当指定的缓冲区正在使用,已被上锁时,就放弃预读/写请求。否则就作为普通的READ/WRITE 命令进行操作。
如果命令是写操作,但是缓冲区中的数据并不“脏”,没有必要往磁盘中写了,直接返回。如果命令是读操作, 但是缓冲区中的数据还是有效的,也就是说缓冲区的数据与进程中的一样,也没必要读,返回。
在读操作优先于写操作,读操作可以沾占满整个队列,但是写操作只能占据整个队列的2/3,写操作不能占满队列,如果前面2/3已经被读操作占据,那么还有后面的1/3给读操作。
kernel/blk_drv/ll_rw_blk.c
static void make_request(int major,int rw, struct buffer_head * bh)
{
struct request * req;
int rw_ahead;
if ((rw_ahead = (rw == READA || rw == WRITEA))) {
if (bh->b_lock) //READA或者WRITEA命令下如果缓冲区已经上锁,就直接退出
return;
if (rw == READA)
rw = READ;
else
rw = WRITE;
}
if (rw!=READ && rw!=WRITE)
panic("Bad block dev command, must be R/W/RA/WA");
//锁定缓冲区,如果缓冲区已经上锁,则当前任务(进程)就会睡眠,直到被明确地唤醒。
lock_buffer(bh);
if ((rw == WRITE && !bh->b_dirt) || (rw == READ && bh->b_uptodate)) {
unlock_buffer(bh);
return;
}
repeat:
if (rw == READ)
req = request+NR_REQUEST; //先把req指针指向request数组最后一项
else
req = request+((NR_REQUEST*2)/3);
while (--req >= request) // 从后向前搜索,最后面的可是读操作,所以读优先
if (req->dev<0)
break;
if (req < request) {
if (rw_ahead) {
unlock_buffer(bh);
return;
}
sleep_on(&wait_for_request); //没有一个空闲项,睡眠后再次重复
goto repeat;
}
//找到空闲项了,赋值
req->dev = bh->b_dev;
req->cmd = rw;
req->errors=0;
req->sector = bh->b_blocknr<<1; // 1块等于两个扇区所以乘以2
req->nr_sectors = 2; // 读取2个扇区即1块
req->buffer = bh->b_data; //缓冲区的数据
req->waiting = NULL;
req->bh = bh;
req->next = NULL;
add_request(major+blk_dev,req); // major+blk_dev就是blk_dev[major]
}
lock_buffer
static inline void lock_buffer(struct buffer_head * bh)
{
cli(); //关闭进程自己的中断
while (bh->b_lock) //如果已经被锁了,就去等待
sleep_on(&bh->b_wait);
bh->b_lock=1; //上锁
sti(); //打开进程自己的中断
}
unlock_buffer
static inline void unlock_buffer(struct buffer_head * bh)
{
if (!bh->b_lock)
printk(DEVICE_NAME ": free buffer being unlocked\n");
bh->b_lock=0;
wake_up(&bh->b_wait); //唤醒在该资源上等待的进程
}
add_request
如果当前还没有请求,就直接把当前请求给req,执行request_fn,对于硬盘是do_hd_request();如果已经有请求了,就根据电梯算法插入到队列中。
static void add_request(struct blk_dev_struct * dev, struct request * req)
{
struct request * tmp;
req->next = NULL;
cli(); //关闭中断,进程不再相应中断
if (req->bh)
req->bh->b_dirt = 0; //清除b_dirt标记
// 如果dev 的当前请求(current_request)子段为空,则表示目前该设备没有请求项
//本次是第1个请求项,因此可将块设备当前请求指针直接指向该请求项,并立刻执行相应设备的请求函数。
if (!(tmp = dev->current_request)) {
dev->current_request = req;
sti(); //打开中断,进程相应中断
(dev->request_fn)(); // 执行设备请求函数,对于硬盘是do_hd_request()
return;
}
//如果目前该设备已经有请求项在等待,则首先利用电梯算法搜索最佳插入位置,然后将当前请求插入
//到请求链表中。电梯算法的作用是让磁盘磁头的移动距离最小,从而改善硬盘访问时间
for ( ; tmp->next ; tmp=tmp->next)
if ((IN_ORDER(tmp,req) ||
!IN_ORDER(tmp,tmp->next)) &&
IN_ORDER(req,tmp->next))
break;
req->next=tmp->next;
tmp->next=req;
sti();
}
do_hd_request
的执行过程看这里
IN_ORDER
#define IN_ORDER(s1,s2) \
((s1)->cmd<(s2)->cmd || ((s1)->cmd==(s2)->cmd && \
((s1)->dev < (s2)->dev || ((s1)->dev == (s2)->dev && \
(s1)->sector < (s2)->sector))))
转换以下就是下面的代码,READ
是0 , WRITE
是1,代码的含义是先比较命令,读命令优先于写命令;如果命令相同,则比较设备号,设备号小的设备优先于设备号大的;如果设备号也相同,则比较扇区号,先处理扇区号小的扇区,尽量让磁头层层递进。
bool inorder(request &s1,request &s2)
{
if (s1.cmd<s2.cmd)
{
return true; //读命令优先
}
else if ( s1.cmd == s2.cmd )
{
if (s1.dev < s2.dev)
{
return true; // 设备号小的设备优先于设备号大的
}
else if ( s1.dev == s2.dev )
{
if (s1.sector<s2.sector)
{
return true; // 读写扇区一层层递进,防止磁头来回读写扇区
}
return false;
}
return false;
}
return false;
}