linux字元裝置解析,linux字元解析
字元裝置的表示當然是指這個struct cdev結構。let us see。
struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count;};
kobj用來表示sys下的目錄,owner模組使用者指標,一般賦值為THIS_MODULE,第三個參數很關鍵file_operations,是使用者層調用open、release、write、read函數時。傳到核心層處理,第四個結構list表示的是一個鏈表節點。第五個是裝置號,沒什麼講的。第六個是count
怎麼分配結構體cdev呢,有兩種方式,一種是靜態定義一個 struct cdev my_cdev,然後在使用cdev_init函數去初始化該結構,第二種方式是利用cdev_alloc函數去動態申請。
有的人會先使用cdev_alloc函數去動態分配,在使用函數cdev_init去釋放,其實這種用法是不太正確的,為什麼呢?且看下面分析。
void cdev_init(struct cdev *cdev, const struct file_operations *fops){ memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops;}struct cdev *cdev_alloc(void){ struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL); if (p) { INIT_LIST_HEAD(&p->list); kobject_init(&p->kobj, &ktype_cdev_dynamic); } return p;}
看出相同之處和不同之處了嗎,yes kobject_init這個函數是關鍵,關鍵在於ktype類型不同、
static struct kobj_type ktype_cdev_default = { .release = cdev_default_release,};static struct kobj_type ktype_cdev_dynamic = { .release = cdev_dynamic_release,};
很明顯,再看
static void cdev_default_release(struct kobject *kobj){ struct cdev *p = container_of(kobj, struct cdev, kobj); cdev_purge(p);}static void cdev_dynamic_release(struct kobject *kobj){ struct cdev *p = container_of(kobj, struct cdev, kobj); cdev_purge(p); kfree(p);}
對,多了一個kfree,也就是說alloc申請出來的cdev佔用的記憶體空間可以在裝置被卸載時自動釋放。
下面來看linux 核心怎麼管理字元裝置號。
核心提供了兩種方式,一種是alloc_chrdev_region另一個是register_chrdev_region,兩個的底層實現如出一轍。下面來分析alloc_chrdev_region。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name){ struct char_device_struct *cd; cd = __register_chrdev_region(0, baseminor, count, name); if (IS_ERR(cd)) return PTR_ERR(cd); *dev = MKDEV(cd->major, cd->baseminor); return 0;}
主要的函數是__register_chrdev_region(0, baseminor, count, name);
static struct char_device_struct *__register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name){ struct char_device_struct *cd, **cp; int ret = 0; int i; cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);//分配裝置號相關的資訊。 if (cd == NULL) return ERR_PTR(-ENOMEM); mutex_lock(&chrdevs_lock); /* temporary */ if (major == 0) {//動態申請 for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) { if (chrdevs[i] == NULL) break; } if (i == 0) { ret = -EBUSY; goto out; } major = i; ret = major; } cd->major = major; cd->baseminor = baseminor; cd->minorct = minorct; strlcpy(cd->name, name, sizeof(cd->name)); i = major_to_index(major); for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) if ((*cp)->major > major || ((*cp)->major == major && (((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor)))) break;//次裝置號的掛接是從小到大, /* Check for overlapping minor ranges. */ if (*cp && (*cp)->major == major) {//檢測是否有裝置號重合衝突 int old_min = (*cp)->baseminor; int old_max = (*cp)->baseminor + (*cp)->minorct - 1; int new_min = baseminor; int new_max = baseminor + minorct - 1; /* New driver overlaps from the left. */ if (new_max >= old_min && new_max <= old_max) { ret = -EBUSY; goto out; } /* New driver overlaps from the right. */ if (new_min <= old_max && new_min >= old_min) { ret = -EBUSY; goto out; } } cd->next = *cp;//掛接到雜湊表裡面 *cp = cd; mutex_unlock(&chrdevs_lock); return cd;out: mutex_unlock(&chrdevs_lock); kfree(cd); return ERR_PTR(ret);}
分析到這裡,就該看一個有意思的資料結構了,也就是所謂的雜湊表。
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];
這就是雜湊表結構,chrdevs是一個指標數組,每一個元素都儲存著一個字元裝置,一看是static類型的,就知道這個chrdevs的每一項初始化後都為NULL,當一個新的裝置來了之後通過主裝置號來掛接到相應的chrdevs裡面,主裝置號就是雜湊表的表頭,而衝突域就用next指標來掛接。比如我註冊了一個major = 254 baseminor= 3 的一個裝置和major = 254 baseminor= 6的一個裝置,這時候chrdevs[254]就應該指向major= 254 baseminor= 3的char_device_struct結構然後該結構的next指標又指向 major= 254 baseminor= 6的char_device_struct結構。