linux裝置模型____宏觀印象
最近一個機會需要研究一個marvell晶片的裝置的驅動,涉及驅動和一些使用者态相關部分,正好學習一下驅動和sysfs,本文先是原理,後面的文章是較長的描述。本文依托的是linux 2.6.32。
事實上,linux裝置模型這個東西,也就是常看到的總線、裝置、驅動、類等等,是我們自己抽象出來的理論;但linux的裝置驅動,還确實是按照這個理論組建的,不論是linux核心啟動過程中的相關初始化過程,還是後續我們自己的驅動的加載删除,都是按照這個理論組建的(也無法不按這種理論組建,因為驅動程式設計必須得調用核心提供的相應函數),這個組建也是比較繁雜的,後面會較長的描述。
另一方面,從代碼角度,這個組建過程是很細緻的一個過程,它的細緻還展現在兩方面,一方面,針對總線、裝置、驅動、類等等有一堆函數涉及一堆邏輯;另一方面這些總線、裝置、驅動、類等等,之是以相關性這麼強,還因為linux核心制造出諸如kobject、kset等多種結構來把這個相關性實作,這些東西也很繁雜,後面也會較長的描述。
鑒于它很麻煩,是以一點一點描述,首先是從裝置模型這個頂層來講:
肯定都知道sysfs挂在/sys目錄下,也肯定知道這個sysfs是和裝置驅動關系非常大,到底sysfs是怎麼回事後面再說,目前隻要知道sysfs和proc很相似、都是基于記憶體的檔案系統、都有檢視和設定核心參數的功能、并且sysfs還是linux統一的裝置模型的管理者、sysfs挂在/sys目錄下即可;先從裝置看/sys目錄下都是些什麼,下面是我的裝置的情況:
/ # ls sys/ -l
total 0
drwxr-xr-x 33 root root 0 Jan 1 00:00 block
drwxr-xr-x 11 root root 0 Jan 1 00:00 bus
drwxr-xr-x 28 root root 0 Jan 1 00:00 class
drwxr-xr-x 4 root root 0 Jan 1 00:02 dev
drwxr-xr-x 6 root root 0 Jan 1 00:00 devices
drwxr-xr-x 2 root root 0 Jan 1 00:03 firmware
drwxr-xr-x 2 root root 0 Jan 1 00:03 fs
drwxr-xr-x 3 root root 0 Jan 1 00:03 kernel
drwxr-xr-x 41 root root 0 Jan 1 00:03 module
可見是一堆目錄,先把firmware、fs、kernel抛開,其餘的block、bus、class、dev、devices、module就是裝置目前的裝置模型的實作結果;其中block是所有塊裝置的“集合”,bus是系統目前所有總線的“集合”,class是系統目前都裝置的類的“集合”,dev暫時先不管,devices是系統目前所有裝置的“集合”,module是系統目前具有非0屬性的子產品;有時候某些linux機器還會有power目錄,這是和電源管理相關的,我這個裝置沒有,下面是稍細緻一點描述這些目錄:
/sys/devices:核心對系統中所有裝置的分層次表達模型;
/sys/dev/:這個目錄下維護一個按字元裝置和塊裝置的主次号碼(major:minor)連結到真實的裝置(/sys/devices下)的符号連結檔案;
/sys/bus/:核心裝置按總線類型分層放置的目錄結構, devices目錄下的所有裝置都是連接配接于某個總線之下,在這裡的每一種具體總線之下可以找到每一個具體裝置的符号連結,bus是構成 Linux 統一裝置模型的重要部分;
/sys/class/:是從一個“類别”的角度區分裝置,如從PCI、USB等角度制造幾個類别,注意,class目錄下的裝置不一定挂在任何一個總線上,挂在某個總線上的裝置不一定出現在class目錄下;
/sys/block/:這裡是系統中目前所有的塊裝置所在;
/sys/firmware/:這裡是系統加載固件機制的對使用者空間的接口,暫不考慮;
/sys/fs/:這裡按照設計是用于描述系統中所有檔案系統,包括檔案系統本身和按檔案系統分類存放的已挂載點,但目前隻有 fuse,gfs2 等少數檔案系統支援 sysfs 接口,一些傳統的虛拟檔案系統(VFS)層次控制參數仍然在 sysctl (/proc/sys/fs) 接口中,比如我的裝置,fs目錄是個空目錄;
/sys/kernel/:這裡是核心所有可調整參數的位置,目前隻有 uevent_helper, kexec_loaded, mm, 和新式的 slab 配置設定器等幾項較新的設計在使用它,其它核心可調整參數仍然位于 sysctl (/proc/sys/kernel) 接口中,比如我的裝置,kernel目錄是個空目錄;
/sys/module/:這裡有系統中所有子產品的資訊,不論這些子產品是以内聯(inlined)方式編譯到核心映像檔案(vmlinuz)中還是編譯為外部子產品(ko檔案),都可能會出現在 /sys/module 中: 編譯為外部子產品(ko檔案)在加載後會出現對應的 /sys/module/<module_name>/, 并且在這個目錄下會出現一些屬性檔案和屬性目錄來表示此外部子產品的一些資訊,如版本号、加載狀态、所提供的驅動程式等;編譯為内聯方式的子產品則隻在當它有非0屬性的子產品參數時會出現對應的/sys/module/<module_name>,這些子產品的可用參數會出現在 /sys/modules/<modname>/parameters/<param_name>中;
/sys/power/:這裡是系統中電源選項,這個目錄下有幾個屬性檔案可以用于控制整個機器的電源狀态,如可以向其中寫入控制指令讓機器關機、重新開機等;
除了上面的這些個,目前的核心可能還有一些其他的目錄,但目前不用考慮這麼多了,目前隻考慮對于了解linux裝置模型和sysfs最重要的幾個,就是上面黑體的4個,bus、device、driver、class;
1、 為什麼要搞linux裝置模型?
這個确實還是有原因的,正如很多文章的例子所講,一個U盤插在電腦的USB總線裝置上,USB總線裝置以PCI裝置的身份挂在PCI總線裝置上,PCI總線裝置直接受CPU管理,當挂起(suspend)電源時,應該以“U盤->USB總線裝置->PCI總線裝置”的順序通知每一個裝置将電源挂起,當恢複(resume)電源時,應該以反方向的順序恢複電源,如果順序不對,裝置将無法正常操作;這就是搞linux裝置模型的需求或原因。
是以,linux的裝置模型要搞成這麼個樣子:最高層次是總線,可以有不同的總線;然後是每個總線下挂的裝置(或是下一級總線,如PCI和USB的關系);而驅動(driver)也挂在總線之下,當它比對某裝置時,也會和該裝置産生一些關系;最後是class,它和前面的還不太一樣,它是按裝置的具體功能分類,底下的各個類和類下邊的裝置不一定和bus下的裝置能夠對應,反之亦然;
上面的描述肯定不清楚,這是因為linux裝置模型的具體實作是一個非常繁雜的系統,不同部分的代碼關聯度很大,這對代碼效率提高非常有幫助,因為避免了重複地搞很多東西,但确實不利于我這樣的初學者了解清楚,下面就是具體描述:
2、 表面了解下什麼是bus、device、driver:
下面是linux核心初始化時的一部分内容:
void __init driver_init(void)
{
devtmpfs_init();
devices_init();
buses_init();
classes_init();
firmware_init();
hypervisor_init();
platform_bus_init();
system_bus_init();
cpu_dev_init();
memory_dev_init();
}
小節:
函數buses_init建立了總線bus這麼個東西,并且建立了第一個總線platform,并且platform目錄下還有device和driver兩個目錄;先不看函數buses_init裡面到底幹什麼,先記住buses_init這個函數讓linux核心有了總線這個概念了,并且有一個platform總線;
函數devices_init建立了裝置devices這麼個東西,并且建立了第一個裝置system;先不看函數devices_init裡面到底幹什麼,先記住devices_init這個函數讓linux核心有了裝置這個概念了,并且有system這個裝置;
函數classes_init建立了類class這麼個東西,先不看函數classes_init裡面到底幹什麼,先記住classes_init這個函數讓linux核心有了類這個概念了;
從這以後,編譯進核心的各個裝置将分别在這些目錄下增加各自的内容,當系統啟動差不多了,再檢視/sys目錄下,比如我的裝置的狀态是這樣的:
/ # ls sys/bus/ -l
total 0
drwxr-xr-x 4 root root 0 Jan 1 16:59 hid
drwxr-xr-x 4 root root 0 Jan 1 00:00 i2c
drwxr-xr-x 4 root root 0 Jan 1 16:59 mmc
drwxr-xr-x 5 root root 0 Jan 1 16:59 pci
drwxr-xr-x 4 root root 0 Jan 1 13:37 platform
drwxr-xr-x 4 root root 0 Jan 1 16:59 scsi
drwxr-xr-x 4 root root 0 Jan 1 16:59 sdio
drwxr-xr-x 4 root root 0 Jan 1 16:59 usb
drwxr-xr-x 4 root root 0 Jan 1 16:59 usb-serial
/ #
這說明,我的裝置現在有一共9個總線;
其中,platform總線裡邊的device目錄是這樣的:
/ # ls sys/bus/platform/devices/
boardEnv/ mv64xxx_i2c.0/ regulatory.0/
cust/ mv88fx_neta.0/ serial8250.0/
ehci_marvell.70059/ mvsdio/ serial8250/
gpon/ neta/ tpm/
kw_cpuidle.0/ orion_wdt/ tpm_sw/
這說明,我的裝置的platform總線下,一共挂載了15個裝置;
另外,platform總線裡邊的driver目錄是這樣的:
/ # ls sys/bus/platform/drivers/
ehci_marvell/ mv64xxx_i2c/ mvsdio/ soc-audio/
kw_cpuidle/ mv88fx_neta/ serial8250/
應該是有7個驅動,可見,platform挂載的裝置不一定都有驅動,這是正常的,因為有的裝置就是為了調試(即sysfs功能,檢視和設定核心參數)裝置參數用的,這也就是說明了很多文章講的“sysfs是linux裝置模型的附屬物”是什麼意思。
再看看/sys/devices目錄下的platform目錄下有什麼内容:
/ # ls sys/devices/platform/
boardEnv/ mv88fx_neta.0/ serial8250/
cust/ mvsdio/ tpm/
ehci_marvell.70059/ neta/ tpm_sw/
gpon/ orion_wdt/ uevent
kw_cpuidle.0/ regulatory.0/
mv64xxx_i2c.0/ serial8250.0/
應該是16個裝置,比/sys/bus/platform/devices目錄下多了一個regulatory.0/,先不管它,其餘都是對應的。
再看看class的情況:
/ # cat sys/class/
bdi/ mem/ pon/ scsi_host/ vc/
firmware/ misc/ ppp/ sound/ vtconsole/
i2c-adapter/ mmc_host/ rtc/ tpm/
i2c-dev/ mtd/ scsi_device/ tty/
ieee80211/ net/ scsi_disk/ ubi/
input/ pci_bus/ scsi_generic/ usb_device/
應該是26個class,有的class下有一個裝置,有的class下有很多裝置,并且基本不和/sys/devices/platform/或/sys/bus/platform/devices/目錄下的内容有什麼對應關系,這就更說明了sysfs的靈活性,它專門用于調試的,linux驅動模型中,除了滿足裝置正常運作的device和driver,還有很多裝置是虛拟的,是有意加的,隻是為了調試。
下面舉幾個實際的例子,先粗略的看裝置是怎麼加入這個模型中去的:
int __init buses_init(void)
{
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
if (!bus_kset)
return -ENOMEM;
return 0;
}
Eg1:函數buses_init告訴linux核心,什麼叫做bus,調用函數kset_create_and_add;
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;
}
Eg2:函數platform_bus_init,首先建立了一個裝置叫platform_bus,這是個總線裝置(platform是總線,同時本身也是個裝置),調用函數device_register;然後建立了一個總線叫platform,調用函數bus_register;
pd = bus_find_device_by_name(&platform_bus_type, NULL, "gpon");
if (!pd) {
platform_device_register_simple("gpon", -1, NULL, 0);
pd = bus_find_device_by_name(&platform_bus_type, NULL, "gpon");
}
Eg3:函數platform_device_register_simple在platform總線下建立了裝置gpon,實際調用函數是platform_device_add;
pon_udev_class = class_create(THIS_MODULE, “pon”);
Eg4:函數class_create建立了一個類叫pon;
pon_udev_dev = device_create(pon_udev_class, NULL, dev, NULL, PON_DEV_NAME);
Eg5:緊跟着,函數device_create在類pon下建立了個裝置也叫pon;
static struct platform_driver mv64xxx_i2c_driver = {
.probe = mv64xxx_i2c_probe,
.remove = __devexit_p(mv64xxx_i2c_remove),
.driver = {
.owner = THIS_MODULE,
.name = MV64XXX_I2C_CTLR_NAME,
},
};
static int __init
mv64xxx_i2c_init(void)
{
return platform_driver_register(&mv64xxx_i2c_driver);
}
Eg6:函數platform_driver_register在platform總線下建立了驅動mv64xxx_i2c_driver;
retval = bus_register(&i2c_bus_type);
Eg7:函數bus_register建立了又一個總線i2c_bus;
retval = i2c_add_driver(&dummy_driver);
Eg8:函數i2c_add_driver在總線i2c_bus下建立了一個驅動dummy_driver,實際調用函數driver_register;
res = i2c_add_driver(&i2cdev_driver);
Eg9:函數i2c_add_driver在總線i2c_bus下又建立了一個驅動i2cdev_driver,實際調用函數driver_register;
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
Eg10:函數class_create建立了一個class,叫i2c-dev;
i2c_adapter_compat_class = class_compat_register("i2c-adapter");
Eg11:函數class_compat_register建立了一個class叫i2c-adapter;
經過上面的11個例子,可以看看,是不是和前面列印的bus、devices、driver、class裡的相應内容對應上。
這時,應該可以有了linux裝置模型的一個初步的概念,即:
1、 bus、devices、driver、class的每一個内容,都可以對應上sysfs即/sys目錄下的一個個目錄;
2、 每一個bus、devices、driver、class本身也都是一個裝置(這個從代碼實作上很好了解,因為它們和具體裝置、驅動一樣,也必須是一個個目錄,隻不過是相對的頂層目錄罷了,可以從資料結構的樹的節點去了解)
3、 bus和class都是在頂層,它們下邊是一個個實際的bus、class條目,每個bus條目下有device和driver兩個目錄,每個目錄下可能有一些裝置目錄,每個class條目下可能有一些裝置目錄;
隻有這些遠遠不夠,後面就要較長的描述了,描述這種裝置模型到底從代碼上是怎麼實作的,這就更加深刻的讓我們了解linux裝置模型到底是什麼、sysfs是什麼以及怎麼用。