1. 程式人生 > >Linux那些事兒之我是Block層(4)濃縮就是精華?(一)

Linux那些事兒之我是Block層(4)濃縮就是精華?(一)

  人,生在床上,死在床上;欲生欲死,還是在床上.這句話非常有道理.有人說它有點俗,但,我並不這麼認為.我因為經常坐在床上一邊看A片一邊看程式碼,所以對這句話體會頗深,事實上它形象的描述了我坐在床上看程式碼時複雜的心情,說欲生欲死,一點也不誇張,尤其是當我看到add_disk()這個無比變態的函式的時候.我不禁感慨,上帝欲使人滅亡,必先使其瘋狂;上帝欲使人瘋狂,必先使其看Linux核心程式碼.     175 /**     176 * add_disk - add partitioning information to kernel list     177 * @disk: per-device partitioning information     178 *     179 * This function registers the partitioning information in @disk     180 * with the kernel.     181 */     182 void add_disk(struct gendisk *disk)     183 {
    184         disk->flags |= GENHD_FL_UP;     185         blk_register_region(MKDEV(disk->major, disk->first_minor),     186                             disk->minors, NULL, exact_match, exact_lock, disk);     187         register_disk(disk);     188         blk_register_queue(disk);     189 }
老實說當我一開始看到這個函式只有四行程式碼的時候,我幾乎喜極而泣.但很快我就發現自己的想法Too Simple, Sometimes Naive了.這個函式雖然只有四行程式碼,可是超級複雜,旗下三個函式,一個比一個拽,我漸漸困惑,寫程式碼的哥們兒有必要寫這種濃縮版的函式麼?要黑趙麗華老師也不至於這麼表現吧? 頭一個,blk_register_region,來自block/genhd.c:     139 /*     140 * Register device numbers dev..(dev+range-1)     141 * range must be nonzero     142 * The hash chain is sorted on range, so that subranges can override.
    143 */     144 void blk_register_region(dev_t dev, unsigned long range, struct module *module,     145                          struct kobject *(*probe)(dev_t, int *, void *),     146                          int (*lock)(dev_t, void *), void *data)     147 {     148         kobj_map(bdev_map, dev, range, module, probe, lock, data);     149 } 這裡kobj_map()其實是遠方的來客,它來自drivers/base/map.c:      32 int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,      33              struct module *module, kobj_probe_t *probe,      34              int (*lock)(dev_t, void *), void *data)      35 {      36         unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;      37         unsigned index = MAJOR(dev);      38         unsigned i;      39         struct probe *p;      40      41         if (n > 255)      42                 n = 255;      43      44         p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);      45      46         if (p == NULL)      47                 return -ENOMEM;      48      49         for (i = 0; i < n; i++, p++) {      50                 p->owner = module;      51                 p->get = probe;      52                 p->lock = lock;      53                 p->dev = dev;      54                 p->range = range;      55                 p->data = data;      56         }      57         mutex_lock(domain->lock);      58         for (i = 0, p -= n; i < n; i++, p++, index++) {      59                 struct probe **s = &domain->probes[index % 255];      60                 while (*s && (*s)->range < range)      61                         s = &(*s)->next;      62                 p->next = *s;      63                 *s = p;      64         }      65         mutex_unlock(domain->lock);      66         return 0;      67 } 結合我們的sd_probe函式來看,我們在sd_probe()中說了,first_minor無非就是0,16,32,48這樣一系列的數,而minors總是16,換言之按照這裡我們的上下文range就是16,這種情況下n只能是1. Domain就是bdev_map,於是我們即便不看程式碼也能猜到,這個函式的主要目的就是為bdev_map的probes這個指標陣列賦值,假設我們的major是8,那麼這裡就是為probes[8]賦值.對比形參實參可以看到,我們為get指標賦的是exact_match().這個函式同樣來自於block/genhd.c:     160 static struct kobject *exact_match(dev_t dev, int *part, void *data)     161 {    162         struct gendisk *p = data;     163         return &p->kobj;     164 } 即,比如說我們的index或者說major number是8的話,那麼這之後,bdev_map->probes[8]所對應的get指標就指向了exact_match. 同時,data指標賦上了disk,即struct gendisk指標disk. 老實說,現在我們完全看不出這麼做的意義,或者說blk_register_region這個函式究竟有什麼價值現在完全體現不出來.但是其實這是Linux中實現的一種管理裝置號的機制,這裡利用了傳說中的雜湊表來管理裝置號,雜湊表的優點大家知道,便於查詢,而我們的目的是為了通過給定的一個裝置號就能迅速得到它所對應的kobject指標,對於塊裝置來說,得到kobject是為了得到其對應的gendisk. 那麼什麼時候會需要這樣做呢?Ok,比如你執行fdisk –l /dev/sda,從而open系統呼叫或者說函式sys_open會被執行,如果你一路跟蹤,你會發現到後來會有一個叫做get_gendisk()的函式被呼叫.這個函式實際上也是我們這邊定義的,來自block/genhd.c:     203 /**     204 * get_gendisk - get partitioning information for a given device     205 * @dev: device to get partitioning information for     206 *     207 * This function gets the structure containing partitioning     208 * information for the given device @dev.     209 */     210 struct gendisk *get_gendisk(dev_t dev, int *part)     211 {     212         struct kobject *kobj = kobj_lookup(bdev_map, dev, part);     213         return kobj ? to_disk(kobj) : NULL;     214 } 於是我們來看kobj_lookup().來自drivers/base/map.c:      96 struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index)      97 {      98         struct kobject *kobj;      99         struct probe *p;     100         unsigned long best = ~0UL;     101     102 retry:     103         mutex_lock(domain->lock);     104         for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) {     105                 struct kobject *(*probe)(dev_t, int *, void *);     106                 struct module *owner;    107                 void *data;     108     109                 if (p->dev > dev || p->dev + p->range - 1 < dev)     110                         continue;     111                 if (p->range - 1 >= best)     112                         break;     113                 if (!try_module_get(p->owner))     114                         continue;     115                 owner = p->owner;     116                 data = p->data;     117                 probe = p->get;     118                 best = p->range - 1;    119                 *index = dev - p->dev;     120                 if (p->lock && p->lock(dev, data) < 0) {     121                         module_put(owner);     122                         continue;     123                 }     124                 mutex_unlock(domain->lock);     125                 kobj = probe(dev, index, data);     126                 /* Currently ->owner protects _only_ ->probe() itself. */     127                 module_put(owner);     128                 if (kobj)     129                         return kobj;     130                 goto retry;     131         }     132         mutex_unlock(domain->lock);     133         return NULL;     134 } 現在我們隱隱約約的感覺到,kobj_map_init()和kobj_map()以及kobj_lookup()是一個系列的,它們都是為Linux裝置號管理服務的,就好比舒淇,李麗珍,鍾麗緹是一個系列的,她們都是為三級片市場服務的.首先,kobj_map_init提供的是一次性服務,它的使命是建立了bdev_map這個struct kobj_map.然後kobj_map是每次在blk_register_region中被呼叫的,然而,在這個五彩繽紛的世界中,呼叫blk_register_region()的地方可真不少,隨便一搜索就是一大把,而我們這個在add_disk中呼叫只是其中之一,其它的比如RAID驅動那邊,軟碟機驅動那邊,都會有呼叫這個blk_register_region的需求,而kobj_lookup()發生在什麼情況下呢?它提供的其實是售後服務.當塊裝置驅動完成了初始化工作,當它在核心中站穩了腳跟,會有一個裝置檔案和它相對應,這個檔案會出現在/dev目錄下.在不久的將來,當open系統呼叫試圖開啟塊裝置檔案的時候就會呼叫它,更準確地說,sys_open經由filp_open然後是dentry_open(),最終會找到blkdev_open,blkdev_open會呼叫do_open,do_open()會呼叫get_gendisk(),要想明白這個理兒,得先看一下dev_t這個結構.dev_t實際上就是u32,也即就是32個bits.前面咱們看到的MKDEV,MAJOR,都來自include/linux/kdev_t.h:       4 #define MINORBITS       20       5 #define MINORMASK       ((1U << MINORBITS) - 1)      6       7 #define MAJOR(dev)      ((unsigned int) ((dev) >> MINORBITS))       8 #define MINOR(dev)      ((unsigned int) ((dev) & MINORMASK))       9 #define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi)) 通過這幾個巨集,我們不難看出dev_t的意義了,32個bits,其中高12位被用來記錄裝置的主裝置號,低20位用來記錄裝置的次裝置號.而MKDEV就是建立一個裝置號.ma代表主裝置號,mi代表次裝置號,ma左移20位再和mi相或,反過來,MAJOR就是從dev中取主裝置號,MINOR就是從dev中取次裝置號.不多說了,杭州西湖畔拉皮條的都知道怎麼回事了. 當一個裝置闖入Linux的內心時,首先它會有一個居住證號,這就是dev_t,很顯然,每個人的居住證號不一樣,它是唯一的.(為什麼不說是身份證號?因為居住證意味著當裝置離開Linux系統的時候就可以銷燬,所以它更能體現裝置的流動性.)建立一個裝置檔案的時候,其裝置號是確定的,而我們每次建立一個檔案都會建立一個結構體變數,它就是struct inode,而struct inode擁有成員dev_t i_dev,所以日後我們從struct inode就可以得到其裝置號dev_t,而這裡kobj_map這一系列函式使得我們可以從dev_t找到對應的kobject,然後進一步作為磁碟驅動,我們不可避免的需要訪問磁碟對應的gendisk結構體指標,而get_gendisk()就是在這時候應運而生並粉墨登場的.咱們看到get_gendisk()的兩個引數,dev_t dev和int *part,前者就是裝置號,而後者傳遞的是一個指標,這表示什麼呢?這表示, 1.如果這個裝置號對應的是一個分割槽,那麼part變數就用來儲存分割槽的編號. 2.如果這個裝置號對應的是整個裝置而不是某個分割槽,那麼part就只要設定成0就ok了. 那麼得到gendisk的目的又是什麼呢?我們注意到struct gendisk有一個成員,struct block_device_operations *fops,而這個指標才是用來真正執行操作的,每一個塊裝置驅動都準備了這麼一個結構體,比如咱們在sd中定義的那個:     872 static struct block_device_operations sd_fops = {     873         .owner                  = THIS_MODULE,     874         .open                   = sd_open,     875         .release                = sd_release,     876         .ioctl                  = sd_ioctl,     877         .getgeo                 = sd_getgeo,     878 #ifdef CONFIG_COMPAT     879         .compat_ioctl           = sd_compat_ioctl,     880 #endif     881         .media_changed          = sd_media_changed,     882         .revalidate_disk        = sd_revalidate_disk,     883 }; 正是因為有這種種曖昧關係,我們才能一步一步從sys_open最終走到sd_open,也才能從使用者層一步一步走到塊裝置驅動層,如同董卿姐姐能夠從上海一步步走向央視.