新聞中心
- 內(nèi)核中是如何部署總線的。
- 設備和驅(qū)動是如何掛載到總線上的。
- 設備和其對應的驅(qū)動是如何通過總線進行匹配的。
1.總線部署
我們從函數(shù)start_kernel來分析總線的部署,實際上在函數(shù)start_kernel調(diào)用之前,會有匯編代碼來處理啟動參數(shù),啟動模式,創(chuàng)建內(nèi)核空間頁表,準備好堆棧等。由于這些同總線部署關系不大,暫且就認為start_kernel就是內(nèi)核的main函數(shù)。start_kernel內(nèi)部會調(diào)用rest_init,rest_init函數(shù)內(nèi)部創(chuàng)建內(nèi)核線程kernel_init,而kernel_init中有如下的函數(shù)調(diào)用過程:

kernel_init-->do_basic_setup->driver_init—>buses_init和platform_bus_init
此處的buses_init和platform_bus_init就是總線的部署函數(shù),也是本小節(jié)的重點,且buses_init必須在platform_bus_init前面調(diào)用。因為Platform總線是掛載在bus總線下的,接下來我們詳細分析下這兩個過程。
buses_init
內(nèi)核中所有的對象如bus,都是一個kobject,而把相同類型的kobject集合到一起就組成了一個kset,而函數(shù)buses_init內(nèi)部就是通過bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL)來注冊bus總線對應的kset,其最后bus_kset如下圖1所示:
圖 1 bus_kset結構
至此算是準備好了bus_kset,我們繼續(xù)往下看一下其他類型的總線是如何和bus進行關聯(lián)的。
platform_bus_init
該函數(shù)主要完成兩個功能,其函數(shù)如下:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}device_register是用來注冊一個device,并添加到系統(tǒng)中,最后會在/sys/devices/目錄下建立 platform目錄對應的設備對象,其路徑是/sys/devices/platform/。
bus_register是將Platform bus總線注冊進系統(tǒng),其實內(nèi)部就是創(chuàng)建了對應的kset和kobject等,且主要完成以下三項工作:
- 初始化必須的結構體,struct subsys_private 和對應的kobkect。
- 同bus總線建立關系,kobject.parent 設置為上一步已經(jīng)創(chuàng)建好的bus_kset.kobj, kobject.kset設置為bus_kset,把對應的kobject.ktype設置為bus_ktype。
- 把對應的kobjet添加到對應的kset的鏈表中,對總線來說,就是添加到bus_kset中的鏈表中。
下面是bus_register函數(shù)的實現(xiàn)(刪除了創(chuàng)建失敗退出時free內(nèi)存等的操作),且我在代碼中增加了注釋,方便大家查閱:
/**
* bus_register - register a bus with the system.
* @bus: bus.
*
* Once we have that, we registered the bus with the kobject
* infrastructure, then register the children subsystems it has:
* the devices and drivers that belong to the bus.
*/
int bus_register(struct bus_type *bus)
{
int retval;
//step:創(chuàng)建并分配,初始化struct subsys_private結構體指針
struct subsys_private *priv;
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->bus = bus;
bus->p = priv;
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
if (retval)
goto out;
//step2:同上面創(chuàng)建的bus_kset進行關聯(lián)
//kset_register時,會設置對應priv->subsys。Kobject->parent = bus_kset.kobj
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;
retval = kset_register(&priv->subsys); //一會添加到bus_kset鏈表中
if (retval)
goto out;
//step3:創(chuàng)建對應的屬性文件
retval = bus_create_file(bus, &bus_attr_uevent);
if (retval)
goto bus_uevent_fail;
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
//step4:初始化兩個比較重要的鏈表,后面內(nèi)容中會提到這兩個鏈表
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
//step5:添加探針文件
retval = add_probe_files(bus);
if (retval)
goto bus_probe_files_fail;
retval = bus_add_attrs(bus);
if (retval)
goto bus_attrs_fail;
pr_debug("bus: '%s': registered\n", bus->name);
return 0;
……
return retval;
}
EXPORT_SYMBOL_GPL(bus_register);
對于其他總線(如IIC等),也是通過bus_register進行注冊的,比如bus_register(&i2c_bus_type)和bus_register(&mmc_bus_type)等,其原理同上面一樣,在此就不挨個介紹了。
通過上面的分析,我們知道了bus總線,且其他總線掛載在bus總線下,等總線部署完成后,不同設備會掛載在對應的總線下面。對于SPI,IIC等設備,他們都可以掛載在對應的總線下同CPU進行數(shù)據(jù)交互。但在嵌入式系統(tǒng)中,有些設備是不屬于這些常見的總線,因此引入了虛擬的Platform總線,本小節(jié)正是通過虛擬的Platform總線來說明總線部署的。
2.設備和驅(qū)動的掛載
我們依然采用Platform總線來說明設備和驅(qū)動的掛載問題。
設備掛載
對于Platform總線來說,可以通過函數(shù)platform_device_register來掛載(有的地方稱之為注冊)設備,也可以通過設備樹來掛載,在內(nèi)核啟動時,會進行設備樹的解析,本文中不涉及設備樹,主要介紹platform_device_register的方式。
該函數(shù)原型如下:
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
return platform_device_add(pdev);
}
函數(shù)在執(zhí)行的過程中,有如下調(diào)用關系:
platform_device_add---->設置struct platform_device中的總線類型及其他參數(shù)--->device_add--->bus_add_device---->klist_add_tail
這個調(diào)用過程省略了一些屬性和節(jié)點等的處理,我關注的重點在函數(shù)klist_add_tail,該函數(shù)是把當前設備添加到platform_bus中的一個鏈表中,這個鏈表在Platform總線部署時就初始化完成了,其初始化函數(shù)就是函數(shù)bus_register中的step4,可以翻閱上一個小節(jié)來查看。
驅(qū)動掛載
對于Platform總線來說,可以通過函數(shù)platform_driver_register來掛載(有的地方稱之為注冊)設備,其函數(shù)原型如下:
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(platform_driver_register);
函數(shù)在執(zhí)行的過程中,有如下調(diào)用關系:
driver_register---> 設置對應的參數(shù)等---->driver_find---> bus_add_driver----> klist_add_tail
相對于設備掛載,多了一個函數(shù)driver_find的調(diào)用,該函數(shù)主要目的就是判斷驅(qū)動是否已經(jīng)掛載上了,其余處理方式同設備掛載相同。最為重要的依然是klist_add_tail,把該驅(qū)動添加到了platform_bus中的一個鏈表中。
其他類型的總線設備和驅(qū)動相同,也會存在兩個鏈表,設備和驅(qū)動均掛載到相應的鏈表中。
3.設備和驅(qū)動的匹配
從第2小節(jié)中,我們知道Platform總線下有兩個鏈表,我采用下面的圖來具體化這兩個鏈表,圖左邊的設備鏈表,圖中僅呈現(xiàn)3個設備,實際上會有很多,圖右邊為驅(qū)動鏈表。不管是左邊的設備還是右邊的驅(qū)動,均有name字段(通常情況下是compatible),這是個非常重要的字段,后面我們會用到它。
圖 2Platform總線的兩個鏈表
針對匹配問題,我依然采用Platform總線來闡述,我們已經(jīng)知道在進行驅(qū)動掛載時,會調(diào)用函數(shù)bus_add_driver,該函數(shù)內(nèi)核實際上還會調(diào)用一個函數(shù)driver_attach(針對設置drivers_autoprobe的情況),下面是函數(shù)driver_attach的調(diào)用情況:
driver_attach
--->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)
---> klist_iter_init_node(&bus->p->klist_devices, &i, (start ? &start->p->knode_bus : NULL));
while ((dev = next_device(&i)) && !error)
error = fn(dev, data);
--->driver_match_device
---> drv->bus->match
---> platform_match
從上面代碼過程可以看出,當掛載驅(qū)動時,會遍歷圖2中左邊的鏈表,最后調(diào)用Platform總線的match函數(shù)platform_match (match函數(shù)是在struct bus_type platform_bus_type中設置的,在總線部署時階段調(diào)用platform_bus_init就設置好了)來進行設備和驅(qū)動的匹配。每個總線都會有自己的match函數(shù),且match函數(shù)里面會通過多種方式匹配,如常見的compitable,name或者id_table,只要有一個能匹配上,則認為驅(qū)動和設備匹配成功。
總結
本文主要采用Platform來說明了內(nèi)核中總線部署,設備和驅(qū)動掛載,及設備和驅(qū)動的匹配問題,實際上其他總線也是采用相同的方式,在我的描述過程中,重點在于總線,忽略了一些sysfs節(jié)點,引用計數(shù),kobject,kset等,但這些在內(nèi)核架構中也是比較重要的環(huán)節(jié),希望大家在了解總線架構后,也能有時間去深入查看內(nèi)核總線的各個處理細節(jié)。
特別說明:不同的內(nèi)核,可能使用到的函數(shù),或者函數(shù)的實現(xiàn)同文章中介紹的存在出入,但其原理及架構相同,可以作為參考。
作者介紹
趙青窕,社區(qū)編輯,從事多年驅(qū)動開發(fā)。研究興趣包含安全OS和網(wǎng)絡安全領域,發(fā)表過網(wǎng)絡相關專利。
文章標題:你需要知道的內(nèi)核總線架構
當前地址:http://fisionsoft.com.cn/article/dpcjije.html


咨詢
建站咨詢
