中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

讓天堂的歸天堂,讓塵土的歸塵土——談Linux的總線、設備、驅動模型

發布時間:2020-06-09 05:43:47 來源:網絡 閱讀:18425 作者:21cnbao 欄目:系統運維

公元1951年5月15日的國會聽證上,美國陸軍五星上將麥克阿瑟建議把朝鮮戰爭擴大至中國,布萊德利隨后發言:“如果我們把戰爭擴大到×××中國,那么我們會被卷入到一場錯誤的時間,錯誤的地點同錯誤的對手打的一場錯誤的戰爭中。”


寫代碼,適用于同樣的原則,那就是把正確的代碼放到正確的位置而不是相反。同樣的一個代碼,可以出現在多個可能的位置,它究竟應該出現在哪里,是軟件架構設計的結果,說白了一切都是為了高內核和低耦合。


1.   陷入絕境

下面我們設想一個名字叫做ABC的簡單的網卡,它需要接在一個CPU(假設CPU為X)的內存總線上,需要地址、數據和控制總線(以及中斷pin腳等)。


讓天堂的歸天堂,讓塵土的歸塵土——談Linux的總線、設備、驅動模型


那么在ABC的網卡驅動里面,我們需要定義ABC的基地址、中斷號等信息。假設在CPU X的電路板上面,ABC的地址為0x100000,中斷號為10。假設我們是這樣定義的宏:

#define ABC_BASE 0x100000  
#define ABC_INTERRUPT 10


并且這樣寫代碼完成發送報文和初始化申請中斷:

#define ABC_BASE 0x100000  
#define ABC_IRQ 10  
  
int abc_send(...)  
{  
        writel(ABC_BASE + REG_X, 1);  
        writel(ABC_BASE + REG_Y, 0x3);  
        ...  
}  
  
int abc_init(...)  
{  
        request_irq(ABC_IRQ,...);  
}


這個代碼的問題在于,一旦重新換板子,ABC_BASE和ABC_IRQ就不再一樣,代碼也需要隨之變更。

有的程序員說我可以這么干:

#ifdef BOARD_A  
#define ABC_BASE 0x100000  
#define ABC_IRQ 10  
  
#elif defined(BOARD_B)  
#define ABC_BASE 0x110000  
#define ABC_IRQ 20  
  
#elif defined(BOARD_C)  
#define ABC_BASE 0x120000  
#define ABC_IRQ 10  
...  
#endif


這么干固然是可以,但是如果你有1萬個不同的板子,你就要ifdef一萬次,這樣寫代碼,找到了一種明顯的砌墻的感覺(你感覺寫代碼,就跟砌墻似的,一塊塊磚頭一樣放進去的時候,簡單重復機械,這個時候,就很危險了,可能代碼里面就已經出現了不好的“味道”)。考慮到Linux向全世界各個產品適配,各種硬件適配的特點,究竟有多少個板子用ABC,還真的誰也說不清楚。


那么,是不是真的#ifdef走一萬次,就一定能解決問題呢?還真的是不能。假設有一個電路板有2個ABC網卡,就徹底傻眼了。難道這樣定義?

#ifdef BOARD_A  
#define ABC1_BASE 0x100000  
#define ABC1_IRQ 10  
#define ABC2_BASE 0x101000  
#define ABC2_IRQ 11  
  
#elif defined(BOARD_B)  
#define ABC1_BASE 0x110000  
#define ABC1_IRQ 20  
...  
#endif


如果這樣做,abc_send()和abc_init()又該如何改?難道這樣:

int abc1_send(...)  
{  
        writel(ABC1_BASE + REG_X, 1);  
        writel(ABC1_BASE + REG_Y, 0x3);  
        ...  
}  
  
int abc1_init(...)  
{  
        request_irq(ABC1_IRQ,...);  
}  
  
int abc2_send(...)  
{  
        writel(ABC2_BASE + REG_X, 1);  
        writel(ABC2_BASE + REG_Y, 0x3);  
        ...  
}  
  
int abc2_init(...)  
{  
        request_irq(ABC2_IRQ,...);  
}  
…


還是這樣?

int abc_send(int id, ...)  
{  
    if (id == 0) {  
            writel(ABC1_BASE + REG_X, 1);  
            writel(ABC1_BASE + REG_Y, 0x3);  
<span >  </span>} else if (id == 1) {  
            writel(ABC2_BASE + REG_X, 1);  
            writel(ABC2_BASE + REG_Y, 0x3);  
    }  
    ...  
}


