转载:浅析字符设备驱动程序

本文深入探讨了Linux 2.6内核中字符设备驱动的管理方式,包括register_chrdev和register_chrdev_region函数的使用及内部实现原理,并通过具体示例帮助理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原博客地址: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 函数,本文将从它入手来分析内核是如何管理字符设备驱动程序的。


 
 
  1. static struct char_device_struct {
  2. struct char_device_struct *next;
  3. unsigned int major;
  4. unsigned int baseminor;
  5. int minorct;
  6. char name[ 64];
  7. struct cdev *cdev; /* will die */
  8. } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

   内核中的每一个字符设备驱动程序都由一个 char_device_struct 结构体来描述,包含主设备号、起始次设备号、次设备号个数等信息。

   内核使用 chrdevs 这个指针数组来管理所有的字符设备驱动程序,数组范围 0-255 ,看上去好像还是只支持 256 个字符设备驱动程序,其实并不然,每一个 char_device_struct 结构都含有一个 next 指针,它可以指向与其主设备号相同的其它字符设备驱动程序,它们之间主设备号相同,各自的次设备号范围相互不重叠。



一、register_chrdev


 
 
  1. static inline int register_chrdev(unsigned int major, const char *name,
  2. const struct file_operations *fops)
  3. {
  4. return __register_chrdev(major, 0, 256, name, fops);
  5. }
  6. int __register_chrdev( unsigned int major, unsigned int baseminor,
  7. unsigned int count, const char *name,
  8. const struct file_operations *fops)
  9. {
  10. struct char_device_struct *cd;
  11. struct cdev *cdev;
  12. cd = __register_chrdev_region(major, baseminor, count, name);
  13. cdev = cdev_alloc();
  14. cdev->owner = fops->owner;
  15. cdev->ops = fops;
  16. kobject_set_name(&cdev->kobj, "%s", name);
  17. err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
  18. cd->cdev = cdev;
  19. return major ? 0 : cd->major;
  20. }
  它调用了 __register_chrdev_region 并强制指定了起始次设备号为0,256个,把一个主设备号下的所有次设备号都申请光了。同时它还封装了 cdev_init 和 cdev_add ,倒是很省事。


二、register_chrdev_region


 
 
  1. int register_chrdev_region(dev_t from, unsigned count, const char *name)
  2. {
  3. struct char_device_struct *cd;
  4. dev_t to = from + count;
  5. dev_t n, next;
  6. for (n = from; n < to; n = next) {
  7. next = MKDEV(MAJOR(n)+ 1, 0);
  8. if (next > to)
  9. next = to;
  10. cd = __register_chrdev_region(MAJOR(n), MINOR(n),
  11. next - n, name);
  12. }
  13. return 0;
  14. }
  register_chrdev_region 则是根据要求的范围进行申请,同时我们需要手动 cdev_init cdev_add 。

三、__register_chrdev_region


 
 
  1. static struct char_device_struct *
  2. __register_chrdev_region(unsigned int major, unsigned int baseminor,
  3. int minorct, const char *name)
  4. {
  5. // **p 是 char_device_struct 类型实例
  6. // *p 是 char_device_struct 实例的指针
  7. // p 是 char_device_struct 实例的指针的指针
  8. struct char_device_struct *cd, **cp;
  9. int ret = 0;
  10. int i;
  11. cd = kzalloc( sizeof(struct char_device_struct), GFP_KERNEL);
  12. mutex_lock(&chrdevs_lock);
  13. /*
  14. * 如果major为0则分配一个没有使用的主设备号
  15. * 注意,从 chrdevs[255] 开始向下查找
  16. */
  17. if (major == 0) {
  18. for (i = ARRAY_SIZE(chrdevs) -1; i > 0; i--) {
  19. if (chrdevs[i] == NULL)
  20. break;
  21. }
  22. if (i == 0) {
  23. ret = -EBUSY;
  24. goto out;
  25. }
  26. major = i;
  27. ret = major;
  28. }
  29. cd->major = major;
  30. cd->baseminor = baseminor;
  31. cd->minorct = minorct;
  32. strlcpy(cd->name, name, sizeof(cd->name));
  33. // return major % CHRDEV_MAJOR_HASH_SIZE;
  34. i = major_to_index(major);
  35. /*
  36. * 不要分析直接分析 for 循环
  37. * 拿实例来分析不容易晕
  38. *
  39. */
  40. // *(a.next) 是 next 实例
  41. // *(a->next)
  42. // *((*a)->next) 是 next 实例
  43. // (*cp)->next 是 next 实例的指针,这个指针存在于 **cp 中
  44. // cp 是 chrdevs[i] 的指针,chrdevs[i]本身就是个指针
  45. // cp == &chrdevs[i].next
  46. for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
  47. if ((*cp)->major > major || // 正常情况下 If 语句不成立
  48. ((*cp)->major == major &&
  49. (((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor))) )
  50. break;
  51. /* 如果有重叠部分,正常情况下应该不重叠 */
  52. if (*cp && (*cp)->major == major) {
  53. int old_min = (*cp)->baseminor;
  54. int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
  55. int new_min = baseminor;
  56. int new_max = baseminor + minorct - 1;
  57. /* New driver overlaps from the left. */
  58. if (new_max >= old_min && new_max <= old_max) {
  59. ret = -EBUSY;
  60. goto out;
  61. }
  62. /* New driver overlaps from the right. */
  63. if (new_min <= old_max && new_min >= old_min) {
  64. ret = -EBUSY;
  65. goto out;
  66. }
  67. }
  68. // 第一次时 *cp == chrdevs[i] == null
  69. cd->next = *cp;
  70. *cp = cd; // 第一次时,*cp == chrdevs[i] 指向 新分配的 char_device_struct 结构
  71. mutex_unlock(&chrdevs_lock);
  72. return cd;
  73. }
  直接分析代码有些吃力,拿个例子来分析。

 
 
  1. alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello");
  2. major = MAJOR(devid);
  3. devid = MKDEV(major, 2);
  4. register_chrdev_region(devid, 1, "hello2");

 
 
  1. int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
  2. const char *name)
  3. {
  4. struct char_device_struct *cd;
  5. cd = __register_chrdev_region( 0, baseminor, count, name);
  6. if (IS_ERR(cd))
  7. return PTR_ERR(cd);
  8. *dev = MKDEV(cd->major, cd->baseminor);
  9. return 0;
  10. }

