Linux0。11kernel目录llrwblk。c详解
该模块的作用是处理块设备的读写,其中最重要的函数就是电梯算法 add_request 函数和 ll_rw_block 函数。 add_requeststatic void add_request(struct blk_dev_struct * dev, struct request * req)
该函数的作用是将块设备读写请求 插入到电梯队列 中。
首先将数据的脏数据标志置为0。 if (req->bh) req->bh->b_dirt = 0;
如果当前设备的请求为空,就将入参中的请求作为设备电梯队列的头节点。并且立即调用request_fn。request_fn对于不同的设备对应不同的回调函数,对于硬盘设备而言,request_fn指的是do_hd_request,关于do_hd_request,将在hd.c中进行讲解。 if (!(tmp = dev->current_request)) { dev->current_request = req; sti(); (dev->request_fn)(); return; }
上面的部分处理了请求队列只有一个请求的场景,接下来便是当请求队列有多个请求时,如何处理优先级顺序的逻辑,也就是电梯算法部分,其中最难理解的便是宏定义IN_ORDER。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))))
上述代码是比较难懂的,可以使用if-else来帮助理解 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;//s1.sector > s2.sector } return false;//s1.dev > s2.dev } return false;//s1.cmd > s2.cmd }
展开上面的if-else结构逻辑就清晰了很多,IN_ORDER实际上就是依次对操作类型,设备号, 扇区号作比较,并且操作类型优先级大于设备号,设备号优先级大于扇区号。
对于操作类型而言,读操作优先级大于写操作。对于设备号而言,设备号小的设备优先级大于设备号大的设备的优先级。对于扇区而言,扇区序号小的扇区优先级高于扇区序号大的扇区。
有了这个认识之后,再看下面的语句,就会简单很多,实际上就是根据优先级找到合适的位置插入数据。 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;
下面的这一段代码可以用两个if语句进行替代,即 定向扫描 和 折返扫描 两个场景。 if ((IN_ORDER(tmp,req) || !IN_ORDER(tmp,tmp->next)) && IN_ORDER(req,tmp->next))
条件1:定向扫描, req在当前扫描的方向上 if(tmp > req && req > tmp->next) { req->next=tmp->next; tmp->next=req; }
条件2: 折返扫描,req在下一轮扫描的方向上 if(tmp < tmp->next && req > tmp->next) { req->next=tmp->next; tmp->next=req; }
这里需要阐明的是,Linux-0.11实际使用的磁盘扫描算法是 C-SCAN算法 ,也是 电梯算法 (可戏称为 跳楼机 )。
其思路就是只 单向寻道 ,到头后 直接复位 再次沿同方向寻道,这样对于所有磁盘位置的请求都是公平的。
我们通过下面的代码实际感受一下这个过程,我们固定cmd和dev, 只让sector号有区别,依次插入50, 80, 60, 30, 20 ,看看最后的结果如何。 #include #include #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->cmdcmd) { 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->devdev) } else if ( s1->dev == s2->dev ) { if (s1->sectorsector) { return true;// when when (s1->cmd==s2->cmd) && (s1->sectorsector) } 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) { 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(" "); } int main(int argc,char ** argv) { struct request s1; struct request * pHead = 0; struct request *req = new struct request; req->cmd = 0; req->dev = 0; req->sector = 50; AddRequest(pHead,req); PrintQueen(pHead); struct request *req3 = new struct request; req3->cmd = 0; req3->dev = 0; req3->sector = 80; AddRequest(pHead,req3); PrintQueen(pHead); struct request *req2 = new struct request; req2->cmd = 0; req2->dev = 0; req2->sector = 60; AddRequest(pHead,req2); PrintQueen(pHead); struct request *req5 = new struct request; req5->cmd = 0; req5->dev = 0; req5->sector = 30; AddRequest(pHead,req5); PrintQueen(pHead); struct request *req4 = new struct request; req4->cmd = 0; req4->dev = 0; req4->sector = 20; AddRequest(pHead,req4); PrintQueen(pHead); return 0; }
上述代码的执行结果如下所示: (0,0,50), (0,0,50),(0,0,80), (0,0,50),(0,0,60),(0,0,80), (0,0,50),(0,0,60),(0,0,80),(0,0,30), (0,0,50),(0,0,60),(0,0,80),(0,0,20),(0,0,30),
可以看出最后的顺序是50->60->80->20->30, 实际上效果就是单方向移动到最后一个位置,再复位进行扫描,再次沿同方向扫描。
c-san算法示意图
make_requeststatic void make_request(int major,int rw, struct buffer_head * bh)
该函数的作用是创建请求项并插入请求队列中。
首先判断命令是否READA或者是WRITEA。READA代表预读取,WRITEA代表预写入。所以当命令是 预读取 或者是 预写入 ,如果bh块被锁,那么就放弃,直接返回。如果bh块没有被锁,那么就当作普通的READ和WRITE。 struct request * req; int rw_ahead; /* WRITEA/READA is special case - it is not really needed, so if the */ /* buffer is locked, we just forget about it, else it"s a normal read */ if ((rw_ahead = (rw == READA || rw == WRITEA))) { if (bh->b_lock) return; if (rw == READA) rw = READ; else rw = WRITE; }
如果命令不是读或者写,那么就是一个致命错误,直接通过panic抛出错误。对命令校验之后,就去锁定该数据块。如果命令是写操作,但是该数据块并没有脏数据,则没有必要去写块设备,就可以对bh块进行解锁。除此以外,如果命令是读操作,但是该bh块中的内容已经是最新的,也没有必要去读块设备,就可以对bh块进行解锁。 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; }
下面需要从request数组中寻找一个位置来创建该请求。对于读请求而言,将会从数组的尾部开始搜索。对于写请求而言,将会从数组的2/3处开始搜索。 如果找到了位置,那么就开始进行创建,如果没有找到位置,就sleep_on进行等待。 if (rw == READ) req = request+NR_REQUEST; else req = request+((NR_REQUEST*2)/3); /* find an empty request */ while (--req >= request) if (req->dev<0) break; /* if none found, sleep on new requests: check for rw_ahead */ if (req < request) { if (rw_ahead) { unlock_buffer(bh); return; } sleep_on(&wait_for_request); goto repeat; }
当找到该位置时,就在该位置上进行构建请求。构建完之后,调用add_request插入到电梯队列中。 /* fill up the request-info, and add it to the queue */ req->dev = bh->b_dev; req->cmd = rw; req->errors=0; req->sector = bh->b_blocknr<<1; req->nr_sectors = 2; req->buffer = bh->b_data; req->waiting = NULL; req->bh = bh; req->next = NULL; add_request(major+blk_dev,req);ll_rw_blockvoid ll_rw_block(int rw, struct buffer_head * bh)
该函数的作用就是读写数据块。
下面一段代码用于对bh块对应的设备做相应的校验。如果主设备号不存在,或者该设备对应的请求操作函数不存在,就显示出错信息。 if ((major=MAJOR(bh->b_dev)) >= NR_BLK_DEV || !(blk_dev[major].request_fn)) { printk("Trying to read nonexistent block-device r"); return; }
如果校验没有问题就调用make_request建立块设备读写请求。 make_request(major,rw,bh);blk_dev_initvoid blk_dev_init(void)
该函数的作用是 初始化块设备 。
遍历request数组,对request数组中每一项的dev设置为-1, 对next指针设置为NULL。 for (i=0 ; ib_lock)//如果缓冲区已被锁定就睡眠,一直到缓冲区解锁 sleep_on(&bh->b_wait); bh->b_lock=1;//立即锁定缓冲区 sti();//开中断unlock_bufferstatic inline void unlock_buffer(struct buffer_head * bh)
该函数的作用是解锁指定的缓冲块。 if (!bh->b_lock)//如果该缓冲区没有加锁,则打印出错信息 printk("ll_rw_block.c: buffer not locked r"); bh->b_lock = 0;//对缓冲区解锁 wake_up(&bh->b_wait);//唤醒等待该缓冲区的任务。
巴西队吃下定心丸!主帅确认内马尔伤情无大碍,将继续参加世界杯在北京时间11月25日凌晨结束的一场卡塔尔世界杯小组赛中,巴西队凭借里沙利松的两粒进球20击败塞尔维亚队,取得开门红。不过,巴西队头号球星内马尔在比赛中因伤离场,却让巴西球迷的心瞬
贺建奎出狱7个月新实验室落户北京,从事罕见遗传病基因治疗衡宇鱼羊发自凹非寺量子位公众号QbitAI基因编辑狂人贺建奎,复出成立新实验室!他在个人微博发布实验室落户北京的最新消息实验室,北京大兴,新起点,新征程!2019年底,因基因编辑案
14岁初中生被清华录取,本硕博连读,数学好的人,能有多吃香?还没到中考季,14岁的初三学生官子钦就已经接到清华大学的通知书,无需参加中考高考,明年2月份直接到清华读大学,本硕博八年连读。同龄人还在为明年的中考做准备,而官同学已经准备开启大学
孕期中,千万别忽视这5个注意事项和禁忌,对准妈妈和宝宝都好孕早期,是胎儿成型最重要的时期,盲目忌口只会导致胎儿所需营养不足,但是肆无忌惮的进补,也同样会影响胎儿发育,接下来这5点对胎儿的健康十分重要,妈妈拿起小本本记好了接触有害物质怀孕初
孕妇怀孕4个月坐公交车,被老人抢座位,一起身全车人冒冷汗现在的职场女性们,即使怀孕了,也依然坚持上班,她们往往舍不得自己的事业,怕怀孕生娃一旦放弃了工作,就会与社会脱节,所以,即使再难再累,依然继续工作。小陈就是一位职场女性,由于家里经
垮掉的信任如何再次建立?人与人之间的交流或是彼此的认知都基于信任,你信任一个人才可能同这个人去交流深层次的东西又或是进一步合作。孩子基于对父母的信任所以在孩提时代无论大事小情都会说给爸爸妈妈听,所以这个时
孕期水肿异常!这样预防可以避免流产在怀孕期间,大约有一半的孕妇会出现浮肿现象,特别是在28周以后,增大到一定程度,长大的胎儿压迫妈妈腹股沟处的大静脉。静脉回流不好的话,妈妈就很容易出现下肢浮肿。随着个月份的逐渐增大
妈妈,我们的世界怎么了?妈妈,我们的世界怎么了?被小魔女突然一问,我当时怔住了,竟不知怎么回答,想着小小年纪的你怎么会这么问呢,小脑袋瓜里究竟想着什么呢?在她的心里,世界是什么?世界究竟有多大呢?(图片非
吃饭可不是小事,5招1汤助你胃气足,吃得好胃病素有三分治,七分养的说法,养胃气预防调理才是首选之策,若在生活中避免上述不良生活方式,改善不良习惯,再加上药膳调理,不但胃病不会找上门,一些较轻的胃病患者也能自行痊愈。饮食规律
浅谈食物对风热型感冒的影响中医认为,风热感冒是感受风热之邪所致的表证,诸病源候论风热候风热病者,风热之气,先从皮毛入于肺也。肺为五脏上盖,候身之皮毛,若服腠虚,则风热之气,先伤皮毛,乃入肺也。其状使人恶风寒
前列腺增生肥大?简单五味药,拯救男人的生命腺前列腺增生症是男性老年人常见病,属于中医癃闭症的范畴。主要症状为尿频滴沥不尽,或少腹胀急出现尿潴留,或滴沥失禁。关于前列腺增生症的发病原因,西医认为是由于内分泌失调引起腺体肥大,压