linux0.11源代码电梯算法解析
2014-02-21 16:36
387 查看
在看linux0.11代码里面的电梯算法的时候,产生了一些疑惑,经过分析解决了,发现网上也有不少讨论,我自己的分析记录下来。
以上代码列出了从file_read函数到add_request的过程,其中add_request使用了电梯算法来添加请求到等待队列。这里关键是分析电梯算法按一个什么样的逻辑插入到等待队列当中。
用结构更清晰的代码开始分析IN_ORDER的原理:
IN_ORDER的内部比较思想是:先比较操作类型,读操作优先于写操作;如果操作类型相同,则比较设备号,设备号小的设备优先于设备号大的;如果设备号也相同,则比较扇区号,先处理扇区号小的扇区,意思就是从磁头从里向外读写了;如果扇区号也相同,那么返回false,也就是前面的s1优先级低于后面的s2。
然后我们就可以来分析插入的代码了:
首先,req是待插入节点,我们以tmp为哨兵节点,IN_ORDER(tmp,req),是比较哨兵节点是否比待插入节点优先级高。
我们作一个约定,a比b优先级高的话使用a>b的记号,否则是a<=b。注意=的临界状态,不少人因为临界状态理解不好影响整个插入算法的理解。
把上面的代码做一个伪代码来理解:
for( ; tmp->next; tmp=tmp->next)
{
if ( tmp>req && req>tmp->next) break;//也就是哨兵节点比待插入节点优先级要高,并且待插入节点的优先级要比哨兵后的一个节点要高,那么应该插入到tmp与tmp->next之间。
else if ( tmp <= tmp->next && req>tmp->next ) break;//也就是哨兵节点不比它后面的节点优先级高——我们知道这是一个已经排序的队列,tmp优先级肯定大于或等于tmp->next,这里出现tmp <= tmp->nex成立的唯一情况就是tmp=tmp->next,也就是两个节点的优先级一样高。那么这个时候req>tmp->next也一定不会成立,就会继续下一个循环;等等!我们漏掉了一个情况——当tmp是current_request的时候,有可能出现tmp<=tmp->next的情况,此时如果req>tmp->next也成立,那么就会插入current_request后面,也就是tmp的后面!这种情况必须要理解!
else continue;//如果不适合插入tmp后面,继续下一个循环;
}
所以上面的总体意思就是:优先级从大到小排列,同一优先级的话,插入这一系统相同优先级的节点的最后面。可以说的细一点,先处理读请求,再处理写请求;同一读或写请求先处理设备号小的设备请求,再处理设备号大的设备请求,同一读或写请求,同一设备,按先里面的扇区再到外面的扇区的顺序处理。
为了更有说服力,以下我按电梯算法编写了代码,可以修改注掉的inorder部分再做测试。
#include <stdio.h>
#include <stdlib.h>
#define READ 0
#define WRITE 1
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;
};
#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)))
// 作为解析,以明白的分支结构重写一个内容一样的inorder函数
bool inorder(struct request *s1,struct request *s2)
{
if (s1->cmd<s2->cmd)
{
return true;//only when s1->cmd = READ; s2->cmd = WRITE;
}
else if ( s1->cmd == s2->cmd )
{
if (s1->dev < s2->dev)
{
return true;// when (s1->cmd==s2->cmd) && (s1->dev<s2->dev)
}
else if ( s1->dev == s2->dev )
{
if (s1->sector<s2->sector)
{
return true;// when when (s1->cmd==s2->cmd) && (s1->sector<s2->sector)
}
return false;// when when (s1->cmd==s2->cmd) && (s1->sector>=s1->sector)
}
return false;// when (s1->cmd==s2->cmd) && (s1->dev>s2->dev)
}
return false;// when s1->cmd>s2->cmd
}
void AddRequest(struct request * &head,struct request *req)
{
if (!head)
{
head = req;
head->next = 0;
return ;
}
struct request * tmp = head;
for (;tmp->next;tmp = tmp->next)
{
// 使用inorder和宏IN_ORDER是一样的结果
//if ( ( inorder(tmp,req)||
// !inorder(tmp,tmp->next)) &&
// inorder(req,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;
return;
}
void PrintQueen(struct request * n)
{
while (n)
{
printf("(%d,%d,%d),",n->cmd,n->dev,n->sector);
n = n->next;
}
printf("\n");
}
int main(int argc,char ** argv)
{
struct request s1;
struct request * pHead = 0;
for (int i = 0;i<100;i++)
{
struct request *req = new struct request;
req->cmd = rand()%2;
req->dev = rand()%10;
req->sector = rand()%100;
AddRequest(pHead,req);
PrintQueen(pHead);
}
return 0;
}
大家看结果,红色的是新插入的节点,和我上述的结果是一致的。
00017 int file_read(struct m_inode * inode, struct file * filp, char * buf, int count) 00018 { 00019 int left,chars,nr; 00020 struct buffer_head * bh; 00021 00022 if ((left=count)<=0) 00023 return 0; 00024 while (left) { 00025 if ((nr = bmap(inode,(filp->f_pos)/BLOCK_SIZE))) { 00026 if (!(bh=bread(inode->i_dev,nr))) 00027 break; 00028 } else 00029 bh = NULL; 00030 nr = filp->f_pos % BLOCK_SIZE; 00031 chars = MIN( BLOCK_SIZE-nr , left ); 00032 filp->f_pos += chars; 00033 left -= chars; 00034 if (bh) { 00035 char * p = nr + bh->b_data; 00036 while (chars-->0) 00037 put_fs_byte(*(p++),buf++); 00038 brelse(bh); 00039 } else { 00040 while (chars-->0) 00041 put_fs_byte(0,buf++); 00042 } 00043 } 00044 inode->i_atime = CURRENT_TIME; 00045 return (count-left)?(count-left):-ERROR; 00046 }
00270 struct buffer_head * bread(int dev,int block) 00271 { 00272 struct buffer_head * bh; 00273 00274 if (!(bh=getblk(dev,block))) 00275 panic("bread: getblk returned NULL\n"); 00276 if (bh->b_uptodate) 00277 return bh; 00278 ll_rw_block(READ,bh); 00279 wait_on_buffer(bh); 00280 if (bh->b_uptodate) 00281 return bh; 00282 brelse(bh); 00283 return NULL; 00284 }
00145 void ll_rw_block(int rw, struct buffer_head * bh) 00146 { 00147 unsigned int major; 00148 00149 if ((major=MAJOR(bh->b_dev)) >= NR_BLK_DEV || 00150 !(blk_dev[major].request_fn)) { 00151 printk("Trying to read nonexistent block-device\n\r"); 00152 return; 00153 } 00154 make_request(major,rw,bh); 00155 }
00088 static void make_request(int major,int rw, struct buffer_head * bh) 00089 { 00090 struct request * req; 00091 int rw_ahead; 00092 00093 /* WRITEA/READA is special case - it is not really needed, so if the */ 00094 /* buffer is locked, we just forget about it, else it's a normal read */ 00095 if ((rw_ahead = (rw == READA || rw == WRITEA))) { 00096 if (bh->b_lock) 00097 return; 00098 if (rw == READA) 00099 rw = READ; 00100 else 00101 rw = WRITE; 00102 } 00103 if (rw!=READ && rw!=WRITE) 00104 panic("Bad block dev command, must be R/W/RA/WA"); 00105 lock_buffer(bh); 00106 if ((rw == WRITE && !bh->b_dirt) || (rw == READ && bh->b_uptodate)) { 00107 unlock_buffer(bh); 00108 return; 00109 } 00110 repeat: 00111 /* we don't allow the write-requests to fill up the queue completely: 00112 * we want some room for reads: they take precedence. The last third 00113 * of the requests are only for reads. 00114 */ 00115 if (rw == READ) 00116 req = request+NR_REQUEST; 00117 else 00118 req = request+((NR_REQUEST*2)/3); 00119 /* find an empty request */ 00120 while (--req >= request) 00121 if (req->dev<0) 00122 break; 00123 /* if none found, sleep on new requests: check for rw_ahead */ 00124 if (req < request) { 00125 if (rw_ahead) { 00126 unlock_buffer(bh); 00127 return; 00128 } 00129 sleep_on(&wait_for_request); 00130 goto repeat; 00131 } 00132 /* fill up the request-info, and add it to the queue */ 00133 req->dev = bh->b_dev; 00134 req->cmd = rw; 00135 req->errors=0; 00136 req->sector = bh->b_blocknr<<1; 00137 req->nr_sectors = 2; 00138 req->buffer = bh->b_data; 00139 req->waiting = NULL; 00140 req->bh = bh; 00141 req->next = NULL; 00142 add_request(major+blk_dev,req); 00143 }
00040 #define IN_ORDER(s1,s2) \ 00041 ((s1)->cmd<(s2)->cmd || ((s1)->cmd==(s2)->cmd && \ 00042 ((s1)->dev < (s2)->dev || ((s1)->dev == (s2)->dev && \ 00043 (s1)->sector < (s2)->sector))))
00064 static void add_request(struct blk_dev_struct * dev, struct request * req) 00065 { 00066 struct request * tmp; 00067 00068 req->next = NULL; 00069 cli(); 00070 if (req->bh) 00071 req->bh->b_dirt = 0; 00072 if (!(tmp = dev->current_request)) { 00073 dev->current_request = req; 00074 sti(); 00075 (dev->request_fn)(); 00076 return; 00077 } 00078 for ( ; tmp->next ; tmp=tmp->next) 00079 if ((IN_ORDER(tmp,req) || 00080 !IN_ORDER(tmp,tmp->next)) && 00081 IN_ORDER(req,tmp->next)) 00082 break; 00083 req->next=tmp->next; 00084 tmp->next=req; 00085 sti(); 00086 }
以上代码列出了从file_read函数到add_request的过程,其中add_request使用了电梯算法来添加请求到等待队列。这里关键是分析电梯算法按一个什么样的逻辑插入到等待队列当中。
#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)))先来了解一下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; };dev是设备号,根据linux0.11代码里面涉及的每个存储设备编了号;cmd是指这个请求的命令,也就是读或写,其中定义READ=0,WRITE=1;sector是扇区号;buffer是读或写的缓冲区;waiting这个task_struct结构是当前进程等待队列指针;next是指向下一请求的指针。另外的errors,nr_sectors,bh我们目前不关注。
用结构更清晰的代码开始分析IN_ORDER的原理:
bool inorder(request &s1,request &s2) { if (s1.cmd<s2.cmd) { return true;//only when s1.cmd = READ; s2.cmd = WRITE; } else if ( s1.cmd == s2.cmd ) { if (s1.dev < s2.dev) { return true;// when (s1.cmd==s2.cmd) && (s1.dev<s2.dev) } else if ( s1.dev == s2.dev ) { if (s1.sector<s2.sector) { return true;// when when (s1.cmd==s2.cmd) && (s1.sector<s2.sector) } return false;// when when (s1.cmd==s2.cmd) && (s1.sector>=s2.sector) } return false;// when (s1.cmd==s2.cmd) && (s1.dev>s2.dev) } return false;// when s1.cmd>s2.cmd }从上面的每个函数返回地方的注释看出返回时的比较状态。用自然语言描述一下:IN_ORDER返回s1与s2的优先级比较结果,s1比s2高的话,返回true;s1不高于s2的话,返回false;
IN_ORDER的内部比较思想是:先比较操作类型,读操作优先于写操作;如果操作类型相同,则比较设备号,设备号小的设备优先于设备号大的;如果设备号也相同,则比较扇区号,先处理扇区号小的扇区,意思就是从磁头从里向外读写了;如果扇区号也相同,那么返回false,也就是前面的s1优先级低于后面的s2。
然后我们就可以来分析插入的代码了:
if (!(tmp = dev->current_request)) { dev->current_request = req; sti(); (dev->request_fn)(); return; }上面代码的意思是:如果设备的当前处理请求项为空,那么把需要处理的请求项设置为当前处理项,当立即调用处理请求函数dev->request_fn()。
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;上面的代码是在设备的当前请求项tmp不为空(也就是设备在忙)的情况下使用。我们可以dev->current_request理解为等待队列的一个头节点;这个头指点指向一个等待队列;我们需要把请求按一定的方法(也就是IN_ORDER算法)插入到等待队列中。
首先,req是待插入节点,我们以tmp为哨兵节点,IN_ORDER(tmp,req),是比较哨兵节点是否比待插入节点优先级高。
我们作一个约定,a比b优先级高的话使用a>b的记号,否则是a<=b。注意=的临界状态,不少人因为临界状态理解不好影响整个插入算法的理解。
把上面的代码做一个伪代码来理解:
for( ; tmp->next; tmp=tmp->next)
{
if ( tmp>req && req>tmp->next) break;//也就是哨兵节点比待插入节点优先级要高,并且待插入节点的优先级要比哨兵后的一个节点要高,那么应该插入到tmp与tmp->next之间。
else if ( tmp <= tmp->next && req>tmp->next ) break;//也就是哨兵节点不比它后面的节点优先级高——我们知道这是一个已经排序的队列,tmp优先级肯定大于或等于tmp->next,这里出现tmp <= tmp->nex成立的唯一情况就是tmp=tmp->next,也就是两个节点的优先级一样高。那么这个时候req>tmp->next也一定不会成立,就会继续下一个循环;等等!我们漏掉了一个情况——当tmp是current_request的时候,有可能出现tmp<=tmp->next的情况,此时如果req>tmp->next也成立,那么就会插入current_request后面,也就是tmp的后面!这种情况必须要理解!
else continue;//如果不适合插入tmp后面,继续下一个循环;
}
所以上面的总体意思就是:优先级从大到小排列,同一优先级的话,插入这一系统相同优先级的节点的最后面。可以说的细一点,先处理读请求,再处理写请求;同一读或写请求先处理设备号小的设备请求,再处理设备号大的设备请求,同一读或写请求,同一设备,按先里面的扇区再到外面的扇区的顺序处理。
为了更有说服力,以下我按电梯算法编写了代码,可以修改注掉的inorder部分再做测试。
#include <stdio.h>
#include <stdlib.h>
#define READ 0
#define WRITE 1
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;
};
#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)))
// 作为解析,以明白的分支结构重写一个内容一样的inorder函数
bool inorder(struct request *s1,struct request *s2)
{
if (s1->cmd<s2->cmd)
{
return true;//only when s1->cmd = READ; s2->cmd = WRITE;
}
else if ( s1->cmd == s2->cmd )
{
if (s1->dev < s2->dev)
{
return true;// when (s1->cmd==s2->cmd) && (s1->dev<s2->dev)
}
else if ( s1->dev == s2->dev )
{
if (s1->sector<s2->sector)
{
return true;// when when (s1->cmd==s2->cmd) && (s1->sector<s2->sector)
}
return false;// when when (s1->cmd==s2->cmd) && (s1->sector>=s1->sector)
}
return false;// when (s1->cmd==s2->cmd) && (s1->dev>s2->dev)
}
return false;// when s1->cmd>s2->cmd
}
void AddRequest(struct request * &head,struct request *req)
{
if (!head)
{
head = req;
head->next = 0;
return ;
}
struct request * tmp = head;
for (;tmp->next;tmp = tmp->next)
{
// 使用inorder和宏IN_ORDER是一样的结果
//if ( ( inorder(tmp,req)||
// !inorder(tmp,tmp->next)) &&
// inorder(req,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;
return;
}
void PrintQueen(struct request * n)
{
while (n)
{
printf("(%d,%d,%d),",n->cmd,n->dev,n->sector);
n = n->next;
}
printf("\n");
}
int main(int argc,char ** argv)
{
struct request s1;
struct request * pHead = 0;
for (int i = 0;i<100;i++)
{
struct request *req = new struct request;
req->cmd = rand()%2;
req->dev = rand()%10;
req->sector = rand()%100;
AddRequest(pHead,req);
PrintQueen(pHead);
}
return 0;
}
大家看结果,红色的是新插入的节点,和我上述的结果是一致的。
相关文章推荐
- linux0.11源代码电梯算法解析
- linux-0.11中进程睡眠函数sleep_on()解析
- Linux0.11内核--进程的调度schedule和switch_to解析
- linux-0.11之setup.s解析
- linux 配置文件读写解析
- Linux0.11内核剖析--内核代码(kernel)--sched.c
- linux0.11学习1之bootsect
- 5月18日,,终于把linux0.11安到一台真正的pc上了
- linux0.11 感受(非技术)
- Linux sort命令解析
- Android 筆記-Linux Kernel SMP (Symmetric Multi-Processors) 開機流程解析 Part(4) Linux 多核心啟動流程-kthreadd 與相關的核
- linux-0.11中保护模式建立过程的分析[3]
- linux 0.11之tty
- 实例解析Linux下iptables的相关配置与命令使用技巧
- linux常用命令解析
- Linux内核0.11版本 fork.c文件
- Linux 0.11对exec的控制
- Linux __setup解析 .
- linux常用命令解析
- linux0.11分析之内存分配(结合uCos和VxWorks)