Linux字元裝置驅動模型--字元裝置的註冊
當我們編寫字元裝置驅動程式的時候,在進行字元裝置物件cdev的分配、初始化,裝置號的註冊這些初始化階段之後,就可以將它加入到系統中,這樣才能使用這個字元裝置。將一個字元裝置加入到系統中呼叫的函式時cdev_add,核心原始碼如下:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
其中,p為要加入到系統中的字元裝置物件的指標(是分配好且初始化好的),dev為改裝置的裝置號,count為從裝置的個數。
cdev_add的核心功能是通過kobj_map函式實現,後者是通過全域性變數cdev_map來把裝置p加入到其中的雜湊表中。
struct kobj_map的定義如下:
struct kobj_map {
struct probe {
struct probe *next;//下一個節點
dev_t dev;//裝置號
unsigned long range;//次裝置個數
struct module *owner;
kobj_probe_t *get ;
int (*lock)(dev_t, void *);
void *data;//私有資料
} *probes[255];//255個元素的指標陣列
struct mutex *lock;
};
一下為kobj_map函式的原始碼:
int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
struct module *module, kobj_probe_t *probe,
int (*lock)(dev_t, void *) , void *data)
{
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
unsigned index = MAJOR(dev);
unsigned i;
struct probe *p;
if (n > 255)
n = 255;
p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
for (i = 0; i < n; i++, p++) {
p->owner = module;
p->get = probe;
p->lock = lock;
p->dev = dev;
p->range = range;
p->data = data;
}
mutex_lock(domain->lock);
for (i = 0, p -= n; i < n; i++, p++, index++) {
struct probe **s = &domain->probes[index % 255];
while (*s && (*s)->range < range)
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
}
kobj_map函式中雜湊表的實現原理和之前介紹的裝置號的註冊分配幾乎完全一樣,通過要加入系統的裝置的主裝置號major(major=MAJOR(dev))來獲得probes陣列的索引值i(i=major%255),然後把一個型別為struct probe的節點物件加入到probe[i]所管理的連結串列中,如下圖:
其中,struct probe 所在的矩形塊中的深色部分是我們重點關係的內容,記錄了當前正在加入系統的字元裝置物件的有關資訊。dev為他的裝置號,range是次裝置的個數,data是void*型別的指標,指向當前正要加入系統的裝置物件指標p。上圖中展示的是兩個滿足主裝置號major%255=2的字元裝置通過cdev_add之後kobj_map所展現出來的資料結構狀態。
總結來說,裝置驅動程式通過呼叫cdev_add把它管理的裝置物件的指標嵌入到一個型別為struct probe的節點之後,然後在把該節點加入到cdev_map所實現的雜湊連結串列中。
對於系統而言,當系統成功呼叫了cdev_add之後,就意味著一個字元裝置物件已經加入到系統,在需要的時候,系統就可以找到它。而對於使用者態的程式而言,cdev_add呼叫之後,就可以通過檔案系統介面呼叫到我們的驅動程式。
最後,介紹下與cdev_add相對的cdev_del,這個函式的作用是:在cdev_add中我們動態分配了struct probe型別的節點,那麼當對應的裝置從系統中移除時,利用此函式將它們從連結串列中刪除並釋放節點所佔用的記憶體空間。
原始碼如下:
cdev_del:
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);
}
此函式呼叫cdev_unmap,原始碼如下:
static void cdev_unmap(dev_t dev, unsigned count)
{
kobj_unmap(cdev_map, dev, count);
}
呼叫的kobj_unmap原始碼如下:
void kobj_unmap(struct kobj_map *domain, dev_t dev, unsigned long range)
{
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
unsigned index = MAJOR(dev);
unsigned i;
struct probe *found = NULL;
if (n > 255)
n = 255;
mutex_lock(domain->lock);
for (i = 0; i < n; i++, index++) {
struct probe **s;
for (s = &domain->probes[index % 255]; *s; s = &(*s)->next) {
struct probe *p = *s;
if (p->dev == dev && p->range == range) {
*s = p->next;
if (!found)
found = p;
break;
}
}
}
mutex_unlock(domain->lock);
kfree(found);
}
對於以核心模組形式存在的驅動程式,作為通用的規則,模組的解除安裝函式應該負責呼叫這個函式來將鎖管理的裝置物件從系統之移除。