在 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");