三、uevent
1. uevent的功能
- uevent是kobject的一部分,用于在kobject狀态發生改變時(增加、移除等),通知使用者空間程式。使用者空間程式收到這樣的事件後,會做相應的處理。該機制通常是用來支援熱拔插裝置的,例如U盤插入後,USB相關的驅動軟體會動态建立用于表示該U盤的device結構(相應的也包括其中的kobject),并告知使用者空間程式,為該U盤動态的建立/dev/目錄下的裝置節點,更進一步,可以通知其它的應用程式,将該U盤裝置mount到系統中,進而動态的支援該裝置。
2. uevent在kernel中的位置
下面圖檔描述了uevent子產品在核心中的位置:
由此可知,uevent的機制是比較簡單的,裝置模型中任何裝置有事件需要上報時,會觸發uevent提供的接口。uevent子產品準備好上報事件的格式後,可以通過兩個途徑把事件上報到使用者空間:一種是通過kmod子產品,直接調用使用者空間的可執行檔案;另一種是通過netlink通信機制,将事件從核心空間傳遞給使用者空間。有關kmod和netlink,會在其它文章中描述,是以本文就不再詳細說明了。
3. uevent的内部邏輯解析
3.1 源代碼的位置
uevent的代碼比較簡單,主要涉及kobject.h和kobject_uevent.c兩個檔案,如下:
- include/linux/kobject.h
- lib/kobject_uevent.c
3.2 資料結構描述
kobject.h定義了uevent相關的常量和資料結構,如下:
- kobject_action
/* include/linux/kobject.h, line 50 */
enum kobject_action {
KOBJ_ADD,
KOBJ_REMOVE,
KOBJ_CHANGE,
KOBJ_MOVE,
KOBJ_ONLINE,
KOBJ_OFFLINE,
KOBJ_MAX
};
- kobject_action定義了event的類型,包括:
ADD/REMOVE: kobject(或上層資料結構)的添加/移除事件。
ONLINE/OFFLINE:kobject(或上層資料結構)的上線/下線事件,即是否使能。
CHANGE: kobject(或上層資料結構)的狀态或者内容發生改變。如果裝置驅動需要上報的事件不再上面事件的範圍内,或者是自定義的事件,可以使用該event,并攜帶相應的參數。
MOVE: kobject(或上層資料結構)更改名稱或者更改parent(意味着在sysfs中更改了目錄結構)。
MAX: 事件種類的最大值
- kobj_uevent_env
/* include/linux/kobject.h, line 31 */
#define UEVENT_NUM_ENVP 32 /* number of env pointers */
#define UEVENT_BUFFER_SIZE 2048 /* buffer for the variables */
/* include/linux/kobject.h, line 116 */
struct kobj_uevent_env {
char *envp[UEVENT_NUM_ENVP];
int envp_idx;
char buf[UEVENT_BUFFER_SIZE];
int buflen;
};
envp,指針數組,用于儲存每個環境變量的位址,最多可支援的環境變量數量為UEVENT_NUM_ENVP。
envp_idx,用于通路環境變量指針數組的index。
buf,儲存環境變量的buffer,最大為UEVENT_BUFFER_SIZE。
buflen,通路buf的變量。
- 前面有提到過,在利用kmod向使用者空間上報event事件時,會直接執行使用者空間的可執行檔案。而在Linux系統,可執行檔案的執行,依賴于環境變量,是以kobj_uevent_env用于組織此次事件上報時的環境變量。
- kset_uevent_ops
/* include/linux/kobject.h, line 123 */
struct kset_uevent_ops {
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);
};
- kset_uevent_ops是為kset量身訂做的一個資料結構,裡面包含filter和uevent兩個回調函數,用處如下:
- filter:當任何kobject需要上報uevent時,它所屬的kset可以通過該接口過濾,阻止(過濾掉)不希望上報的event,進而達到從整體上管理的目的。
- uevent:當任何kobject需要上報uevent時,它所屬的kset可以通過該接口統一為這些event添加環境變量。因為很多時候上報uevent時的環境變量都是相同的,是以可以由kset統一處理,就不需要讓每個kobject獨自添加了。
- name:該接口可以傳回kset的名稱。如果一個kset沒有合法的名稱,則其下的所有kobject将不允許上報uevent。
3.3 内部接口
- 通過
,uevent子產品提供了如下的API(這些API的實作是在kobject.h
檔案中):lib/kobject\_uevent.c
/* include/linux/kobject.h, line 206 */
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp[]);
int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...);
int kobject_action_type(const char *buf, size_t count, enum kobject_action *type);
3.3.1 kobject_uevent_env
-
函數以envp為環境變量,上報一個指定action的uevent。環境變量的作用是為執行使用者空間程式指定運作環境。具體動作如下:kobject_uevent_env
- 查找kobj本身或者其parent是否從屬于某個kset,如果不是,則報錯傳回(如果一個kobject沒有加入kset,是不允許上報uevent的);
- 檢視kobj->uevent_suppress是否設定,如果設定,則忽略所有的uevent上報并傳回(可以通過kobject的uevent_suppress标志,管控kobject的uevent的上報);
- 如果所屬的kset有
函數,則調用該函數,過濾此次上報(kset可以通過filter接口過濾不希望上報的event,進而達到整體的管理效果);uevent_ops->filter
- 判斷所屬的kset是否有合法的名稱(稱作subsystem,和前期的核心版本有差別),否則不允許上報uevent;
- 配置設定一個用于此次上報的、存儲環境變量的buffer(結果儲存在env指針中),并獲得該kobject在sysfs中的路徑資訊(使用者空間軟體需要依據該路徑資訊在sysfs中通路它);
- 調用add_uevent_var接口(下面會介紹),将action、路徑資訊、subsystem等資訊,添加到env指針中;
- 如果傳入的envp不空,則解析傳入的環境變量,同樣調用add_uevent_var接口,添加到env指針中;
- 如果所屬的kset存在uevent_ops->uevent接口,調用該接口,添加kset統一的環境變量到env指針;
- 根據ACTION的類型,設定kobj->state_add_uevent_sent和kobj->state_remove_uevent_sent變量,以記錄正确的狀态;
- 調用add_uevent_var接口,添加格式為"SEQNUM=%llu”的序列号;
- 如果定義了"CONFIG_NET”,則使用netlink發送該uevent;
- 以uevent_helper、subsystem以及添加了标準環境變量(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指針為參數,調用kmod子產品提供的
函數,上報uevent。call_usermodehelper
-
的内容是由核心配置項CONFIG_UEVENT_HELPER_PATH(位于./drivers/base/Kconfig)決定的(可參考lib/kobject_uevent.c, line 32),該配置項指定了一個使用者空間程式(或者腳本),用于解析上報的uevent,例如"/sbin/hotplug”。uevent_helper
-
的作用,就是fork一個程序,以uevent為參數,執行uevent_helper。call_usermodehelper
3.3.2 kobject_uevent
-
,和kobject_uevent_env功能一樣,隻是沒有指定任何的環境變量。kobject_uevent
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
}
3.3.3 add_uevent_var
-
以格式化字元的形式(類似printf、printk等),将環境變量copy到env指針中。add_uevent_var
3.3.4 kobject_action_type
-
将enum kobject_action類型的action轉換為字元串。kobject_action_type
- 注意:怎麼指定處理uevent的使用者空間程式(簡稱uevent helper)?
- 上面介紹kobject_uevent_env的内部動作時,有提到,uevent子產品通過Kmod上報uevent時,會通過call_usermodehelper函數,調用使用者空間的可執行檔案(或者腳本,簡稱*uevent helper* )處理該event。而該uevent helper的路徑儲存在**uevent_helper數組中。
- 可以在編譯核心時,通過CONFIG_UEVENT_HELPER_PATH配置項,靜态指定uevent helper。但這種方式會為每個event fork一個程序,随着核心支援的裝置數量的增多,這種方式在系統啟動時将會是緻命的(可以導緻記憶體溢出等)。是以隻有在早期的核心版本中會使用這種方式,現在核心不再推薦使用該方式。是以核心編譯時,需要把該配置項留白。
- 在系統啟動後,大部分的裝置已經ready,可以根據需要,重新指定一個uevent helper,以便檢測系統運作過程中的熱拔插事件。這可以通過把helper的路徑寫入到
檔案中實作。實際上,核心通過sysfs檔案系統的形式,将uevent_helper數組開放到使用者空間,供使用者空間程式修改通路,具體可參考/sys/kernel/uevent_helper
中相應的代碼,這裡不再較長的描述。./kernel/ksysfs.c
四、sysfs
1. 前言
sysfs是一個基于RAM的檔案系統,它和kobject一起,可以将kernel的資料結構導出到使用者空間,以檔案目錄結構的形式,提供對這些資料結構(以及資料結構的屬性)的通路支援。sysfs是一種表示核心對象、對象屬性,以及對象關系的一種機制,一般核心對象、屬性、以及對象關系組織成樹狀形式。其中核心對象被映射為使用者态的目錄;對象屬性被映射為使用者态的檔案,檔案在目錄下;對象關系被映射成使用者空間的符号連結。
sysfs具備檔案系統的所有屬性,而本文主要側重其裝置模型的特性,是以不會涉及過多的檔案系統實作細節,而隻介紹sysfs在Linux裝置模型中的作用和使用方法。具體包括:
- sysfs和kobject的關系
- attribute的概念
- sysfs的檔案系統操作接口
2. sysfs和kobject的關系
在前面有提到過,每一個kobject,都會對應sysfs中的一個目錄。是以在将kobject添加到kernel時,
create_dir
函數會調用sysfs檔案系統的建立目錄接口,建立和kobject對應的目錄,相關的代碼如下:
/* lib/kobject.c, line 47 */
static int create_dir(struct kobject *kobj)
{
int error = 0;
if (kobject_name(kobj)) {
error = sysfs_create_dir(kobj);
if (!error) {
error = populate_dir(kobj);
if (error)
sysfs_remove_dir(kobj);
}
}
return error;
}
- 這個函數接受唯一的一個參數,也就是要為其建立目錄的kobject的位址。它完成如下操作:
- 檢查傳遞進來kobject對象的name的有效性,若無效,則傳回0。
- 若有效,則調用sysfs_create_dir(kobj)來在sysfs中建立目錄。
/* fs/sysfs/dir.c, line 736 */
/*
* sysfs_create_dir - create a directory for an object.
*/
int sysfs_create_dir(struct kobject * kobj)
{
enum kobj_ns_type type;
struct sysfs_dirent *parent_sd, *sd;
const void *ns = NULL;
int error = 0;
BUG_ON(!kobj);
if (kobj->parent)
parent_sd = kobj->parent->sd;
else
parent_sd = &sysfs_root;
error = create_dir(kobj, parent_sd, kobject_name(kobj), &sd);
if (!error)
kobj->sd = sd;
return error;
}
- 這個函數首先會找到要為其建立目錄的kobject的sysfs_dirent對象的父sysfs_dirent,通常是由父kobject的sd字段所指向。若父kobject為NULL,則設為sysfs根目錄的sysfs_dirent。
- 然後調用create_dir(kobj, parent_sd, kobject_name(kobj) , &sd)來建立目錄。
- 最後,若成功,設定kobject的sd字段指向為其新建立的sysfs_dirent,并傳回0。若失敗則傳回錯誤碼。
3. attribute
3.1 attribute的功能概述
在sysfs中,為什麼會有attribute的概念呢?其實它是對應kobject而言的,指的是kobject的屬性。我們知道,sysfs中的目錄描述了kobject,而kobject是特定資料類型變量(如struct device)的展現。是以kobject的屬性,就是這些變量的屬性。它可以是任何東西,名稱、一個内部變量、一個字元串等等。而attribute,在sysfs檔案系統中是以檔案的形式提供的,即:kobject的所有屬性,都在它對應的sysfs目錄下以檔案的形式呈現。這些檔案一般是可讀寫的,而kernel中定義了這些屬性的子產品,會根據使用者空間的讀寫操作,記錄和傳回這些attribute的值。
總結一下:所謂的attibute,就是核心空間和使用者空間進行資訊互動的一種方法。例如某個驅動定義了一個變量,卻希望使用者空間程式可以修改該變量,以控制driver的運作行為,那麼就可以将該變量以sysfs attribute的形式開放出來。
Linux核心中,attribute分為普通的attribute和二進制attribute,如下:
/* include/linux/sysfs.h, line 26 */
struct attribute
{
const char *name;
umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1;
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
/* include/linux/sysfs.h, line 100 */
struct bin_attribute {
struct attribute attr;
size_t size;
void *private;
ssize_t (*read)(struct file *, struct kobject *, struct bin_attribute *, char *, loff_t, size_t);
ssize_t (*write)(struct file *,struct kobject *, struct bin_attribute *, char *, loff_t, size_t);
int (*mmap)(struct file *, struct kobject *, struct bin_attribute *attr, struct vm_area_struct *vma);
};
struct attribute為普通的attribute,使用該attribute生成的sysfs檔案,隻能用字元串的形式讀寫(後面會說為什麼)。而struct bin_attribute在struct attribute的基礎上,增加了read、write等函數,是以它所生成的sysfs檔案可以用任何方式讀寫。
說完基本概念,我們要問兩個問題:
kernel怎麼把attribute變成sysfs中的檔案呢?
使用者空間對sysfs的檔案進行的讀寫操作,怎麼傳遞給kernel呢?
下面來看看這個過程。
3.2 attibute檔案的建立
在linux核心中,attibute檔案的建立是由fs/sysfs/file.c中
sysfs_create_file
接口完成的,該接口的實作沒有什麼特殊之處,大多是檔案系統相關的操作,和裝置模型沒有太多的關系,這裡先略過不提。
3.3 attibute檔案的read和write
看到3.1章節struct attribute的原型時,也許我們會犯嘀咕,該結構很簡單啊,name表示檔案名稱,mode表示檔案模式,其它的字段都是核心用于debug kernel Lock的,那檔案操作的接口在哪裡呢?
不着急,我們去fs/sysfs目錄下看看sysfs相關的代碼邏輯。
所有的檔案系統,都會定義一個
struct file_operations
變量,用于描述本檔案系統的操作接口,sysfs也不例外:
/* fs/sysfs/file.c, line 472 */
const struct file_operations sysfs_file_operations = {
.read = sysfs_read_file,
.write = sysfs_write_file,
.llseek = generic_file_llseek,
.open = sysfs_open_file,
.poll = sysfs_poll,
};
attribute檔案的read操作,會由VFS轉到sysfs_file_operations的read(也就是sysfs_read_file)接口上,讓我們大概看一下該接口的處理邏輯。
/* fs/sysfs/file.c, line 127 */
static ssize_t sysfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
struct sysfs_buffer *buffer = file->private_data;
ssize_t retval = 0;
mutex_lock(&buffer->mutex);
if (buffer->needs_read_fill || *ppos == 0) {
retval = fill_read_buffer(file->f_path.dentry, buffer);
if (retval)
goto out;
}
...
}
/* fs/sysfs/file.c, line 67 */
static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer)
{
struct sysfs_dirent *attr_sd = dentry->d_fsdata;
struct kobject *kobj = attr_sd->s_parent->s_dir.kobj;
const struct sysfs_ops * ops = buffer->ops;
...
count = ops->show(kobj, attr_sd->s_attr.attr, buffer->page);
...
}
read處理看着很簡單,sysfs_read_file從file指針中取一個私有指針,轉換為一個struct sysfs_buffer類型的指針,以此為參數(buffer),轉身就調用fill_read_buffer接口。而fill_read_buffer接口,直接從buffer指針中取出一個struct sysfs_ops指針,調用該指針的show函數,即完成了檔案的read操作。
那麼後續呢?當然是由ops->show接口接着處理咯。而具體怎麼處理,就是其它子產品(例如某個driver)的事了,sysfs不再關心(其實,Linux大多的核心代碼,都是隻提供架構和機制,具體的實作,也就是苦力,留給那些碼農吧!這就是設計的魅力)。
不過還沒完,這個struct sysfs_ops指針哪來的?好吧,我們再看看open(sysfs_open_file)接口吧。
/* fs/sysfs/file.c, line 326 */
static int sysfs_open_file(struct inode *inode, struct file *file)
{
struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
struct kobject *kobj = attr_sd->s_parent->s_dir.kobj;
struct sysfs_buffer *buffer;
const struct sysfs_ops *ops;
int error = -EACCES;
/* need attr_sd for attr and ops, its parent for kobj */
if (!sysfs_get_active(attr_sd))
return -ENODEV;
/* every kobject with an attribute needs a ktype assigned */
if (kobj->ktype && kobj->ktype->sysfs_ops)
ops = kobj->ktype->sysfs_ops;
else {
WARN(1, KERN_ERR "missing sysfs attribute operations for kobject: %s\n", kobject_name(kobj));
goto err_out;
}
...
buffer = kzalloc(sizeof(struct sysfs_buffer), GFP_KERNEL);
if (!buffer)
goto err_out;
mutex_init(&buffer->mutex);
buffer->needs_read_fill = 1;
buffer->ops = ops;
file->private_data = buffer;
...
}
原來和ktype有關系。這個指針是從該attribute所從屬的kobject中拿的。再去看一下ktype的定義,裡面有一個struct sysfs_ops的指針。
注意:通過注釋“every kobject with an attribute needs a ktype assigned”以及其後代碼邏輯可知,如果從屬的kobject(就是attribute檔案所在的目錄)沒有ktype,或者沒有ktype->sysfs_ops指針,是不允許它注冊任何attribute的!
經過确認後,sysfs_open_file從ktype中取出struct sysfs_ops指針,并在随後的代碼邏輯中,配置設定一個struct sysfs_buffer類型的指針(buffer),并把struct sysfs_ops指針儲存在其中,随後把buffer指針交給file的private_data,随後read/write等接口便可以取出使用。這是核心中的常用方法!
順便看一下struct sysfs_ops吧,我想你已經能夠猜到了。
/* include/linux/sysfs.h, line 124 */
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *,char *);
ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t);
const void *(*namespace)(struct kobject *, const struct attribute *);
};
attribute檔案的write過程和read類似,這裡就不再多說。另外,上面隻分析了普通attribute的邏輯,而二進制類型的呢?也類似,去看看fs/sysfs/bin.c吧,這裡也不說了。
講到這裡,應該已經結束了,事實卻不是如此。上面read/write的資料流,隻到kobject(也就是目錄)級别哦,而真正需要操作的是attribute(檔案)啊!這中間一定還有一層轉換!确實,不過又交給其它子產品了。 下面我們通過一個例子,來說明如何轉換的。
4. sysfs在裝置模型中的應用總結
讓我們通過裝置模型class.c中有關sysfs的實作,來總結一下sysfs的應用方式。
首先,在class.c中,定義了class所需的ktype以及sysfs_ops類型的變量,如下:
/* drivers/base/class.c, line 86 */
static const struct sysfs_ops class_sysfs_ops = {
.show = class_attr_show,
.store = class_attr_store,
.namespace = class_attr_namespace,
};
static struct kobj_type class_ktype = {
.sysfs_ops = &class_sysfs_ops,
.release = class_release,
.child_ns_type = class_child_ns_type,
};
由前面章節的描述可知,所有class_type的kobject下面的attribute檔案的讀寫操作,都會交給class_attr_show和class_attr_store兩個接口處理。以class_attr_show為例:
/* drivers/base/class.c, line 24 */
#define to_class_attr(_attr) container_of(_attr, struct class_attribute, attr)
static ssize_t class_attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
struct class_attribute *class_attr = to_class_attr(attr);
struct subsys_private *cp = to_subsys_private(kobj);
ssize_t ret = -EIO;
if (class_attr->show)
ret = class_attr->show(cp->class, class_attr, buf);
return ret;
}
/* include/linux/device.h, line 399 */
struct class_attribute {
struct attribute attr;
ssize_t (*show)(struct class *class, struct class_attribute *attr,char *buf);
ssize_t (*store)(struct class *class, struct class_attribute *attr, const char *buf, size_t count);
const void *(*namespace)(struct class *class, const struct class_attribute *attr);
};