字符设备驱动理解

设备驱动的含义
    C库中通过open/read/write/seek等来操作文件,所谓字符设备驱动,简单来说就是实现这几个函数的具体内容,linux能把设备抽象为文件,用户调用open/read/write/seek对抽象的文件进行操作就可以操作实际硬件设备(或抽象的设备)。所以字符设备驱动的重点,在于编写内核空间的open/read/write/seek等函数。
    可见,字符设备并不是一个完整的main函数,其框架更像是一个支持fileOperations的lib。用户空间通过系统调用(int 80,软中断)来使用这个lib,lib之间的调用通过内核符号表来完成解析。

内核编程与驱动开发的关系    
    驱动开发是内核编程的一种,内核编程包括的范围很大(文件系统,内存管理,源码修改)。

字符设备驱动框架
(1)init、exit函数,供模块加载(初始化)、卸载(扫尾工作)使用;
(2)file operations: open/read/write/seek/ioctl等;


框架加载流程

(1)动态分配主设备号(标识驱动),从设备号(标识设备);
(2)字符设备(struct cdev)空间分配(cdev_alloc);
(3)字符设备cdev_init,与file operation关联;
(4)添加字符设备(cdev_add)。

通过insmod具体过程理解字符设备
(1)分配内存空间,加载字符设备驱动正文;
(2)通过内核符号表解析正文;
(3)调用module_init宏指定的init函数(init完成后就将init函数扔掉,释放部分内存)。

重要结构:
(1)struct file_operations: open/read/write/seek/ioctl等
(2)struct file(文件描述符):*f_op(即file_operations), *private_data(各file_operations间交互中介)
(3)struct inode(表示文件节点):节点号,uid,gid,引用计数
(4)struct cdev(表示字符设备):设备号,file operation

