简单字符设备驱动程序分析

下面是一个简单的字符设别驱动程序的源代码,本文会分析代码中涉及到的函数。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>


MODULE_AUTHOR("XXX Technologies GmbH");
MODULE_LICENSE("GPL");

#define	CORDLESS_MAJOR	240

static char dev0_name[] = "char_device0";
static char dev1_name[] = "char_device1";
static char dev2_name[] = "char_device2";

static dev_t dev0_dev;
static dev_t dev1_dev;
static dev_t dev2_dev;

static struct cdev dev0_cdev;
static struct cdev dev1_cdev;
static struct cdev dev2_cdev;

static int
dev0_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arq)
{
	printk("dev0_ioctl\n");
	return 0;
}
static int
dev0_read(struct file *filp, char __user *buf, size_t count_want,loff_t *f_pos)
{
	printk("dev0_read\n");
	return 0;
}
static int
dev0_open(struct inode *inode, struct file *filp)
{
	printk("dev0_open\n");
	return 0;
}
static int
dev0_release(struct inode *inode, struct file *filp)
{
	printk("dev0_release\n");
	return 0;
}
static struct file_operations dev0_fops = {
	.owner = 	THIS_MODULE,
	.ioctl = 	dev0_ioctl,
	.read = 	dev0_read,
	.open = 	dev0_open,
	.release = 	dev0_release,
};
static int
dev1_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arq)
{
	printk("dev1_ioctl\n");
	return 0;
}
static int
dev1_read(struct file *filp, char __user *buf, size_t count_want,loff_t *f_pos)
{
	printk("dev1_read\n");
	return 0;
}
static int
dev1_open(struct inode *inode, struct file *filp)
{
	printk("dev1_open\n");
	return 0;
}
static int
dev1_release(struct inode *inode, struct file *filp)
{
	printk("dev1_release\n");
	return 0;
}
static struct file_operations dev1_fops = {
	.owner = 	THIS_MODULE,
	.ioctl = 	dev1_ioctl,
	.read = 	dev1_read,
	.open = 	dev1_open,
	.release = 	dev1_release,
};
static int
dev2_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arq)
{
	printk("dev2_ioctl\n");
	return 0;
}
static int
dev2_read(struct file *filp, char __user *buf, size_t count_want,loff_t *f_pos)
{
	printk("dev2_read\n");
	return 0;
}
static int
dev2_open(struct inode *inode, struct file *filp)
{
	printk("dev2_open\n");
	return 0;
}
static int
dev2_release(struct inode *inode, struct file *filp)
{
	printk("dev2_release\n");
	return 0;
}
static struct file_operations dev2_fops = {
	.owner = 	THIS_MODULE,
	.ioctl = 	dev2_ioctl,
	.read = 	dev2_read,
	.open = 	dev2_open,
	.release = 	dev2_release,
};

static int __init 
char_dev_test_init(void)
{
	dev0_dev = MKDEV(CORDLESS_MAJOR,0);
	register_chrdev_region(dev0_dev,1,dev0_name);
	
	dev1_dev = MKDEV(CORDLESS_MAJOR,1);
	register_chrdev_region(dev1_dev,1,dev1_name);
	
	dev2_dev = MKDEV(CORDLESS_MAJOR,2);
	register_chrdev_region(dev2_dev,1,dev2_name);

	cdev_init(&dev0_cdev,&dev0_fops);
	cdev_init(&dev1_cdev,&dev1_fops);
	cdev_init(&dev2_cdev,&dev2_fops);

	dev0_cdev.owner = THIS_MODULE;
	cdev_add(&dev0_cdev,dev0_dev,1);
	
	dev1_cdev.owner = THIS_MODULE;
	cdev_add(&dev1_cdev,dev1_dev,1);

	dev2_cdev.owner = THIS_MODULE;
	cdev_add(&dev2_cdev,dev2_dev,1);

	return 0;
}

