Linux驱动学习----字符设备驱动(一)
2012-11-15 10:59
281 查看
写在前面的话:
上一篇,我们讲到了简单模块的编写,以及它的加载编译。大家可能会感觉这是不是太简单了,确实,它仅仅只是一个HelloWorld的模块,没有什么实际的意义。
今天,我们就来点实际的,目标就是编写一个完整的字符设备驱动程序。
首先,我们来看看怎么样的文件时字符设备驱动。它们通常位于/dev目录下,字符设备驱动程序的设备文件可通过“ls-l”命令输出的第一列中的"c"来识别。块设备也在/dev目录下,它们由字符"b"来识别。
如下:
除此之外,我们还可以看见,第4列和第5列的两个数字表示了相应设备的主设备号和次设备号。通常而言,主设备号标识设备对应的驱动程序,而次设备号由内核使用,用于正确确定设备文件所指的设备。这是在系统中显示的设备编号,那么在内核中它又是怎么样的呢?dev_t(<linux/types.h>中定义)保存设备编号----包含主设备号和次设备号。在2.6.0版本中,dev_t是一个32位整数,其中的12位用来表示主设备号,其余20位用来表示次设备号。
好了,讲了这么多,接下来,让我们来看一下整个字符设备驱动的框架该怎么去搭建。
既然设备是按照它自己的编号去识别,那么,我们首要的任务就是去获得一个或多个设备编号。这里有两个函数可以做到:
但是在使用这个函数时,有一个前提条件,就是你必须知道设备编号,可是我们很多时候都是不知道的,因此,我只好将这个工作交给内核了:利用下面这个函数:
这里还有一个至关重要的问题,就是我们在编写程序该用哪一个函数呢?各有千秋吧!对的,那么,我们究竟应该将它们都应用到我们的程序中:
这是我的程序中字符设备驱动初始化函数。其中将两个初始化函数结合起来了。
正如ldd3中所写的:分配主设备号的最佳方式是,默认采用动态分配方式,同时保留在加载甚至是编译时指定主设备号的余地。
当然,不管是使用哪个函数,我们现在已经可以将设备编号成功的申请好了。
那么下面我就要来说如何去释放它,因为你不可能一直都占有着它。当你要去释放它的时候,就要使用如下函数:
void unregister_chrdev_region(dev_t from, unsigned count);
讲完了设备编号的获得和释放,接下来我们来看看一个与字符设备驱动密切相关的数据结构----cdev。它定义在<linux/cdev.h>
对于这个结构体的使用,你既可以单独地将它作为一个结构来使用,也可以将其嵌进你自己设备特定的结构中。个人觉得,将其嵌进自己的结构体会变得方便和简洁。如下:
那么接下来的问题还是很实际的问题,该如何去使用它。
首先,它作为一个结构体,我们就需要去填充它,即初始化。利用下面这个函数(定义在fs/char_dev.c):
其中也有一个所有者字段,应被置为THIS_MODULE。
其次,就应该将其信息告诉内核,利用下面的函数(定义在fs/char_dev.c):
当然,要有始有终,既然有add,就必然会有delete。
最后,提醒一句:在驱动程序还没有完全准备好处理设备上的操作时,就不能调用cdev_add。
因为,cdev_add一调用,整个驱动就开始运行了,开始了设备上的操作;但是如果你的操作都没有弄好,你的驱动又该会驶往何方……
好了,就先讲到cdev吧。
至此,我们已经将字符设备的注册已经将将完了;下次,我们来看看字符设备的那些操作结构体,那样,我们的程序就完整了!
上一篇,我们讲到了简单模块的编写,以及它的加载编译。大家可能会感觉这是不是太简单了,确实,它仅仅只是一个HelloWorld的模块,没有什么实际的意义。
今天,我们就来点实际的,目标就是编写一个完整的字符设备驱动程序。
首先,我们来看看怎么样的文件时字符设备驱动。它们通常位于/dev目录下,字符设备驱动程序的设备文件可通过“ls-l”命令输出的第一列中的"c"来识别。块设备也在/dev目录下,它们由字符"b"来识别。
如下:
1 [root@localhost dev]# ls -l 2 crw-rw---- 1 root tty 2, 10 1月 7 02:13 ptypa 3 crw-rw---- 1 root tty 2, 11 1月 7 02:13 ptypb 4 crw-rw---- 1 root tty 2, 12 1月 7 02:13 ptypc 5 crw-rw---- 1 root tty 2, 13 1月 7 02:13 ptypd 6 crw-rw---- 1 root tty 2, 14 1月 7 02:13 ptype 7 crw-rw---- 1 root tty 2, 15 1月 7 02:13 ptypf 8 brw-rw---- 1 root disk 1, 0 1月 7 02:13 ram0 9 brw-rw---- 1 root disk 1, 1 1月 7 02:13 ram1 10 brw-rw---- 1 root disk 1, 10 1月 7 02:13 ram10 11 brw-rw---- 1 root disk 1, 11 1月 7 02:13 ram11 12 brw-rw---- 1 root disk 1, 12 1月 7 02:13 ram12 13 brw-rw---- 1 root disk 1, 13 1月 7 02:13 ram13 14 brw-rw---- 1 root disk 1, 14 1月 7 02:13 ram14 15 brw-rw---- 1 root disk 1, 15 1月 7 02:13 ram15
除此之外,我们还可以看见,第4列和第5列的两个数字表示了相应设备的主设备号和次设备号。通常而言,主设备号标识设备对应的驱动程序,而次设备号由内核使用,用于正确确定设备文件所指的设备。这是在系统中显示的设备编号,那么在内核中它又是怎么样的呢?dev_t(<linux/types.h>中定义)保存设备编号----包含主设备号和次设备号。在2.6.0版本中,dev_t是一个32位整数,其中的12位用来表示主设备号,其余20位用来表示次设备号。
好了,讲了这么多,接下来,让我们来看一下整个字符设备驱动的框架该怎么去搭建。
既然设备是按照它自己的编号去识别,那么,我们首要的任务就是去获得一个或多个设备编号。这里有两个函数可以做到:
int register_chrdev_region(dev_t from, unsigned count, const char *name); dev_t from:要分配的设备编号范围的起始值,其次设备号经常设置为0。 unsigned count:所请求的连续设备编号的个数。 const char *name:和该编号范围关联的设备名称,它将出现在/proc/devices和sysfs中。
但是在使用这个函数时,有一个前提条件,就是你必须知道设备编号,可是我们很多时候都是不知道的,因此,我只好将这个工作交给内核了:利用下面这个函数:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name); dev_t *dev:用于输出的参数,在成功完成调用后,将保存已分配范围的第一个编号。 unsigned baseminor:要使用的被请求的第一个次设备号,通常是0。 unsigned count和const char *name和上面的意义一样。
这里还有一个至关重要的问题,就是我们在编写程序该用哪一个函数呢?各有千秋吧!对的,那么,我们究竟应该将它们都应用到我们的程序中:
static int __init module_framework_init(void) { int result; dev_t devno = 0; if (module_framework_major) { devno = MKDEV(module_framework_major, module_framework_minor); result = register_chrdev_region(devno, 1, "module_framework"); } else { result = alloc_chrdev_region(&devno, module_framework_major, 1, "module_framework"); module_framework_major = MAJOR(devno); } if (result < 0) return result; module_framework_devp = kmalloc(sizeof(struct module_framework_dev), GFP_KERNEL); if (!module_framework_devp) { result = -ENOMEM; goto fail_malloc; } memset(module_framework_devp, 0, sizeof(struct module_framework_dev)); module_framework_setup_cdev(module_framework_devp, 0); return 0; fail_malloc: unregister_chrdev_region(devno, 1); return result; }
这是我的程序中字符设备驱动初始化函数。其中将两个初始化函数结合起来了。
正如ldd3中所写的:分配主设备号的最佳方式是,默认采用动态分配方式,同时保留在加载甚至是编译时指定主设备号的余地。
当然,不管是使用哪个函数,我们现在已经可以将设备编号成功的申请好了。
那么下面我就要来说如何去释放它,因为你不可能一直都占有着它。当你要去释放它的时候,就要使用如下函数:
void unregister_chrdev_region(dev_t from, unsigned count);
static void __exit module_framework_exit(void) { cdev_del(&module_framework_devp->cdev); kfree(module_framework_devp); unregister_chrdev_region(MKDEV(module_framework_major, module_framework_minor), 1); }
讲完了设备编号的获得和释放,接下来我们来看看一个与字符设备驱动密切相关的数据结构----cdev。它定义在<linux/cdev.h>
struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; };
对于这个结构体的使用,你既可以单独地将它作为一个结构来使用,也可以将其嵌进你自己设备特定的结构中。个人觉得,将其嵌进自己的结构体会变得方便和简洁。如下:
struct globalmem_dev { struct cdev cdev; unsigned char mem[GLOBALMEM_SIZE]; };
那么接下来的问题还是很实际的问题,该如何去使用它。
首先,它作为一个结构体,我们就需要去填充它,即初始化。利用下面这个函数(定义在fs/char_dev.c):
void cdev_init(struct cdev *cdev,conststruct file_operations *fops);
其中也有一个所有者字段,应被置为THIS_MODULE。
其次,就应该将其信息告诉内核,利用下面的函数(定义在fs/char_dev.c):
int cdev_add(struct cdev *p, dev_t dev, unsigned count); struct cdev *p:cdev结构体 dev_t dev:该设备对应的第一个设备编号 unsigned count:应该和该设备关联的设备编号的数量,经常取1
当然,要有始有终,既然有add,就必然会有delete。
void cdev_del(struct cdev *p);
最后,提醒一句:在驱动程序还没有完全准备好处理设备上的操作时,就不能调用cdev_add。
因为,cdev_add一调用,整个驱动就开始运行了,开始了设备上的操作;但是如果你的操作都没有弄好,你的驱动又该会驶往何方……
好了,就先讲到cdev吧。
至此,我们已经将字符设备的注册已经将将完了;下次,我们来看看字符设备的那些操作结构体,那样,我们的程序就完整了!
相关文章推荐
- Linux嵌入式学习-烟雾传感器驱动-字符设备驱动-按键驱动
- linux字符设备驱动学习笔记(一):简单的字符设备驱动
- Linux驱动模型学习(二)---字符设备驱动模型之二---初窥字符设备驱动
- Linux设备驱动程式学习(5)-高级字符驱动程式操作[(2)阻塞型I/O和休眠]
- 基于mini6410的linux驱动学习总结(四 设计字符设备驱动程序)
- 嵌入式linux学习笔记4之字符设备驱动
- linux字符设备驱动-重新学习-笔记-2
- linux驱动学习--第二十一天:第十二章:Linux 字符设备驱动综合实例(一) 键盘驱动
- linux驱动学习(四) linux字符设备驱动 cdev
- linux驱动学习--第二十三天:第十二章:Linux 字符设备驱动综合实例(三)NVRAM 设备驱动 和 看门狗设备驱动
- Linux设备驱动程式学习(6)-高级字符驱动程式操作[(3)设备文档的访问控制]
- [Linux驱动]字符设备驱动学习笔记(一)
- [Linux驱动]字符设备驱动学习笔记(二)———实例
- linux驱动学习--第二十二天:第十二章:Linux 字符设备驱动综合实例(二) 触摸屏的设备驱动 和 linux输入子系统
- linux驱动学习记录(一)-字符设备框架
- Linux设备驱动开发学习(1)--字符设备驱动
- [Linux驱动]字符设备驱动学习笔记(三)———高级
- 国嵌--linux字符设备驱动学习之memdev设备
- Linux驱动学习(二)——字符设备驱动程序入门 .
- 一步一步学习 Linux 驱动之字符设备 LED