分类: LINUX
2015-04-14 12:26:10
原文地址:linux设备驱动第三篇:写一个简单的字符设备驱动 作者:846717529
在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动。本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存。
下面就开始学习如何写一个简单的字符设备驱动。首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作。
inode 结构由内核在内部用来表示文件. 因此, 它和代表打开文件描述符的文件结构是不同的. 可能有代表单个文件的多个打开描述符的许多文件结构, 但是它们都指向一个单个 inode 结构。
inode 结构包含大量关于文件的信息。但对于驱动程序编写来说一般不用关心,暂且不说。
有 2 种方法来分配和初始化一个这些结构. 如果你想在运行时获得一个独立的 cdev 结构, 你可以为此使用这样的代码:
更多的情况是把cdv结构嵌入到你自己封装的设备结构中,这时需要使用下面的方法来分配和初始化:
- struct cdev *my_cdev = cdev_alloc();
- my_cdev->ops = &my_fops;
后面的例子程序就是这么做的。一旦 cdev 结构建立, 最后的步骤是把它告诉内核:
- void cdev_init(struct cdev *cdev, struct file_operations *fops);
- int cdev_add(struct cdev *dev, dev_t num, unsigned int count)
这里, dev 是 cdev 结构, num 是这个设备响应的第一个设备号, count 是应当关联到设备的设备号的数目. 常常 count 是 1。
从系统去除一个字符设备, 调用:
- void cdev_del(struct cdev *dev);
4、一个简单的字符设备
上面大致介绍了实现一个字符设备所要做的工作,下面就来一个真实的例子来总结上面介绍的内容。源码中的关键地方已经作了注释。Makefile文件如下:
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #define CDEVDEMO_MAJOR 255 /*预设cdevdemo的主设备号*/
- static int cdevdemo_major = CDEVDEMO_MAJOR;
- /*设备结构体,此结构体可以封装设备相关的一些信息等
- 信号量等也可以封装在此结构中,后续的设备模块一般都
- 应该封装一个这样的结构体,但此结构体中必须包含某些
- 成员,对于字符设备来说,我们必须包含struct cdev cdev*/
- struct cdevdemo_dev
- {
- struct cdev cdev;
- };
- struct cdevdemo_dev *cdevdemo_devp; /*设备结构体指针*/
- /*文件打开函数,上层对此设备调用open时会执行*/
- int cdevdemo_open(struct inode *inode, struct file *filp)
- {
- printk(KERN_NOTICE "======== cdevdemo_open ");
- return 0;
- }
- /*文件释放,上层对此设备调用close时会执行*/
- int cdevdemo_release(struct inode *inode, struct file *filp)
- {
- printk(KERN_NOTICE "======== cdevdemo_release ");
- return 0;
- }
- /*文件的读操作,上层对此设备调用read时会执行*/
- static ssize_t cdevdemo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
- {
- printk(KERN_NOTICE "======== cdevdemo_read ");
- }
- /* 文件操作结构体,文中已经讲过这个结构*/
- static const struct file_operations cdevdemo_fops =
- {
- .owner = THIS_MODULE,
- .open = cdevdemo_open,
- .release = cdevdemo_release,
- .read = cdevdemo_read,
- };
- /*初始化并注册cdev*/
- static void cdevdemo_setup_cdev(struct cdevdemo_dev *dev, int index)
- {
- printk(KERN_NOTICE "======== cdevdemo_setup_cdev 1");
- int err, devno = MKDEV(cdevdemo_major, index);
- printk(KERN_NOTICE "======== cdevdemo_setup_cdev 2");
- /*初始化一个字符设备,设备所支持的操作在cdevdemo_fops中*/
- cdev_init(&dev->cdev, &cdevdemo_fops);
- printk(KERN_NOTICE "======== cdevdemo_setup_cdev 3");
- dev->cdev.owner = THIS_MODULE;
- dev->cdev.ops = &cdevdemo_fops;
- printk(KERN_NOTICE "======== cdevdemo_setup_cdev 4");
- err = cdev_add(&dev->cdev, devno, 1);
- printk(KERN_NOTICE "======== cdevdemo_setup_cdev 5");
- if(err)
- {
- printk(KERN_NOTICE "Error %d add cdevdemo %d", err, index);
- }
- }
- int cdevdemo_init(void)
- {
- printk(KERN_NOTICE "======== cdevdemo_init ");
- int ret;
- dev_t devno = MKDEV(cdevdemo_major, 0);
- struct class *cdevdemo_class;
- /*申请设备号,如果申请失败采用动态申请方式*/
- if(cdevdemo_major)
- {
- printk(KERN_NOTICE "======== cdevdemo_init 1");
- ret = register_chrdev_region(devno, 1, "cdevdemo");
- }else
- {
- printk(KERN_NOTICE "======== cdevdemo_init 2");
- ret = alloc_chrdev_region(&devno,0,1,"cdevdemo");
- cdevdemo_major = MAJOR(devno);
- }
- if(ret < 0)
- {
- printk(KERN_NOTICE "======== cdevdemo_init 3");
- return ret;
- }
- /*动态申请设备结构体内存*/
- cdevdemo_devp = kmalloc(sizeof(struct cdevdemo_dev), GFP_KERNEL);
- if(!cdevdemo_devp) /*申请失败*/
- {
- ret = -ENOMEM;
- printk(KERN_NOTICE "Error add cdevdemo");
- goto fail_malloc;
- }
- memset(cdevdemo_devp,0,sizeof(struct cdevdemo_dev));
- printk(KERN_NOTICE "======== cdevdemo_init 3");
- cdevdemo_setup_cdev(cdevdemo_devp, 0);
- /*下面两行是创建了一个总线类型,会在/sys/class下生成cdevdemo目录
- 这里的还有一个主要作用是执行device_create后会在/dev/下自动生成
- cdevdemo设备节点。而如果不调用此函数,如果想通过设备节点访问设备
- 需要手动mknod来创建设备节点后再访问。*/
- cdevdemo_class = class_create(THIS_MODULE, "cdevdemo");
- device_create(cdevdemo_class, NULL, MKDEV(cdevdemo_major, 0), NULL, "cdevdemo");
- printk(KERN_NOTICE "======== cdevdemo_init 4");
- return 0;
- fail_malloc:
- unregister_chrdev_region(devno,1);
- }
- void cdevdemo_exit(void) /*模块卸载*/
- {
- printk(KERN_NOTICE "End cdevdemo");
- cdev_del(&cdevdemo_devp->cdev); /*注销cdev*/
- kfree(cdevdemo_devp); /*释放设备结构体内存*/
- unregister_chrdev_region(MKDEV(cdevdemo_major,0),1); //释放设备号
- }
- MODULE_LICENSE("Dual BSD/GPL");
- module_param(cdevdemo_major, int, S_IRUGO);
- module_init(cdevdemo_init);
- module_exit(cdevdemo_exit);
温馨提示:测试环境为Linux ubuntu 3.16.0-33-generic。
- ifneq ($(KERNELRELEASE),)
- obj-m := cdevdemo.o
- else
- KERNELDIR ?= /lib/modules/$(shell uname -r)/build
- PWD := $(shell pwd)
- default:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
- endif
- clean:
- rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers
5、总结
本篇主要介绍了简单字符设备的编写与实现以及其中的关键点。下一篇会主要讲解下驱动的一些常用的调试技巧。
第一时间获得博客更新提醒,以及更多技术信息分享,欢迎关注个人微信公众平台:程序员互动联盟(coder_online),扫一扫下方二维码或搜索微信号coder_online即可关注,我们可以在线交流。