static void __exit
char_dev_test_exit(void)
{
	cdev_del(&dev0_cdev);
	unregister_chrdev_region(dev0_dev,1);

	cdev_del(&dev1_cdev);
	unregister_chrdev_region(dev1_dev,1);

	cdev_del(&dev2_cdev);
	unregister_chrdev_region(dev2_dev,1);
}
module_init(char_dev_test_init);
module_exit(char_dev_test_exit);


编译成ko文件并加载后,用mknod命令创建三个字符设备文件

# mknod /dev/char_device0  c  240  0
# mknod /dev/char_device1  c  240  1
# mknod /dev/char_device2  c  240  2

然后运行cat 命令,会得到以下结果

# cat /dev/char_device0
[  272.210000] dev0_open
[  272.210000] dev0_read
[  272.210000] dev0_release
# cat /dev/char_device1
[  274.030000] dev1_open
[  274.030000] dev1_read
[  274.040000] dev1_release
# cat /dev/char_device2
[  275.050000] dev2_open
[  275.050000] dev2_read
[  275.070000] dev2_release
从代码中可以看出该字符设备程序主要涉及三个变量:设备名(char),设备号(dev_t),字符设备结构(cdev).


字符设备初始化过程也分为三个步骤:

1.首先构造设备号,再将设备号、设备名等信息添加到全局变量chrdevs[]数组中第major%255个元素所指向的链表中。
dev0_dev = MKDEV(CORDLESS_MAJOR,0);
register_chrdev_region(dev0_dev,1,dev0_name);

int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
	struct char_device_struct *cd;
	dev_t to = from + count;
	dev_t n, next;

	for (n = from; n < to; n = next) {
		next = MKDEV(MAJOR(n)+1, 0);
		if (next > to)
			next = to;
		cd = __register_chrdev_region(MAJOR(n), MINOR(n),
			       next - n, name);
		if (IS_ERR(cd))
			goto fail;
	}
	return 0;
fail:
	to = n;
	for (n = from; n < to; n = next) {
		next = MKDEV(MAJOR(n)+1, 0);
		kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
	}
	return PTR_ERR(cd);
}
register_chrdev_region()函数表示从from开始,注册count个设备,设备名是name。
之所以有个for循环的原因是每个设备号下面可以有1<<8或者1<<20个次设备号,如果
起始设备号from+加上想要注册的设备数count大于下一个主设备号组成的设备号,就需要
分两次进行注册。如果将该例子中的
register_chrdev_region(dev0_dev,1,dev0_name);
改为
register_chrdev_region(dev0_dev,256,dev0_name);
那么需要循环两次进行注册(假设一个主设备号下面最多有255个次设备号)。
下面分析__register_chrdev_region()函数
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
			   int minorct, const char *name)
{
	struct char_device_struct *cd, **cp;
	int ret = 0;
	int i;

	/*分配一个char_device_struct结构*/
	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
	if (cd == NULL)
		return ERR_PTR(-ENOMEM);

	mutex_lock(&chrdevs_lock);

	/*如果主设备号为0,表示由系统自动分配设备号*/
	/* temporary */
	if (major == 0) {
		for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
			if (chrdevs[i] == NULL)
				break;
		}

		if (i == 0) {
			ret = -EBUSY;
			goto out;
		}
		major = i;
		ret = major;
	}
	/*设置刚才分配的char_device_struct结构*/
	cd->major = major;
	cd->baseminor = baseminor;
	cd->minorct = minorct;
	strlcpy(cd->name, name, sizeof(cd->name));
	
	/*由major确定将要添加到的链表在chrdevs[]中的什么位置*/
	i = major_to_index(major);

	/*从链表头开始查找*/
	for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
	{
		/*如果找到的major大于要添加的major或者是其他情况,
		 *表示找到要插入的位置。
		 *第一次注册时,cp为NULL。
		 *如果之前注册的设备号都小的话,cp也为NULL。
		 *也就是说链表是按照设备号的大小链接的,小的在前面
		 */
		if ((*cp)->major > major ||
		    ((*cp)->major == major &&
		     (((*cp)->baseminor >= baseminor) ||
		      ((*cp)->baseminor + (*cp)->minorct > baseminor))))
			break;
	}
	/*判断设备号是否重叠*/
	/* Check for overlapping minor ranges.  */
	if (*cp && (*cp)->major == major) {
		int old_min = (*cp)->baseminor;
		int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
		int new_min = baseminor;
		int new_max = baseminor + minorct - 1;

		/* New driver overlaps from the left.  */
		if (new_max >= old_min && new_max <= old_max) {
			ret = -EBUSY;
			goto out;
		}

		/* New driver overlaps from the right.  */
		if (new_min <= old_max && new_min >= old_min) {
	
			ret = -EBUSY;
			goto out;
		}
	}
	/*将刚才分配到的char_device_struct结构插入链表*/
	cd->next = *cp;
	*cp = cd;
	mutex_unlock(&chrdevs_lock);
	return cd;
