转载自http://blog.youkuaiyun.com/suppercoder/article/details/19619777
如果涉及版权,请通知我,本人将立即删除,谢谢!
在看linux0.11代码里面的电梯算法的时候,产生了一些疑惑,经过分析解决了,发现网上也有不少讨论,我自己的分析记录下来。
- 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)))
- 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;
- };
用结构更清晰的代码开始分析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的内部比较思想是:先比较操作类型,读操作优先于写操作;如果操作类型相同,则比较设备号,设备号小的设备优先于设备号大的;如果设备号也相同,则比较扇区号,先处理扇区号小的扇区,意思就是从磁头从里向外读写了;如果扇区号也相同,那么返回false,也就是前面的s1优先级低于后面的s2。
然后我们就可以来分析插入的代码了:
- if (!(tmp = dev->current_request)) {
- dev->current_request = req;
- sti();
- (dev->request_fn)();
- 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;
首先,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;
- }
大家看结果,红色的是新插入的节点,和我上述的结果是一致的。