您的位置:首页 > 运维架构 > Linux

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

2012-11-15 10:59 281 查看
写在前面的话:

上一篇,我们讲到了简单模块的编写,以及它的加载编译。大家可能会感觉这是不是太简单了,确实,它仅仅只是一个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吧。

至此,我们已经将字符设备的注册已经将将完了;下次,我们来看看字符设备的那些操作结构体,那样,我们的程序就完整了!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