第一次 
  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 指向上一次分配的实例

  相当于从链表头部插入了一个节点,此时,再来看这个图应该更清晰了。



四、字符设备驱动程序模板

 
 
  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/init.h>
  5. #include <linux/delay.h>
  6. #include <linux/irq.h>
  7. #include <asm/uaccess.h>
  8. #include <asm/irq.h>
  9. #include <asm/io.h>
  10. #include <asm/arch/regs-gpio.h>
  11. #include <asm/hardware.h>
  12. #include <linux/poll.h>
  13. #include <linux/cdev.h>
  14. /* 1. 确定主设备号 */
  15. static int major;
  16. static int hello_open(struct inode *inode, struct file *file)
  17. {
  18. printk( "hello_open\n");
  19. return 0;
  20. }
  21. static int hello2_open(struct inode *inode, struct file *file)
  22. {
  23. printk( "hello2_open\n");
  24. return 0;
  25. }
  26. /* 2. 构造file_operations */
  27. static struct file_operations hello_fops = {
  28. .owner = THIS_MODULE,
  29. .open = hello_open,
  30. };
  31. static struct file_operations hello2_fops = {
  32. .owner = THIS_MODULE,
  33. .open = hello2_open,
  34. };
  35. #define HELLO_CNT 2
  36. static struct cdev hello_cdev;
  37. static struct cdev hello2_cdev;
  38. static struct class *cls;
  39. static int hello_init(void)
  40. {
  41. dev_t devid;
  42. /* 3. 告诉内核 */
  43. #if 0 // 老方法
  44. /* (major, 0), (major, 1), ..., (major, 255)都对应hello_fops */
  45. major = register_chrdev( 0, "hello", &hello_fops);
  46. #else // 新方法
  47. if (major) {
  48. devid = MKDEV(major, 0);
  49. /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
  50. register_chrdev_region(devid, HELLO_CNT, "hello");
  51. } else {
  52. /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
  53. alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello");
  54. major = MAJOR(devid);
  55. }
  56. cdev_init(&hello_cdev, &hello_fops);
  57. cdev_add(&hello_cdev, devid, HELLO_CNT);
  58. devid = MKDEV(major, 2);
  59. register_chrdev_region(devid, 1, "hello2");
  60. cdev_init(&hello2_cdev, &hello2_fops);
  61. cdev_add(&hello2_cdev, devid, 1);
  62. #endif
  63. cls = class_create(THIS_MODULE, "hello");
  64. class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0"); /* /dev/hello0 */
  65. class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1"); /* /dev/hello1 */
  66. class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2"); /* /dev/hello2 */
  67. class_device_create(cls, NULL, MKDEV(major, 3), NULL, "hello3"); /* /dev/hello3 */
  68. return 0;
  69. }
  70. static void hello_exit(void)
  71. {
  72. class_device_destroy(cls, MKDEV(major, 0));
  73. class_device_destroy(cls, MKDEV(major, 1));
  74. class_device_destroy(cls, MKDEV(major, 2));
  75. class_device_destroy(cls, MKDEV(major, 3));
  76. class_destroy(cls);
  77. cdev_del(&hello_cdev);
  78. unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);
  79. cdev_del(&hello2_cdev);
  80. unregister_chrdev_region(MKDEV(major, 2), 1);
  81. }
  82. module_init(hello_init);
  83. module_exit(hello_exit);
  84. 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;

}`

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值