设备文件位于 /dev 下。
设备文件和普通文件的区别:
设备号
设备号共 32 位:
主设备号:占高12位,标识一个类型的设备,全局唯一。
次设备号:占低20位,标识一个类型下的不同设备,每个主设备号下的次设备号互不干扰,不会冲突。
// 拼接成设备号
dev_t devno = MKDEV(major,minor);
// 获取主设备号
int major = MAJOR(devno);
// 获取次设备号
int minor = MINOR(devno);设备号相同的驱动无法加载。
设备文件位于 /dev 目录下。
相关函数:
(1)静态申请设备号
int register_chrdev_region(dev_t first, unsigned count, const char *name);(2)释放设备号
void unregister_chrdev_region(dev_t first, unsigned count);(2)动态分配设备号,主设备号由内核分配
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);dev:输出参数,返回分配到的起始设备号(dev_t)
baseminor:起始次设备号(通常为 0)
count:要申请的连续次设备号数量
name:设备名称(出现在 /proc/devices 中)
返回值:
成功:0
失败:负数错误码(如 -EINVAL, -ENOMEM)
字符设备驱动
字符设备驱动(driver)= 管理一个或多个
cdev的代码逻辑cdev= 一个字符设备实例(device)的内核表示,字符设备最终表现为 /dev/ 目录下的一个节点。cdev.file_operations = 定义“如何操作这个设备”
字符设备驱动模板:
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#define DEVICE_NAME "mychardev"
#define DEVICE_MAJOR 200
#define DEVICE_MINOR 0
#define DEVICE_COUNT 4 // 最大支持设备数量
static int __init chardev_init(void) {
int ret;
dev_t dev = MKDEV(DEVICE_MAJOR, DEVICE_MINOR);
ret = register_chrdev_region(dev, DEVICE_COUNT, DEVICE_NAME);
if (ret)
goto fail;
return 0;
fail:
return ret;
}
static void __exit chardev_exit(void) {
dev_t dev = MKDEV(DEVICE_MAJOR, DEVICE_MINOR);
unregister_chrdev_region(dev, DEVICE_COUNT);
}
module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("HLTJ");
MODULE_DESCRIPTION("simple char driver");
MODULE_VERSION("1.0");
(1)cdev 相关操作函数
1、动态分配 dev
1、初始化 cdev 结构体,关联文件操作集
void cdev_init(struct cdev *cdev, const struct file_operations *fops);2、添加 cdev 到内核中
int cdev_add(struct cdev *cdev, dev_t dev, unsigned count);dev:设备号
count:
通常为 1
从设备号 dev 开始,接下来的 count 个次设备号都属于这个 cdev
比如设为 3,那就是 /dev/mydev0、/dev/mydev1、/dev/mydev2
返回值:
0:成功
负数:失败,错误码
3、删除 cdev
void cdev_del(struct cdev *cdev);(2)设备节点相关操作函数
1、创建设备节点:
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...);class:所属的类,不能为NULL
parent:父设备,通常为 NULL
devt:设备号
drvdata:私有数据(可通过
dev_get_drvdata()获取)fmt:设备节点名称格式字符串(如 "mydev%d", i)
返回值:
成功:指向 struct device 的指针
失败:ERR_PTR(err)
2、删除设备节点:
void device_destroy(struct class *class, dev_t devt);3、创建设备类:
struct class *class_create(struct module *owner, const char *name);设备类用于自动创建设备节点
参数:
owner:指向拥有者的模块(通常是 THIS_MODULE)
name:类名(如 "mychar")
返回值:
成功:指向 struct class的指针
失败:ERR_PTR(err),需用 IS_ERR()检查
4、销毁设备类:
void class_destroy(struct class *cls);使用 mknod 命令手动创建设备文件:
mknod /dev/设备文件名 c 256 0查询:
ls /dev
cat /proc/devices
一个驱动支持多个设备
两种方案:
static ssize_t device_read_function(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos) {
return 0;
}
static int device_open_function(struct inode *inode, struct file *filp) {
return 0;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.read = device_read_function,
.open = device_open_function,
};操作函数
打开
关闭
读操作
写操作
ioctl 操作
用于处理设备的非数据操作(数据操作就是read、write)。
Linux 应用开发使用的 ioctl :
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);request:操作码,告诉驱动要干什么。
后面的可变参数根据前面的操作码可有可无,通常是一个指向数据缓冲区的指针。有时也可以是一个整数值。
操作码的构成:

方向:数据传输的方向:
_IOC_NONE:无数据传输_IOC_READ:读数据到用户空间_IOC_WRITE:写数据到内核空间_IOC_READ | _IOC_WRITE:双向传输
大小:传输数据的字节数(sizeof)
类型:魔数,驱动程序中检查这个操作码是不是该驱动对应的。
编号:自定义命令码
用户空间和内核空间数据传输函数:
int copy_from_user(void *to, const void __user *from, unsigned long n)
int copy_to_user(void __user *to, const void *from, unsigned long n)__user:没啥特殊功能,只是标记这个数据是用户空间的。
使用:
头文件,应用程序和驱动程序共享:
// 幻数 'M'
#define MYDEVICE_MAGIC 'M'
// 命令编号
#define MYDEVICE_GET_STATUS _IOR(MYDEVICE_MAGIC, 0, int)
#define MYDEVICE_SET_STATUS _IOW(MYDEVICE_MAGIC, 1, int)
#define MYDEVICE_RESET _IO(MYDEVICE_MAGIC, 2)应用程序:
驱动程序:
#include <linux/uaccess.h> // for copy_to/from_user
#include "mydevice_ioctl.h" // 共享的头文件
static long mydevice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
int ret = 0;
int temp_status;
// 1. 检查魔数
if (_IOC_TYPE(cmd) != MYDEVICE_MAGIC) {
pr_err("Invalid magic number 0x%x\n", _IOC_TYPE(cmd));
return -ENOTTY; // 通常用于“不支持的ioctl”错误
}
// 2. 根据命令号分发
switch (cmd) {
case MYDEVICE_GET_STATUS:
// 检查方向和大小是否匹配
if (_IOC_DIR(cmd) != _IOC_READ) {
return -EINVAL;
}
// 从内核空间复制数据到用户空间
temp_status = device_status;
ret = copy_to_user((int __user *)arg, &temp_status, sizeof(temp_status));
if (ret != 0) {
pr_err("copy_to_user failed\n");
return -EFAULT;
}
break;
case MYDEVICE_SET_STATUS:
if (_IOC_DIR(cmd) != _IOC_WRITE) {
return -EINVAL;
}
// 从用户空间复制数据到内核空间
ret = copy_from_user(&temp_status, (int __user *)arg, sizeof(temp_status));
if (ret != 0) {
pr_err("copy_from_user failed\n");
return -EFAULT;
}
device_status = temp_status;
pr_info("Status set to %d\n", device_status);
break;
case MYDEVICE_RESET:
if (_IOC_DIR(cmd) != _IOC_NONE) {
return -EINVAL;
}
device_status = 0;
pr_info("Device reset\n");
break;
default:
pr_err("Unknown command 0x%x\n", cmd);
return -ENOTTY;
}
return 0; // 成功
}netlink、memfd+ seals 或 io_uring
虚拟串口设备