天天看點

Linux裝置驅動模型架構分析(六)——LDDM的裝置管理:uevent,mdevueventmdev

uevent

uevent是kobject的一部分,用于在kobject狀态發生改變時,例如增加、移除等,通知使用者空間程式。使用者空間程式收到這樣的事件後,會做相應的處理。

該機制通常是用來支援熱拔插裝置的,例如U盤插入後,USB相關的驅動軟體會動态建立用于表示該U盤的device結構(相應的也包括其中的kobject),并告知使用者空間程式,為該U盤動态的建立/dev/目錄下的裝置節點,更進一步,可以通知其它的應用程式,将該U盤裝置mount到系統中,進而動态的支援該裝置。

Linux裝置驅動模型架構分析(六)——LDDM的裝置管理:uevent,mdevueventmdev

uevent的機制是比較簡單的,裝置模型中任何裝置有事件需要上報時,會觸發uevent提供的接口。uevent子產品準備好上報事件的格式後,可以通過兩個途徑把事件上報到使用者空間:一種是通過kmod子產品,直接調用使用者空間的可執行檔案;另一種是通過netlink通信機制,将事件從核心空間傳遞給使用者空間。

其中,netlink是一種socket,專門用來進行核心空間和使用者空間的通信;kmod是管理核心子產品的工具集,類似busybox,我們熟悉的lsmod,insmod等是指向kmod的連結。

uevent有幾個核心的資料結構,按照慣例,先獨立分析各個核心類,然後通過類之間的關系全面了解uevent機制。

kobject_action

Linux裝置驅動模型架構分析(六)——LDDM的裝置管理:uevent,mdevueventmdev

kobject_action定義了event的類型,包括:

ADD/REMOVE,kobject(或上層資料結構)的添加/移除事件。

ONLINE/OFFLINE,kobject(或上層資料結構)的上線/下線事件,其實是是否使能。

CHANGE,kobject(或上層資料結構)的狀态或者内容發生改變。

MOVE,kobject(或上層資料結構)更改名稱或者更改parent(意味着在sysfs中更改了目錄結構)。

CHANGE,如果裝置驅動需要上報的事件不再上面事件的範圍内,或者是自定義的事件,可以使用該event,并攜帶相應的參數。

kobj_uevent_env

Linux裝置驅動模型架構分析(六)——LDDM的裝置管理:uevent,mdevueventmdev

前面有提到過,在利用Kmod向使用者空間上報event事件時,會直接執行使用者空間的可執行檔案。而在Linux系統,可執行檔案的執行,依賴于環境變量,是以kobj_uevent_env用于組織此次事件上報時的環境變量。

envp,指針數組,用于儲存每個環境變量的位址,最多可支援的環境變量數量為UEVENT_NUM_ENVP。

envp_idx,用于通路環境變量指針數組的index。

buf,儲存環境變量的buffer,最大為UEVENT_BUFFER_SIZE。

buflen,通路buf的變量。

argv,argv[0]存儲uevent_helper的值,uevent_helper的内容是由核心配置項CONFIG_UEVENT_HELPER_PATH決定的,該配置項指定了一個使用者空間程式(或者腳本),用于解析上報的uevent,例如"/sbin/hotplug”。可以這樣了解,uevent子產品通過kmod上報Uevent時,會通過call_usermodehelper函數,調用使用者空間的可執行檔案(或者腳本,簡稱uevent helper )處理該event。而該uevent helper的路徑儲存在uevent_helper數組中。對于uevent_helper還有一點要注意,在編譯核心時,通過CONFIG_UEVENT_HELPER_PATH配置項,靜态指定uevent helper的方式,會為每個event fork一個程序,随着核心支援的裝置數量的增多,這種方式在系統啟動時将會是緻命的(可以導緻記憶體溢出等),現在核心不再推薦使用該方式。是以核心編譯時,需要把該配置項留白。在系統啟動後,大部分的裝置已經ready,可以根據需要,重新指定一個uevent helper,以便檢測系統運作過程中的熱拔插事件。這可以通過把helper的路徑寫入到"/sys/kernel/uevent_helper”檔案中實作。實際上,核心通過sysfs檔案系統的形式,将uevent_helper數組開放到使用者空間,供使用者空間程式修改通路。argv[1]存儲了本kobj_uevent_env的buf指針,argv[2]一般為NULL。

