Linux字符设备是Linux系统中三类主要设备之一,与块设备、网络设备并列,核心特征是数据以字节流的形式进行顺序读写,不支持像块设备那样的随机寻址访问,常见的键盘、鼠标、串口都属于字符设备范畴。

Linux字符设备的核心特点
字符设备的核心特性决定了它的适用场景,主要有以下几个特点:
- 数据读写以字节为单位,按顺序进行,无法跳过中间数据直接访问后续内容
- 没有缓存机制或者缓存机制非常简单,数据读写通常直接和硬件交互
- 通过文件系统中的设备节点进行访问,设备节点对应主设备号和次设备号
- 驱动需要实现
file_operations结构体中的相关操作函数,供上层应用调用
常见的Linux字符设备类型
1. 输入类字符设备
这类设备用于接收用户输入,是最常见的一类字符设备,包括键盘、鼠标、触摸屏等。它们的数据输出是连续的事件流,比如按键按下、鼠标移动坐标等,应用层通过读取设备节点获取输入事件。
2. 串口类字符设备
串口设备比如RS232、USB转串口设备等,用于实现串行通信,数据按位顺序传输。这类设备通常用于嵌入式设备调试、外接传感器数据读取等场景,读写操作都是按字节流进行。
3. 音频类字符设备
声卡相关的设备节点大多属于字符设备,比如/dev/dsp、/dev/audio等,用于音频数据的采集和播放。音频数据以连续的字节流形式传输,不支持随机访问某一帧音频数据。
4. 自定义字符设备
开发者在驱动开发中自行注册的字符设备,用于实现特定硬件的访问逻辑,比如自定义的GPIO控制设备、特定的传感器读取设备等。这类设备的主设备号可以动态申请,也可以静态指定。
字符设备驱动的基本开发流程
开发字符设备驱动需要遵循Linux内核的驱动框架,核心步骤如下:
第一步:定义设备结构体
首先需要定义包含cdev结构体的自定义设备结构体,cdev是内核中表示字符设备的核心结构体。
#include <linux/cdev.h>
#include <linux/fs.h>
// 自定义字符设备结构体
struct my_char_dev {
struct cdev cdev; // 内核cdev结构体
int data; // 自定义设备数据
};
struct my_char_dev *dev; // 设备实例指针
第二步:实现file_operations操作函数
file_operations结构体定义了设备支持的操作,比如打开、读取、写入、释放等,需要根据设备功能实现对应的函数。
// 设备打开函数
static int my_open(struct inode *inode, struct file *filp) {
// 将设备结构体指针保存到file结构体的私有数据中
filp->private_data = container_of(inode->i_cdev, struct my_char_dev, cdev);
return 0;
}
// 设备读取函数
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {
struct my_char_dev *dev = filp->private_data;
// 这里实现读取逻辑,将数据拷贝到用户空间
// copy_to_user(buf, &dev->data, count);
return count;
}
// 设备写入函数
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {
struct my_char_dev *dev = filp->private_data;
// 这里实现写入逻辑,从用户空间拷贝数据
// copy_from_user(&dev->data, buf, count);
return count;
}
// 设备操作结构体
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.read = my_read,
.write = my_write,
};
第三步:注册字符设备
需要申请设备号,初始化cdev结构体,然后将设备添加到内核中。
static int major = 0; // 主设备号,0表示动态申请
static int minor = 0; // 次设备号起始值
static dev_t devno; // 设备号
static int __init my_char_init(void) {
int ret;
// 动态申请主设备号
ret = alloc_chrdev_region(&devno, minor, 1, "my_char_dev");
if (ret < 0) {
return ret;
}
major = MAJOR(devno); // 获取分配的主设备号
// 分配设备结构体内存
dev = kzalloc(sizeof(struct my_char_dev), GFP_KERNEL);
if (!dev) {
unregister_chrdev_region(devno, 1);
return -ENOMEM;
}
// 初始化cdev结构体,绑定操作函数
cdev_init(&dev->cdev, &my_fops);
dev->cdev.owner = THIS_MODULE;
// 添加cdev到内核
ret = cdev_add(&dev->cdev, devno, 1);
if (ret) {
kfree(dev);
unregister_chrdev_region(devno, 1);
return ret;
}
return 0;
}
module_init(my_char_init);
第四步:注销字符设备
模块卸载时需要释放相关资源,避免内存泄漏。
static void __exit my_char_exit(void) {
// 删除cdev
cdev_del(&dev->cdev);
// 释放设备结构体内存
kfree(dev);
// 释放设备号
unregister_chrdev_region(devno, 1);
}
module_exit(my_char_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kernel developer");
字符设备与块设备的区别
很多开发者容易混淆字符设备和块设备,两者的核心区别如下:
| 对比项 | 字符设备 | 块设备 |
|---|---|---|
| 数据读写单位 | 字节流 | 数据块(通常512字节及以上) |
| 访问方式 | 顺序访问,不支持随机寻址 | 支持随机寻址,可直接访问任意块 |
| 缓存机制 | 几乎无缓存或缓存简单 | 有复杂的页缓存机制 |
| 典型设备 | 键盘、鼠标、串口 | 硬盘、U盘、SSD |
了解Linux字符设备的类型和开发流程,是学习Linux驱动开发的基础,开发者可以根据实际需求选择对应的设备类型进行驱动开发,满足不同的硬件访问需求。
Linux字符设备设备驱动file_operationscdev修改时间:2026-07-04 12:33:35