linux驅動---字元裝置的註冊register_chrdev說
首先我們在註冊函式裡面呼叫了register_chrdev(MEM_MAJOR,"mem",&memory_fops),向核心註冊了一個字元裝置。
第一個引數是主裝置號,0代表動態分配,這裡的MEM_MAJOR是1。第二個引數是裝置的名字,第三個引數是檔案操作指標。
每個裝置檔案對應有兩個裝置號:一個是主裝置號,標識該裝置的種類,也標識了該裝置所使用的驅動程式;另一個是次裝置號,標識使用同一裝置驅動程式的不同硬體裝置。裝置檔案的主裝置號必須與裝置驅動程式在登入該裝置時申請的主裝置號一致,否則使用者程序將無法訪問到裝置驅動程式。
一般說來,PCI卡通常都屬於字元裝置
完成註冊後,在/proc/devices中的第一個字元裝置我們就看到了:1 mem。
1.前面提到了註冊,那這個字元裝置到底註冊到哪裡去了呢?這是要弄明白的第一個問題。
其實是註冊到一個存放字元裝置的連結串列中了:
fs/char_dev.c
- static struct char_device_struct {
- struct char_device_struct *next;
- unsigned int major;
- unsigned int baseminor;
- int minorct;
- char name[64];
- struct cdev *cdev; /* will die */
- } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
這裡的CHRDEV_MAJOR_HASH_SIZE的大小是255,也就是這裡最多能夠存放的主裝置號最多有255個。
這裡還是一點需要注意的是這裡分配的是一個指標陣列。
- cd->next = *cp;
- *cp = cd;
首先*cp代表的是當前相同的主裝置號中最後面的一個,當然這這裡的*cp是指向NULL的,然後把*cp更新為cd。
可以注意到這裡好像並沒有struct cdev什麼事情,下面就對其進行初始化。
2.cdev_add第二個任務,add a char device to the system,make it live immediately
先介紹兩個遇到的結構體:
include/linux/cdev.h
- struct cdev {
- struct kobject kobj;
- struct module *owner;
- const struct file_operations *ops;
- struct list_head list;
- dev_t dev;
- unsigned int count;
- };
可進行的操作有:
- void cdev_init(struct cdev *, const struct file_operations *);
- struct cdev *cdev_alloc(void);
- void cdev_put(struct cdev *p);
- int cdev_add(struct cdev *, dev_t, unsigned);
- void cdev_del(struct cdev *);
- void cd_forget(struct inode *);
在結構體cdev裡出現了另外一個極其重要的結構體struct kobject,include/linux/kobject.h
- struct kobject {
- const char *name;
- struct list_head entry;
- struct kobject *parent;
- struct kset *kset;
- struct kobj_type *ktype;
- struct sysfs_dirent *sd;
- struct kref kref;
- unsigned int state_initialized:1;
- unsigned int state_in_sysfs:1;
- unsigned int state_add_uevent_sent:1;
- unsigned int state_remove_uevent_sent:1;
- unsigned int uevent_suppress:1;
- };
主要的操作有:
- extern void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
- extern void kobject_del(struct kobject *kobj);
- extern struct kobject *kobject_get(struct kobject *kobj);
- extern void kobject_put(struct kobject *kobj);extern void kobject_put(struct kobject *kobj);
cdev_add裡面只調用了一個函式:kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
cdev_map是fs/char_dev.h裡定義的一個結構體變數,而kobj_map的作用就是初始化它。
- static struct kobj_map *cdev_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];
- struct mutex *lock;
- };
kobj_map:
核心中所有都字元裝置都會記錄在一個 kobj_map 結構的 cdev_map 變數中。這個結構的變數中包含一個散列表用來快速存取所有的物件。kobj_map() 函式就是用來把字元裝置編號和 cdev 結構變數一起儲存到 cdev_map 這個散列表裡。當後續要開啟一個字元裝置檔案時,通過呼叫 kobj_lookup() 函式,根據裝置編號就可以找到 cdev 結構變數,從而取出其中的 ops 欄位。
- 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;
- }
現在完成的僅僅是註冊,下面還有一些重要的事情需要完成。