Linux早期時候,一個驅動對應一個設備,也就對應一個硬件地址,那當有兩個一樣的設備的時候,就要寫兩個驅動,顯然是不合理的。應該是從Linux2.5開始,就引入了device-bus-driver模型。 其中設備驅動模型主要結構分為kset、kobject、ktype。
kset是同類型kobject對象的集合,可以說是一個容器。
kobject是總線、驅動、設備的三種對象的一個基類,實現公共接口。
ktype,記錄了kobject對象的一些屬性。
設備驅動模型的核心即是kobject,是為了管理日益增多的設備,使得設備在底層都具體統一的接口。他與sysfs文件系統緊密相連,每個注冊的kobject都對應sysfs文件系統中的一個目錄。為了直觀管理,統一存放的路徑,使用了kset。但是僅僅有這些目錄沒有意義,這兩個結構體只能表示出設備的層次關系,所以基本不單獨使用,會嵌入到更大的結構體中,(如希望在驅動目錄下能看到掛在該總線上的各種驅動,而在設備目錄下能看到掛在該總線的各種設備,就將kobject嵌入到描述設備以及驅動的結構體中,這樣每次注冊設備或驅動,都會在sys目錄下有描述)
放上一個經典的圖:
這個圖其實還漏了一個ktype,kobject都應該包含一個ktype。
Linux設備模型的目的是:為內核建立起一個統一的設備模型,從而有一個對系統結構的一般性抽象描述。
我們可以先看下一個小的測試程序:
可以看到,我們在使用kobject、kset、ktype結構,就在sysfs虛擬文件系統下創建(通過kset_create_and_add和kobject_init_and_add函數)了一些子目錄(kobject_test)和屬性文件。kset和kobject都可以創建出目錄,但是kset的目錄下存放kobject目錄,kobject下存放屬性文件(可以對屬性文件進行讀寫操作,如上圖name屬性文件,而且kobject目錄下也可以存放kobject目錄,只需parent指向它即可)。
這個小程序沒看懂?沒關系,先看下面的分析:(看到文章末尾再回頭看這個程序,你會有更直觀的理解)
我們對著Linux kernel源碼分析下,可以下看看三個結構體的成員:
其實說到設備驅動模型,很容易想到platform,下面我們就來具體分析這個吧:
init/main.c里:
這是driver_init函數:
我們看下devices_init函數:
這里面調用kset_create_and_add創建kset并返回給devices_kset,注意這里的devices_kset,可以說是/sys下最大的boss之一了,所有的物理設備都會在device目錄下管理,/sys/device/目錄是內核對系統中所有設備的分層次表達模型,保存了系統所有的設備。
然后調用kobject_create_and_add函數在/sys/目錄下創建dev目錄,/sys/dev目錄下維護一個按照字符設備和塊設備的主次號碼(major:minor)鏈接到真是設備(/sys/devices)的符號鏈接文件,應用程序通過對這些文件的讀寫和控制,可以訪問實際的設備。
最后再以dev_kobj為父節點,在/sys/dev/目錄下創建block和char目錄。
這里我們先看kobject_create_and_add函數,再分析kset_create_and_add函數:
其實里面函數也沒啥,先創建kobject,初始化它,再添加,沒啥好說的。
倒是除了kobject_create_and_add函數,還有一個類似的函數:kobject_init_and_add。
kobject_init_and_add傳入一個kobject指針和kobj_type指針,然后進行初始化
kobject_create_and_add創建一個kobject變量,并返回其指針,它不用傳入kobj_type指針
在kset_create_and_add函數里也會用到kobject,所以我們現在來分析下kset_create_and_add函數:
里面就是具體的創建和注冊kset了。
先說創建函數:
staticstructkset *kset_create(constchar*name,
conststructkset_uevent_ops *uevent_ops,
structkobject *parent_kobj)
{
structkset *kset;
intretval;
kset = kzalloc(sizeof(*kset), GFP_KERNEL);//分配kset空間
if(!kset)
returnNULL;//失敗就返回
retval = kobject_set_name(&kset->kobj, "%s", name);//設置kset的名字,也即內嵌kobject的名字
if(retval) {
kfree(kset);
returnNULL;
}
kset->uevent_ops = uevent_ops;//kset屬性操作
kset->kobj.parent= parent_kobj;//設置其parent
kset->kobj.ktype= &kset_ktype;//ktype指定為kset_ktype
kset->kobj.kset= NULL;
returnkset;
}
可以看出kset_create函數內容為:
1)調用kobject_set_name函數設置kobject的名稱2)設置kobject的uevent_ops、parent為傳入的形參uevent_ops、parent_kobj3)設置kobject的ktype為系統定義好的ktype變量4)設置kobject的所屬kset為NULL,意思是kobject所屬的kset就是kset本身,因為kset結構體包含了一個kobject成員。
這里需要一個注意的,就是ktype 這個結構,即kset_ktype:
這里填充了一個釋放函數,每個kobject必須有一個釋放函數,并且這個kobject必須保持直到這個釋放函數被調用到。如果這個條件不能被滿足,則這個代碼是有缺陷的。注意,假如你忘了提供釋放函數,內核會提出警告的;不要嘗試提供一個空的釋放函數來消除這個警告,你會收到kobject維護者的無情嘲笑。
至于kobj_sysfs_ops,則是關于讀寫操作相關的操作集:
讀文件時,會調用到.show的回調函數。寫文件時,會調用到.store的回調函數。
看完了創建函數,接下來是注冊函數:
kset_init函數主要是對kset初始化,會將初始化引用計數器(即kobj->kref)為1(當計數器引用計數沒到0之前不可以被釋放)。接著初始化entry鏈表結點,用于與所屬的kset的list成員組成鏈表(INIT_LIST_HEAD(&kobj->entry)),以及一些參數的賦值。最后,還初始化以list成員為頭結點的鏈表,它和子kobject的entry成員組成鏈表(INIT_LIST_HEAD(&k->list))。
kobject_add_internal函數就是關鍵的kobject函數了:
static intkobject_add_internal(struct kobject *kobj)
{
interror= 0;
struct kobject *parent;
if(!kobj)
return-ENOENT;
if(!kobj->name || !kobj->name[0]) {//如果kobject的名字為空.退出
WARN(1, "kobject: (%p): attempted to be registered with empty "
"name!\n", kobj);
return-EINVAL;
}
parent= kobject_get(kobj->parent);//如果kobj-parent為真,則增加kobj->kref計數,即父節點的引用計數
/* join kset if set, use it as parent if we do not already have one */
if(kobj->kset) {
if(!parent)
parent= kobject_get(&kobj->kset->kobj);//如果parent父節點為NULL那么就用kobj->kset->kobj作其父節點,并增加其引用計數
kobj_kset_join(kobj);//把kobj的entry成員添加到kobj->kset>list的尾部,現在的層次就是kobj->kset->list指向kobj->entry
kobj->parent= parent;
}
/*刪除了部分調試內容*/
error= create_dir(kobj);//利用kobj創建目錄和屬性文件,其中會判斷,如果parent為NULL那么就在sysfs_root_kn下創建
if(error) {
/*刪除了部分內容*/
} else
kobj->state_in_sysfs = 1;//如果創建成功。將state_in_sysfs建為1。表示該object已經在sysfs中了
returnerror;
}
kobject_add_internal函數內容在注釋里都寫好了,可以概括為:
1)如果kobject的parent成員為NULL,則把它指向kset的kobject成員。2)如果kobject的kset成員不為NULL,它會調用kobj_kset_join函數把kobject的entry成員添加到kset的list鏈表中3)最后調用create_dir函數創建sys目錄
注冊函數里最后一個調用就是kobject_uevent函數了,應該是關于熱拔插機制的,這不是我們現在關心的內容。
好了,經過上面的折騰,就會在/sys/目錄下建立一個devices目錄。
接下來繼續回到文章開頭進入到的devices_init函數:
我們之前分析的是devices_init函數,其實接下來幾個函數都是一樣的,在/sys/目錄下創建各個目錄。
只需要記住devices_kset對應/sys/devices目錄bus_kset對應/sys/bus目錄devices_kset對應/sys/devices目錄system_kset對應/sys/devices/system目錄class_kset對應/sys/class目錄firmware_kobj對應/sys/firmware目錄hypervisor_kobj對應/sys/hypervisor目錄
接下來看下platform_bus_init函數
也就是我們之前用的platform總線了!!
在driver/base/platform.c文件:
這里,device_register就是在/sys/device/目錄下創建platform
其實也就包含兩個函數,一個初始化,一個添加:
voiddevice_initialize(struct device *dev)
{
dev->kobj.kset =devices_kset;//設置設備的kobject所屬集合,devices_kset即對應/sys/devices/
kobject_init(&dev->kobj, &device_ktype);//初始化設備的kobject
INIT_LIST_HEAD(&dev->dma_pools);//初始化設備的DMA池,用于傳遞大數據
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);//初始化自旋鎖,用于同步子設備鏈表
INIT_LIST_HEAD(&dev->devres_head);//初始化子設備鏈表頭
device_pm_init(dev);
set_dev_node(dev, -1);#ifdefCONFIG_GENERIC_MSI_IRQ
INIT_LIST_HEAD(&dev->msi_list);#endif
}
注釋都寫好了,看下device_add函數:
int device_add(struct device *dev)
{
struct device *parent=NULL;
struct kobject *kobj;
struct class_interface *class_intf;
int error =-EINVAL;
struct kobject *glue_dir =NULL;
dev =get_device(dev);//增加設備的kobject的引用計數
if(!dev)
goto done;
if(!dev->p) {
error =device_private_init(dev);//初始化dev的私有成員,及其鏈表操作函數
if(error)
goto done;
}
if(dev->init_name) {//保存設備名,以后需要獲取時使用dev_name函數獲取
dev_set_name(dev, "%s", dev->init_name);
dev->init_name =NULL;
}
/* subsystems can specify simple device enumeration */
if(!dev_name(dev) &&dev->bus &&dev->bus->dev_name)
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
if(!dev_name(dev)) {
error =-EINVAL;
goto name_error;
}
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
parent=get_device(dev->parent);//返回父節點,增加父節點引用計數,如果沒有返回NULL
kobj =get_device_parent(dev, parent);//以上層devices為準重設dev->kobj.parent
if(kobj)
dev->kobj.parent=kobj;
/* use parent numa_node */
if(parent&&(dev_to_node(dev) ==NUMA_NO_NODE))
set_dev_node(dev, dev_to_node(parent));
/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
error =kobject_add(&dev->kobj, dev->kobj.parent, NULL);//設置dev->kobj的名字和父對象,并建立相應目錄
if(error) {
glue_dir =get_glue_dir(dev);
goto Error;
}
/* notify platform of device entry */
if(platform_notify)
platform_notify(dev);
error =device_create_file(dev, &dev_attr_uevent);//建立uevent屬性文件
if(error)
goto attrError;
error =device_add_class_symlinks(dev);
if(error)
goto SymlinkError;
error =device_add_attrs(dev);
if(error)
goto AttrsError;
error =bus_add_device(dev);
if(error)
goto BusError;
error =dpm_sysfs_add(dev);
if(error)
goto DPMError;
device_pm_add(dev);
if(MAJOR(dev->devt)) {
error =device_create_file(dev, &dev_attr_dev);//在sys下產生dev屬性文件
if(error)
goto DevAttrError;
error =device_create_sys_dev_entry(dev);//在/sys/dev目錄建立對設備的軟鏈接
if(error)
goto SysEntryError;
devtmpfs_create_node(dev);
}
/* Notify clients of device addition. This call must come
* after dpm_sysfs_add() and before kobject_uevent().
*/
if(dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
kobject_uevent(&dev->kobj, KOBJ_ADD);//向用戶空間發出KOBJ_ADD 事件
bus_probe_device(dev);//檢測驅動中有無適合的設備進行匹配,現在只添加了設備,還沒有加載驅動,所以不會進行匹配
if(parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);//把該設備的節點掛到其父節點的鏈表
if(dev->class) {
mutex_lock(&dev->class->p->mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
&dev->class->p->klist_devices);
/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->interfaces, node)
if(class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->mutex);
}
/*省略部分error內容*/
}
device_add函數是比較重要的,注釋基本都寫好了,可以概括為:
1)增加kobj->kref計數2)初始化dev的私有成員3)設置設備名稱4)增加父節點引用計數5)將dev->kobj添加到dev->kobj.parent對應目錄下6)dev->kobj下創建屬性文件7)在/sys/dev目錄建立對設備的軟鏈接8)驅動檢測
其中,驅動檢測函數:bus_probe_device可以自行百度一下。
最后,我們接著看 bus_register(&platform_bus_type);
今天篇幅有點長了,函數就寫點重要的即可:
再次強調:
priv->subsys.kobj.kset = bus_kset;priv->subsys.kobj.ktype = &bus_ktype;
這里設置了所屬的kset和ktype。ktype結構體里包含了sysfs_ops結構體,里面就是對文件的讀寫操作:
最后,bus_register函數里還調用了kset_create_and_add函數在/sys/platform/目錄下創建devices和drivers目錄,里面存放我們platform平臺下注冊的設備和驅動。
好了,到此,我們就來再次小小歸納下 :
在kset下還可能會有更深的kset
kset包含一個或多個kobject,方便管理
kobject并不一定需要kset
kobject下有屬性文件,·向用戶層提供了表示和操作這個 kobject 的屬性特征的接口
kobject 下還有一些符號鏈接文件,指向其它的 kobject
現在,是不是對設備驅動模型有了更為直觀的認識?現在回頭看看文章開頭的小程序,是不是輕而易舉的理解了呢?
-
Linux
+關注
關注
87文章
11312瀏覽量
209705 -
設備驅動
+關注
關注
0文章
68瀏覽量
10897
原文標題:面試如果被問到Linux設備驅動模型怎么答?看完這篇就能給出滿意答案了
文章出處:【微信號:gh_c472c2199c88,微信公眾號:嵌入式微處理器】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論