Linux内核 register_chrdev_region和alloc_chrdev_region区别

Linux内核的字符设备号注册有两个函数,一个是register_chrdev_region,另外一个是alloc_chrdev_region。两个函数的区别就是register_chrdev_region需要开发者指定设备的主设备号,而alloc_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); // 调用__register_chrdev_region函数分配设备号
		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);
}
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); // 调用__register_chrdev_region函数分配设备号
	if (IS_ERR(cd))
		return PTR_ERR(cd);
	*dev = MKDEV(cd->major, cd->baseminor);
	return 0;
}

可以看到两个函数的核心都是调用__register_chrdev_region去分配主设备号,但不同的是register_chrdev_region传入的是开发者传递的主设备号,而alloc_chrdev_region传入的主设备号是0。继续看__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;

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

	mutex_lock(&chrdevs_lock);

	/* temporary */
	if (major == 0) {  // 当主设备号为0时,内核会在设备号表中找到一个空闲的位置去分配
		for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
			if (chrdevs[i] == NULL)
				break;
		}

		if (i == 0) {
			ret = -EBUSY;
			goto out;
		}
		major = i;
	}

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

	i = major_to_index(major);
	/***************省略**********************/
}

可以看__register_chrdev_region函数的实现非常简单,首先会判断major是否为0,如果为0,就在内核维护的设备表寻找一个空闲的位置并把该位置的下标作为主设备号返回给驱动。内核的设备表其实就是一个数组,该数组的最大下标为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];

在了解了内核的两个设备号注册函数之后,接下来就实际写一段驱动程序使用这两个函数来完成设备的注册。

  • register_chrdev_region使用
    在使用register_chrdev_region前,我们需要查看内核中有哪些设备的主设备号是已经被使用了,被使用的主设备号是不能拿来注册的。在命令行输入cat /proc/devices来查看主设备号。
 cat /proc/devices
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  7 vcs
 10 misc
 13 input
 29 fb
 81 video4linux
 89 i2c
 90 mtd
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
207 ttymxc
226 drm
249 ttyLP
250 iio
251 watchdog
252 ptp
253 pps
254 rtc

Block devices:
  1 ramdisk
259 blkext
  7 loop
  8 sd
 31 mtdblock
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
/ #

以上是在本人开发平台上已经注册了的主设备号,具体需要根据每个人的开发平台来选择合适的主设备号。我们来关注Character devices字符设备的设备号。可以看到已经有很多设备号已经给使用了,在这里我选择240设备号来做实验。
程序1

struct cdev chrdevcdev;
dev_t dev;

ssize_t chrdev_read (struct file *file, char __user *usr, size_t size, loff_t *loff)
{
	printk("%s\r\n",__func__);
	return 0;
}
int chrdev_open (struct inode *inode, struct file *file)
{
	printk("%s\r\n",__func__);
	return 0;
}
int chrdev_release (struct inode *inode, struct file *file)
{
	printk("%s\r\n",__func__);
	return 0;
}
struct file_operations fops = 
{
	.open    = chrdev_open,
	.read    = chrdev_read,
	.release = chrdev_release,
};

static int __init chrdev_init(void)
{
	int ret = 0,error = 0;
	
	dev = MKDEV(CHRDEV_MAJOR,CHRDEV_MAION);  // 利用MKDEV构造设备号,主设备号240,次设备号为0
	error = register_chrdev_region(dev,CHRDEV_COUNT,CHRDEV_NAME); // 注册设备号
	if(error < 0){
		printk("register_chrdev_region error\r\n");
		ret =  -EBUSY;
		goto fail;
	}
	
	cdev_init(&chrdevcdev, &fops); // 绑定字符设备操作函数集
	error = cdev_add(&chrdevcdev,dev,CHRDEV_COUNT);   // 添加字符设备
	if(error < 0){
		printk("cdev_add error\r\n");
		ret =  -EBUSY;
		goto fail1;
	}
fail1:
	unregister_chrdev_region(dev,CHRDEV_COUNT);
fail:
	return ret;
}

