欧美一区二区三区老妇人-欧美做爰猛烈大尺度电-99久久夜色精品国产亚洲a-亚洲福利视频一区二区

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

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

在樟樹等地區(qū),都構建了全面的區(qū)域性戰(zhàn)略布局,加強發(fā)展的系統(tǒng)性、市場前瞻性、產品創(chuàng)新能力,以專注、極致的服務理念,為客戶提供成都做網站、成都網站建設、成都外貿網站建設 網站設計制作按需定制設計,公司網站建設,企業(yè)網站建設,品牌網站制作,網絡營銷推廣,外貿營銷網站建設,樟樹網站建設費用合理。


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

1.   陷入絕境

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

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

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

#define ABC_BASE 0x100000  
#define ABC_INTERRUPT 10

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

#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);  
    }  
    ...  
}

無論你怎么改,這個代碼實在都已經是慘不忍睹了,連自己都看不下去了。我們?yōu)槭裁磿萑脒@樣的困境,是因為我們犯了未能“把正確的代碼,放入正確的位置的錯誤”,這樣引入了極大的耦合。

2.   迷途反思

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

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

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

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

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

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

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

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

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

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

3.   柳岸花明

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

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

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

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

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

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

實體

功能

代碼

設備

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

arch/arm

arch/blackfin

arch/xxx

等目錄

驅動

完成外設的功能,如網卡收發(fā)包,聲卡錄放,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的總線這個統(tǒng)一紐帶上,自然就知道板子上面有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設備,都統(tǒng)一使用了一個不變的drivers/net/ethernet/abc.c驅動,而arch/arm/mach-yyy/board-a.c這樣的代碼,則有很多很多份。

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

4. 更上層樓

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

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

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

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

分享名稱:讓天堂的歸天堂,讓塵土的歸塵土——談Linux的總線、設備、驅動模型
本文路徑:http://www.chinadenli.net/article18/pgiedp.html

成都網站建設公司_創(chuàng)新互聯,為您提供Google建站公司定制網站小程序開發(fā)全網營銷推廣關鍵詞優(yōu)化

廣告

聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯

成都網站建設