無論你怎么改,這個代碼實在都已經是慘不忍睹了,連自己都看不下去了。我們為什么會陷入這樣的困境,是因為我們犯了未能“把正確的代碼,放入正確的位置的錯誤”,這樣引入了極大的耦合。


2.   迷途反思


我們犯的致命的錯誤,在于把板級互連信息,耦合進了驅動的代碼,導致驅動無法跨平臺。


讓天堂的歸天堂,讓塵土的歸塵土——談Linux的總線、設備、驅動模型


我們轉念想一想,ABC的驅動的真正職責是完成ABC網卡的收發流程,試問,這個流程,真的與它接在什么CPU(TI、三星、Broad、Allwinner等)有半毛錢關系嗎?又和接在哪個板子上有半毛錢關系嗎?

答案是真的沒有什么關系!ABC網卡,不會因為你是TI的ARM,你是龍芯,還是你是Blackfin有什么不同。任你外面什么板子排山倒海,狗急跳墻,ABC自己都是巋然不動。


既然沒有什么關系,那么這些板子級別的互連信息,又為什么要放在驅動的代碼里面呢?基本上,我們可以認為,ABC不會因誰而變,所以它的代碼應該是天然跨平臺的。故此,我們認為“#defineABC_BASE 0x100000, #define ABC_IRQ 10”這樣的代碼,出現在驅動里面,屬于“在錯誤的地點,和錯誤的敵人,打一場錯誤的戰爭”。它沒有被放在正確的位置上,而我們寫代碼,一定“讓天堂的歸天堂, 讓塵土的歸塵土”。我們真實的期待,恐怕是這個樣子:


讓天堂的歸天堂,讓塵土的歸塵土——談Linux的總線、設備、驅動模型


軟件工程強調高內聚、低耦合。若一個模塊內各元素聯系的越緊密,則它的內聚性就越高;模塊之間聯系越不緊密,其耦合性就越低。所以高內聚、低耦合強調,內部的要緊緊抱團,外面的給我滾蛋。對于驅動而言,板級互連信息,顯然屬于應該滾蛋的。每個軟件模塊最好是一個宅男,不談戀愛,不看電影,不吃大餐,不踢足夠,和外界唯一的聯系就是“餓了嗎”,這樣的軟件,顯然是又高內聚、又低耦合。


 有一次我在一個德國外企,問到工程師們“高內聚和低耦合是什么關系”,有一個工程師非常積極地回答,“高內聚和低耦合是一對矛盾”。我覺得他的腦子好亂,如果一定要用一個關系來描述高內聚和低耦合的關系,我認為他們符合馬列主義,×××思想強調的“高內聚和低耦合,相互依存,缺一不可,相輔相成,共同促進”,它其實反映了同一個事物兩個不同的側面,總之,把政治課本背一遍就對了。


你寫個串口的代碼,里面從頭到尾都是串口相關的東西,聚地緊,它也自然不會滿世界亂跑到SPI里面去耦合。SPI要和串口低耦合,它也勢必要求UART內部代碼把串口的東東全部聚一起,不要亂竄,沒有SPI的戶口,居住證也不發給你,就給我滾回老家去。


讓天堂的歸天堂,讓塵土的歸塵土——談Linux的總線、設備、驅動模型



3.   柳岸花明

現在板級互連信息已經和驅動分離開來了,讓它們彼此出現在不同的軟件模塊。但是,最終它們仍然有一定的聯系,因為,驅動最終還是要取出基地址、中斷號等板級信息的。怎么取,這是個大問題。

一種方法是ABC的驅動滿世界詢問各個板子,“請問你的基地址,中斷號是幾?”,“你媽貴姓?”這仍然是一個嚴重的耦合。因為,驅動還是得知道板子上有沒有ABC,哪個板子有,怎么個有法。它還是在和板子直接耦合。

讓天堂的歸天堂,讓塵土的歸塵土——談Linux的總線、設備、驅動模型

可不可以有另外一種方法,我們維護一個共同的類似數據庫的東西,板子上有什么網卡,基地址中斷號是什么,都統一在一個地方維護。然后,驅動問一個統一的地方,通過一個統一的API來獲取即好?

讓天堂的歸天堂,讓塵土的歸塵土——談Linux的總線、設備、驅動模型

基于這樣的想法,linux把設備驅動分為了總線、設備和驅動三個實體,總線是上圖中的統一紐帶,設備是上圖中的板級互連信息,這三個實體完成的職責分別如下:

