本文轉載于: http://edsionte.com/techblog/archives/1393
如何找到一個有效的切入點去深入分析內核源碼,這是一個令人深思的問題。本文以 前文 中未詳細說明的函數為切入點,深入分析char_dev.c文件的代碼。如果你已經擁有了C語言基礎和一些數據結構基礎,那么還等什么?Let’s go!
在《字符設備驅動分析》一文中,我們說到register_chrdev_region函數的功能是在已知起始設備號的情況下去申請一組連續的設備號。不過大部分驅動書籍都沒有去深入說明此函數,可能是因為這個函數內部封裝了__register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name)函數的原因。不過我們不用苦惱,這正好促使我們去分析這個函數。
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);
}
首先值得我們注意的是,這個函數每次分配的是一組設備編號。其中from參數是這組連續設備號的起始設備號,count是這組設備號的大小(也是次設備號的個數),name參數處理本組設備的驅動名稱。另外,當次設備號數目過多(count過多)的時候,次設備號可能會溢出到下一個主設備。因此我們在for語句中可以看到,首先得到下一個主設備號(其實也是一個設備號,只不過此時的次設備號為0)并存儲于next中。然后判斷在from的基礎上再追加count個設備是否已經溢出到下一個主設備號。如果沒有溢出(next小于to),那么整個for語句就只執行個一次__register_chrdev_region函數;否則當設備號溢出時,會把當前溢出的設備號范圍劃分為幾個小范圍,分別調用__register_chrdev_region函數。
如果在某個小范圍調用__register_chrdev_region時出現了失敗,那么會將此前分配的設備號都釋放。
其實register_chrdev_region函數還沒有完全說清除設備號分配的具體過程,因為具體某個小范圍的設備號是由__register_chrdev_region函數來完成的。可能你已經注意到在register_chrdev_region函數源碼中出現了struct char_device_struct結構,我們首先來看這個結構體:
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函數中,在每個字符設備號的小范圍上調用__register_chrdev_region函數,都會返回一個struct char_device_struct類型的指針。因此我們可以得知,struct char_device_struct類型對應的并不是每一個字符設備,而是具有連續設備號的一組字符設備。從這個結構體內部的字段也可以看出,這組連續的設備號的主設備號為major,次設備號起始為baseminor,次設備號范圍為minorct,這組設備號對應的設備驅動名稱為name,cdev為指向這個字符設備驅動的指針。
這里要特別說明的是,內核中所有已分配的字符設備編號都記錄在一個名為chrdevs散列表里。該散列表中的每一個元素是一個 char_device_struct結構,這個散列表的大小為255(CHRDEV_MAJOR_HASH_SIZE),這是因為系統屏蔽了12位主設備號的前四位。既然說到散列表,那么肯定會出現沖突現象,因此next字段就是沖突鏈表中的下一個元素的指針。
接下來我們詳細來析__register_chrdev_region函數。首先為cd變量分配內存并用零來填充(這就是用kzalloc而不是kmalloc的原因)。接著通過P操作使得后續要執行的語句均處于臨界區。
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,也就是未指定一個具體的主設備號,需要動態分配。那么接下來的if語句就在整個散列表中為這組設備尋找合適的位置,即從散列表的末尾開始尋找chrdevs[i]為空的情況。若找到后,那么i不僅代表這組設備的主設備號,也代表其在散列表中的關鍵字。當然,如果主設備號實現已指定,那么可不去理會這部分代碼。
??????
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;
??????? }
接著對將參數中的值依次賦給cd變量的對應字段。當主設備號非零,即事先已知的話,那么還要通過major_to_index函數對其進行除模255運算,因此整個散列表關鍵字的范圍是0~254。
??????? cd->major = major;
??????? cd->baseminor = baseminor;
??????? cd->minorct = minorct;
??????? strlcpy(cd->name, name,
sizeof
(cd->name));
?
??????? i = major_to_index(major);
至此,我們通過上面的代碼會得到一個有效的主設備號(如果可以繼續執行下面代碼的話),那么接下來還不能繼續分配。正如你所知的那樣,散列表中的沖突是在所難免的。因此我們得到major的值后,我們要去便利沖突鏈表,為當前我們所述的char_device_struct類型的變量cd去尋找正確的位置。更重要的是,我們要檢查當前的次設備號范圍,即baseminor~baseminor+minorct,是否和之前的已分配的次設備號(前提是major相同)范圍有重疊。
下面的for循環就是在沖突鏈表中查找何時的位置,當出現以下三種情況時,for語句會停止。
(1)如果沖突表中正被遍歷的結點的主設備號(*(cp)->major)大于我們所分配的主設備號(major),那么就可以跳出for語句,不再繼續查找。此時應該說設備號分配成功了,那么cd結點只需等待被插到沖突鏈表當中(*cp節點之前)。
(2)如果(*cp)結點和cd結點的主設備號相同,但是前者的次設備號起點比cd結點的大,那么跳出for語句,等待下一步的范圍重疊的檢測。
(3)如果(*cp)結點和cd結點的主設備號相同,但是cd結點的次設備號起點小于(*cp)結點的次設備號的終點,那么會跳出for語句。此時很可能兩個范圍的次設備號發生了重疊。
由上面的分析可以看出,沖突表中是按照設備號遞增的順序排列的。
???????
for
(cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
if
((*cp)->major > major ||
((*cp)->major == major &&
(((*cp)->baseminor >= baseminor) ||
((*cp)->baseminor + (*cp)->minorct > baseminor))))
???????????
break
;
接下來檢測當主設備號相同時,次設備范圍是否發生了重疊。首先依次計算出新老次設備號的范圍,接著進行范圍判斷。第一個判斷語句是檢測新范圍的終點是否在老范圍的之間;第二個判斷語句是檢測新范圍的起點是否在老范圍之間。
???????
/* 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描述符插入到中途鏈表中。至此,一次小范圍的設備號分配成功。并且此時離開臨界區,進行V操作。如果上述過程中有任何失敗,則會跳轉到out處,返回錯誤信息。
??????? cd->next = *cp;
??????? *cp = cd;
??????? mutex_unlock(&chrdevs_lock);
???????
return
cd;
out:
?? mutex_unlock(&chrdevs_lock);
??????? kfree(cd);
???????
return
ERR_PTR(ret);
}
至此,我們已經分析完了字符設備號分配函數。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