out:
	mutex_unlock(&chrdevs_lock);
	kfree(cd);
	return ERR_PTR(ret);
}
可以改变代码中register_chrdev_region()函数的第二个参数来验证设备号重叠的情况。

2.初始化cdev,注册操作函数集

cdev_init(&dev0_cdev,&dev0_fops);
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	/*清零*/
	memset(cdev, 0, sizeof *cdev);
	/*初始化list,open设备时会将设备文件索引节点inode->i_devices
	 *连接到该list上,从而建立设备文件和cdev结构之间的关联
	 */
	INIT_LIST_HEAD(&cdev->list);
	/*初始化kobj,*/
	kobject_init(&cdev->kobj, &ktype_cdev_default);
	/*设置操作函数集,当应用程序调用open,read,write等函数时,这些函数最终后调用
	 *fops里相应的函数
	 */
	cdev->ops = fops;
}
其中的kobject_init(&cdev->kobj, &ktype_cdev_default)是初始化cdev结构中的kobj变量。
cdev中含有kobj变量的一个主要原因是想利用kobj里的ref引用计数。
全局变量ktype_cdev_default里的cdev_default_release()函数会在cdev_del()中调用(引用计数减为0时)。
cdev_del()->kobject_put()->kobject_release()->kobject_cleanup()->cdev_default_release()

3.注册cdev

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	/*将设备号保存到cdev结构中*/
	p->dev = dev;
	p->count = count;
	/*映射到cdev_map[]数组中*/
	return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}

cdev_map是在chrdev_init()中初始化的。
void __init chrdev_init(void)
{
	cdev_map = kobj_map_init(base_probe, &chrdevs_lock);
	bdi_init(&directly_mappable_cdev_bdi);
}
先看下kobj_map_init

struct kobj_map {
	struct probe {
		struct probe *next;
		dev_t dev;
		unsigned long range;
		struct module *owner;
		kobj_probe_t *get;
		int (*lock)(dev_t, void *);
		void *data;
	} *probes[255];
	struct mutex *lock;
};
struct kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct mutex *lock)
{
  /*分配一个kobj_map结构*/
	struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL);
	/*分配一个probe结构*/

	struct probe *base = kzalloc(sizeof(*base), GFP_KERNEL);
	int i;

	if ((p == NULL) || (base == NULL)) {
		kfree(p);
		kfree(base);
		return NULL;
	}
	/*设置probe结构*/
	base->dev = 1;
	base->range = ~0;
	base->get = base_probe;
	/*将kobj_map结构中的probep[]数组中的255个元素都指向刚分配的probe结构*/
	for (i = 0; i < 255; i++)
		p->probes[i] = base;
	/*设置kobj_map结构中的锁,以后要操作该结构,都需先获取锁*/	
	p->lock = lock;
	return p;
}
kobj_map_init()是将probep[]数组中的255个元素都指向同一个probe结构。
而kobj_map()是将probep[]数组中的某个元素指向新的probe结构。也可以说成将一个新的包含有字符设备信息的
probe结构注册到probep[]数组中。

