字符设备驱动理解"/>
字符设备驱动理解
设备驱动的含义 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等;
框架加载流程
通过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
- /**********************************************
- *** 一个简单的linux字符设备驱动示例
- *** Autor: ybdesire
- *** Create Date: 2012-11-12
- *** Completion Date:2012-11-25
- **********************************************/
- #include <linux/init.h> //模块init/exti必备
- #include <linux/module.h> //同上
- #include <linux/sched.h> //struct task_struct *current
- #include <linux/fs.h> //file_operation, file, inode
- #include <linux/types.h> //设备号相关,eg MAJOR, MINOR, MKDEV
- #include <linux/cdev.h> //字符设备相关操作,init,add,del
- #include <linux/slab.h> //kmalloc, kfree
- #include <asm/uaccess.h> //copy_to_user, copy_from_user
- //各个file operation之间可用struct file中的private_data传递参数
- int chardd_open( struct inode *inode, struct file *filp)
- {
- printk(KERN_EMERG "chardd_open\n");
- printk(KERN_EMERG "chardd open thread pid: %d\n", current->pid);//显示当前open设备的进程号
- return 0;
- }
- int chardd_release( struct inode *inode, struct file *flip )
- {
- printk(KERN_EMERG "chardd_release\n");
- return 0;
- }
- int chardd_read( struct file *filp, char *buf, size_t count, loff_t *ppos )
- {
- if(filp->private_data==NULL)
- printk(KERN_EMERG "in read: private data in file struct is NULL\n");
- else
- {
- printk(KERN_EMERG "in read: private data in file struct is not NULL\n");
- copy_to_user(buf, filp->private_data, count);//将内核空间中filp->private_data指向的数据拷贝到用户空间buf
- }
- kfree(filp->private_data); //拷贝完后释放kmalloc出来的堆空间
- filp->private_data = NULL;
- return 0;
- }
- int chardd_write( struct file *filp, const char *buf, size_t count, loff_t *offp )
- {
- if(filp->private_data==NULL)//若指针为空,为其分配空间
- {
- printk(KERN_EMERG "in write: private data in file struct is NULL\n");
- filp->private_data = (void*)kmalloc(100, GFP_KERNEL);
- }
- else
- printk(KERN_EMERG "in write: private data in file struct is not NULL\n");
- copy_from_user(filp->private_data, buf, count);//将用户空间buf中的数据拷贝到内核空间filp->private_data
- return 0;
- }
- struct file_operations chardd_fops =
- {
- .owner = THIS_MODULE,
- .open = chardd_open,
- .release = chardd_release,
- .read = chardd_read,
- .write = chardd_write,
- };
- dev_t dev; //device number
- struct cdev *chardd_dev = NULL;
- static int __init initchardd(void)
- {
- int rt;
- int chardd_minor = 5;
- int chardd_count = 2;
- //动态申请主设备号,为chardd_count个设备申请,得到的主设备号依次递增
- //若返回主设备号为200,则设备1的设备号(主200, 从5),设备2的设备号(主200, 从6)
- //本例设备名为chardd,注册成功可用cat /proc/devices查看
- rt = alloc_chrdev_region(&dev, chardd_minor, chardd_count, "chardd");
- if(rt==0)
- {
- printk(KERN_INFO "char device driver init ok");
- printk(KERN_INFO "char device drive major device num: %d\n", MAJOR(dev));
- printk(KERN_INFO "char device drive minor device num: %d\n", MINOR(dev));
- }
- else
- printk(KERN_INFO "char device driver init NOT ok");
- chardd_dev = cdev_alloc();//为字符设备申请空间
- if(chardd_dev==NULL)
- printk(KERN_INFO "cdev alloc error");
- else
- printk(KERN_INFO "cdev alloc ok");
- chardd_dev->ops = &chardd_fops;//绑定fileoepration操作
- cdev_init(chardd_dev, &chardd_fops);
- printk(KERN_INFO "cdev init ok");
- rt = cdev_add(chardd_dev, dev, 1); //添加字符设备
- if(rt==0)
- printk(KERN_INFO "cdev add ok");
- else
- printk(KERN_INFO "cdev add error");
- return 0;
- }
- static void __exit exitchardd(void)
- {
- int chardd_count = 2;
- //释放设备号
- unregister_chrdev_region(dev, chardd_count);
- //删除字符设备
- cdev_del(chardd_dev);
- printk(KERN_INFO "char device driver exit");
- }
- module_init(initchardd);
- module_exit(exitchardd);
- MODULE_LICENSE("Dual BSD/GPL");
- MODULE_AUTHOR("ybdesire");
- MODULE_DESCRIPTION("a .ko character device driver demo");
- MODULE_VERSION("v10");
示例代码2:用户空间使用驱动(c语言) [cpp] view plain copy
- /**********************************************
- *** 字符设备驱动测试程序
- *** Autor: ybdesire
- *** Create Date: 2012-11-23
- *** Completion Date:2012-11-25
- **********************************************/
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- int main()
- {
- int fp, ret;
- unsigned char ch[]="hello kernel device driver!";
- unsigned char *buf1 = (unsigned char *)malloc(sizeof(ch)+1);
- unsigned char *buf2 = (unsigned char *)malloc(sizeof(ch)+1);
- //init buff
- memset(buf1, 0, sizeof(ch)+1);
- memset(buf2, 0, sizeof(ch)+1);
- strcpy(buf1, ch);//ch to buf1
- //打开设备驱动,前提是用mknode把字符设备建立为/dev/chardd
- fp = open("/dev/chardd", O_RDONLY);
- printf("fp is %d\n", fp);
- ret = write(fp, buf1, sizeof(ch));//write buf1 to fp
- printf("write return : %d\n", ret);
- ret = read(fp, buf2, sizeof(ch));//read fp to buf2
- printf("read return : %d\n", ret);
- printf("read data:%s\n", buf2);
- close(fp);
- return 0;
- }
【编译过程及使用示例】
[cpp] view plain copy
- ybde @ubuntu:~/Test/Driver/ch3/chardd$ sudo insmod chardd.ko
- ybde @ubuntu:~/Test/Driver/ch3/chardd$ mknod /dev/chardd c 250 5
- ybde: "/dev/chardd": 权限不够
- ybde @ubuntu:~/Test/Driver/ch3/chardd$ sudo mknod /dev/chardd c 250 5
- ybde @ubuntu:~/Test/Driver/ch3/chardd$ cd main/ (main中为测试代码)
- ybde @ubuntu:~/Test/Driver/ch3/chardd/main$ gcc main.c
- ybde @ubuntu:~/Test/Driver/ch3/chardd/main$ ./a.out
- fp is -1
- write return : -1
- read return : -1
- read data:
- ybde @ubuntu:~/Test/Driver/ch3/chardd/main$ sudo ./a.out
- fp is 3
- write return : 0
- read return : 0
- read data:hello kernel device driver!
- 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中做内存管理
更多推荐
字符设备驱动理解
发布评论