static void __exit chrdev_exit(void)
{
	cdev_del(&chrdevcdev);/*  删除cdev */
	unregister_chrdev_region(dev,CHRDEV_COUNT);
}

module_init(chrdev_init);
module_exit(chrdev_exit);

MODULE_DESCRIPTION("xxxxxx");
MODULE_AUTHOR("xxxxxx");
MODULE_LICENSE("GPL");

在程序1中用MKDEV构造设备号,主设备是240,次设备号是0,然后调用register_chrdev_region函数注册设备号。最后通过cdev_init和cdev_add注册设备。将驱动放到开发板中并加载驱动观察结果。
Linux内核 register_chrdev_region和alloc_chrdev_region区别_第1张图片
insmod加载驱动,驱动加载成功后用mknod命令创建字符设备节点。设备节点创建完后,利用ls命令查看设备的详细信息,可以看到设备的主设备号是240,次设备号是0。用cat命令读取设备也成功。

  • alloc_chrdev_region
    下面用alloc_chrdev_region来注册设备,只需在程序1中做一点小修改就可以实现跟程序1一样的效果。
    程序2:
#define CHRDEV_MAJOR 240  // 主设备号
#define CHRDEV_MAION 0    // 次设备号
#define CHRDEV_COUNT 1    // 次设备号个数
#define CHRDEV_NAME  "testchrdev"

struct cdev chrdevcdev;
dev_t dev;

ssize_t chrdev_read (struct file *file, char __user *usr, size_t size, loff_t *loff)
{
	printk("%s\r\n",__func__);
	return 0;
}
int chrdev_open (struct inode *inode, struct file *file)
{
	printk("%s\r\n",__func__);
	return 0;
}
int chrdev_release (struct inode *inode, struct file *file)
{
	printk("%s\r\n",__func__);
	return 0;
}
struct file_operations fops = 
{
	.open    = chrdev_open,
	.read    = chrdev_read,
	.release = chrdev_release,
};


static int __init chrdev_init(void)
{
	int ret = 0,error = 0;
	
	
	error = alloc_chrdev_region(&dev,CHRDEV_MAION,CHRDEV_COUNT,CHRDEV_NAME); // 利用alloc_chrdev_region自动分配设备号
	printk("MAJOR = %d MINOR = %d\r\n",MAJOR(dev),MINOR(dev)); // 打印出主次设备号
	if(error < 0){
		printk("alloc_chrdev_region error\r\n");
		ret =  -EBUSY;
		goto fail;
	}
	
	cdev_init(&chrdevcdev, &fops); // 绑定字符设备操作函数集
	error = cdev_add(&chrdevcdev,dev,1);   // 添加字符设备
	if(error < 0){
		printk("cdev_add error\r\n");
		ret =  -EBUSY;
		goto fail1;
	}
	return 0;
fail1:
	unregister_chrdev_region(dev,CHRDEV_COUNT);
fail:
	return ret;
}


static void __exit chrdev_exit(void)
{
	cdev_del(&chrdevcdev);/*  删除cdev */
	unregister_chrdev_region(dev,CHRDEV_COUNT);
}

module_init(chrdev_init);
module_exit(chrdev_exit);

MODULE_DESCRIPTION("xxxxxx");
MODULE_AUTHOR("xxxxxx");
MODULE_LICENSE("GPL");

程序2中调用alloc_chrdev_region让内核自动分配设备号,然后打印出主次设备号以便创建字符设备,加载驱动观察运行结果
Linux内核 register_chrdev_region和alloc_chrdev_region区别_第2张图片
可以看到内核分配的主设备号是248。然后就是mknod创建设备节点,最后的运行结果跟程序1一样。

总结:
内核提供register_chrdev_region和alloc_chrdev_region主次设备号,个人建议最好使用alloc_chrdev_region来让内核自动分配并注册设备号。如果要使用register_chrdev_region函数,请先查看内核有哪些设备号是空闲,不能使用已经注册过设备号。

你可能感兴趣的:(linux)