Linux中字符設備的注冊過程是比較簡單的。我們通常可以調用misc_register()函數來注冊一個字符設備。Misc設備是一種字符設備,通過該設備可以將fops請求轉發給注冊的misc設備,從而實現字符設備的功能。用戶調用該接口注冊Misc字符設備時,可以動態分配設備Minor號,當獲取Minor號之後調用class_simple_device_add()或者device_create()函數完成字符設備的創建。Misc字符設備注冊函數如下所示:
int misc_register(struct miscdevice * misc) { struct miscdevice *c; dev_t dev; int err = 0; INIT_LIST_HEAD(&misc->list); mutex_lock(&misc_mtx); //獲取misc設備信號量 list_for_each_entry(c, &misc_list, list) { //檢查設備是否已經存在 if (c->minor == misc->minor) { mutex_unlock(&misc_mtx); return -EBUSY; //如果設備存在,直接返回 } } if (misc->minor == MISC_DYNAMIC_MINOR) { //動態分配分配minor號 int i = DYNAMIC_MINORS; while (--i >= 0) if ( (misc_minors[i>>3] & (1 << (i&7))) == 0) break; if (i<0) { mutex_unlock(&misc_mtx); return -EBUSY; } misc->minor = i; } if (misc->minor < DYNAMIC_MINORS) misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7); dev = MKDEV(MISC_MAJOR, misc->minor); misc->this_device = device_create(misc_class, misc->parent, dev, "%s", misc->name); //創建字符設備(Misc設備) if (IS_ERR(misc->this_device)) { err = PTR_ERR(misc->this_device); goto out; } /* * Add it to the front, so that later devices can "override" * earlier defaults */ list_add(&misc->list, &misc_list); //將設備保存至misc設備鏈中,設備訪問時需要操作該鏈表 out: mutex_unlock(&misc_mtx); return err; }
需要注意的是2.6.12內核中創建系統設備時需要調用simple_device的接口class_simple_device_add()。在2.6.23中需要調用device_create()函數完成設備注冊。在3.2內核中,simple_device的接口已經不存在了,所以必須調用device_create函數,另外,3.2內核不支持具有相同minor號的字符設備,在2.6.x內核中是支持的。
系統是如何完成fops函數調用的呢?回答這個問題需要分析Misc設備打開過程。在打開Misc設備驅動的時候,Misc設備驅動會根據訪問設備的Minor號重定向fops函數集,該程序說明如下:
static int misc_open(struct inode * inode, struct file * file) { int minor = iminor(inode); struct miscdevice *c; int err = -ENODEV; const struct file_operations *old_fops, *new_fops = NULL; mutex_lock(&misc_mtx); list_for_each_entry(c, &misc_list, list) { //通過minor號來匹配對應的fops函數集 if (c->minor == minor) { new_fops = fops_get(c->fops); break; } } if (!new_fops) { mutex_unlock(&misc_mtx); request_module("char-major-%d-%d", MISC_MAJOR, minor); mutex_lock(&misc_mtx); list_for_each_entry(c, &misc_list, list) { if (c->minor == minor) { new_fops = fops_get(c->fops); break; } } if (!new_fops) goto fail; } err = 0; old_fops = file->f_op; file->f_op = new_fops; //重定向fops函數 if (file->f_op->open) { //打開設備 err=file->f_op->open(inode,file); if (err) { fops_put(file->f_op); file->f_op = fops_get(old_fops); } } fops_put(old_fops); fail: mutex_unlock(&misc_mtx); return err; }
很多時候我們不想創建Misc字符設備,想要自己創建一個字符設備類,然後再創建該設備類的字符設備,那麼整個創建過程可以描述如下:
1,調用register_chrdev_region函數注冊設備號區間
2,調用cdev_alloc函數分配一個字符設備
3,調用cdev_add函數添加內核字符設備
4,調用device_create函數通知udev創建設備節點,並且注冊到sysfs中。
register_chrdev_region函數用來分配設備號。在linux系統中維護了一個char_device的Hash表,每個Major占用一個Hash項。通過register_chrdev_region函數就是向全局的char_device Hash表注冊設備號。該函數說明如下:
int register_chrdev_region(dev_t from, unsigned count, const char *name) { struct char_device_struct *cd; dev_t to = from + count; dev_t n, next; for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0); //一個Major設備類最多可以容納1M個Minor設備 if (next > to) next = to; //取最小設備號 cd = __register_chrdev_region(MAJOR(n), MINOR(n), next - n, name); //注冊設備號區間 if (IS_ERR(cd)) goto fail; } return 0; fail: to = n; for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0); kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n)); } return PTR_ERR(cd); }
當設備號區間分配完成之後,通過cdev_alloc()函數分配一個內核字符設備,然後通過cdev_add函數將該字符設備注冊到內核cdev_map->probes[]數組中,至此,內核的字符設備創建完畢。但是,此時,應用層還無法看到字符設備節點,因此,可以調用device_create函數通知udev去創建設備節點,並且將設備添加到sysfs系統樹中。至此,應用層可以通過設備節點訪問字符設備了。
字符設備是Linux中最簡單的一種設備,其系統注冊過程也相對比較簡單。
本文出自 “存儲之道” 博客,請務必保留此出處http://alanwu.blog.51cto.com/3652632/1123908