轉載自 http://blog.csdn.net/yj4231/article/details/7799245 作者:yj4231
本文将對Linux系統中的sysfs進行簡單的分析,要分析sysfs就必須分析核心的driver-model(驅動模型),兩者是緊密聯系的。在分析過程中,本文将以platform總線和spi主要制器的platform驅動為例來進行講解。其實,platform機制是基于driver-model的,通過本文,也會對platform機制有個簡單的了解。
核心版本:2.6.30
個人了解:sysfs向使用者空間展示了驅動裝置的層次結構。我們都知道裝置和對應的驅動都是由核心管理的,這些對于使用者空間是不可見的。現在通過sysfs,可以在使用者空間直覺的了解裝置驅動的層次結構。
我們來看看sysfs的檔案結構:
[root@yj423 /sys]#ls
block class devices fs module
bus dev firmware kernel power
block:塊裝置
bus:系統中的總線
class: 裝置類型,比如輸入裝置
dev:系統中已注冊的裝置節點的視圖,有兩個子目錄char和block。
devices:系統中所有裝置拓撲結構視圖
fireware:固件
fs:檔案系統
kernel:核心配置選項和狀态資訊
module:子產品
power:系統的電源管理資料
要分析sysfs,首先就要分析kobject和kset,因為驅動裝置的層次結構的構成就是由這兩個東東來完成的。
kobject是一個對象的抽象,它用于管理對象。每個kobject對應着sysfs中的一個目錄。
kobject用struct kobject來描述。
kset是一些kobject的集合,這些kobject可以有相同的ktype,也可以不同。同時,kset自己也包含一個kobject。在sysfs中,kset也是對應這一個目錄,但是目錄下面包含着其他的kojbect。
kset使用struct kset來描述。
每個kobject對象都内嵌有一個ktype,該結構定義了kobject在建立和删除時所采取的行為。
當kobject的引用計數為0時,通過release方法來釋放相關的資源。
attribute為屬性,每個屬性在sysfs中都有對應的屬性檔案。
sysfs_op的兩個方法用于實作讀取和寫入屬性檔案時應該采取的行為。
下面這張圖非常經典。最下面的kobj都屬于一個kset,同時這些kobj的父對象就是kset内嵌的kobj。通過連結清單,kset可以擷取所有屬于它的kobj。
從sysfs角度而言,kset代表一個檔案夾,而下面的kobj就是這個檔案夾裡面的内容,而内容有可能是檔案也有可能是檔案夾。
在上一節中,我們知道sys下有一個bus目錄,這一将分析如何通過kobject建立bus目錄。
下面代碼位于drivers/base/bus.c
這裡直接調用kset_create_and_add,第一個參數為要建立的目錄的名字,而第三個參數表示沒有父對象。
下面代碼位于drivers/base/kobject.c
這裡主要調用了兩個函數,接下分别來看下。
這個函數中,動态配置設定了kset結構,調用kobject_set_name設定kset->kobj->name為bus,也就是我們要建立的目錄bus。同時這裡kset->kobj.parent為NULL,
也就是沒有父對象。因為要建立的bus目錄是在sysfs所在的根目錄建立的,自然沒有父對象。
随後簡要看下由kobject_set_name函數調用引發的一系列調用。
下面代碼位于drivers/base/kobject.c。
這裡面調用了3個函數。這裡先介紹前兩個函數。
該函數用于初始化kset。
下面代碼位于drivers/base/kobject.c。
該函數将在sysfs中建立目錄。
下面代碼位于drivers/base/kobject.c。
在上面的kset_create中有kset->kobj.kset = NULL,是以if (kobj->kset)條件不滿足。是以在這個函數中,對name進行了必要的檢查之後,調用了create_dir在sysfs中建立目錄。
在create_dir執行完成以後會在sysfs的根目錄(/sys/)建立檔案夾bus。該函數的詳細分析将在後面給出。
至此,對bus目錄的建立有了簡單而直覺的了解。我們可以看出kset其實就是表示一個檔案夾,而kset本身也含有一個kobject,而該kobject的name字段即為該目錄的名字,本例中為bus。
第2節所介紹的是最底層,最核心的内容。下面開始将描述較為高層的内容。
Linux裝置模型使用了三個資料結構分别來描述總線、裝置和驅動。所有的裝置和對應的驅動都必須挂載在某一個總線上,通過總線,可以綁定裝置和驅動。
這個屬于分離的思想,将裝置和驅動分開管理。
同時驅動程式可以了解到所有它所支援的裝置,同樣的,裝置也能知道它對應驅動程式。
總線是處理器與一個裝置或者多個裝置之間的通道。在裝置模型中,所有的裝置都挂載在某一個總線上。總線使用struct bus_type來表述。
下列代碼位于include/linux/device.h。
我們看到每個bus_type都包含一個kset對象subsys,該kset在/sys/bus/目錄下有着對應的一個目錄,目錄名即為字段name。後面我們将看到platform總線的建立。
drivers_kset和devices_kset對應着兩個目錄,該兩個目錄下将包含該總線上的裝置和相應的驅動程式。
同時總線上的裝置和驅動将分别儲存在兩個連結清單中:klist_devices和klist_drivers。
裝置對象在driver-model中使用struct device來表示。
device本身包含一個kobject,也就是說這個device在sysfs的某個地方有着一個對應的目錄。
該device所挂載的bus由knode_bus指定。
該device所對應的裝置驅動由knode_driver指定。
裝置裝置對象在driver-model中使用struct device_driver來表示。
device_driver本身包含一個kobject,也就是說這個device_driver在sysfs的某個地方有着一個對應的目錄。
該裝置驅動所支援的裝置由klist_devices指定。
該裝置驅動所挂載的總線由knode_bus制定。
本節我們将以platform總線為例,來看看,/sys/bus/platform是如何建立的。
platform總線的注冊是由platform_bus_init函數完成的。該函數在核心啟動階段被調用,我們來簡單看下調用過程:
start_kernel() -> rest_init() ->kernel_init() -> do_basic_setup() -> driver_init() -> platform_bus_init()。
注:kernel_init()是在rest_init函數中建立核心線程來執行的。
從bus_type,我們看到該總線的名字為platform。
調用了兩個函數,我們隻關注bus_register函數。
函數中,首先調用kobject_set_name設定了bus對象的subsys.kobject->name 為 platform,也就是說會建立一個名為platform的目錄。kobject_set_name函數在3.1小節中已經給出。
在這裡還用到了bus_kset這個變量,這個變量就是在第3節buses_init函數中建立bus目錄所對應的kset對象。
接着,priv->subsys.kobj.kset = bus_kset,設定subsys的kobj在bus_kset對象包含的集合中,也就是說bus目錄下将包含subsys對象所對應的目錄,即platform。
緊接着調用了kset_register,參數為&priv->subsys。該函數在3.2節中以給出。在該函數的調用過程中,将調用kobj_kset_join函數,該函數将kobject添加到kobject->kset的連結清單中。
kset_register函數執行完成後,将在/sys/bus/下建立目錄platform。此刻,我們先來看下kset和kobject之間的關系。
然後,調用了bus_create_file函數在/sys/bus/platform/下建立檔案uevent。
有關底層的sysfs将在後面叙述,這裡隻要關注參數&bus->p->subsys.kobj,表示在該kset下建立檔案,也就是platform下建立。
接着調用了2次kset_create_and_add,分别在/sys/bus/platform/下建立了檔案夾devices和drivers。該函數位于第3節開始處。
這裡和第3節調用kset_create_and_add時的最主要一個差別就是:此時的parent參數不為NULL,而是&priv->subsys.kobj。
也就是說,将要建立的kset的kobject->parent = &priv->subsys.kobj,也即建立的kset被包含在platform檔案夾對應的kset中。
我們來看下關系圖:
随後,調用了add_probe_files建立了屬性檔案drivers_autoprobe和drivers_probe。
該函數隻是簡單的調用了兩次bus_create_file,該函數已在前面叙述過。
最後調用bus_add_attrs建立總線相關的屬性檔案。
我們可以看到這個函數将根據bus_type->bus_arrts來建立屬性檔案。不過,在本例中,bus_arrts從未給出定義,是以次函數不做任何工作。
好了,整個bus_register調用完成了,我們來看下sysfs中實際的情況。
[root@yj423 platform]#pwd
/sys/bus/platform
[root@yj423 platform]#ls
devices drivers drivers_autoprobe drivers_probe uevent
最後,我們對整個bus_register的過程進行一個小結。
本節将首先講述如何在/sys/devices下建立虛拟的platform裝置,然後再講述如何在/sys/devices/platform/下建立子裝置。
之是以叫虛拟是因為這個platform并不代表任何實際存在的裝置,但是platform将是所有具體裝置的父裝置。
在第5節,platform_bus_init函數中還調用了device_register,現在對其做出分析。
下列函數位于drivers/base/core.c。
一個裝置的注冊分成兩部,每步通過調用一個函數函數。首先先看第一步:
首先其中用到了devices_kset對象,這個對象和第3節當中的bus_kset是同樣的性質,也就是說該對象表示一個目錄。
該對象的建立是在devices_init函數中完成的。
由此可見,devices_kset對象表示的目錄為/sys下的devices目錄。
下列函數位于lib/kojbect.c。
該函數在做了一系列的必要檢查後,調用kobject_init_internal初始化了kobject的某些字段。
參數val為0,設定該device不能夠喚醒。
設定電源的狀态。
如果使用NUMA,則設定NUMA節點。
接下來是注冊的第二步:調用device_add。
該函數調用了非常多的其他函數,接下來對主要的函數做出分析。
下列代碼位于drivers/base/core.c。
該函數将設定dev對象的parent。在這裡實際傳入的parent為NULL,同時dev->class也沒有定義過。是以這個函數什麼都沒有做。
下列代碼位于lib/kobject.c。
在調用時,參數parent為NULL,且dev->kobj.kset在6.1節device_initialize函數中設定為devices_kset。
而devices_kset對應着/sys/devices目錄,是以該函數調用完成後将在/sys/devices目錄下生成目錄platform。
但是這裡比較奇怪的是,為什麼platform目錄沒有對應的kset對象???
在調用該函數之前,會在/sys/devices/platform/下生成屬性檔案。接着如果該device的裝置号不為0,則建立屬性檔案dev,并調用本函數。
但是,在本例中裝置号devt從未設定過,顯然為0,那麼本函數實際并未執行。
由于dev->class為NULL,本函數其實沒做任何工作。
同樣dev->class為空,什麼都沒幹。
由于dev->bus未指定,是以這個函數什麼都沒幹。
該函數将建立三個symlink,在sysfs中建立總線和裝置間的關系。
下列代碼位于drivers/base/bus.c。
下列代碼位于drivers/base/power/sysfs.c。
該函數将在XXX目錄下建立power子目錄,并在該子目錄下建立屬性檔案wakeup。
在本例中,将在/sys/bus/platform下建立子目錄power并在子目錄下建立wakeup檔案。
下列代碼位于drivers/base/power/main.c。
該函數隻是将裝置添加到電源管理連結清單中。
在本例中,由于bus未指定,該函數實際不做任何工作。
如果bus存在的話,将會調用device_attach函數進行綁定工作。該函數首先判斷dev->driver,如果非0,表示該裝置已經綁定了驅動,隻要在sysfs中建立連結關系即可。
為0表示沒有綁定,接着調用bus_for_each_drv,注意作為參數傳入的__device_attach,這是個函數,後面會調用它。
我們來看下bus_for_each_drv:
該函數将周遊總線的drivers目錄下的所有驅動,也就是/sys/bus/XXX/drivers/下的目錄,為該driver調用fn函數,也就是__device_attach。我們來看下:
該函數首先調用driver_match_device函數,後者将會調用總線的match方法,如果有的話,來進行比對工作。如果沒有該方法,則傳回1,表示比對成功。
我們這裡是針對platform總線,該總線的方法将在7.6.2節中看到。
随後,又調用了driver_probe_device函數。該函數将首先判斷該device是否已在sysfs中,如果在則調用really_probe,否則傳回出錯。
really_probe将會調用驅動的probe并完成綁定的工作。該函數将在7.6.2節中分析。
在本例中,當device_register調用完成以後,将在/sys/devices/下建立目錄platform,并在platfrom下建立屬性檔案uevent和子目錄power,最後在power子目錄下建立wakeup屬性檔案。
最後以函數調用過程的總結來結束第6.2小結。
本節對一個特定的platform裝置進行講解,那就是spi主要制器的平台裝置。
在核心的啟動階段,platform裝置将被注冊進核心。我們來看下。
下列代碼位于arch/arm/mach-s3c2440/mach-smdk2440.c
在smdk2440_machine_init函數中,通過調用platform_add_devices将裝置注冊到核心中。接着來看下該函數。
該函數将根據devs指針數組,調用platform_device_register将platform裝置逐一注冊進核心。
調用了兩個函數,第一個函數在6.1節已經分析過。我們來看下第二個函數。
在這個函數的最後赫然出現了device_add函數。我們回憶下在6.1節中device_register的注冊過程,該函數隻調用了兩個函數,一個是device_initialize函數,另一個就是device_add。
本節的platform_device_register函數,首先也是調用了device_initialize,但是随後他做了一些其他的工作,最後調用了device_add。
那麼這個"其他的工作"幹了些什麼呢?
首先,它将該SPI主要制對應的平台裝置的父裝置設為虛拟的platform裝置(platform_bus),然後将該平台裝置挂在至platform總線(platform_bus_type)上,這兩步尤為重要,後面我們将看到。
然後,調用了dev_set_name設定了pdev->dev-kobj.name,也就是該裝置對象的名字,這裡的名字為s3c2410-spi.0,這個名字将被用來建立一個目錄。
最後,将平台的相關資源添加到資源樹中。這不是本篇文章讨論的重點所在,是以不做過多說明。
在"其他的工作""幹完之後,調用了device_add函數。那麼後面的函數調用過程将和6.2小結的一緻。
由于“其他的工作”的原因,實際執行的過程和結果将有所差別。我們來分析下。
首先,在device_add被調用之前,有若幹個非常重要的條件已經被設定了。如下:
pdev->dev->kobj.kset = devices_kset
pdev->dev-.parent = &platform_bus
pdev->dev.bus = &platform_bus_type
set_up函數執行時,由于參數parent為&platform_bus,是以最後将設定pdev->dev->kobj.parent = platform_bus.kobj。平台裝置對象的父對象為虛拟的platform裝置。
kobject_add函數執行時,由于參數parent的存在,将在parent對象所對應的目錄下建立另一個目錄。parent對象代表目錄/sys/devices/下的platform,是以将在/sys/devices/platform下建立目錄s3c2410-spi.0。
device_create_file建立屬性檔案uevent。
bus_add_device函數執行時,由于dev.bus 為&platform_bus_type,是以将建立三個symlink。
/sys/devices/platform/s3c2410-spi.0下建立連結subsystem和bus,他們指向/sys/bus/platform。
/sys/bus/platform/devices/下建立連結s3c2410-spi.0,指向/sys/devices/platform/s3c2410-spi.0。
dpm_sysfs_add函數在/sys/devices/platform/s3c2410-spi.0下建立子目錄power,并在該子目錄下建立屬性檔案wakeup。
執行到這裡時,sysfs已将核心中新添加的SPI主要制器平台裝置呈現出來了,我們來驗證下。
[root@yj423 s3c2410-spi.0]#pwd
/sys/devices/platform/s3c2410-spi.0
[root@yj423 s3c2410-spi.0]#ll
lrwxrwxrwx 1 root root 0 Jan 1 00:29 bus -> ../../../bus/platform
lrwxrwxrwx 1 root root 0 Jan 1 00:29 driver -> ../../../bus/platform/drivers/s3c2410-spi
-r--r--r-- 1 root root 4096 Jan 1 00:29 modalias
drwxr-xr-x 2 root root 0 Jan 1 00:29 power
drwxr-xr-x 3 root root 0 Jan 1 00:00 spi0.0
drwxr-xr-x 3 root root 0 Jan 1 00:00 spi0.1
lrwxrwxrwx 1 root root 0 Jan 1 00:29 spi_master:spi0 -> ../../../class/spi_master/spi0
lrwxrwxrwx 1 root root 0 Jan 1 00:29 subsystem -> ../../../bus/platform
-rw-r--r-- 1 root root 4096 Jan 1 00:29 uevent
[root@yj423 devices]#pwd
/sys/bus/platform/devices
[root@yj423 devices]#ll s3c2410-spi.0
lrwxrwxrwx 1 root root 0 Jan 1 00:44 s3c2410-spi.0 -> ../../../devices/platform/s3c2410-spi.0
通過sysfs将裝置驅動的模型層次呈現在使用者空間以後,将更新核心的裝置模型之間的關系,這是通過修改連結清單的指向來完成的。
bus_attach_device函數執行時,将裝置添加到總線的裝置連結清單中,同時也會嘗試綁定驅動,不過會失敗。
接着,由于dev->parent的存在,将SPI主要制器裝置添加到父裝置platform虛拟裝置的兒子連結清單中。
我們已經介紹過platform總線的注冊,也講述了SPI主要制器裝置作為平台裝置的注冊過程,在本節,将描述SPI主要制器的platform驅動是如何注冊的。
下列代碼位于drivers/spi/spi_s3c24xx.c。
驅動注冊通過調用platform_driver_probe來完成。
注意:driver.name字段使用來比對裝置的,該字段必須和6.3節一開始給出的pdev.name字段相同。
下列代碼位于drivers/base/platform.c。
這裡的重點是platform_driver_register,由它來完成了platform驅動的注冊。
driver_register函數就是driver注冊的核心函數。需要注意的是,在調用函數之前,将該驅動所挂載的總線設定為platform總線(platform_bus_type)。
下列代碼位于drivers/base/driver.c。
這裡主要調用兩個函數driver_find和bus_add_driver。前者将通過總線來搜尋該驅動是否存在,後者将添加驅動到總線中。
接下來就分析這兩個函數。
這裡調用了kset_find_obj函數,傳入的實參bus->p->drivers_kset,它對應的就是/sys/bus/platform/下的drivers目錄,然後通過連結清單,它将搜尋該目錄下的所有檔案,來尋找是否有名為s3c2410-spi的檔案。還記得嗎? kobject就是一個檔案對象。如果沒有找到将傳回NULL,接着将調用bus_add_driver把驅動注冊進核心。
下列代碼位于drivers/base/bus.c
在設定driver的kobj.kset為drivers目錄所對應的kset之後,調用了kobject_init_and_add,我們來看下。
該函數中調用了兩個函數,這兩個函數分别在6.1.2和6.2.2中講述過,這裡不再贅述。
調用該函數時由于parent為NULL,但kobj.kset為drivers目錄,是以将在/sys/bus/platform/drivers/下建立目錄,名為s3c2410-spi。
我們來驗證下:
[root@yj423 s3c2410-spi]#pwd
/sys/bus/platform/drivers/s3c2410-spi
接着由于drivers_autoprobe在bus_register執行的時候已經置1,将調用driver_attach。
下列代碼位于drivers/base/dd.c。
該函數将調用bus_for_each_dev來尋找總線上的每個裝置,這裡的總線即為platform總線,然後嘗試綁定裝置。
這裡需要注意的是最後一個參數__driver_attach,這是一個函數名,後面将會調用它。
通過klist将周遊該總線上的所有裝置,并為其調用__driver_attach函數。
首先調用了driver_match_device函數,該函數進會進行比對,如果比對成功将傳回1。我們看下這個函數:
這裡直接調用了platform總線的match方法,我們來看下這個方法。
該方法的核心其實就是使用stcmp進行字元比對,判斷pdev->name和drv->name是否相等。
在本例中兩者同為s3c2410-spi。是以比對完成,傳回1。
傳回後,由于dev->driver為NULL,将調用driver_probe_device函數。我們來看下:
該函數将調用really_probe來綁定裝置和它的驅動。
在這個函數中調用4個函數。
第一個函數driver_sysfs_add将更新sysfs。
執行完以後,建立了兩個連結。
在/sys/bus/platform/drivers/s3c2410-spi下建立連結,指向/sys/devices/platform/s3c2410-spi.0
在/sys/devices/platform/s3c2410-spi.0下建立連結,指向/sys/devices/platform/s3c2410-spi.0。
這樣就在使用者空間呈現出驅動和裝置的關系了。我們來驗證下。
[root@yj423 s3c2410-spi]#ll s3c2410-spi.0
lrwxrwxrwx 1 root root 0 Jan 1 02:28 s3c2410-spi.0 -> ../../../../devices/platform/s3c2410-spi.0
[root@yj423 s3c2410-spi.0]#ll driver
lrwxrwxrwx 1 root root 0 Jan 1 02:26 driver -> ../../../bus/platform/drivers/s3c2410-spi
第2個函數執行總線的probe方法,由于platform總線沒有提供probe方法,是以不執行。
第3個函數執行驅動的probe方法,驅動提供了probe,是以調用它,該函數的細節超過了本文的讨論内容,是以略過。
第4個函數執行driver_bound,用來綁定裝置和驅動,來看下這個函數。
其實,所謂的綁定,就是将裝置的驅動節點添加到驅動支援的裝置連結清單中。
至此,通過核心連結清單,這個platform device 和platform driver 已經綁定完成,将繼續周遊核心連結清單嘗試比對和綁定,直到連結清單結束。
在driver_attach執行完畢以後,bus_add_driver函數還有些剩餘工作要完成。
首先,将驅動添加到總線的驅動清單中。
接着,如果定義了驅動屬性檔案,則建立。
最後,在/sys/bus/platform/drivers/s3c2410-spi/下建立屬性檔案uevent,并在同一目錄下建立檔案bind和unbind。
[root@yj423 s3c2410-spi]#ls
bind s3c2410-spi.0 uevent unbind
在本節中,我們看到了platform driver是如何注冊到核心中,在注冊過程中,通過更新了sysfs,向使用者空間展示總線,裝置和驅動之間的關系。
同時,還更新了連結清單的指向,在核心中展現了同樣的關系。
最後以platform driver的注冊過程結束本章。
下面講述的内容将基于VFS,有關VFS的基本内容超過本文的範圍,請參考<<深入了解Linux核心>>一書的第12章。
在前面講述的過程中,我們知道裝置驅動模型是如何通過kobject将總線,裝置和驅動間的層次關系在使用者空間呈現出來的。事實上,就是通過目錄,檔案和symlink來呈現互相之間的關系。在前面的叙述中,我們并沒有對目錄,檔案和symlink的建立進行 講解,本章就對這些底層函數進行講解。在講解這些函數之前,我們先來看下,sysfs檔案系統是如何注冊的。
sysfs檔案系統的注冊是調用sysfs_init函數來完成的,該函數在核心啟動階段被調用,我們來看下大緻函數調用流程,這裡不作分析。
start_kernel( ) -> vfs_caches_init( ) -> mnt_init( ) -> mnt_init( ) -> sysfs_init( )。
下列代碼位于fs/filesystems.c。
該函數将調用函數file_system_type,此函數根據name字段(sysfs)來查找要注冊的檔案系統是否已經存在。
如果不存在,表示還未注冊,則将新的fs添加到連結清單中,連結清單的第一項為全局變量file_systems。
該全局變量為單項連結清單,所有已注冊的檔案系統都被插入到這個連結清單當中。
下列代碼位于include/linux/fs.h
下列代碼位于fs/sysfs/mount.c
kern_mount實際上最後是調用了vfs_kern_mount函數。我們來看下:
該函數在首先調用alloc_vfsmnt來配置設定struct vfsmount結構,并做了一些初試化工作。
下列函數位于fs/super.c
配置設定好結構體以後,由于參數data為NULL,将直接調用檔案系統類型提供的get_sb方法,該方法就是函數sysfs_get_sb。我們來看下:
下列函數位于fs/sysfs/mount.c。
這裡直接調用了get_sb_single函數,注意這裡的第4個實參sysfs_fill_super,該參數是函數名,後面将會調用該函數。
該函數将配置設定sysfs檔案系統的superblock,擷取檔案系統根目錄的inode和dentry。
該函數的執行過程相當複雜,在下一節單獨講述。
首先調用了sget函數來查找是否
下列函數位于fs/super.c。
該函數将周遊屬于sysfs檔案系統的所有superblock,本例中由于之前沒有任何superblock建立,周遊立即結束。
然後調用alloc_super函數來建立新的struct super_block。
配置設定完以後,調用作為參數傳入的函數指針set,也就是set_anon_super函數,該函數用來設定s->s_dev。
配置設定了super_block之後,将判斷該super_block是否有root dentry。本例中,顯然沒有。然後調用形參fill_super指向的函數,也就是sysfs_fill_super函數。
在設定了一些字段後,設定了sysfs_sb這個全局變量,該全局變量表示的就是sysfs的super_block。
随後,調用了sysfs_get_inode函數,來擷取sysfs的根目錄的dirent。該函數的參數sysfs_root為全局變量,表示sysfs的根目錄的sysfs_dirent。
我們看些這個sysfs_dirent資料結構:
其中比較關鍵的就是那個聯合體,針對不同的形式(目錄,symlink,屬性檔案和可執行檔案)将使用不同的資料結構。
另外,sysfs_dirent将最為dentry的fs專有資料被儲存下來,這一點會在下面中看到。
接着,在來看下sysfs_get_inode函數:
下列函數位于fs/sysfs/inode.c。
該函數首先調用了,iget_locked來查找該inode是否已存在,如果不存在則建立。如果是新建立的inode,則對inode進行初始化。
再擷取了根目錄的inode和sysfs_dirent後,調用d_alloc_root來獲得dirent。
該函數首先調用了d_alloc來建立struct dentry,參數parent為NULL,既然是為根( / )建立dentry,自然沒有父對象。
接着調用d_instantiate來綁定inode和dentry之間的關系。
在sysfs_fill_super函數執行的最後,将sysfs_root儲存到了dentry->d_fsdata。
可見,在sysfs中用sysfs_dirent來表示目錄,但是對于VFS,還是要使用dentry來表示目錄。
下列代碼位于fs/super.c。
這個函數用來修改挂在選項,這個函數就不分析了,不是重點。
下列函數位于fs/namespace.c。
該函數設定了vfsmount的superblock和根dentry。
這裡,對sysfs的注冊過程做一個總結。
sysfs_init函數調用過程示意圖如下:
在整個過程中,先後使用和建立了許多struct
第一,根據file_system_type表示的sysfs檔案系統的類型注冊了sysfs。
第二,建立了vfsmount。
第三,建立了超級塊super_block。
第四,根據sysfs_dirent表示的根目錄,建立了inode。
最後,根據剛才建立的inode建立了dentry。
除了sysfs_dirent,其他5個結構體都是VFS中基本的資料結構,而sysfs_dirent則是特定于sysfs檔案系統的資料結構。
在前面的描述中,使用sysfs_create_dir在sysfs下建立一個目錄。我們來看下這個函數是如何來建立目錄的。
下列代碼位于fs/sysfs/dir.c。
函數中,首先擷取待建目錄的父sysfs_dirent,然後将它作為參數 來調用create_dir函數。
很明顯,就是要在父sysfs_dirent下建立新的sysfs_dirent,建立立的sysfs_dirent将儲存到參數sd中。
這裡要注意一下mode變量,改變了使用了宏定義SYSFS_DIR,這個就表示要建立的是一個目錄。
mode還有幾個宏定義可以使用,如下:
在create_dir函數中,首先調用了sysfs_new_dirent來建立一個新的sysfs_dirent結構體。
配置設定了sysfs_dirent後,設定了該結構中的聯合體資料。先來看下聯合體中的四個資料結構。
根據sysfs_dirent所代表的類型不同,也就是目錄,synlink,屬性檔案和bin檔案,将分别使用該聯合體中相應的struct。
在本例中要建立的是目錄,自然使用sysfs_elem_dir結構體,然後儲存了kobject對象。
在8.4和8.5中我們将分别看到sysfs_elem_attr和sysfs_elem_symlink的使用。
在擷取了父sysfs_dirent,調用sysfs_addrm_start來擷取與之對應的inode。
注意形參sysfs_addrm_cxt,該結構作用是臨時存放資料。
該函數直接調用了__sysfs_add_one,後者先調用sysfs_find_dirent來查找該parent_sd下有無該的sysfs_dirent,如果沒有,則設定建立好的新的sysfs_dirent的s_parent字段。也就是将新的sysfs_dirent添加到父sys_dirent中。接着調用sysfs_link_sibling函數,将建立的sysfs_dirent添加到sd->s_parent->s_dir.children連結清單中。
該函數結束了添加sysfs_dirent的工作,這個就不多做說明了。
至此,添加一個目錄的工作已經完成了,添加目錄的工作其實就是建立了一個新的sysfs_dirent,并把它添加到父sysfs_dirent中。
下面我們看下如何添加屬性檔案。
添加屬性檔案使用sysfs_create_file函數。
下列函數位于fs/sysfs/file.c。
sysfs_create_file用參數SYSFS_KOBJ_ATTR(表示建立屬性檔案)來調用了sysfs_add_file,後者又直接調用了sysfs_add_file_mode。
sysfs_add_file_mode函數的執行和8.3節的create_dir函數非常類&#20284;,隻不過它并沒有儲存kobject對象,也就是說該sysfs_dirent并沒有一個對應的kobject對象。
需要注意的是,這裡是建立屬性檔案,是以使用了聯合體中的結構體s_attr。
最後,來看下symlink的建立。
這個函數的執行也和8.3節的create_dir函數非常類&#20284;。其次,symlink同樣沒有對應的kobject對象。
因為sysfs_dirent表示的是symlink,這裡使用了聯合體中的s_symlink。同時設定了s_symlink.target_sd指向的目标sysfs_dirent為參數targed_sd。
本節首先對syfs這一特殊的檔案系統的注冊過程進行了分析。接着對目錄,屬性檔案和symlink的建立進行了分析。這三者的建立過程基本一緻,但是目錄
有kobject對象,而剩餘兩個沒有。其次,這三者的每個sysfs_dirent中,都使用了自己的聯合體資料。
本文首先對sysfs的核心資料kobject,kset等資料結構做出了分析,正是通過它們才能向使用者空間呈現出裝置驅動模型。
接着,以/sys/bus目錄的建立為例,來說明如何通過kobject和kset來建立該bus目錄。
随後,介紹了驅動模型中表示總線,裝置和驅動的三個資料結構。
然後,介紹了platform總線(bus/platform)的注冊,再介紹了虛拟的platform裝置(devices/platform)的添加過程。
之後 ,以spi主要制器的platform裝置為例,介紹了該platform裝置和相應的驅動的注冊過程。
最後,介紹了底層sysfs檔案系統的注冊過程和如何建立目錄,屬性檔案和symlink的過程。
更新說明:
2012.09.14 在6.2.9中,添加分析 bus_for_each_drv。