1. 设备的本质
在 Linux 里,设备就是内核暴露给用户空间的接口,让程序可以操作硬件或内核功能。
- 不一定是硬件:例如
/dev/null、/dev/random、/dev/zero都是虚拟设备 - 内核通过 驱动程序 控制设备行为
- 核心接口就是系统调用:
open/read/write/ioctl/mmap
2. 设备分类
Linux 设备主要分三类:
| 类别 | 内核驱动类型 | 用户空间访问 | /dev 节点 |
|---|---|---|---|
| 块设备 Block | block driver | read/write/ioctl | /dev/sda、/dev/loop0 |
| 字符设备 Char | char driver | read/write/ioctl | /dev/ttyS0、/dev/null |
| 网络设备 Net | net driver | socket API(send/recv) | 没有 /dev 文件 |
重点:块/字符设备有
/dev节点,网络设备靠 socket API。
3. /dev 节点
每个 /dev 文件对应内核中的设备对象:
- major number:告诉内核用哪个驱动处理
- minor number:同一驱动下的不同实例
用户态通过文件操作访问设备,内核驱动处理操作:
1
2
int fd = open("/dev/ttyS0", O_RDWR);
write(fd, buf, size);
4. 举例
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
// ----------------- 块设备 Block -----------------
// /dev/sda 是硬盘,块设备,可以随机读写
#include <fcntl.h>
#include <unistd.h>
int fd = open("/dev/sda", O_RDONLY);
char buf[512];
lseek(fd, 1024*512, SEEK_SET); // 跳到第1024块
read(fd, buf, 512); // 读取一块数据
close(fd);
// ----------------- 字符设备 Char -----------------
// /dev/ttyS0 是串口,字符设备,顺序流式访问
#include <fcntl.h>
#include <unistd.h>
int fd = open("/dev/ttyS0", O_RDWR);
char buf[100];
int n = read(fd, buf, sizeof(buf)); // 读取串口数据
write(fd, "hello\n", 6); // 向串口发送数据
close(fd);
// /dev/null 是字符设备,但丢弃数据
int fd = open("/dev/null", O_WRONLY);
write(fd, "any data", 8); // 写进去立即丢弃
close(fd);
// ----------------- 网络设备 Net -----------------
// eth0 是网卡,使用 socket API 访问,没有 /dev 节点
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int sock = socket(AF_INET, SOCK_DGRAM, 0); // UDP 套接字
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(12345);
inet_pton(AF_INET, "192.168.1.10", &addr.sin_addr);
sendto(sock, "hello", 5, 0, (struct sockaddr*)&addr, sizeof(addr));
close(sock);
5. 设备号
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/*
* =========================
* Linux 设备号(major/minor)详解
* =========================
*
* 1️⃣ 设备节点对应内核驱动
*
* 在 Linux 中,/dev 下的文件只是一个设备节点,并不是真正的设备。
* 当你操作 /dev/sda、/dev/ttyS0 时,内核根据这个节点的编号
* 把请求路由给对应的驱动。
*
* 例如:
* int fd = open("/dev/sda", O_RDONLY);
* 内核会根据 major/minor 找到对应的驱动,并将请求映射到硬件。
*
* -------------------------
*
* 2️⃣ major 和 minor 的定义
*
* 每个设备节点有两个数字:
*
* major number : 指明使用哪个内核驱动处理操作。
* 例如,major=8 表示 Linux 的 SCSI 硬盘驱动。
* minor number : 同一驱动下不同实例的标识。
* 例如,8/0 是 /dev/sda,8/1 是 /dev/sdb。
*
* 查看设备号示例:
*
* $ ls -l /dev/sda
* brw-rw---- 1 root disk 8, 0 Oct 9 11:00 /dev/sda
*
* 解析:
* 'b' → 块设备
* 8 → major
* 0 → minor
*
* 对字符设备同理:
*
* $ ls -l /dev/ttyS0
* crw-rw---- 1 root dialout 4, 64 Oct 9 11:00 /dev/ttyS0
*
* 解析:
* 'c' → 字符设备
* 4 → major(对应 tty 驱动)
* 64 → minor(对应具体串口 ttyS0)
*
* -------------------------
*
* 3️⃣ 系统调用流程
*
* 当程序调用 read/write/open/close 等系统调用时:
*
* 1. 内核通过 major 找到对应驱动
* 2. 内核通过 minor 选择具体实例
* 3. 驱动执行对应操作,将请求映射到真实硬件
*
* -------------------------
*
* 4️⃣ 拓展知识
*
* - 杂项设备(misc device)通常用 major=10,不同 minor 表示不同模块,
* 例如 /dev/kni。
* - 网络设备没有 /dev 节点,没有 major/minor,通过 socket API 访问。
*
*/
6. 杂项设备
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/*
* =========================
* Linux 杂项设备(misc device)解析
* =========================
*
* 1️⃣ 背景问题
*
* 在 Linux 中,每个设备类型都需要一个 major 号,驱动注册时通过 major 区分哪个驱动处理请求:
*
* register_chrdev(major, "name", &fops);
* register_blkdev(major, "name");
*
* 早期做法是每个驱动都分配独立 major 号。
*
* 问题:
* - major 号有限(0~255 对于字符设备)
* - 随着设备和模块数量增多,major 很快不够
* - 为每个小功能设备分配 major 太浪费,也很麻烦
*
* -------------------------
*
* 2️⃣ 杂项设备(misc device)的设计目的
*
* 核心思想:
* - 将小型、使用频率低或实验性设备归类到一个统一类别
* - 统一使用 major=10,用 minor 区分具体设备
*
* 优点:
* 1. 节省 major 号:所有杂项设备共享 major=10,minor 区分功能
* 2. 模块化方便:每个 misc 设备可以独立注册自己的驱动和 file_operations
* 3. 减少冲突:统一 major + minor 避免和其他设备冲突
* 4. 适合小型或虚拟设备:小功能模块不需要独立 major
*
* -------------------------
*
* 3️⃣ 类比理解
*
* 可以把 Linux 设备号比作邮政系统:
*
* 类别 | major | minor | 类比
* ------------ | ----------- | --------------- | ------------------------
* 普通块设备 | 独立 major | minor 标识实例 | 大型建筑,每栋楼有自己编号
* 杂项设备 | major=10 | minor 区分设备 | 同一个小区,minor 是房间号,每个房间独立管理
*
* -------------------------
*
* 4️⃣ /dev、设备节点和杂项设备
*
* - /dev 是用户空间访问设备的入口
* - 大部分文件是设备节点(block/char device)
* - 用户程序通过 open/read/write/ioctl 操作 /dev 下文件
* - 杂项设备在 /dev 下也有节点,例如 /dev/kni、/dev/rtc
*
* 工作流程:
* 用户程序 open("/dev/kni")
* |
* v
* 内核检查 major=10,minor=对应设备
* |
* v
* 调用 misc 驱动的 file_operations
* |
* v
* 驱动执行具体操作
*
* /dev 提供接口入口,并不直接实现功能
*
* -------------------------
*
* 5️⃣ /proc 目录作用
*
* - /proc 是内核信息伪文件系统,提供内核数据结构和状态接口
* - /proc/misc 列出所有注册的杂项设备
* 格式:minor 设备名
*
* 示例:
* 1 psaux
* 4 rtc
* 256 kni
*
* /proc 目录不提供硬件访问,只显示内核状态
*
* -------------------------
*
* 6️⃣ /dev 和 /proc 与杂项设备关系
*
* 位置 | 内容 | 作用
* --------- | -------------------------- | -----------------------------------------
* /dev | 设备节点(字符设备 c) | 用户空间接口,通过 major/minor 调用驱动
* /proc/misc| misc 设备列表(minor+名字) | 内核维护的注册信息,查看状态
* 驱动 | 内核模块 | 处理实际系统调用,执行功能
*
* 关系示意:
*
* 用户程序
* |
* v
* /dev/kni <-- major=10, minor=256 --> 内核 misc 驱动
* ^
* |
* /proc/misc(显示注册信息)
*
* 💡 总结:
* - /dev:用户空间访问通道
* - /proc/misc:内核维护的 misc 设备注册列表,用于查看设备和 minor
* - 杂项设备本身:major=10,minor 区分具体设备,每个设备有独立驱动
* - 工作流程:用户访问 /dev -> 内核通过 major/minor 找到驱动 -> 驱动执行操作 -> /proc/misc 显示状态
*
* -------------------------
*
* 7️⃣ 杂项设备的主要目的
*
* - 节省 major 号资源
* - 方便管理小型或实验性设备
* - 避免驱动冲突
* - 支持动态添加和删除设备
*
* 核心理解:
* major=10 -> 表示 misc 类别
* minor -> 唯一标识设备实例
* 每个 misc 设备都有独立驱动逻辑
*/