register_chrdev,register_chrdev_region和alloc_chrdev_region的关系和区别

register_chrdev,register_chrdev_region和alloc_chrdev_region最终都是调用的__register_chrdev_region函数。

而与之相关的主设备号可以通过/proc/devices来查看。

相关代码如下:

register_chrdev源码解析:

从0-255中查找可用的设备号,并返回。

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;
	int err = -ENOMEM;

	cd = __register_chrdev_region(major, baseminor, count, name);
	if (IS_ERR(cd))
		return PTR_ERR(cd);

	cdev = cdev_alloc();
	if (!cdev)
		goto out2;

	cdev->owner = fops->owner;
	cdev->ops = fops;
	kobject_set_name(&cdev->kobj, "%s", name);

	err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
	if (err)
		goto out;

	cd->cdev = cdev;

	return major ? 0 : cd->major;
out:
	kobject_put(&cdev->kobj);
out2:
	kfree(__unregister_chrdev_region(cd->major, baseminor, count));
	return err;
}

完整代码:

1.自动获取主设备号:

/* hello.c */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
// MODULE_LICENSE("leo BSD/GPL");

static const struct file_operations hello_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = NULL,
	.open = NULL,
};

int major;
#define HELLO_MAJOR (66)
static int hello_init(void)
{

	printk(KERN_ALERT "Hello, world\n");
	printk("Hello, world\n");
	major = register_chrdev(0, "hello_world", &hello_fops);
	printk(KERN_WARNING "hello_world: register character device %d.", major);
	if (major < 0)
		printk(KERN_WARNING "hello_world: Failed to register character device.");
	return 0;
}

static void hello_exit(void)
{
	printk(KERN_ALERT "Goodbye, Hello world\n");
	printk("Goodbye, Hello world\n");
	printk(KERN_WARNING "hello_world: unregister character device %d.", major);

	unregister_chrdev(major, "hello_world");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("bigcatio");
MODULE_DESCRIPTION("A good start");
MODULE_VERSION("0.0.1");

 

2.指定主设备号:

 

/* hello.c */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
// MODULE_LICENSE("leo BSD/GPL");

static const struct file_operations hello_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = NULL,
	.open = NULL,
};

int major;
#define HELLO_MAJOR (66)
static int hello_init(void)
{

	printk(KERN_ALERT "Hello, world\n");
	printk("Hello, world\n");
	major = register_chrdev(HELLO_MAJOR, "hello_world", &hello_fops);
	printk(KERN_WARNING "hello_world: register character device %d.", HELLO_MAJOR);
	if (major < 0)
		printk(KERN_WARNING "hello_world: Failed to register character device.");
	return 0;
}

static void hello_exit(void)
{
	printk(KERN_ALERT "Goodbye, Hello world\n");
	printk("Goodbye, Hello world\n");
	printk(KERN_WARNING "hello_world: unregister character device %d.", HELLO_MAJOR);
	unregister_chrdev(HELLO_MAJOR, "hello_world");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("bigcatio");
MODULE_DESCRIPTION("A good start");
MODULE_VERSION("0.0.1");

 

 register_chrdev_region源码解析:

从from开始往后的count个设备号中,查找n个可用的设备号并返回。

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);
}

 完整代码:

/* hello.c */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
// MODULE_LICENSE("leo BSD/GPL");

static const struct file_operations hello_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = NULL,
	.open = NULL,
};

int major;
#define HELLO_MAJOR (66)
static struct cdev hello_device_cdev;
#define MAX_MINORS 256
static int hello_init(void)
{
	int retval;
	dev_t dev = MKDEV(HELLO_MAJOR, 0);

	if ((retval = register_chrdev_region(dev, MAX_MINORS, "hello_world")) != 0)
	{
		pr_err("hello_world: unable to get major %d\n", HELLO_MAJOR);
		return retval;
	}

	cdev_init(&hello_device_cdev, &hello_fops);
	if ((retval = cdev_add(&hello_device_cdev, dev, MAX_MINORS)) != 0)
	{
		pr_err("hello_world: unable register character device\n");
	}

	return 0;
}

static void hello_exit(void)
{
	printk(KERN_ALERT "Goodbye, Hello world\n");
	printk("Goodbye, Hello world\n");
	printk(KERN_WARNING "hello_world: unregister character device %d.", HELLO_MAJOR);
	cdev_del(&hello_device_cdev);
	unregister_chrdev_region(MKDEV(HELLO_MAJOR, 0), MAX_MINORS);
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("bigcatio");
MODULE_DESCRIPTION("A good start");
MODULE_VERSION("0.0.1");

 

 alloc_chrdev_region源码解析:

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;
}

 

完整代码:

/* hello.c */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
// MODULE_LICENSE("leo BSD/GPL");

static const struct file_operations hello_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = NULL,
	.open = NULL,
};

