原博客地址:https://blog.youkuaiyun.com/lizuobin2/article/details/52695533
在 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");
`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;
}`