日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
你需要知道的內(nèi)核總線架構(gòu)
  1. 內(nèi)核中是如何部署總線的。
  2. 設(shè)備和驅(qū)動(dòng)是如何掛載到總線上的。
  3. 設(shè)備和其對(duì)應(yīng)的驅(qū)動(dòng)是如何通過(guò)總線進(jìn)行匹配的。

1.總線部署

我們從函數(shù)start_kernel來(lái)分析總線的部署,實(shí)際上在函數(shù)start_kernel調(diào)用之前,會(huì)有匯編代碼來(lái)處理啟動(dòng)參數(shù),啟動(dòng)模式,創(chuàng)建內(nèi)核空間頁(yè)表,準(zhǔn)備好堆棧等。由于這些同總線部署關(guān)系不大,暫且就認(rèn)為start_kernel就是內(nèi)核的main函數(shù)。start_kernel內(nèi)部會(huì)調(diào)用rest_init,rest_init函數(shù)內(nèi)部創(chuàng)建內(nèi)核線程kernel_init,而kernel_init中有如下的函數(shù)調(diào)用過(guò)程:

kernel_init-->do_basic_setup->driver_init—>buses_init和platform_bus_init

此處的buses_init和platform_bus_init就是總線的部署函數(shù),也是本小節(jié)的重點(diǎn),且buses_init必須在platform_bus_init前面調(diào)用。因?yàn)镻latform總線是掛載在bus總線下的,接下來(lái)我們?cè)敿?xì)分析下這兩個(gè)過(guò)程。

buses_init

內(nèi)核中所有的對(duì)象如bus,都是一個(gè)kobject,而把相同類型的kobject集合到一起就組成了一個(gè)kset,而函數(shù)buses_init內(nèi)部就是通過(guò)bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL)來(lái)注冊(cè)bus總線對(duì)應(yīng)的kset,其最后bus_kset如下圖1所示:

圖 1 bus_kset結(jié)構(gòu)

至此算是準(zhǔn)備好了bus_kset,我們繼續(xù)往下看一下其他類型的總線是如何和bus進(jìn)行關(guān)聯(lián)的。

platform_bus_init

該函數(shù)主要完成兩個(gè)功能,其函數(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是用來(lái)注冊(cè)一個(gè)device,并添加到系統(tǒng)中,最后會(huì)在/sys/devices/目錄下建立 platform目錄對(duì)應(yīng)的設(shè)備對(duì)象,其路徑是/sys/devices/platform/。

bus_register是將Platform bus總線注冊(cè)進(jìn)系統(tǒng),其實(shí)內(nèi)部就是創(chuàng)建了對(duì)應(yīng)的kset和kobject等,且主要完成以下三項(xiàng)工作:

  • 初始化必須的結(jié)構(gòu)體,struct subsys_private 和對(duì)應(yīng)的kobkect。
  • 同bus總線建立關(guān)系,kobject.parent 設(shè)置為上一步已經(jīng)創(chuàng)建好的bus_kset.kobj, kobject.kset設(shè)置為bus_kset,把對(duì)應(yīng)的kobject.ktype設(shè)置為bus_ktype。
  • 把對(duì)應(yīng)的kobjet添加到對(duì)應(yīng)的kset的鏈表中,對(duì)總線來(lái)說(shuō),就是添加到bus_kset中的鏈表中。

下面是bus_register函數(shù)的實(shí)現(xiàn)(刪除了創(chuàng)建失敗退出時(shí)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結(jié)構(gòu)體指針
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進(jìn)行關(guān)聯(lián)
//kset_register時(shí),會(huì)設(shè)置對(duì)應(yīng)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); //一會(huì)添加到bus_kset鏈表中
if (retval)
goto out;
//step3:創(chuàng)建對(duì)應(yī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:初始化兩個(gè)比較重要的鏈表,后面內(nèi)容中會(huì)提到這兩個(gè)鏈表
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);

對(duì)于其他總線(如IIC等),也是通過(guò)bus_register進(jìn)行注冊(cè)的,比如bus_register(&i2c_bus_type)和bus_register(&mmc_bus_type)等,其原理同上面一樣,在此就不挨個(gè)介紹了。

通過(guò)上面的分析,我們知道了bus總線,且其他總線掛載在bus總線下,等總線部署完成后,不同設(shè)備會(huì)掛載在對(duì)應(yīng)的總線下面。對(duì)于SPI,IIC等設(shè)備,他們都可以掛載在對(duì)應(yīng)的總線下同CPU進(jìn)行數(shù)據(jù)交互。但在嵌入式系統(tǒng)中,有些設(shè)備是不屬于這些常見(jiàn)的總線,因此引入了虛擬的Platform總線,本小節(jié)正是通過(guò)虛擬的Platform總線來(lái)說(shuō)明總線部署的。

2.設(shè)備和驅(qū)動(dòng)的掛載

我們依然采用Platform總線來(lái)說(shuō)明設(shè)備和驅(qū)動(dòng)的掛載問(wèn)題。

設(shè)備掛載

對(duì)于Platform總線來(lái)說(shuō),可以通過(guò)函數(shù)platform_device_register來(lái)掛載(有的地方稱之為注冊(cè))設(shè)備,也可以通過(guò)設(shè)備樹(shù)來(lái)掛載,在內(nèi)核啟動(dòng)時(shí),會(huì)進(jìn)行設(shè)備樹(shù)的解析,本文中不涉及設(shè)備樹(shù),主要介紹platform_device_register的方式。

該函數(shù)原型如下:

int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
return platform_device_add(pdev);
}

