count web page visits
Panasonic Viera 3D

Linux驱动学习----字符设备驱动(一)

写在前面的话:       

上一篇,我们讲到了简单模块的编写,以及它的加载编译。大家可能会感觉这是不是太简单了,确实,它仅仅只是一个HelloWorld的模块,没有什么实际的意义。

今天,我们就来点实际的,目标就是编写一个完整的字符设备驱动程序。


首先,我们来看看怎么样的文件时字符设备驱动。它们通常位于/dev目录下,字符设备驱动程序的设备文件可通过“ls-l”命令输出的第一列中的"c"来识别。块设备也在/dev目录下,它们由字符"b"来识别。

如下:

 1     1 [root@localhost dev]# ls -l
 2     2 crw-rw---- 1 root tty       2,  10  1月  7 02:13 ptypa
 3     3 crw-rw---- 1 root tty       2,  11  1月  7 02:13 ptypb
 4     4 crw-rw---- 1 root tty       2,  12  1月  7 02:13 ptypc
 5     5 crw-rw---- 1 root tty       2,  13  1月  7 02:13 ptypd
 6     6 crw-rw---- 1 root tty       2,  14  1月  7 02:13 ptype
 7     7 crw-rw---- 1 root tty       2,  15  1月  7 02:13 ptypf
 8     8 brw-rw---- 1 root disk      1,   0  1月  7 02:13 ram0
 9     9 brw-rw---- 1 root disk      1,   1  1月  7 02:13 ram1
10    10 brw-rw---- 1 root disk      1,  10  1月  7 02:13 ram10
11    11 brw-rw---- 1 root disk      1,  11  1月  7 02:13 ram11
12    12 brw-rw---- 1 root disk      1,  12  1月  7 02:13 ram12
13    13 brw-rw---- 1 root disk      1,  13  1月  7 02:13 ram13
14    14 brw-rw---- 1 root disk      1,  14  1月  7 02:13 ram14
15    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和上面的意义一样。

这里还有一个至关重要的问题,就是我们在编写程序该用哪一个函数呢?各有千秋吧!对的,那么,我们究竟应该将它们都应用到我们的程序中:

 1 static int __init module_framework_init(void)
 2 {
 3     int result;
 4     dev_t devno = 0;
 5  
 6     if (module_framework_major) {
 7         devno = MKDEV(module_framework_major, module_framework_minor);
 8         result = register_chrdev_region(devno, 1, "module_framework");
 9     } else {
10         result = alloc_chrdev_region(&devno, module_framework_major, 1, "module_framework");
11         module_framework_major = MAJOR(devno);
12     }
13     if (result < 0)
14         return result;
15  
16     module_framework_devp = kmalloc(sizeof(struct module_framework_dev), GFP_KERNEL);
17     if (!module_framework_devp) {
18         result = -ENOMEM;
19         goto fail_malloc;
20     }
21     memset(module_framework_devp, 0, sizeof(struct module_framework_dev));
22     module_framework_setup_cdev(module_framework_devp, 0);
23  
24     return 0;
25 fail_malloc:
26     unregister_chrdev_region(devno, 1);
27     return result;
28 }

这是我的程序中字符设备驱动初始化函数。其中将两个初始化函数结合起来了。

正如ldd3中所写的:分配主设备号的最佳方式是,默认采用动态分配方式,同时保留在加载甚至是编译时指定主设备号的余地。

当然,不管是使用哪个函数,我们现在已经可以将设备编号成功的申请好了。

那么下面我就要来说如何去释放它,因为你不可能一直都占有着它。当你要去释放它的时候,就要使用如下函数:

void unregister_chrdev_region(dev_t from, unsigned count);

1 static void __exit module_framework_exit(void)
2 {
3     cdev_del(&module_framework_devp->cdev);
4     kfree(module_framework_devp);
5     unregister_chrdev_region(MKDEV(module_framework_major, module_framework_minor), 1);    
6 }

讲完了设备编号的获得和释放,接下来我们来看看一个与字符设备驱动密切相关的数据结构----cdev。它定义在<linux/cdev.h>

1 struct cdev {
2         struct kobject kobj;
3         struct module *owner;
4         const struct file_operations *ops;
5         struct list_head list;
6         dev_t dev;
7         unsigned int count;
8 };

 对于这个结构体的使用,你既可以单独地将它作为一个结构来使用,也可以将其嵌进你自己设备特定的结构中。个人觉得,将其嵌进自己的结构体会变得方便和简洁。如下:

1 struct globalmem_dev
2 {
3         struct cdev cdev;
4         unsigned char mem[GLOBALMEM_SIZE];
5 };

那么接下来的问题还是很实际的问题,该如何去使用它。

首先,它作为一个结构体,我们就需要去填充它,即初始化。利用下面这个函数(定义在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吧。

至此,我们已经将字符设备的注册已经将将完了;下次,我们来看看字符设备的那些操作结构体,那样,我们的程序就完整了!

posted @ 2012-11-15 10:59  夜半私时  阅读(944)  评论(0编辑  收藏  举报