實體

功能

代碼

設備

描述基地址、中斷號、時鐘、DMA、復位等信息

arch/arm

arch/blackfin

arch/xxx

等目錄

驅動

完成外設的功能,如網卡收發包,聲卡錄放,SD卡讀寫…

drivers/net

sound

drivers/mmc

等目錄

總線

完成設備和驅動的關聯

drivers/base/platform.c

drivers/pci/pci-driver.c

我們把所有的板子互連信息填入設備端,然后讓設備端向總線注冊告知總線自己的存在,總線上面自然關聯了這些設備,并進一步間接關聯了設備的板級連接信息。比如arch/blackfin/mach-bf533/boards/ip0x.c這塊板子有2個DM9000的網卡,它是這樣注冊的:

static struct resource dm9000_resource1[] = {  
    {  
        .start = 0x20100000,  
        .end   = 0x20100000 + 1,  
        .flags = IORESOURCE_MEM  
    },{  
        .start = 0x20100000 + 2,  
        .end   = 0x20100000 + 3,  
        .flags = IORESOURCE_MEM  
    },{  
        .start = IRQ_PF15,  
        .end   = IRQ_PF15,  
        .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE  
    }  
};  
  
static struct resource dm9000_resource2[] = {  
    {  
        .start = 0x20200000,  
        .end   = 0x20200000 + 1,  
        .flags = IORESOURCE_MEM  
    }…  
};  
  
…  
static struct platform_device dm9000_device1 = {  
    .name           = "dm9000",  
    .id             = 0,  
    .num_resources  = ARRAY_SIZE(dm9000_resource1),  
    .resource       = dm9000_resource1,  
};  
  
…  
static struct platform_device dm9000_device2 = {  
    .name           = "dm9000",  
    .id             = 1,  
    .num_resources  = ARRAY_SIZE(dm9000_resource2),  
    .resource       = dm9000_resource2,  
};  
  
static struct platform_device *ip0x_devices[] __initdata = {  
    &dm9000_device1,  
    &dm9000_device2,  
…  
};  
  
static int __init ip0x_init(void)  
{  
    platform_add_devices(ip0x_devices, ARRAY_SIZE(ip0x_devices));  
    …  
}


這樣platform的總線這個統一紐帶上,自然就知道板子上面有2個DM9000的網卡。一旦DM9000的驅動也被注冊,由于platform總線已經關聯了設備,驅動自然可以根據已經存在的DM9000設備信息,獲知如下的內存基地址、中斷等信息了:

static struct resource dm9000_resource1[] = {  
    {  
        .start = 0x20100000,  
        .end   = 0x20100000 + 1,  
        .flags = IORESOURCE_MEM  
    },{  
        .start = 0x20100000 + 2,  
        .end   = 0x20100000 + 3,  
        .flags = IORESOURCE_MEM  
    },{  
        .start = IRQ_PF15,  
        .end   = IRQ_PF15,  
        .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE  
    }  
};


總線存在的目的,則是把這些驅動和這些設備,一一配對的匹配在一起。如下圖,某個電路板子上有2個ABC,1個DEF,1個HIJ設備,以及分別1個的ABC、DEF、HIJ驅動,那么總線,就是讓2個ABC設備和1個ABC驅動匹配,DEF設備和驅動一對一匹配,HIJ設備和驅動一對一匹配。

讓天堂的歸天堂,讓塵土的歸塵土——談Linux的總線、設備、驅動模型

驅動本身,則可以用最簡單的API取出設備端填入的互連信息,看一下drivers/net/ethernet/davicom/dm9000.c的dm9000_probe()代碼:

static int dm9000_probe(struct platform_device *pdev)  
{  
    …  
db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);  
db->irq_res  = platform_get_resource(pdev, IORESOURCE_IRQ, 0);  
…  
}


這樣,板級互連信息,再也不會闖入驅動,而驅動,看起來也沒有和設備之間直接耦合,因為它調用的都是總線級別的標準API:platform_get_resource()。總線里面有個match()函數,來完成哪個設備由哪個驅動來服務的職責,比如對于掛在內存上的platform總線而言,它的匹配類似(最簡單的匹配方法就是設備和驅動的name字段一樣):

