
字符设备通过字符(一个接一个的字符)以流方式向用户程序传递数据,就像串行端口那样。字符设备驱动通过 /dev 目录下的特殊文件公开设备的属性和功能。字符设备在内核中表示为 struct cdev 的实例,定义在 include/linux/cdev.h 中:
c
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
一、主设备和次设备的概念
字符设备在 /dev 目录下,主设备号和次设备号标识设备,并将其与驱动程序进行绑定。用 ls -l 命令能够查看:

每一列的第一个字符代表文件类型:
- c:字符设备文件
- b:块设备文件
- l:符号链接
- d:目录
- s:套接字
- p:命名管道
二、设备号类型
申请的设备号类型为 dev_t,在 内核源码/include/linux/types.h 文件中定义如下:
c
typedef u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;1
2
2
dev_t 为 u32 类型,是一个无符号的 32 位整型类型。其中:
- 高 12 位表示主设备号
- 低 20 位表示次设备号
在 内核源码/include/linux/kdev_t.h 中提供了设备号相关的宏定义:
c
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))1
2
3
4
5
6
2
3
4
5
6
三、设备号的分配和释放
设备号在系统范围内标识设备文件,这意味着可以有两种不同的方法来分配设备号码:
register_chrdev_region(静态)alloc_chrdev_region(动态)
1、静态方法
c
int register_chrdev_region(dev_t first, unsigned int count, const char *name);1
参数:
dev_t first:起始的设备号,包括主设备号和次设备号unsigned int count:需要分配的连续设备号的数量const char *name:注册的设备名称
返回值:返回 0 表示成功,如果失败返回一个负的错误码。
案例:
c
#define VIDEO_MAJOR 811

2、动态方法
alloc_chrdev_region 会自动分配一个空闲的主设备号:
c
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;
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
3、__register_chrdev_region 分析
所有字符设备的信息(主设备号)记录在 chrdevs(全局变量)中。


两种分配方法的区别
- 静态方法:必须事先知道所需的设备号,仅在教学中使用。
- 动态方法:更清晰、更安全。因为内核帮助获取一个合适的设备号,避免设备号的冲突和麻烦。
四、案例
1、静态案例

2、动态案例




五、实验程序的编写
源代码下载
bash
git clone git@gitee.com:yangxuesong314/linux-driver.git1
- 代码位于:
linux-driver/05.字符设备驱动模型/01.主次设备号
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
static int major;
static int minor;
module_param(major, int, S_IRUGO);
module_param(minor, int, S_IRUGO);
static dev_t dev_num;
static int __init dev_t_init(void)
{
int ret;
if (major) {
dev_num = MKDEV(major, minor);
printk("major is %d\n", major);
printk("minor is %d\n", minor);
ret = register_chrdev_region(dev_num, 1, "chrdev_name");
if (ret < 0) {
printk("register_chrdev_region is error\n");
}
printk("register_chrdev_region is ok\n");
} else {
ret = alloc_chrdev_region(&dev_num, 0, 1, "chrdev_num");
if (ret < 0) {
printk("alloc_chrdev_region is error\n");
}
printk("alloc_chrdev_region is ok\n");
major = MAJOR(dev_num);
minor = MINOR(dev_num);
printk("major is %d\n", major);
printk("minor is %d\n", minor);
}
return 0;
}
static void __exit dev_t_exit(void)
{
unregister_chrdev_region(dev_num, 1);
printk("module exit \n");
}
module_init(dev_t_init);
module_exit(dev_t_exit);
MODULE_LICENSE("GPL v2");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
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
六、实验程序的运行
insmod char.ko

insmod char.ko major=200 minor=0