函數(shù)在執(zhí)行的過(guò)程中,有如下調(diào)用關(guān)系:

platform_device_add---->設(shè)置struct platform_device中的總線類型及其他參數(shù)--->device_add--->bus_add_device---->klist_add_tail

這個(gè)調(diào)用過(guò)程省略了一些屬性和節(jié)點(diǎn)等的處理,我關(guān)注的重點(diǎn)在函數(shù)klist_add_tail,該函數(shù)是把當(dāng)前設(shè)備添加到platform_bus中的一個(gè)鏈表中,這個(gè)鏈表在Platform總線部署時(shí)就初始化完成了,其初始化函數(shù)就是函數(shù)bus_register中的step4,可以翻閱上一個(gè)小節(jié)來(lái)查看。

驅(qū)動(dòng)掛載

對(duì)于Platform總線來(lái)說(shuō),可以通過(guò)函數(shù)platform_driver_register來(lái)掛載(有的地方稱之為注冊(cè))設(shè)備,其函數(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í)行的過(guò)程中,有如下調(diào)用關(guān)系:

driver_register---> 設(shè)置對(duì)應(yīng)的參數(shù)等---->driver_find---> bus_add_driver----> klist_add_tail

相對(duì)于設(shè)備掛載,多了一個(gè)函數(shù)driver_find的調(diào)用,該函數(shù)主要目的就是判斷驅(qū)動(dòng)是否已經(jīng)掛載上了,其余處理方式同設(shè)備掛載相同。最為重要的依然是klist_add_tail,把該驅(qū)動(dòng)添加到了platform_bus中的一個(gè)鏈表中。

其他類型的總線設(shè)備和驅(qū)動(dòng)相同,也會(huì)存在兩個(gè)鏈表,設(shè)備和驅(qū)動(dòng)均掛載到相應(yīng)的鏈表中。

3.設(shè)備和驅(qū)動(dòng)的匹配

從第2小節(jié)中,我們知道Platform總線下有兩個(gè)鏈表,我采用下面的圖來(lái)具體化這兩個(gè)鏈表,圖左邊的設(shè)備鏈表,圖中僅呈現(xiàn)3個(gè)設(shè)備,實(shí)際上會(huì)有很多,圖右邊為驅(qū)動(dòng)鏈表。不管是左邊的設(shè)備還是右邊的驅(qū)動(dòng),均有name字段(通常情況下是compatible),這是個(gè)非常重要的字段,后面我們會(huì)用到它。

圖 2Platform總線的兩個(gè)鏈表

針對(duì)匹配問(wèn)題,我依然采用Platform總線來(lái)闡述,我們已經(jīng)知道在進(jìn)行驅(qū)動(dòng)掛載時(shí),會(huì)調(diào)用函數(shù)bus_add_driver,該函數(shù)內(nèi)核實(shí)際上還會(huì)調(diào)用一個(gè)函數(shù)driver_attach(針對(duì)設(shè)置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

從上面代碼過(guò)程可以看出,當(dāng)掛載驅(qū)動(dòng)時(shí),會(huì)遍歷圖2中左邊的鏈表,最后調(diào)用Platform總線的match函數(shù)platform_match (match函數(shù)是在struct bus_type platform_bus_type中設(shè)置的,在總線部署時(shí)階段調(diào)用platform_bus_init就設(shè)置好了)來(lái)進(jìn)行設(shè)備和驅(qū)動(dòng)的匹配。每個(gè)總線都會(huì)有自己的match函數(shù),且match函數(shù)里面會(huì)通過(guò)多種方式匹配,如常見(jiàn)的compitable,name或者id_table,只要有一個(gè)能匹配上,則認(rèn)為驅(qū)動(dòng)和設(shè)備匹配成功。

總結(jié)

本文主要采用Platform來(lái)說(shuō)明了內(nèi)核中總線部署,設(shè)備和驅(qū)動(dòng)掛載,及設(shè)備和驅(qū)動(dòng)的匹配問(wèn)題,實(shí)際上其他總線也是采用相同的方式,在我的描述過(guò)程中,重點(diǎn)在于總線,忽略了一些sysfs節(jié)點(diǎn),引用計(jì)數(shù),kobject,kset等,但這些在內(nèi)核架構(gòu)中也是比較重要的環(huán)節(jié),希望大家在了解總線架構(gòu)后,也能有時(shí)間去深入查看內(nèi)核總線的各個(gè)處理細(xì)節(jié)。

特別說(shuō)明:不同的內(nèi)核,可能使用到的函數(shù),或者函數(shù)的實(shí)現(xiàn)同文章中介紹的存在出入,但其原理及架構(gòu)相同,可以作為參考。

作者介紹

趙青窕,社區(qū)編輯,從事多年驅(qū)動(dòng)開(kāi)發(fā)。研究興趣包含安全OS和網(wǎng)絡(luò)安全領(lǐng)域,發(fā)表過(guò)網(wǎng)絡(luò)相關(guān)專利。


網(wǎng)頁(yè)名稱:你需要知道的內(nèi)核總線架構(gòu)
文章位置:http://www.dlmjj.cn/article/dpcjije.html