天天看點

Linux裝置模型——裝置驅動模型和sysfs檔案系統解讀

轉載自 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函數非常類似,隻不過它并沒有儲存kobject對象,也就是說該sysfs_dirent并沒有一個對應的kobject對象。

需要注意的是,這裡是建立屬性檔案,是以使用了聯合體中的結構體s_attr。

最後,來看下symlink的建立。

這個函數的執行也和8.3節的create_dir函數非常類似。其次,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。

繼續閱讀