Linux设备驱动程序学习笔记06:字符设备驱动程序IV

本文将一块内存当做一个字符设备,并为它写一个驱动程序。

字符设备结构体

我们可以用一个cdev结构体来表示一个设备。鼠标、键盘、触摸屏等都可以被当做一个字符设备,这些设备都有一些自己独有的属性。比如我们将要用到的这块内存,它有一个属性是这块内存的大小,这个属性对于鼠标键盘来说是没有的。要如何通过一个通用的cdev结构体来表示包含有各种不同属性的结构体呢?

内核虽然是用C和汇编语言编写的,如果去阅读内核代码就会发现其中包含了大量的面向对象的编程思想。其中一个技巧就是将一个结构体A嵌入到另一个结构体B中。仿照面向对象的语言,在某种意义上我们可以将结构体A理解成结构体B的父类(基类),当然结构体B就是结构体A的子类(派生类)。运用这个技巧,我们可以为我们的设备定义一个结构体:

struct memdev {
	struct cdev cdev;
	unsigned char mem[MEM_SIZE];
};

接下来就为我们的结构体创建一个实例:

struct memdev *devp;
devp = kmalloc(sizeof(struct memdev), GFP_KERNEL);

在使用设备结构体实例之前,应先将里面的内容清零,否则在后面的使用过程中可能会出现无法预知的错误(注:结构体中包含可大量的内容,我们后面只会对部分内容进行初始化,如果不先将整个结构体清零的话有些内容可能会存在垃圾值而导致出错)。采用下面的系统调用即可将我们的设备结构体实例清零:

memset(devp, 0, sizeof(struct memdev));

字符设备的注册流程

到此我们已经知道了怎么申请设备号,构建file_operations结构体(对字符设备的实际操作在这里定义),以及如何创建我们的设备结构体实例。只需将这些内容同内核联系起来就是一个完整的字符设备驱动程序了。系统提供了下面的过程来实施这些联系:

void cdev_init(struct cdev *cdev, const struct file_operations *fops);
int cdev_add(struct cdev *p, dev_t dev, unsigned count);

我们来分别分析一下这两个函数:

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	memset(cdev, 0, sizeof *cdev);
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default);
	cdev->ops = fops;
}

从字面也好理解cdev_init的作用是初始化cdev结构体的。它主要完成了下面的几个动作:

1、  将cdev结构体清零,我们在创建结构体实例的时候应经做过这个动作了。

2、  初始化cdev的list链表,这个链表用于收集相同字符设备驱动程序所对应的字符设备文件的索引节点。

3、  初始化kobject成员,kobject是linux驱动模型中一个不可缺少的结构体。当学到linux设备驱动模型的时候再来做深入探讨。

4、  将file_operations结构体同我们的字符设备联系起来。

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	p->dev = dev;
	p->count = count;
	return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}

cdev_add主要完成了下面的几个动作:

1、  初始化我们字符设备的设备号以及设备号范围。

2、  kobj_map的内部机制比较复杂,暂时可以简单地理解为将我们的cdev注册到内核的设备驱动模型中即将我们之前所介绍的内容同内核联系起来。

cdev结构体的ower成员应该在调用cdev_add之前初始化。使用我们自己定义的设备结构体,整个注册流程应该是这样的:

cdev_init(&devp->cdev, &memdev_fops);
devp->cdev.owner = THIS_MODULE;
ret = cdev_add(&devp->cdev, devno, 1);

到这里,这个字符设备驱动程序的整体框架已经完成了。要实际操作我们的设备,还需要实现file_operations结构体中的回调函数。

 

字符设备的操作函数

我们驱动程序中的file_operations结构体定义如下:

static struct file_operations memdev_fops = {
	.owner = THIS_MODULE,
	.llseek = memdev_llseek,
	.read = memdev_read,
	.write = memdev_write,
	.ioctl = memdev_ioctl,
	.open = memdev_open,
	.release = memdev_release,
};

只需要实现其中的回调函数,就完成整个设备驱动程序,这些函数都是同实际设备相关的,针对我们的这块内存。这里只简单介绍一下memdev_open和memdev_release的实现。

int memdev_open(struct inode *nodep, struct file *fp)
{
	struct memdev *devp;
	devp = container_of(nodep->i_cdev, struct memdev, cdev);
	fp->private_data = devp;
	return 0;
}

当上层应用程序调用open的时候,内核会memdev_open。它应该完成一些硬件检测、申请资源、初始化等一些工作,并配置file结构体的private_data成员。由于我们的设备只是一块内存,我们只需要初始化private_data就可以了,通常我们将它指向我们的设备结构实例。

前面我们讲过当inode指向一个字符设备文件时,其i_cdev成员是一个指向cdev结构体的指针。但我们需要的是我们的memdev实例,而cdev是memdev的一个成员。内核提供了container_of宏,使我们可以通过cdev的指针来获取memdev的指针。

int memdev_release(struct inode *nodep, struct file *fp)
{
	return 0;
}

当上层应用程序调用close的时候调用memdev_release。注意:并不是每个close调用都会引起对release方法的调用。内核对每个file结构为其被使用次数的计数器。无论是fork还是dup,都不会创建新的数据结构,它们只是增加已有结构体中的计数。只有在file结构的计数归零时,close调用才会引起release的调用。release主要完成的任务是释放由open分配的资源及保存在file->private_data中的内容。针对我们这个例子,我们只需要简单返回就可以了。

在写read和write回调函数时需要注意:当需要在用户空间和内核空间之间进行数据传输时需要用到下面两个调用:

static inline long copy_to_user(void __user *to, const void *from, unsigned long n);
static inline long copy_from_user(void *to, const void __user * from, unsigned long n);

至此字符设备驱动程序的所有基本内容都已介绍完了。将在下一篇文章中,给出完整的驱动程序代码以及一个简单的测试程序。








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值