內核提供了三個函數來注冊一組字符設備編號,這三個函數分別是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。
(1)register_chrdev 比較老的內核注冊的形式 早期的驅動
(2)register_chrdev_region/alloc_chrdev_region + cdev 新的驅動形式
區別:register_chrdev()函數是老版本里面的設備號注冊函數,可以實現靜態和動態注冊兩種方法,主要是通過給定的主設備號是否為0來進行區別,為0的時候為動態注冊。register_chrdev_region以及alloc_chrdev_region就是將上述函數的靜態和動態注冊設備號進行了拆分的強化。
register_chrdev_region(dev_t first,unsigned int count,char *name)
First :要分配的設備編號范圍的初始值, 這組連續設備號的起始設備號, 相當於register_chrdev()中主設備號
Count:連續編號范圍. 是這組設備號的大小(也是次設備號的個數)
Name:編號相關聯的設備名稱. (/proc/devices); 本組設備的驅動名稱
alloc_chrdev_region函數,來讓內核自動給我們分配設備號
(1)register_chrdev_region是在事先知道要使用的主、次設備號時使用的;要先查看cat /proc/devices去查看沒有使用的。
(2)更簡便、更智能的方法是讓內核給我們自動分配一個主設備號,使用alloc_chrdev_region就可以自動分配了。
(3)自動分配的設備號,我們必須去知道他的主次設備號,否則后面沒法去mknod創建他對應的設備文件。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
1:這個函數的第一個參數,是輸出型參數,獲得一個分配到的設備號。可以用MAJOR宏和MINOR宏,將主設備號和次設備號,提取打印出來,看是自動分配的是多少,方便我們在mknod創建設備文件時用到主設備號和次設備號。 mknod /dev/xxx c 主設備號 次設備號
2:第二個參數:次設備號的基准,從第幾個次設備號開始分配。
3:第三個參數:次設備號的個數。
4:第四個參數:驅動的名字。
5:返回值:小於0,則錯誤,自動分配設備號錯誤。否則分配得到的設備號就被第一個參數帶出來。
cdev介紹
cdev是一個結構體,里面的成員來共同幫助我們注冊驅動到內核中,表達字符設備的,將這個struct cdev結構體進行填充,主要填充的內容就是
struct cdev { struct kobject kobj; struct module *owner;//填充時,值要為 THIS_MODULE,表示模塊 const struct file_operations *ops;//這個file_operations結構體,注冊驅動的關鍵,要填充成這個結構體變量 struct list_head list; dev_t dev;//設備號,主設備號+次設備號 unsigned int count;//次設備號個數 };
file_operations這個結構體變量,讓cdev中的ops成員的值為file_operations結構體變量的值。這個結構體會被cdev_add函數想內核注冊
cdev結構體,可以用很多函數來操作他。
如:
cdev_alloc:讓內核為這個結構體分配內存的
cdev_init:將struct cdev類型的結構體變量和file_operations結構體進行綁定的
cdev_add:向內核里面添加一個驅動,注冊驅動
cdev_del:從內核中注銷掉一個驅動。注銷驅動
設備號
(1)dev_t類型(包括了主設備號和次設備號 不同的內核中定義不一樣有的是16位次設備號和16位主設備號構成 有的是20為次設備號12位主設備號 )
(2)MKDEV、MAJOR、MINOR三個宏
MKDEV: 是用來將主設備號和次設備號,轉換成一個主次設備號的。(設備號)
MAJOR: 從設備號里面提取出來主設備號的。
MINOR宏:從設備號中提取出來次設備號的。
register_chrdev_region的使用對比register_chrdev:
1 // 模塊安裝函數 2 static int __init chrdev_init(void) 3 { 4 int retval; 5 6 printk(KERN_INFO "chrdev_init helloworld init\n"); 7 8 /* 9 // 在module_init宏調用的函數中去注冊字符設備驅動 10 // major傳0進去表示要讓內核幫我們自動分配一個合適的空白的沒被使用的主設備號 11 // 內核如果成功分配就會返回分配的主設備好;如果分配失敗會返回負數 12 mymajor = register_chrdev(0, MYNAME, &test_fops); 13 if (mymajor < 0) 14 { 15 printk(KERN_ERR "register_chrdev fail\n"); 16 return -EINVAL; 17 } 18 printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor); 19 */ 20 21 // 使用新的cdev接口來注冊字符設備驅動 22 // 新的接口注冊字符設備驅動需要2步 23 24 // 第1步:注冊/分配主次設備號 25 mydev = MKDEV(MYMAJOR, 0); 26 retval = register_chrdev_region(mydev, MYCNT, MYNAME);//
//動態時如下直接改 同時將2526行去掉 其他都一樣
//int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) 27 if (retval) { 28 printk(KERN_ERR "Unable to register minors for %s\n", MYNAME); 29 return -EINVAL; 30 } 31 printk(KERN_INFO "register_chrdev_region success\n"); 32 // 第2步:注冊字符設備驅動 33 cdev_init(&test_cdev, &test_fops); 34 retval = cdev_add(&test_cdev, mydev, MYCNT); 35 if (retval) { 36 printk(KERN_ERR "Unable to cdev_add\n"); 37 return -EINVAL; 38 } 39 printk(KERN_INFO "cdev_add success\n"); 40 41 42 // 使用動態映射的方式來操作寄存器 43 if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) 44 return -EINVAL; 45 if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON")) 46 return -EINVAL; 47 48 pGPJ0CON = ioremap(GPJ0CON_PA, 4); 49 pGPJ0DAT = ioremap(GPJ0DAT_PA, 4); 50 51 *pGPJ0CON = 0x11111111; 52 *pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮 53 54 55 return 0; 56 } 57 58 // 模塊下載函數 59 static void __exit chrdev_exit(void) 60 { 61 printk(KERN_INFO "chrdev_exit helloworld exit\n"); 62 63 *pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); 64 65 // 解除映射 66 iounmap(pGPJ0CON); 67 iounmap(pGPJ0DAT); 68 release_mem_region(GPJ0CON_PA, 4); 69 release_mem_region(GPJ0DAT_PA, 4); 70 71 /* 72 // 在module_exit宏調用的函數中去注銷字符設備驅動 73 unregister_chrdev(mymajor, MYNAME); 74 */ 75 76 // 使用新的接口來注銷字符設備驅動 77 // 注銷分2步: 78 // 第一步真正注銷字符設備驅動用cdev_del 79 cdev_del(&test_cdev); 80 // 第二步去注銷申請的主次設備號 81 unregister_chrdev_region(mydev, MYCNT); 82 }
完整代碼:
1 #include <linux/module.h> // module_init module_exit 2 #include <linux/init.h> // __init __exit 3 #include <linux/fs.h> 4 #include <asm/uaccess.h> 5 #include <mach/regs-gpio.h> 6 #include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h 7 #include <linux/string.h> 8 #include <linux/io.h> 9 #include <linux/ioport.h> 10 #include <linux/cdev.h> 11 12 13 14 #define MYMAJOR 200 15 #define MYCNT 1 16 #define MYNAME "testchar" 17 18 #define GPJ0CON S5PV210_GPJ0CON 19 #define GPJ0DAT S5PV210_GPJ0DAT 20 21 #define rGPJ0CON *((volatile unsigned int *)GPJ0CON) 22 #define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT) 23 24 #define GPJ0CON_PA 0xe0200240 25 #define GPJ0DAT_PA 0xe0200244 26 27 unsigned int *pGPJ0CON; 28 unsigned int *pGPJ0DAT; 29 30 31 int mymajor; 32 static dev_t mydev; 33 static struct cdev test_cdev; 34 35 char kbuf[100]; // 內核空間的buf 36 37 38 static int test_chrdev_open(struct inode *inode, struct file *file) 39 { 40 // 這個函數中真正應該放置的是打開這個設備的硬件操作代碼部分 41 // 但是現在暫時我們寫不了這么多,所以用一個printk打印個信息來做代表。 42 printk(KERN_INFO "test_chrdev_open\n"); 43 44 rGPJ0CON = 0x11111111; 45 rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮 46 47 return 0; 48 } 49 50 static int test_chrdev_release(struct inode *inode, struct file *file) 51 { 52 printk(KERN_INFO "test_chrdev_release\n"); 53 54 rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); 55 56 return 0; 57 } 58 59 ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) 60 { 61 int ret = -1; 62 63 printk(KERN_INFO "test_chrdev_read\n"); 64 65 ret = copy_to_user(ubuf, kbuf, count); 66 if (ret) 67 { 68 printk(KERN_ERR "copy_to_user fail\n"); 69 return -EINVAL; 70 } 71 printk(KERN_INFO "copy_to_user success..\n"); 72 73 74 return 0; 75 } 76 77 // 寫函數的本質就是將應用層傳遞過來的數據先復制到內核中,然后將之以正確的方式寫入硬件完成操作。 78 static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, 79 size_t count, loff_t *ppos) 80 { 81 int ret = -1; 82 83 printk(KERN_INFO "test_chrdev_write\n"); 84 85 // 使用該函數將應用層傳過來的ubuf中的內容拷貝到驅動空間中的一個buf中 86 //memcpy(kbuf, ubuf); // 不行,因為2個不在一個地址空間中 87 memset(kbuf, 0, sizeof(kbuf)); 88 ret = copy_from_user(kbuf, ubuf, count); 89 if (ret) 90 { 91 printk(KERN_ERR "copy_from_user fail\n"); 92 return -EINVAL; 93 } 94 printk(KERN_INFO "copy_from_user success..\n"); 95 96 if (kbuf[0] == '1') 97 { 98 rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); 99 } 100 else if (kbuf[0] == '0') 101 { 102 rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); 103 } 104 105 106 /* 107 // 真正的驅動中,數據從應用層復制到驅動中后,我們就要根據這個數據 108 // 去寫硬件完成硬件的操作。所以這下面就應該是操作硬件的代碼 109 if (!strcmp(kbuf, "on")) 110 { 111 rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); 112 } 113 else if (!strcmp(kbuf, "off")) 114 { 115 rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); 116 } 117 */ 118 119 120 121 return 0; 122 } 123 124 125 // 自定義一個file_operations結構體變量,並且去填充 126 static const struct file_operations test_fops = { 127 .owner = THIS_MODULE, // 慣例,直接寫即可 128 129 .open = test_chrdev_open, // 將來應用open打開這個設備時實際調用的 130 .release = test_chrdev_release, // 就是這個.open對應的函數 131 .write = test_chrdev_write, 132 .read = test_chrdev_read, 133 }; 134 135 136 // 模塊安裝函數 137 static int __init chrdev_init(void) 138 { 139 int retval; 140 141 printk(KERN_INFO "chrdev_init helloworld init\n"); 142 143 /* 144 // 在module_init宏調用的函數中去注冊字符設備驅動 145 // major傳0進去表示要讓內核幫我們自動分配一個合適的空白的沒被使用的主設備號 146 // 內核如果成功分配就會返回分配的主設備好;如果分配失敗會返回負數 147 mymajor = register_chrdev(0, MYNAME, &test_fops); 148 if (mymajor < 0) 149 { 150 printk(KERN_ERR "register_chrdev fail\n"); 151 return -EINVAL; 152 } 153 printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor); 154 */ 155 156 // 使用新的cdev接口來注冊字符設備驅動 157 // 新的接口注冊字符設備驅動需要2步 158 159 // 第1步:注冊/分配主次設備號 160 mydev = MKDEV(MYMAJOR, 0); 161 retval = register_chrdev_region(mydev, MYCNT, MYNAME); 162 if (retval) { 163 printk(KERN_ERR "Unable to register minors for %s\n", MYNAME); 164 return -EINVAL; 165 } 166 printk(KERN_INFO "register_chrdev_region success\n"); 167 // 第2步:注冊字符設備驅動 168 cdev_init(&test_cdev, &test_fops); 169 retval = cdev_add(&test_cdev, mydev, MYCNT); 170 if (retval) { 171 printk(KERN_ERR "Unable to cdev_add\n"); 172 return -EINVAL; 173 } 174 printk(KERN_INFO "cdev_add success\n"); 175 176 177 // 使用動態映射的方式來操作寄存器 178 if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) 179 return -EINVAL; 180 if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON")) 181 return -EINVAL; 182 183 pGPJ0CON = ioremap(GPJ0CON_PA, 4); 184 pGPJ0DAT = ioremap(GPJ0DAT_PA, 4); 185 186 *pGPJ0CON = 0x11111111; 187 *pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮 188 189 190 return 0; 191 } 192 193 // 模塊下載函數 194 static void __exit chrdev_exit(void) 195 { 196 printk(KERN_INFO "chrdev_exit helloworld exit\n"); 197 198 *pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); 199 200 // 解除映射 201 iounmap(pGPJ0CON); 202 iounmap(pGPJ0DAT); 203 release_mem_region(GPJ0CON_PA, 4); 204 release_mem_region(GPJ0DAT_PA, 4); 205 206 /* 207 // 在module_exit宏調用的函數中去注銷字符設備驅動 208 unregister_chrdev(mymajor, MYNAME); 209 */ 210 211 // 使用新的接口來注銷字符設備驅動 212 // 注銷分2步: 213 // 第一步真正注銷字符設備驅動用cdev_del 214 cdev_del(&test_cdev); 215 // 第二步去注銷申請的主次設備號 216 unregister_chrdev_region(mydev, MYCNT); 217 } 218 219 220 module_init(chrdev_init); 221 module_exit(chrdev_exit); 222 223 // MODULE_xxx這種宏作用是用來添加模塊描述信息 224 MODULE_LICENSE("GPL"); // 描述模塊的許可證 225 MODULE_AUTHOR("aston"); // 描述模塊的作者 226 MODULE_DESCRIPTION("module test"); // 描述模塊的介紹信息 227 MODULE_ALIAS("alias xxx"); // 描述模塊的別名信息
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <string.h> 6 7 8 #define FILE "/dev/test" // 剛才mknod創建的設備文件名 9 10 char buf[100]; 11 12 int main(void) 13 { 14 int fd = -1; 15 int i = 0; 16 17 fd = open(FILE, O_RDWR); 18 if (fd < 0) 19 { 20 printf("open %s error.\n", FILE); 21 return -1; 22 } 23 printf("open %s success..\n", FILE); 24 25 /* 26 // 讀寫文件 27 write(fd, "on", 2); 28 sleep(2); 29 write(fd, "off", 3); 30 sleep(2); 31 write(fd, "on", 2); 32 sleep(2); 33 */ 34 /* 35 write(fd, "1", 1); 36 sleep(2); 37 write(fd, "0", 1); 38 sleep(2); 39 write(fd, "1", 1); 40 sleep(2); 41 */ 42 while (1) 43 { 44 memset(buf, 0 , sizeof(buf)); 45 printf("請輸入 on | off \n"); 46 scanf("%s", buf); 47 if (!strcmp(buf, "on")) 48 { 49 write(fd, "1", 1); 50 } 51 else if (!strcmp(buf, "off")) 52 { 53 write(fd, "0", 1); 54 } 55 else if (!strcmp(buf, "flash")) 56 { 57 for (i=0; i<3; i++) 58 { 59 write(fd, "1", 1); 60 sleep(1); 61 write(fd, "0", 1); 62 sleep(1); 63 } 64 } 65 else if (!strcmp(buf, "quit")) 66 { 67 break; 68 } 69 } 70 71 72 // 關閉文件 73 close(fd); 74 75 return 0; 76 }
cdev_alloc給定義出來的struct cdev類型的指針分配堆空間
cdev_alloc內部其實就是調用了內核中的kmalloc來進行給struct_cdev來分配堆內存的,內存大小就是這個struct cdev的大小。
例:如果我們定義了一個全局的 static struct cdev *pcdev; 我們就可以用 pcdev = cdev_alloc();來給這個pcdev分配堆內存空間。cdev_del(pcdev)時會釋放掉這個申請到的堆內存,cdev_del函數內部是能知道你的struct cdev定義的對象是用的堆內存還是棧內存還是數據段內存的。這個函數cdev_del調用時,會先去看你有沒有使用堆內存,如果有用到的話,會先去釋放掉你用的堆內存,然后在注銷掉你這個設備驅動。防止內存泄漏。注意如果struct cdev要用堆內存一定要用內核提供的這個cdev_alloc去分配堆內存,因為內部會做記錄,這樣在cdev_del注銷掉這個驅動的時候,才會去釋放掉那段堆內存。
使用cdev_alloc可以直接pcdev->ops = fops; 來進行綁定,就不需要cdev_init函數了,
代碼:
static dev_t mydev;
//static struct cdev test_cdev;
static struct cdev *pcdev;
.........
// 第2步:注冊字符設備驅動 pcdev = cdev_alloc(); // 給pcdev分配內存,指針實例化 //cdev_init( &test_cdev, &test_fops);//下面的代碼可以講該句替換了 pcdev->owner = THIS_MODULE; pcdev->ops = &test_fops;
cdev_init源碼:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
cdev->kobj.ktype = &ktype_cdev_default;
kobject_init(&cdev->kobj);
cdev->ops = fops;
}
cdev_init中的其他的操作在cdev_alloc中都有做了。
整理自
http://blog.csdn.net/tommy_wxie/article/details/7195471
http://blog.sina.com.cn/s/blog_14f1867f70102wlrj.html
http://edu.51cto.com/pack/view/id-529.html
http://blog.csdn.net/zqixiao_09/article/details/50839042(好貼)