kset_uevent_ops

Linux裝置驅動模型架構分析(六)——LDDM的裝置管理:uevent,mdevueventmdev

前面在分析kset的時候,有一個屬性uevent_ops就是kobj_uevent_ops結構的。

filter,當任何kobject需要上報uevent時,它所屬的kset可以通過該接口過濾,阻止不希望上報的event,進而達到從整體上管理的目的。

name,該接口可以傳回kset的名稱。如果一個kset沒有合法的名稱,則其下的所有Kobject将不允許上報uvent

uevent,當任何kobject需要上報uevent時,它所屬的kset可以通過該接口統一為這些event添加環境變量。因為很多時候上報uevent時的環境變量都是相同的,是以可以由kset統一處理,就不需要讓每個kobject獨自添加了。

這些回調函數很簡單,不過他們的函數指針原型的定義可以關注一下,他們的原型是這樣的:

int (* const filter)(struct kset *kset, struct kobject *kobj);

const char *(* const name)(struct kset *kset, struct kobject *kobj);

int (* const uevent)(struct kset *kset, struct kobject *kobj,struct kobj_uevent_env *env);

這裡有一堆const,這些const是什麼意思,如果搞清楚了相信你對const就有更深入的認識了。算是思考題,原意深入思考的可以解答一下,順便說一下,面試c語言程式員經常會被問到const的。

kobject_action, kobj_uevent_env和kobj_uevent_ops的關系

當裝置加載或解除安裝時,是怎麼通過這幾個uevent的核心類通知使用者空間的呢?通過前面的分析,大家應該知道,裝置加載或解除安裝最直覺的展現在/sys下目錄的變化,/sys下的目錄和kobject是對應的,是以還得從kobject說起。

Linux裝置驅動模型架構分析(六)——LDDM的裝置管理:uevent,mdevueventmdev

這幾個類之間的關系比較簡單,而且很多都是通過方法産生依賴關系,類之間的關系比較弱。通過序列圖可以更清楚的看到類之間如何互動。

Linux裝置驅動模型架構分析(六)——LDDM的裝置管理:uevent,mdevueventmdev

uevent_helper常用環境變量

環境變量 說明
ACTION 對應kobject_action定義的kobject動作,不過是将枚舉轉換成了字元串
DEVPATH 被建立或删除的kobject在sysfs中的路徑
SEQNUM 熱插拔事件序列号,使程式可以區分熱插拔事件
SUBSYSTEM 描述子系統的字元串,與class中的name對應。

mdev

概述

熟悉linux驅動程式編寫的人都知道,需要在/dev下建立裝置檔案,但是如果用LDDM來寫驅動程式可能就看不到熟悉的mknod,modprobe等了,這些操作并非消失了,而是由其他機制代替人工做了。本章就這方面内容進行一個介紹。

大家都知道建立裝置節點的工作是在使用者空間進行的,為什麼不能由驅動直接建立呢?試想,如果建立裝置由驅動程式來做,驅動位于核心層,如果由其負責這個任務,那麼驅動就得知道它要建立的裝置名。簡單的字元驅動還好,如果是USB等可插拔的裝置,驅動怎麼知道自己要建立什麼裝置名呢?有人說可以寫明一套規則。确實如此,但如果把這套規則放到應用層,由應用程式開發人員去明确這個規則(mdev正是這樣做的),會不會更好?因為是應用程式直接程式設計通路這個裝置名對應的裝置驅動的。是以裝置驅動不應該直接負責裝置檔案的建立。

使用者層建立裝置檔案也有兩種方法:

一種方法就是使用者在shell中使用mknod指令建立裝置檔案,同時傳入裝置名和裝置号。這應該是大家最熟悉的一種方法,但是這種人工的做法,很不科學。它隻是一種示範的方法,不适于作為工程方法。

另外一種方法就是依賴裝置模型來輔助建立裝置檔案。這也是裝置模型的作用之一。

udev和mdev就是使用第二種方法依賴LDDM自動建立裝置檔案的機制。

udev是建構在linux的sysfs之上的,是一個使用者程式,它能夠根據系統中的硬體裝置的狀态動态更新裝置檔案。mdev是busybox自帶的一個簡化版的udev,它比udev占用的記憶體更小,是以更适合嵌入式系統的應用。

