簡單字符設備驅動程序分析

下面是一個簡單的字符設別驅動程序的源代碼,本文會分析代碼中涉及到的函數。

#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。
具體過程這裏就不分析了。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章