int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
	     struct module *module, kobj_probe_t *probe,
	     int (*lock)(dev_t, void *), void *data)
{
	/*如果dev+range大于一个主设备号下次设备号的最大数。就需要创建多个probe结构,注册多次*/
	unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
	/*确定注册到probep[]数组第几个元素中*/
	unsigned index = MAJOR(dev);
	unsigned i;
	struct probe *p;

	
	if (n > 255)
		n = 255;

	/*分配probe结构*/
	p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);

	if (p == NULL)
		return -ENOMEM;
  /*将字符设备信息保存到probe中,open字符设备时,会用到这些信息*/
	for (i = 0; i < n; i++, p++) {
		p->owner = module;
		p->get = probe;
		p->lock = lock;
		p->dev = dev;
		p->range = range;
		p->data = data;//指向cdev结构,里面有fops操作函数集
	}
	mutex_lock(domain->lock);
	for (i = 0, p -= n; i < n; i++, p++, index++) {
		struct probe **s = &domain->probes[index % 255];
		/*按range从小到大的顺序,将probe链接到probes[index % 255]指向的链表中*/
		while (*s && (*s)->range < range)
			s = &(*s)->next;
		p->next = *s;
		*s = p;
	}
	mutex_unlock(domain->lock);
	return 0;
}
字符设备初始化就结束了,主要难点是dev_t设备号的获取和kobj的映射。
最后,分析下char_open函数。
在之前分析mknod()的文章中提到,用mknod创建字符设备文件时,会将文件的inode结构中的
i_fop.open函数设置为chrdev_open()。当应用程序调用open函数打开字符设备文件时会调用
char_open(),char_open()最终会调用cdev结构中fops.open函数。本例中就是dev0_open(),
dev1_open(),dev2_open()等函数。

static int chrdev_open(struct inode *inode, struct file *filp)
{
	struct cdev *p;
	struct cdev *new = NULL;
	int ret = 0;

	spin_lock(&cdev_lock);
	/*第一次打开时为空*/
	p = inode->i_cdev;
	if (!p) {
		struct kobject *kobj;
		int idx;
		spin_unlock(&cdev_lock);
		/*从cdev_map中找到cdev结构中kobj变量*/
		kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
		if (!kobj)
			return -ENXIO;
		/*根据kobj找到代表字符设备的cdev结构*/
		new = container_of(kobj, struct cdev, kobj);
		spin_lock(&cdev_lock);
		/* Check i_cdev again in case somebody beat us to it while
		   we dropped the lock. */
		p = inode->i_cdev;
		if (!p) {
			/*将inode->i_cdev指向cdev结构*/
			inode->i_cdev = p = new;
			/*将inode->i_devices添加到cdev->list中*/
			list_add(&inode->i_devices, &p->list);
			new = NULL;
		} else if (!cdev_get(p))
			ret = -ENXIO;
	} else if (!cdev_get(p))
		ret = -ENXIO;
	spin_unlock(&cdev_lock);
	cdev_put(new);
	if (ret)
		return ret;

	ret = -ENXIO;
	/*获取字符设备自己的fops操作函数集*/
	filp->f_op = fops_get(p->ops);
	if (!filp->f_op)
		goto out_cdev_put;
	/*执行open函数*/
	if (filp->f_op->open) {
		ret = filp->f_op->open(inode,filp);
		if (ret)
			goto out_cdev_put;
	}

	return 0;

 out_cdev_put:
	cdev_put(p);
	return ret;
}
kobj_lookup()函数先从cdev_map.probe[]数组中找到之前注册的cdev结构,然后返回cdev结构中的kobj。
具体过程这里就不分析了。






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值