本文轉(zhuǎn)載于: http://edsionte.com/techblog/archives/1393
如何找到一個有效的切入點去深入分析內(nèi)核源碼,這是一個令人深思的問題。本文以 前文 中未詳細說明的函數(shù)為切入點,深入分析char_dev.c文件的代碼。如果你已經(jīng)擁有了C語言基礎(chǔ)和一些數(shù)據(jù)結(jié)構(gòu)基礎(chǔ),那么還等什么?Let’s go!
在《字符設(shè)備驅(qū)動分析》一文中,我們說到register_chrdev_region函數(shù)的功能是在已知起始設(shè)備號的情況下去申請一組連續(xù)的設(shè)備號。不過大部分驅(qū)動書籍都沒有去深入說明此函數(shù),可能是因為這個函數(shù)內(nèi)部封裝了__register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name)函數(shù)的原因。不過我們不用苦惱,這正好促使我們?nèi)シ治鲞@個函數(shù)。
int register_chrdev_region(dev_t from, unsigned count,
const
char
*name)
{
??????
struct
char_device_struct *cd;
?????? dev_t to = from + count;
?????? dev_t n, next;
?
??????
for
(n = from; n <\ to; n = next)
?????? {
?????????
next = MKDEV(MAJOR(n)+1, 0);
??????????
if
(next >\ to)
??????????????? next = to;
??????????????? cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
???????????????
if
(IS_ERR(cd))
????????????????????
goto
fail;
??????
}
??????????
return
0;
fail:
to = n;
??????
for
(n = from; n <\ to; n = next)
?????? {
???????????
next = MKDEV(MAJOR(n)+1, 0);
??????????? kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
?????? }
??????
return
PTR_ERR(cd);
}
首先值得我們注意的是,這個函數(shù)每次分配的是一組設(shè)備編號。其中from參數(shù)是這組連續(xù)設(shè)備號的起始設(shè)備號,count是這組設(shè)備號的大小(也是次設(shè)備號的個數(shù)),name參數(shù)處理本組設(shè)備的驅(qū)動名稱。另外,當次設(shè)備號數(shù)目過多(count過多)的時候,次設(shè)備號可能會溢出到下一個主設(shè)備。因此我們在for語句中可以看到,首先得到下一個主設(shè)備號(其實也是一個設(shè)備號,只不過此時的次設(shè)備號為0)并存儲于next中。然后判斷在from的基礎(chǔ)上再追加count個設(shè)備是否已經(jīng)溢出到下一個主設(shè)備號。如果沒有溢出(next小于to),那么整個for語句就只執(zhí)行個一次__register_chrdev_region函數(shù);否則當設(shè)備號溢出時,會把當前溢出的設(shè)備號范圍劃分為幾個小范圍,分別調(diào)用__register_chrdev_region函數(shù)。
如果在某個小范圍調(diào)用__register_chrdev_region時出現(xiàn)了失敗,那么會將此前分配的設(shè)備號都釋放。
其實register_chrdev_region函數(shù)還沒有完全說清除設(shè)備號分配的具體過程,因為具體某個小范圍的設(shè)備號是由__register_chrdev_region函數(shù)來完成的。可能你已經(jīng)注意到在register_chrdev_region函數(shù)源碼中出現(xiàn)了struct char_device_struct結(jié)構(gòu),我們首先來看這個結(jié)構(gòu)體:
static
struct
char_device_struct {
???????
struct
char_device_struct *next;
??????? unsigned
int
major;
??????? unsigned
int
baseminor;
???????
int
minorct;
???????
char
name[64];
???????
struct
cdev *cdev;?????????????
/* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
在register_chrdev_region函數(shù)中,在每個字符設(shè)備號的小范圍上調(diào)用__register_chrdev_region函數(shù),都會返回一個struct char_device_struct類型的指針。因此我們可以得知,struct char_device_struct類型對應(yīng)的并不是每一個字符設(shè)備,而是具有連續(xù)設(shè)備號的一組字符設(shè)備。從這個結(jié)構(gòu)體內(nèi)部的字段也可以看出,這組連續(xù)的設(shè)備號的主設(shè)備號為major,次設(shè)備號起始為baseminor,次設(shè)備號范圍為minorct,這組設(shè)備號對應(yīng)的設(shè)備驅(qū)動名稱為name,cdev為指向這個字符設(shè)備驅(qū)動的指針。
這里要特別說明的是,內(nèi)核中所有已分配的字符設(shè)備編號都記錄在一個名為chrdevs散列表里。該散列表中的每一個元素是一個 char_device_struct結(jié)構(gòu),這個散列表的大小為255(CHRDEV_MAJOR_HASH_SIZE),這是因為系統(tǒng)屏蔽了12位主設(shè)備號的前四位。既然說到散列表,那么肯定會出現(xiàn)沖突現(xiàn)象,因此next字段就是沖突鏈表中的下一個元素的指針。
接下來我們詳細來析__register_chrdev_region函數(shù)。首先為cd變量分配內(nèi)存并用零來填充(這就是用kzalloc而不是kmalloc的原因)。接著通過P操作使得后續(xù)要執(zhí)行的語句均處于臨界區(qū)。
static
struct
char_device_struct *
__register_chrdev_region(unsigned
int
major, unsigned
int
baseminor,
int
minorct,
const
char
*name)
{
??????
struct
char_device_struct *cd, **cp;
??????
int
ret = 0;
??????
int
i;
?????? cd = kzalloc(
sizeof
(
struct
char_device_struct), GFP_KERNEL);
??????
if
(cd == NULL)
???????????
return
ERR_PTR(-ENOMEM);
?
??????????? mutex_lock(&chrdevs_lock);
如果major為0,也就是未指定一個具體的主設(shè)備號,需要動態(tài)分配。那么接下來的if語句就在整個散列表中為這組設(shè)備尋找合適的位置,即從散列表的末尾開始尋找chrdevs[i]為空的情況。若找到后,那么i不僅代表這組設(shè)備的主設(shè)備號,也代表其在散列表中的關(guān)鍵字。當然,如果主設(shè)備號實現(xiàn)已指定,那么可不去理會這部分代碼。
??????
if
(major == 0)
?????? {
??????????
for
(i = ARRAY_SIZE(chrdevs)-1; i > 0; i—)
?????????? {
???????????????
if
(chrdevs[i] == NULL)
???????????????????
break
;
?????????? }
?
??????????
if
(i == 0)
?????????? {
??????????????? ret = -EBUSY;
???????????????
goto
out;
?????????? }
?????????? major = i;
?????????? ret = major;
??????? }
接著對將參數(shù)中的值依次賦給cd變量的對應(yīng)字段。當主設(shè)備號非零,即事先已知的話,那么還要通過major_to_index函數(shù)對其進行除模255運算,因此整個散列表關(guān)鍵字的范圍是0~254。
??????? cd->major = major;
??????? cd->baseminor = baseminor;
??????? cd->minorct = minorct;
??????? strlcpy(cd->name, name,
sizeof
(cd->name));
?
??????? i = major_to_index(major);
至此,我們通過上面的代碼會得到一個有效的主設(shè)備號(如果可以繼續(xù)執(zhí)行下面代碼的話),那么接下來還不能繼續(xù)分配。正如你所知的那樣,散列表中的沖突是在所難免的。因此我們得到major的值后,我們要去便利沖突鏈表,為當前我們所述的char_device_struct類型的變量cd去尋找正確的位置。更重要的是,我們要檢查當前的次設(shè)備號范圍,即baseminor~baseminor+minorct,是否和之前的已分配的次設(shè)備號(前提是major相同)范圍有重疊。
下面的for循環(huán)就是在沖突鏈表中查找何時的位置,當出現(xiàn)以下三種情況時,for語句會停止。
(1)如果沖突表中正被遍歷的結(jié)點的主設(shè)備號(*(cp)->major)大于我們所分配的主設(shè)備號(major),那么就可以跳出for語句,不再繼續(xù)查找。此時應(yīng)該說設(shè)備號分配成功了,那么cd結(jié)點只需等待被插到?jīng)_突鏈表當中(*cp節(jié)點之前)。
(2)如果(*cp)結(jié)點和cd結(jié)點的主設(shè)備號相同,但是前者的次設(shè)備號起點比cd結(jié)點的大,那么跳出for語句,等待下一步的范圍重疊的檢測。
(3)如果(*cp)結(jié)點和cd結(jié)點的主設(shè)備號相同,但是cd結(jié)點的次設(shè)備號起點小于(*cp)結(jié)點的次設(shè)備號的終點,那么會跳出for語句。此時很可能兩個范圍的次設(shè)備號發(fā)生了重疊。
由上面的分析可以看出,沖突表中是按照設(shè)備號遞增的順序排列的。
???????
for
(cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
if
((*cp)->major > major ||
((*cp)->major == major &&
(((*cp)->baseminor >= baseminor) ||
((*cp)->baseminor + (*cp)->minorct > baseminor))))
???????????
break
;
接下來檢測當主設(shè)備號相同時,次設(shè)備范圍是否發(fā)生了重疊。首先依次計算出新老次設(shè)備號的范圍,接著進行范圍判斷。第一個判斷語句是檢測新范圍的終點是否在老范圍的之間;第二個判斷語句是檢測新范圍的起點是否在老范圍之間。
???????
/* Check for overlapping minor ranges.? */
?????? if
(*cp && (*cp)->major == major)
?????? {
???????????
int
old_min = (*cp)->baseminor;
???????????
int
old_max = (*cp)->baseminor + (*cp)->minorct - 1;
???????????
int
new_min = baseminor;
???????????
int
new_max = baseminor + minorct - 1;
?
??????????
/* New driver overlaps from the left.? */
???????????
if
(new_max >= old_min && new_max <= old_max)
??????????? {
???????????????? ret = -EBUSY;
????????????????
goto
out;
??????????? }
???????????
/* New driver overlaps from the right.? */
???????????
if
(new_min <= old_max && new_min >= old_min)
??????????? {
???????????????? ret = -EBUSY;
????????????????
goto
out;
??????????? }
??????? }
當一切都正常后,就將char_device_struct描述符插入到中途鏈表中。至此,一次小范圍的設(shè)備號分配成功。并且此時離開臨界區(qū),進行V操作。如果上述過程中有任何失敗,則會跳轉(zhuǎn)到out處,返回錯誤信息。
??????? cd->next = *cp;
??????? *cp = cd;
??????? mutex_unlock(&chrdevs_lock);
???????
return
cd;
out:
?? mutex_unlock(&chrdevs_lock);
??????? kfree(cd);
???????
return
ERR_PTR(ret);
}
至此,我們已經(jīng)分析完了字符設(shè)備號分配函數(shù)。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
