1.之前注冊字符設備用的如下函數注冊字符設備驅動:
register_chrdev(unsigned int major, const char *name,const struct file_operations *fops);
但其實這個函數是linux版本2.4之前的注冊方式,它的原理是:
(1)確定一個主設備號
(2)構造一個file_operations結構體, 然后放在chrdevs數組中
(3)注冊:register_chrdev
然后當讀寫字符設備的時候,就會根據主設備號從chrdevs數組中取出相應的結構體,並調用相應的處理函數。
它會有個很大的缺點:
每注冊個字符設備,還會連續注冊0~255個次設備號,使它們綁定在同一個file_operations操作方法結構體上,在大多數情況下,都只用極少的次設備號,所以會浪費很多資源.
2.所以在2.4版本后,內核里就加入了以下幾個函數也可以來實現注冊字符設備:
分為了靜態注冊(指定設備編號來注冊)、動態分配(不指定設備編號來注冊),以及有連續注冊的次設備編號范圍區間,避免了register_chrdev()浪費資源的缺點
2.1:
/*指定設備編號來靜態注冊一個字符設備*/ int register_chrdev_region(dev_t from, unsigned count, const char *name);
from: 注冊的指定起始設備編號,比如:MKDEV(100, 0),表示起始主設備號100, 起始次設備號為0
count:需要連續注冊的次設備編號個數,比如: 起始次設備號為0,count=100,表示0~99的次設備號都要綁定在同一個file_operations操作方法結構體上
*name:字符設備名稱
當返回值小於0,表示注冊失敗
2.2:
/*動態分配一個字符設備,注冊成功並將分配到的主次設備號放入*dev里*/ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
*dev: 存放起始設備編號的指針,當注冊成功, *dev就會等於分配到的起始設備編號,可以通過MAJOR()和MINNOR()函數來提取主次設備號
baseminor:次設備號基地址,也就是起始次設備號
count:需要連續注冊的次設備編號個數,比如: 起始次設備號(baseminor)為0,baseminor=2,表示0~1的此設備號都要綁定在同一個file_operations操作方法結構體上
*name:字符設備名稱
當返回值小於0,表示注冊失敗
2.3:
/*初始化cdev結構體,並將file_operations結構體放入cdev-> ops 里*/ void cdev_init(struct cdev *cdev, const struct file_operations *fops);
其中cdev結構體的成員,如下所示:
struct cdev { struct kobject kobj; // 內嵌的kobject對象 struct module *owner; //所屬模塊 const struct file_operations *ops; //操作方法結構體 struct list_head list; //與 cdev 對應的字符設備文件的 inode->i_devices 的鏈表頭 dev_t dev; //起始設備編號,可以通過MAJOR(),MINOR()來提取主次設備號 unsigned int count; //連續注冊的次設備號個數 };
2.4:
/*將cdev結構體添加到系統中,並將dev(注冊好的設備編號)放入cdev-> dev里, count(次設備編號個數)放入cdev->count里*/ int cdev_add(struct cdev *p, dev_t dev, unsigned count);
2.5:
/*將系統中的cdev結構體刪除掉*/ void cdev_del(struct cdev *p);
2.6:
/*注銷字符設備*/ void unregister_chrdev_region(dev_t from, unsigned count);
from: 注銷的指定起始設備編號,比如:MKDEV(100, 0),表示起始主設備號100, 起始次設備號為0
count:需要連續注銷的次設備編號個數,比如: 起始次設備號為0,baseminor=100,表示注銷掉0~99的次設備號
3.接下來,我們便來寫一個字符設備驅動
里面調用兩次上面的函數,構造兩個不同的file_operations操作結構體,
次設備號0~1對應第一個file_operations,
次設備號2~3對應第二個file_operations,
然后在/dev/下,通過次設備號(0~4)創建5個設備節點, 利用應用程序打開這5個文件,看有什么現象
3.1 驅動代碼如下:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/irq.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/list.h> #include <linux/cdev.h> static int hello_fops1_open(struct inode *inode, struct file *file) { printk("open_hello1!!!\n"); return 0; } static int hello_fops2_open (struct inode *inode, struct file *file) { printk("open_hello2!!!\n"); return 0; } /* 操作結構體1 */ static struct file_operations hello1_fops={ .owner=THIS_MODULE, .open =hello_fops1_open, }; /* 操作結構體2 */ static struct file_operations hello2_fops={ .owner=THIS_MODULE, .open =hello_fops2_open, }; static int major; //主設備 static struct cdev hello1_cdev; //保存 hello1_fops操作結構體的字符設備 static struct cdev hello2_cdev; //保存 hello2_fops操作結構體的字符設備 static struct class *cls; static int chrdev_ragion_init(void) { dev_t devid; alloc_chrdev_region(&devid, 0, 4,"hello"); //動態分配字符設備: (major,0) (major,1) (major,2) (major,3) major=MAJOR(devid); cdev_init(&hello1_cdev, &hello1_fops); cdev_add(&hello1_cdev, MKDEV(major,0), 2); //(major,0) (major,1) cdev_init(&hello2_cdev, &hello2_fops); cdev_add(&hello2_cdev,MKDEV(major,2), 2); //(major,2) (major,3) cls=class_create(THIS_MODULE, "hello"); /*創建字符設備節點*/ class_device_create(cls,0, MKDEV(major,0), 0, "hello0"); //對應hello_fops1操作結構體 class_device_create(cls,0, MKDEV(major,1), 0, "hello1"); //對應hello_fops1操作結構體 class_device_create(cls,0, MKDEV(major,2), 0, "hello2"); //對應hello_fops2操作結構體 class_device_create(cls,0, MKDEV(major,3), 0, "hello3"); //對應hello_fops2操作結構體 class_device_create(cls,0, MKDEV(major,4), 0, "hello4"); //對應空 return 0; } void chrdev_ragion_exit(void) { class_device_destroy(cls, MKDEV(major,4)); class_device_destroy(cls, MKDEV(major,3)); class_device_destroy(cls, MKDEV(major,2)); class_device_destroy(cls, MKDEV(major,1)); class_device_destroy(cls, MKDEV(major,0)); class_destroy(cls); cdev_del(&hello1_cdev); cdev_del(&hello2_cdev); unregister_chrdev_region(MKDEV(major,0), 4); //注銷(major,0)~(major,3) } module_init(chrdev_ragion_init); module_exit(chrdev_ragion_exit); MODULE_LICENSE("GPL");
3.2 測試代碼如下所示:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> void print_useg(char arg[]) //打印使用幫助信息 { printf("useg: \n"); printf("%s [dev]\n",arg); } int main(int argc,char **argv) { int fd; if(argc!=2) { print_useg(argv[0]); return -1; } fd=open(argv[1],O_RDWR); if(fd<0) printf("can't open %s \n",argv[1]); else printf("can open %s \n",argv[1]); return 0; }
4.運行測試:
如下圖,掛載驅動后,通過 ls /dev/hello* -l ,看到創建了5個字符設備節點

接下來開始測試驅動,如下圖所示,
打開/dev/hello0時,調用的是驅動代碼的操作結構體hello1_fops里的.open(),
打開/dev/hello2時,調用的是驅動代碼的操作結構體hello1_fops里的.open(),
打開/dev/hello4時,打開無效,因為在驅動代碼里沒有分配次設備號4的操作結構體,

總結:
使用register_chrdev_region()等函數來注冊字符設備,里面可以存放多個不同的file_oprations操作結構體,實現各種不同的功能
