1. 程式人生 > >普通字元裝置驅動的兩種註冊方式(新&舊)

普通字元裝置驅動的兩種註冊方式(新&舊)

普通字元裝置驅動的兩種註冊方式(新&舊)

在核心中,對於一個普通的字元裝置驅動,不難發現有兩種註冊方式:

  1. register_chrdev族函式+建立裝置類、檔案的函式:這種方法是2.4版本流行的舊方法。優點是簡單;缺點是無法指定次裝置號
  2. register_chrdev_region族+cdev族+建立裝置類、檔案的函式:這種方法是2.6版本推薦的新方法。優點是可以設定主次裝置號;缺點是比較複雜

1.舊版本的註冊方式

/*需要的定義*/
int test_major = -1;
static struct class *mydev_pclass;
#define TEST_NAME   "test"
#define MYDEV_CNT 3 /*正式註冊*/ test_major = register_chrdev(0, TEST_NAME, &test_fops); if (test_major < 0) { printk(KERN_INFO "register test error\n"); goto out_err_0; } /*用class_create建立一個裝置類,這是建立裝置檔案的前置任務*/ mydev_pclass = class_create(THIS_MODULE, "mydevice"); if (IS_ERR(mydev_pclass)) { //排錯
printk(KERN_ERR "can't register device mydevice class\n"); goto out_err_1; } /*由device_create正式建立裝置檔案*/ device_create(mydev_pclass, NULL, MKDEV(test_major, 0), NULL, "mydevice"); return 0; /*倒影式錯誤處理流程*/ out_err_2: class_destroy(mydev_pclass); out_err_1: unregister_chrdev(test_major); out_err_0: return
-EINVAL;
  • register_chrdev族函式是形成驅動裝置的核心。register_chrdev的作用是申請並註冊一個裝置號、構建一個字元裝置體、向核心註冊此字元裝置
  • register_chrdev函式的major引數可以設定主裝置號,若major引數設為0則系統會自動分配一個裝置號(這種做法比較可靠、方便)。register_chrdev的返回值就是分配到的主裝置號,一般賦給一個全域性變數來傳遞給unregister_chrdev登出函式,登出函式需要主裝置號來作為引數
  • 模組解除安裝函式內必須要包含unregister_chrdev函式,與這裡的register_chrdev對應,只不過負責的是登出
  • 最後由核心通知udev(即mdev)來建立一個位於/dev的裝置檔案。這一步其實是建立API與裝置體之間的紐帶,這樣APP才能通過API操作裝置檔案(即呼叫 file_operations內的函式)。主要分為兩步class_create先例項化新建一個裝置類,然後由device_create正式建立裝置檔案
  • 值得注意的是,建立裝置檔案的前提是系統開機時已經啟動了udev(即mdev)功能。可以在/etc/init.d/rcS 或/etc/inittab中設定相關命令,詳見手動構建rootfs及檔案功能分析
  • 關於錯誤處理,這裡使用了核心中常見的goto方法。錯誤處理就是前面那些操作的逆操作,旨在發生錯誤時釋放那些申請的資源,執行順序必須符合“倒影式錯誤處理流程”

2.新版本的註冊方式

/*需要用到的定義*/
#define MYDEV_NUM       MKDEV(MYMAJOR, 0)
#define MYNAME      "mydevice"
#define MYDEV_CNT     3

static struct cdev *mydev_pcdev;
static struct class *mydev_pclass;
dev_t mydev_num = 0;
unsigned int mydev_major = 0;

/*正式開始註冊*/
/*讓核心給我們自動分配裝置號(主次裝置號)*/
ret = alloc_chrdev_region(&mydev_num, 0, MYDEV_CNT, MYNAME);
mydev_major = MAJOR(mydev_num);
if (ret < 0) {
        printk(KERN_INFO "alloc_chrdev_region fail\n");
        goto out_err_0;
}
printk(KERN_INFO "MAJOR %d\n", mydev_major);

/*cdev_alloc例項化一個字元裝置體。為cdev分配記憶體*/
mydev_pcdev = cdev_alloc();

/*填充cdev裝置體。最主要是將file_operations填充進去*/
cdev_init(mydev_pcdev, &mydev_fops);

/*將裝置體與裝置號繫結並向核心註冊一個字元裝置*/
ret = cdev_add(mydev_pcdev, MYDEV_NUM, MYDEV_CNT);
if (ret) {
        printk(KERN_INFO "cdev_add error\n");
        goto out_err_1;
}

/*用class_create建立一個裝置類,這是建立裝置檔案的前置任務*/
mydev_pclass = class_create(THIS_MODULE, "mydevice");
if (IS_ERR(mydev_pclass)) {     //排錯
printk(KERN_ERR "can't register device mydevice class\n");
        goto out_err_2;
}

/*由device_create正式建立裝置檔案*/
device_create(mydev_pclass, NULL, MYDEV_NUM, NULL, "mydevice");

return 0;

/*倒影式錯誤處理流程*/
out_err_3:
        class_destroy(mydev_pclass);

out_err_2:
        cdev_del(mydev_pcdev);

out_err_1:
        unregister_chrdev_region(mydev_num, MYDEV_CNT);

out_err_0:
        return -EINVAL;
  • 關於分配主次裝置號,register_chrdev_region是使用者自定義裝置號的申請。並且可以通過引數設定裝置的個數(即次裝置號) ,裝置號是通過MKDEV這個巨集獲得,它可以通過主裝置號和次裝置號來計算裝置號。其實更方便的做法是使用alloc_chrdev_region讓核心給我們自動分配一個,這樣就不會導致裝置號之間重複。
  • 利用cdev_alloc例項化一個字元裝置體,為cdev分配記憶體 。使用cdev_alloc分配記憶體的好處是cdev_del時核心會為我們自動釋放cdev佔用的空間,而如果我們自己malloc的話就要自己釋放了。
  • 利用cdev_init填充cdev裝置體 ,最主要是將file_operations填充進去。有的驅動裡面是沒用cdev_init()的,而是手動去填充,例如 mydev_pcdev-> owner = THIS_MODULE; mydev_pcdev-> ops = &mydev_fops; 說到底cdev_init()其實就相當於上面兩句
  • 利用cdev_add將裝置體與裝置號繫結,並向核心註冊一個字元裝置
  • 最後由核心通知udev(即mdev)來建立一個位於/dev的裝置檔案。這一步其實是建立API與裝置體之間的紐帶,這樣APP才能通過API操作裝置檔案(即呼叫 file_operations內的函式)。主要分為兩步class_create先例項化新建一個裝置類,然後由device_create正式建立裝置檔案
  • 值得注意的是,建立裝置檔案的前提是系統開機時已經啟動了udev(即mdev)功能。可以在/etc/init.d/rcS 或/etc/inittab中設定相關命令,詳見手動構建rootfs及檔案功能分析
  • 關於錯誤處理,這裡使用了核心中常見的goto方法。錯誤處理就是前面那些操作的逆操作,旨在發生錯誤時釋放那些申請的資源,執行順序必須符合“倒影式錯誤處理流程”