浅析字符设备驱动程序__register_chrdev_region

  在 2.4 的内核我们使用 register_chrdev(0, "hello", &hello_fops) 来进行字符设备设备节点的分配,这种方式每一个主设备号只能存放一种设备,它们使用相同的 file_operation 结构体,也就是说内核最多支持 256 个字符设备驱动程序。

   在 2.6 的内核之后,新增了一个 register_chrdev_region 函数,它支持将同一个主设备号下的次设备号进行分段,每一段供给一个字符设备驱动程序使用,使得资源利用率大大提升,同时,2.6 的内核保留了原有的 register_chrdev 方法。在 2.6 的内核中这两种方法都会调用到 __register_chrdev_region 函数,本文将从它入手来分析内核是如何管理字符设备驱动程序的。

static struct char_device_struct {
	struct char_device_struct *next;
	unsigned int major;
	unsigned int baseminor;
	int minorct;
	char name[64];
	struct cdev *cdev;		/* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

   内核中的每一个字符设备驱动程序都由一个 char_device_struct 结构体来描述,包含主设备号、起始次设备号、次设备号个数等信息。

   内核使用 chrdevs 这个指针数组来管理所有的字符设备驱动程序,数组范围 0-255 ,看上去好像还是只支持 256 个字符设备驱动程序,其实并不然,每一个 char_device_struct 结构都含有一个 next 指针,它可以指向与其主设备号相同的其它字符设备驱动程序,它们之间主设备号相同,各自的次设备号范围相互不重叠。



一、register_chrdev

static inline int register_chrdev(unsigned int major, const char *name,
				  const struct file_operations *fops)
{
	return __register_chrdev(major, 0, 256, name, fops);
}

int __register_chrdev(unsigned int major, unsigned int baseminor,
		      unsigned int count, const char *name,
		      const struct file_operations *fops)
{
	struct char_device_struct *cd;
	struct cdev *cdev;

	cd = __register_chrdev_region(major, baseminor, count, name);
	
	cdev = cdev_alloc();

	cdev->owner = fops->owner;
	cdev->ops = fops;
	kobject_set_name(&cdev->kobj, "%s", name);
		
	err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);

	cd->cdev = cdev;

	return major ? 0 : cd->major;
}
  它调用了 __register_chrdev_region 并强制指定了起始次设备号为0,256个,把一个主设备号下的所有次设备号都申请光了。同时它还封装了 cdev_init 和 cdev_add ,倒是很省事。


二、register_chrdev_region

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);
	}
	return 0;
}
  register_chrdev_region 则是根据要求的范围进行申请,同时我们需要手动 cdev_init cdev_add 。

三、__register_chrdev_region

static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
			   int minorct, const char *name)
{
	// **p 是 char_device_struct 类型实例
	//  *p 是 char_device_struct 实例的指针
	//   p 是 char_device_struct 实例的指针的指针
	struct char_device_struct *cd, **cp;
	int ret = 0;
	int i;

	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);

	mutex_lock(&chrdevs_lock);

	/*
	 * 如果major为0则分配一个没有使用的主设备号
	 * 注意,从 chrdevs[255] 开始向下查找
	 */
	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;
	}

	cd->major = major;
	cd->baseminor = baseminor;
	cd->minorct = minorct;
	strlcpy(cd->name, name, sizeof(cd->name));
	
	// return major % CHRDEV_MAJOR_HASH_SIZE;
	i = major_to_index(major);

	/*
	 *  不要分析直接分析 for 循环
	 *  拿实例来分析不容易晕
	 *  
	 */
	 // *(a.next) 是 next 实例
	 // *(a->next)
	 // *((*a)->next) 是 next 实例
	 // (*cp)->next 是 next 实例的指针,这个指针存在于 **cp 中
	 // cp 是 chrdevs[i] 的指针,chrdevs[i]本身就是个指针
	 // cp == &chrdevs[i].next
	for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
		if ((*cp)->major > major || 	// 正常情况下 If 语句不成立
	       ((*cp)->major == major && 
		   (((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor))) )
			break;

	/* 如果有重叠部分,正常情况下应该不重叠  */
	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;
		}
	}
	// 第一次时 *cp == chrdevs[i] == null
	cd->next = *cp;
	*cp = cd;	// 第一次时,*cp == chrdevs[i] 指向 新分配的 char_device_struct 结构
	mutex_unlock(&chrdevs_lock);
	return cd;
}
  直接分析代码有些吃力,拿个例子来分析。
	alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello");
	major = MAJOR(devid);                     
	devid = MKDEV(major, 2);
	register_chrdev_region(devid, 1, "hello2");
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
			const char *name)
{
	struct char_device_struct *cd;
	cd = __register_chrdev_region(0, baseminor, count, name);
	if (IS_ERR(cd))
		return PTR_ERR(cd);
	*dev = MKDEV(cd->major, cd->baseminor);
	return 0;
}

