11_申请字符类设备号

通过静态和动态的方式申请设备号

Posted by Jerry Chen on July 31, 2019

之前介绍了misc杂项设备的注册(主设备号固定,次设备号上次写的自动)并自动生成设备节点,本文介绍申请自定义的主、次设备号。

了解内容

值得注意的是:注册杂项设备函数misc_register包含了注册设备和自动生成设备节点。

而本文介绍的仅是注册设备。

注册设备函数

字符设备注册函数在路径“include/linux/fs.h”下有定义,分别是:

  • register_chrdev_region(dev_t, unsigned, const char *); 根据主、次设备号注册设备:
    • dev_t类型参数是组合设备号;
    • unsigned类型参数一般情况是次设备号个数,满了才会跳一个主设备号;
    • const char *类型参数是设备名称;
  • alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *); :动态分配未使用的主、次设备号;
  • __register_chrdev() :不建议,早期linux使用(当时只有主设备号);

与上对应的取消注册函数为 unregister_chrdev_region(dev_t, unsigned);

注册设备的结构体和宏

设备号需要使用“dev_t”类型,其定义在“include/linux/cdev.h”文件中;

MKDEV(ma,mi)宏定义用于组合主、次设备号,一般把结果作为注册设备函数的传参,其定义在“include/linlux/kdev_t.h”文件中。

开始

编写静态注册模块

主要变动点:

  1. 加入头文件:include/linux/cdev.h、include/linlux/kdev_t.h和include/linux/fs.h;
  2. insmod传递主次设备号值:dev_major、dev_minor;

  3. 定义将申请的设备数量DEVICE_NUM(会优先递增次设备号);
  4. init时注册设备,exit时释放设备。

regdev_demo.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <linux/init.h>
#include <linux/module.h>
#include <linux/stat.h>
#include <linux/moduleparam.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Jerry");

static dev_t dev; //设备号
static int dev_major; //主设备号
static int dev_minor; //从设备号
#define DEVICE_NAME "dev_demo"
#define DEVICE_NUM 2  //连续分配的设备个数(优秀递增从设备号)

module_param(dev_major,int,S_IRUSR);
module_param(dev_minor,int,S_IRUSR);

static int demo_init(void){
	int ret;
	
	dev = MKDEV(dev_major,dev_minor);
	if(dev_major == 0) //如果传参主设备号为0,这里选择不注册
	{
		printk(KERN_EMERG "Register dev error!\n");
		return 0;
	}
	
	ret = register_chrdev_region(dev,DEVICE_NUM,DEVICE_NAME);
	if(ret < 0){
		unregister_chrdev_region(dev,DEVICE_NUM); //失败就释放
	}
	
	printk(KERN_EMERG "dev_major is : %d!\n",MAJOR(dev));
	printk(KERN_EMERG "dev_minor is : %d!\n",MINOR(dev));
	return 0;
}

static void demo_exit(void){
	unregister_chrdev_region(dev,DEVICE_NUM);
	return;
}

module_init(demo_init);
module_exit(demo_exit);

静态的传值验证

编写简单Makefile:参考这里

编译生成模块后,拷贝到开发板中:

  • 然后先查看哪些主设备号未被注册,为下一步传值做准备

    1
    
    cat /proc/devices
    
  • 上一步我发现主设备号248未被使用,故传值加载

  • 加载后进行筛选设备名,可以看到248已经被注册为dev_demo

加入自动分配设备号

主要变动点:

  1. 使用了alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);函数,第一参数是:将被更新的设备号;第二参数是次设备号;第三参数是连续个数;第四参数是设备名;

    没有使用条件,这里我们在主设备号为0时使用它。

改进后的regdev_demo.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <linux/init.h>
#include <linux/module.h>
#include <linux/stat.h>
#include <linux/moduleparam.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Jerry");

static dev_t dev; //设备号
static int dev_major; //主设备号
static int dev_minor; //从设备号
#define DEVICE_NAME "dev_demo"
#define DEVICE_NUM 2  //连续分配的设备个数(优秀递增从设备号)

module_param(dev_major,int,S_IRUSR);
module_param(dev_minor,int,S_IRUSR);

static int demo_init(void){
	int ret;
	
	dev = MKDEV(dev_major,dev_minor);
	if(dev_major == 0){ // 自动分配
		ret = alloc_chrdev_region(&dev,dev_minor,DEVICE_NUM,DEVICE_NAME);
	}
	else{
		ret = register_chrdev_region(dev,DEVICE_NUM,DEVICE_NAME);
	}
	
	if(ret < 0){
		unregister_chrdev_region(dev,DEVICE_NUM); //失败就释放
	}
	
	printk(KERN_EMERG "dev_major is : %d!\n",MAJOR(dev));
	printk(KERN_EMERG "dev_minor is : %d!\n",MINOR(dev));
	return 0;
}

static void demo_exit(void){
	unregister_chrdev_region(dev,DEVICE_NUM);
	return;
}

module_init(demo_init);
module_exit(demo_exit);

自动分配的验证

编写简单Makefile:参考这里

编译生成模块后,拷贝到开发板中:

  • 直接加载模块,不传值(初值为0)

  • 加载后进行筛选设备名,可以看到248已经被注册为dev_demo