static int platform_match(struct device *dev, struct device_driver *drv)  
{  
        struct platform_device *pdev = to_platform_device(dev);  
        struct platform_driver *pdrv = to_platform_driver(drv);  
  
        /* When driver_override is set, only bind to the matching driver */  
        if (pdev->driver_override)   
                return !strcmp(pdev->driver_override, drv->name);  
  
        /* Attempt an OF style match first */  
        if (of_driver_match_device(dev, drv))  
                return 1;  
  
        /* Then try ACPI style match */  
        if (acpi_driver_match_device(dev, drv))  
                return 1;  
  
        /* Then try to match against the id table */  
        if (pdrv->id_table)  
                return platform_match_id(pdrv->id_table, pdev) != NULL;  
  
        /* fall-back to driver name match */  
        return (strcmp(pdev->name, drv->name) == 0);  
}


VxBus是風河公司新的設備驅動程序架構,它是在VxWorks 6.2及以后版本被增加到VxWorks中的,直至VxWorks 6.9,基本都已經VxBus化了。但是,這個VxBus,可以說和Linux的總線、設備、驅動模型是極大地雷同的。但是,請問,你為什么要叫VxBus呢,它非常地Vx嗎?


所以,這個時候我們看到的代碼會是這樣,無論是哪個板子的ABC設備,都統一使用了一個不變的drivers/net/ethernet/abc.c驅動,而arch/arm/mach-yyy/board-a.c這樣的代碼,則有很多很多份。


讓天堂的歸天堂,讓塵土的歸塵土——談Linux的總線、設備、驅動模型


4. 更上層樓

我們仍然看到大量的arch/arm/mach-yyy/board-a.c這樣的代碼,沖刺著描述板級信息的細節代碼,盡管它本身已經和驅動解耦了。這些代碼的存在,簡直是對Linux內核的污染和對Linus Torvalds的無情藐視,因為,太木有技術含量了!


我們有理由,把這些設備端的信息,用一個非C的腳本語言來描述,這個腳本文件,就是傳說中的Device Tree(設備樹)。


設備樹,是一種dts文件,它用最簡單的語法描述每個板子上的所有設備,以及這些設備的連接信息。比如arch/arm/boot/dts/ imx1-apf9328.dts下面的DM9000就是這樣的腳本,基地址、中斷號都成為了DM9000設備節點的一個屬性:

eth: eth@4,c00000 {  
        compatible = "davicom,dm9000";  
        reg = <   
                4 0x00c00000 0x2  
                4 0x00c00002 0x2  
        >;  
        interrupt-parent = <&gpio2>;  
        interrupts = <14 IRQ_TYPE_LEVEL_LOW>;  
        …  
};


之后,C代碼被剔除,arch/arm/mach-xxx/board-a.c這樣的文件永遠地進入了歷史的故紙堆,代碼就變成這樣的架構,換個板子,只要換個Device Tree就好。“讓天堂的歸天堂, 讓塵土的歸塵土”,讓驅動的歸驅動C代碼,讓設備的歸設備樹腳本。


讓天堂的歸天堂,讓塵土的歸塵土——談Linux的總線、設備、驅動模型


我們很高興也很悲痛地看到,VxWorks 7的新版,也采用Device Tree了。我們高興的是,它終于來了;我們悲痛的是,它終于又來晚了。Linux的車輪滾滾向前,無情碾壓一切。人類的千年軌跡,滄海桑田,斗轉星移,重復地進行著歷史的歸于歷史,未來還是歸于歷史的過程。這是現實的悲愴,也是歷史的豪邁。


 《孫子兵法》曰:“水因地而制流,兵因敵而制勝。故兵無常勢,水無常形;能因敵變化而取勝者,謂之神。”一切不過是順勢而為,把正確的代碼,安放到正確的位置。

為了更進一步深入地探討這個話題,CSDN學院聯合博主組織了2017年7月5日8PM~9PM的關于《探究Linux的總線、設備、驅動模型》直播活動,有314人參與了在線直播,活動已經結束,想觀看錄播視頻的讀者可以進入:

http://edu.csdn.net/huiyiCourse/detail/426?ref=0


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

富蕴县| 微博| 门头沟区| 巩义市| 武强县| 石河子市| 鄢陵县| 东城区| 甘肃省| 合水县| 柘城县| 竹北市| 瑞安市| 德格县| 化隆| 平潭县| 蓬溪县| 本溪| 新泰市| 江川县| 石景山区| 邹城市| 安西县| 波密县| 玉树县| 巴马| 乌苏市| 鹤庆县| 德阳市| 湖南省| 耒阳市| 河北区| 久治县| 长丰县| 翁牛特旗| 高碑店市| 天峻县| 锡林郭勒盟| 侯马市| 黄龙县| 东乌|