示例代码1:字符设备驱动模块程序(c语言)
[cpp]  view plain  copy
  1. /********************************************** 
  2. *** 一个简单的linux字符设备驱动示例 
  3. *** Autor:          ybdesire 
  4. *** Create Date:    2012-11-12 
  5. *** Completion Date:2012-11-25 
  6. **********************************************/  
  7.   
  8.   
  9. #include <linux/init.h>       //模块init/exti必备    
  10. #include <linux/module.h> //同上  
  11. #include <linux/sched.h>  //struct task_struct *current  
  12. #include <linux/fs.h>     //file_operation, file, inode  
  13. #include <linux/types.h>  //设备号相关,eg MAJOR, MINOR, MKDEV  
  14. #include <linux/cdev.h>       //字符设备相关操作,init,add,del  
  15. #include <linux/slab.h>       //kmalloc, kfree  
  16. #include <asm/uaccess.h>  //copy_to_user, copy_from_user  
  17.   
  18. //各个file operation之间可用struct file中的private_data传递参数  
  19. int chardd_open( struct inode *inode, struct file *filp)  
  20. {  
  21.     printk(KERN_EMERG "chardd_open\n");  
  22.     printk(KERN_EMERG "chardd open thread pid: %d\n", current->pid);//显示当前open设备的进程号  
  23.     return 0;  
  24. }  
  25.   
  26. int chardd_release( struct inode *inode, struct file *flip )  
  27. {  
  28.     printk(KERN_EMERG "chardd_release\n");  
  29.     return 0;  
  30. }  
  31.   
  32. int chardd_read( struct file *filp, char *buf, size_t count, loff_t *ppos )  
  33. {  
  34.     if(filp->private_data==NULL)  
  35.         printk(KERN_EMERG "in read: private data in file struct is NULL\n");  
  36.     else  
  37.     {  
  38.         printk(KERN_EMERG "in read: private data in file struct is not NULL\n");  
  39.         copy_to_user(buf, filp->private_data, count);//将内核空间中filp->private_data指向的数据拷贝到用户空间buf  
  40.     }  
  41.   
  42.     kfree(filp->private_data); //拷贝完后释放kmalloc出来的堆空间  
  43.     filp->private_data = NULL;  
  44.     return 0;  
  45. }  
  46.   
  47. int chardd_write( struct file *filp, const char *buf, size_t count, loff_t *offp )  
  48. {  
  49.     if(filp->private_data==NULL)//若指针为空,为其分配空间  
  50.     {  
  51.         printk(KERN_EMERG "in write: private data in file struct is NULL\n");  
  52.         filp->private_data = (void*)kmalloc(100, GFP_KERNEL);  
  53.     }  
  54.     else  
  55.         printk(KERN_EMERG "in write: private data in file struct is not NULL\n");  
  56.   
  57.     copy_from_user(filp->private_data, buf, count);//将用户空间buf中的数据拷贝到内核空间filp->private_data  
  58.   
  59.     return 0;  
  60. }  
  61.   
  62. struct file_operations chardd_fops =  
  63. {  
  64.     .owner = THIS_MODULE,  
  65.     .open = chardd_open,  
  66.     .release = chardd_release,  
  67.     .read = chardd_read,  
  68.     .write = chardd_write,  
  69. };  
  70.   
  71.   
  72. dev_t dev; //device number  
  73. struct cdev *chardd_dev = NULL;  
  74.   
  75. static int __init initchardd(void)  
  76. {  
  77.     int rt;  
  78.     int chardd_minor = 5;  
  79.     int chardd_count = 2;  
  80.     //动态申请主设备号,为chardd_count个设备申请,得到的主设备号依次递增  
  81.     //若返回主设备号为200,则设备1的设备号(主200, 从5),设备2的设备号(主200, 从6)  
  82.     //本例设备名为chardd,注册成功可用cat /proc/devices查看  
  83.     rt = alloc_chrdev_region(&dev, chardd_minor, chardd_count, "chardd");  
  84.   
  85.     if(rt==0)  
  86.     {  
  87.         printk(KERN_INFO "char device driver init ok");  
  88.         printk(KERN_INFO "char device drive major device num: %d\n", MAJOR(dev));  
  89.         printk(KERN_INFO "char device drive minor device num: %d\n", MINOR(dev));  
  90.     }  
  91.     else  
  92.         printk(KERN_INFO "char device driver init NOT ok");  
  93.   
  94.     chardd_dev = cdev_alloc();//为字符设备申请空间  
  95.     if(chardd_dev==NULL)  
  96.         printk(KERN_INFO "cdev alloc error");  
  97.     else  
  98.         printk(KERN_INFO "cdev alloc ok");  
  99.   
  100.     chardd_dev->ops = &chardd_fops;//绑定fileoepration操作  
  101.     cdev_init(chardd_dev, &chardd_fops);  
  102.   
  103.     printk(KERN_INFO "cdev init ok");  
  104.     rt = cdev_add(chardd_dev, dev, 1); //添加字符设备  
  105.     if(rt==0)  
  106.         printk(KERN_INFO "cdev add ok");  
  107.     else  
  108.         printk(KERN_INFO "cdev add error");  
  109.     return 0;  
  110.       
  111. }  
  112.   
  113. static void __exit exitchardd(void)  
  114. {  
  115.     int chardd_count = 2;  
  116.     //释放设备号  
  117.     unregister_chrdev_region(dev, chardd_count);  
  118.     //删除字符设备  
  119.     cdev_del(chardd_dev);  
  120.     printk(KERN_INFO "char device driver exit");  
  121. }  
  122.   
  123.   
  124. module_init(initchardd);  
  125. module_exit(exitchardd);  
  126.   
  127.   
  128. MODULE_LICENSE("Dual BSD/GPL");  
  129. MODULE_AUTHOR("ybdesire");  
  130. MODULE_DESCRIPTION("a .ko character device driver demo");  
  131. MODULE_VERSION("v10");  
【使用】编译(make),加载(insmod),mknod 到/dev/chardd