udev和mdev都依賴uevent機制,個人了解,udev使用netlink機制,mdev使用kmod機制。在分析kobj_uevent_env的argv成員是已經提到了,kmod最終會調用使用者程式,即uevent_helper處理uevent消息,mdev實際就是uevent_helper程式。通過:

echo /sbin/mdev > /sys/kernel/uevent_helper

實作使用使用mdev處理uevent的目的(有的資料說是将mdev加到/proc/sys/kernel/hotplug_helper,其實這兩個是一樣的,前面說過/sys改進了/proc,是以新的系統應該優先使用/sys)。

那麼問題又來了?

LDDM也是核心程式,為什麼就能包含裝置名呢?這不是與上面講的沖突嗎?既然講了這麼多LDDM的内容了,思考一下按照你的了解給出一個答案吧。

mdev原理

1、執行mdev -s指令時,mdev掃描/sys/block(塊裝置儲存在/sys/block目錄下,核心2.6.25版本以後,塊裝置也儲存在/sys /class/block目錄下。mdev掃描/sys/block是為了實作向後相容)和/sys/class兩個目錄下的dev屬性檔案,從該dev 屬性檔案中擷取到裝置編号(dev屬性檔案以”major:minor”形式儲存裝置編号),并以包含該dev屬性檔案的目錄名稱作為裝置名 device_name(即包含dev屬性檔案的目錄稱為device_name,而/sys/class和device_name之間的那部分目錄稱為 subsystem。也就是每個dev屬性檔案所在的路徑都可表示為/sys/class/subsystem/device_name/dev),在 /dev目錄下建立相應的裝置檔案。例如,cat /sys/class/tty/tty0/dev會得到4:0,subsystem為tty,device_name為tty0。

2、當mdev因uevnet事件(以前叫hotplug事件)被調用時,mdev通過由uevent事件傳遞給它的環境變量擷取到:引起該uevent 事件的裝置action及該裝置所在的路徑device path。

然後判斷引起該uevent事件的action是什麼。若該action是add,即有新裝置加入到系統中,不管該裝置是虛拟裝置還是實際實體裝置,mdev都會通過device path路徑下的dev屬性檔案擷取到裝置編号,然後以device path路徑最後一個目錄(即包含該dev屬性檔案的目錄)作為裝置名,在/dev目錄下建立相應的裝置檔案。若該action是remove,即裝置已從系統中移除,則删除/dev目錄下以device path路徑最後一個目錄名稱作為檔案名的裝置檔案。如果該action既不是add也不是remove,mdev則什麼都不做。

由上面可知,如果我們想在裝置加入到系統中或從系統中移除時,由mdev自動地建立和删除裝置檔案,那麼就必須做到以下三點:

1、在/sys/class 的某一subsystem目錄下,

2、建立一個以裝置名device_name作為名稱的目錄,

3、并且在該device_name目錄下還必須包含一個 dev屬性檔案,該dev屬性檔案以”major:minor\n”形式輸出裝置編号。

這裡就用到了LDDM的class相關知識,大家可以參看相關章節。

也就是說,大家判斷能不能使用mdev自動生成裝置節點,可以在加載完驅動後,到/class/dev下看看有沒有驅動主從裝置号的符号連結,如果有,證明此類裝置的核心已經處理了相關過程,如果沒有,若還想使用mdev動态生成裝置節點的話,則需要你在驅動程式中顯式的調用函數建立子系統節點。

用示意圖表示次過程就是:

Linux裝置驅動模型架構分析(六)——LDDM的裝置管理:uevent,mdevueventmdev

mdev使用

mdev最基本的使用是在啟動時或熱插拔時,生成/dev裝置節點,在啟動腳本中加入

mount -t proc proc /proc

mount -t sysfs sysfs /sys

echo /sbin/mdev > /proc/sys/kernel/hotplug

mdev -s

除了基本功能外mdev也提供了簡單的根據規則執行操作的能力,這需要編輯mdev的規則檔案/etc/mdev.conf,規則是這樣的:

<device regex> <uid>:<gid> <octal permissions> [<@$*><cmd>]

@ 建立節點後執行的

$ 删除節點前執行的

* 建立後和删除前都運作的

看到這,大家對于概述部分LDDM為什麼就可以包含裝置名稱的問題有沒有思路呢?