您的位置:首页 > 其它

浅析字符设备驱动程序__register_chrdev_region

2016-09-29 01:15 459 查看
  在 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");
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