示例代码2:用户空间使用驱动(c语言)
[cpp]  view plain  copy
  1. /********************************************** 
  2. *** 字符设备驱动测试程序 
  3. *** Autor:          ybdesire 
  4. *** Create Date:    2012-11-23 
  5. *** Completion Date:2012-11-25 
  6. **********************************************/  
  7.   
  8. #include <stdio.h>  
  9. #include <stdlib.h>  
  10. #include <string.h>  
  11. #include <unistd.h>  
  12. #include <sys/types.h>  
  13. #include <sys/stat.h>  
  14. #include <fcntl.h>  
  15.   
  16. int main()  
  17. {  
  18.     int fp, ret;  
  19.     unsigned char ch[]="hello kernel device driver!";  
  20.     unsigned char *buf1 = (unsigned char *)malloc(sizeof(ch)+1);  
  21.     unsigned char *buf2 = (unsigned char *)malloc(sizeof(ch)+1);  
  22.     //init buff  
  23.     memset(buf1, 0, sizeof(ch)+1);  
  24.     memset(buf2, 0, sizeof(ch)+1);  
  25.     strcpy(buf1, ch);//ch to buf1  
  26.     //打开设备驱动,前提是用mknode把字符设备建立为/dev/chardd  
  27.     fp = open("/dev/chardd", O_RDONLY);  
  28.     printf("fp is %d\n", fp);  
  29.       
  30.     ret = write(fp, buf1, sizeof(ch));//write buf1 to fp  
  31.     printf("write return : %d\n", ret);  
  32.       
  33.     ret = read(fp, buf2, sizeof(ch));//read fp to buf2  
  34.   
  35.     printf("read return : %d\n", ret);  
  36.     printf("read data:%s\n", buf2);  
  37.     close(fp);  
  38.     return 0;  
  39. }  

【编译过程及使用示例】

[cpp]  view plain  copy
  1. ybde @ubuntu:~/Test/Driver/ch3/chardd$ sudo insmod chardd.ko   
  2. ybde @ubuntu:~/Test/Driver/ch3/chardd$ mknod /dev/chardd c 250 5  
  3. ybde: "/dev/chardd": 权限不够  
  4. ybde @ubuntu:~/Test/Driver/ch3/chardd$ sudo mknod /dev/chardd c 250 5  
  5. ybde @ubuntu:~/Test/Driver/ch3/chardd$ cd main/ (main中为测试代码)      
  6. ybde @ubuntu:~/Test/Driver/ch3/chardd/main$ gcc main.c   
  7. ybde @ubuntu:~/Test/Driver/ch3/chardd/main$ ./a.out   
  8. fp is -1  
  9. write return : -1  
  10. read return : -1  
  11. read data:  
  12. ybde @ubuntu:~/Test/Driver/ch3/chardd/main$ sudo ./a.out   
  13. fp is 3  
  14. write return : 0  
  15. read return : 0  
  16. read data:hello kernel device driver!  
  17. ybde @ubuntu:~/Test/Driver/ch3/chardd/main$   

注意:
1、insmod后的驱动模块,可以cat /proc/devices查看到,但只有/dev/中出现,用户程序才好open这个设备,怎么让/dev/中也出现设备呢?
【解答】 $ mknode     /dev/mydev    c     majorDeviceNum     minorDeviceNum

2、应用程序中不能使用字符设备,比如open出错,
【解答】可能是权限问题,sudo ./a.out就能正确

3、可以通过strace来查看程序执行所调用过的所有系统调用,以及它们的返回值等执行状态
【例如】
$  strace ./a.out
...
open("/dev/chardd", O_RDONLY)           = 3
...
write(3, "hello kernel device driver!\0", 28) = -1 EBADF (Bad file descriptor)
...
可发现write报错Bad file descriptor,再往上看,open返回的文件描述符是3,也没错啊。但是,open的文件模式是readonly,肯定不能write的,改成O_RDWR就好了。

4、多个字符设备重名怎么办(cat /proc/devices下的名字)
【解答】只要设备号不同就没关系,用户空间要打开的都是mknode到/dev/中的,mknode就指定了设备号

5、需考虑并发,一般在write中做内存管理
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值