第一次 
  alloc_chrdev_region(&devid, 0, 2, "hello"); 
  major = MAJOR(devid); 
  __register_chrdev_region(unsigned int major = 0, unsigned int baseminor = 0,int minorct = 2, const char *name)
  chrdevs[i] == null
  cp = &chrdevs[i] -> *cp == chrdevs[i] == null
  cd->next = *cp == null (*cp 内容是空的,它的内容是别人的地址)
  cp == chrdevs[i] = cd (指针和指针赋值,将cd指向的实体的地址赋给 *cp 也就是 chrdevs[i])



第二次
  register_chrdev_region(devid, 1, "hello2");
  __register_chrdev_region(unsigned int major = 0, unsigned int baseminor = 2,int minorct = 1, const char *name)
  if 语句条件不成立,因此,cp = &(*cp)->next ,*cp == (*cp)->next == chrdevs[i]->next == null 跳出 for 循环
  cp = &chrdevs[i] -> (*cp) == chrdevs[i] -> (*cp)->next == chrdevs[i]->next == null
  cd->next = *cp; cd->next 指向了上一次分配的实例
  cp = cd -> chrdevs[i] = cd 指针和指针之间的赋值,chrdevs[i] 就指向了新分配的实例,新分配的实例.next 指向上一次分配的实例

  相当于从链表头部插入了一个节点,此时,再来看这个图应该更清晰了。



四、字符设备驱动程序模板
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#include <linux/cdev.h>

/* 1. 确定主设备号 */
static int major;

static int hello_open(struct inode *inode, struct file *file)
{
	printk("hello_open\n");
	return 0;
}

static int hello2_open(struct inode *inode, struct file *file)
{
	printk("hello2_open\n");
	return 0;
}


/* 2. 构造file_operations */
static struct file_operations hello_fops = {
	.owner = THIS_MODULE,
	.open  = hello_open,
};

static struct file_operations hello2_fops = {
	.owner = THIS_MODULE,
	.open  = hello2_open,
};


#define HELLO_CNT   2

static struct cdev hello_cdev;
static struct cdev hello2_cdev;
static struct class *cls;

static int hello_init(void)
{
	dev_t devid;
	
	/* 3. 告诉内核 */
#if 0 // 老方法
    /* (major,  0), (major, 1), ..., (major, 255)都对应hello_fops */
	major = register_chrdev(0, "hello", &hello_fops); 
#else // 新方法
	if (major) {
		devid = MKDEV(major, 0);
		 /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
		register_chrdev_region(devid, HELLO_CNT, "hello"); 
	} else {
		/* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
		alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello"); 
		major = MAJOR(devid);                     
	}
	
	cdev_init(&hello_cdev, &hello_fops);
	cdev_add(&hello_cdev, devid, HELLO_CNT);

	devid = MKDEV(major, 2);
	register_chrdev_region(devid, 1, "hello2");
	cdev_init(&hello2_cdev, &hello2_fops);
	cdev_add(&hello2_cdev, devid, 1);
	
#endif

	cls = class_create(THIS_MODULE, "hello");
	class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0"); /* /dev/hello0 */
	class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1"); /* /dev/hello1 */
	class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2"); /* /dev/hello2 */
	class_device_create(cls, NULL, MKDEV(major, 3), NULL, "hello3"); /* /dev/hello3 */
	
	
	return 0;
}

static void hello_exit(void)
{
	class_device_destroy(cls, MKDEV(major, 0));
	class_device_destroy(cls, MKDEV(major, 1));
	class_device_destroy(cls, MKDEV(major, 2));
	class_device_destroy(cls, MKDEV(major, 3));
	class_destroy(cls);

	cdev_del(&hello_cdev);
	unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);

	cdev_del(&hello2_cdev);
	unregister_chrdev_region(MKDEV(major, 2), 1);
}

module_init(hello_init);
module_exit(hello_exit);


MODULE_LICENSE("GPL");








  • 21
    点赞
  • 97
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值