int major;
static struct cdev hello_device_cdev;
#define MAX_MINORS 256
dev_t dev = 0;
static int hello_init(void)
{
	int ret;
	int retval;
	/* Get a range of minor numbers (starting with 0) to work with */
	ret = alloc_chrdev_region(&dev, 0, MAX_MINORS, "hello_world");
	if (ret < 0)
	{
		pr_err("hello_world: alloc_chrdev_region() failed\n");
		return 0;
	}
	cdev_init(&hello_device_cdev, &hello_fops);
	if ((retval = cdev_add(&hello_device_cdev, dev, MAX_MINORS)) != 0)
	{
		pr_err("hello_world: unable register character device\n");
	}

	printk(KERN_WARNING "hello_world: register character device %d.", MAJOR(dev));
	return 0;
}

static void hello_exit(void)
{
	printk(KERN_ALERT "Goodbye, Hello world\n");
	printk("Goodbye, Hello world\n");
	printk(KERN_WARNING "hello_world: unregister character device %d.", MAJOR(dev));
	cdev_del(&hello_device_cdev);
	unregister_chrdev_region(dev, MAX_MINORS);
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("bigcatio");
MODULE_DESCRIPTION("A good start");
MODULE_VERSION("0.0.1");

 

 下面进一步剖析_register_chrdev_region:

_register_chrdev_region通过调用find_dynamic_major找到可用的主设备号major,

/*
 * Register a single major with a specified minor range.
 *
 * If major == 0 this functions will dynamically allocate a major and return
 * its number.
 *
 * If major > 0 this function will attempt to reserve the passed range of
 * minors and will return zero on success.
 *
 * Returns a -ve errno on failure.
 */
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;

	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); 
	if (cd == NULL)
		return ERR_PTR(-ENOMEM);

	mutex_lock(&chrdevs_lock);

	if (major == 0) {
		ret = find_dynamic_major();
		if (ret < 0) {
			pr_err("CHRDEV \"%s\" dynamic allocation region is full\n",
			       name);
			goto out;
		}
		major = ret;
	}

	if (major >= CHRDEV_MAJOR_MAX) {
		pr_err("CHRDEV \"%s\" major requested (%u) is greater than the maximum (%u)\n",
		       name, major, CHRDEV_MAJOR_MAX-1);
		ret = -EINVAL;
		goto out;
	}

	cd->major = major;
	cd->baseminor = baseminor;
	cd->minorct = minorct;
	strlcpy(cd->name, name, sizeof(cd->name));

	i = major_to_index(major);

	for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
		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;
		}
	}

	cd->next = *cp;
	*cp = cd;
	mutex_unlock(&chrdevs_lock);
	return cd;
out:
	mutex_unlock(&chrdevs_lock);
	kfree(cd);
	return ERR_PTR(ret);
}

 

 

主设备号为0时,会动态寻找可用的空闲的主设备号。

内核中字符设备号最多可以到512,但实际最多能够容纳255个设备,这是由*chrdevs[CHRDEV_MAJOR_HASH_SIZE]决定的,chrdevs中的每一个非空元素都对应着一个字符设备。

 查找可用的主设备号:

find_dynamic_major会优先从254开始向CHRDEV_MAJOR_DYN_END(234)寻找可用的主设备号,当返现可用的设备号后则返回该值,否则继续利用哈希值(对255%)从511向384,共127个设备号进行查询,若未使用,则返回该设备号,否则返回失败。

可知,Linux下字符设备的主设备号的范围{129······254}

#define CHRDEV_MAJOR_HASH_SIZE 255

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];

/* index in the above */
static inline int major_to_index(unsigned major)
{
    return major % CHRDEV_MAJOR_HASH_SIZE;
}
/* fs/char_dev.c */
#define CHRDEV_MAJOR_MAX 512
/* Marks the bottom of the first segment of free char majors */
#define CHRDEV_MAJOR_DYN_END 234
/* Marks the top and bottom of the second segment of free char majors */
#define CHRDEV_MAJOR_DYN_EXT_START 511
#define CHRDEV_MAJOR_DYN_EXT_END 384
static int find_dynamic_major(void)
{
	int i;
	struct char_device_struct *cd;

	for (i = ARRAY_SIZE(chrdevs)-1; i >= CHRDEV_MAJOR_DYN_END; i--) {
		if (chrdevs[i] == NULL)
			return i;
	}

	for (i = CHRDEV_MAJOR_DYN_EXT_START;
	     i >= CHRDEV_MAJOR_DYN_EXT_END; i--) {
		for (cd = chrdevs[major_to_index(i)]; cd; cd = cd->next)
			if (cd->major == i)
				break;

		if (cd == NULL)
			return i;
	}

	return -EBUSY;
}

 

 
 
posted @ 2020-12-02 23:44  稀里糊涂的胡闹  阅读(284)  评论(0)    收藏  举报