驱动/内核异步通知
在Linux驱动程序中,
异步通知机制允许内核模块在特定事件发生时主动通知用户空间进程。
这样的设计通常用于管理输入输出操作,
确保用户程序可以及时获得数据或状态变化而无需频繁查询设备状态。
在Linux驱动中,常见的异步通知机制主要有以下几种:
信号(Signals):通过信号机制,内核可以向用户空间的进程发送通知(如SIGIO)。
等待队列(Wait Queues):允许进程在条件未满足时进入睡眠状态,而在条件满足时被唤醒。
文件描述符的事件通知(non-blocking I/O 和 poll):通过重载poll或select等操作,使得用户空间可以通过轮询的方式获知I/O事件的发生。
使用异步通知的好处
效率:通过非阻塞I/O和轮询的方法,可以有效降低系统开销。
实时性:驱动程序可以在数据可用时立即通知用户进程,减少等待时间。
简化操作:用户空间不需要轮询检查设备状态,可以直接过来响应事件。
示例
#include <linux/module.h> // 包含模块的基本定义
#include <linux/kernel.h> // 包含内核相关的基本函数
#include <linux/fs.h> // 包含文件操作相关的结构体与函数
#include <linux/cdev.h> // 包含字符设备的相关操作
#include <linux/uaccess.h> // 用户空间与内核空间数据交换的函数
#include <linux/signal.h> // 信号相关的定义与函数
#include <linux/sched.h> // 进程管理相关的结构体与函数
#define DEVICE_NAME "mydevice" // 设备名称
#define CLASS_NAME "myclass" // 设备类名称
static int major_number; // 主设备号
static struct class* myclass = NULL; // 设备类结构体
static struct device* mydevice = NULL; // 设备结构体
static struct cdev mycdev; // 字符设备结构体
static struct fasync_struct *fasync_queue = NULL; // 异步通知队列
// 设备打开函数
static int mydevice_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "mydevice: device opened\n"); // 打开设备时打印信息
return 0; // 成功打开设备
}
// 设备关闭函数
static int mydevice_release(struct inode *inode, struct file *file) {
printk(KERN_INFO "mydevice: device closed\n"); // 关闭设备时打印信息
return 0; // 成功关闭设备
}
// 设备读取函数
static ssize_t mydevice_read(struct file *file, char __user *buffer, size_t length, loff_t *offset) {
printk(KERN_INFO "mydevice: read operation\n"); // 读取设备时打印信息
return 0; // 在此示例中,读取操作不返回任何数据
}
// 设备写入函数
static ssize_t mydevice_write(struct file *file, const char __user *buffer, size_t length, loff_t *offset) {
printk(KERN_INFO "mydevice: write operation\n"); // 写入设备时打印信息
// 模拟设备状态变化,发送SIGIO信号给异步通知队列
if (fasync_queue) {
// 发送异步通知信号
//@param fasync_queue: 异步通知队列
// @param signal: 信号
// @param state: 状态
kill_fasync(&fasync_queue, SIGIO, POLL_IN); // 发送异步通知信号
}
return length; // 返回写入的数据长度
}
// 异步通知设置函数
//@param fd: 文件描述符
//@param filp: 文件指针
//@param on: 注册或注销标志
//@param fasync_queue: 异步通知队列
// 返回值: 成功返回0,失败返回-EIO
static int mydevice_fasync(int fd, struct file *filp, int on) {
// 注册或注销异步通知
//@todo: 实现异步通知
//@param fd: 文件描述符
//@param filp: 文件指针
//@param on: 注册或注销标志
//@param fasync_queue: 异步通知队列
// 返回值: 成功返回0,失败返回-EIO
if (fasync_helper(fd, filp, on, &fasync_queue) >= 0) { // 设置异步通知
printk(KERN_INFO "mydevice: fasync setup successful\n"); // 设置成功时打印信息
return 0; // 成功
}
return -EIO; // 设置失败,返回错误
}
// 文件操作结构体
static struct file_operations fops = {
.owner = THIS_MODULE, // 模块所有者
.open = mydevice_open, // 打开设备的函数指针
.release = mydevice_release, // 关闭设备的函数指针
.read = mydevice_read, // 读取设备的函数指针
.write = mydevice_write, // 写入设备的函数指针
.fasync = mydevice_fasync, // 异步通知函数的指针
};
// 模块初始化函数
static int __init mydevice_init(void) {
// 注册字符设备
// @param major_number: 主设备号
// @param DEVICE_NAME: 设备名称
// @param fops: 文件操作结构体
// 返回值: 成功返回主设备号,失败返回错误号
major_number = register_chrdev(0, DEVICE_NAME, &fops); // 注册字符设备
if (major_number < 0) {
printk(KERN_ALERT "mydevice: failed to register a major number\n"); // 注册失败打印警告
return major_number; // 返回错误号
}
printk(KERN_INFO "mydevice: registered correctly with major number %d\n", major_number); // 打印注册成功信息
// 创建设备类
// @param THIS_MODULE: 当前模块
// @param CLASS_NAME: 设备类名称
// 返回值: 成功返回设备类结构体,失败返回错误号
myclass = class_create(THIS_MODULE, CLASS_NAME); // 创建设备类
if (IS_ERR(myclass)) {
// 销毁设备类
unregister_chrdev(major_number, DEVICE_NAME); // 注销设备号
printk(KERN_ALERT "mydevice: failed to register device class\n");
return PTR_ERR(myclass); // 返回错误号
}
printk(KERN_INFO "mydevice: device class registered correctly\n"); // 打印设备类注册成功信息
// 创建设备
// @param myclass: 设备类结构体
// @param NULL: 父设备
// @param MKDEV(major_number, 0): 设备号
// @param NULL: 设备参数
// @param DEVICE_NAME: 设备名称
// 返回值: 成功返回设备结构体,失败返回错误号
mydevice = device_create(myclass, NULL, MKDEV(major_number, 0), NULL, DEVICE_NAME); // 创建设备
if (IS_ERR(mydevice)) {
class_destroy(myclass); // 销毁设备类
unregister_chrdev(major_number, DEVICE_NAME); // 注销设备号
printk(KERN_ALERT "mydevice: failed to create the device\n");
return PTR_ERR(mydevice); // 返回错误号
}
printk(KERN_INFO "mydevice: device class created correctly\n"); // 打印设备创建成功信息
// 初始化字符设备
// @param &mycdev: 字符设备结构体
// @param &fops: 文件操作结构体
cdev_init(&mycdev, &fops); // 初始化字符设备
mycdev.owner = THIS_MODULE; // 设置设备所有者
// 添加字符设备
// @param &mycdev: 字符设备结构体
// @param MKDEV(major_number, 0): 设备号
// @param 1: 设备数量
cdev_add(&mycdev, MKDEV(major_number, 0), 1); // 添加字符设备
return 0; // 初始化成功
}
// 模块退出函数
static void __exit mydevice_exit(void) {
if (fasync_queue) {
fasync_helper(-1, NULL, 0, &fasync_queue); // 清除异步通知队列
}
device_destroy(myclass, MKDEV(major_number, 0)); // 销毁设备
class_unregister(myclass); // 注销设备类
class_destroy(myclass); // 销毁设备类
unregister_chrdev(major_number, DEVICE_NAME); // 注销字符设备
printk(KERN_INFO "mydevice: Goodbye from the LKM!\n"); // 模块退出时打印信息
}
module_init(mydevice_init); // 模块初始化入口
module_exit(mydevice_exit); // 模块退出入口
MODULE_LICENSE("GPL"); // 模块许可证
MODULE_AUTHOR("gopher"); // 模块作者
MODULE_DESCRIPTION("A simple character device driver with async notification"); // 模块描述
MODULE_VERSION("1.0"); // 模块版本
测试
#include <stdio.h> // 包含标准输入输出库
#include <stdlib.h> // 包含标准库,提供通用的函数
#include <fcntl.h> // 包含文件控制相关的定义
#include <unistd.h> // 包含与Unix标准的函数
#include <signal.h> // 包含信号处理相关的定义
#include <string.h> // 包含字符串处理相关的函数
#define DEVICE_FILE "/dev/mydevice" // 定义设备文件路径
// 信号处理函数
void sigio_handler(int signo) {
// 当接收到SIGIO信号时调用该函数
if (signo == SIGIO) { // 检查信号是否为SIGIO
printf("Received SIGIO signal\n"); // 打印接收到的信号信息
}
}
int main() {
int fd; // 文件描述符
int flags; // 文件状态标志
// 设置SIGIO信号处理函数
// @note: 信号处理函数的第一个参数是信号值,第二个参数是信号处理函数的指针
// @note: 这里将信号处理函数设置为sigio_handler
signal(SIGIO, sigio_handler); // 设置信号处理函数,当接收到SIGIO信号时,调用sigio_handler
// 打开设备文件,进行读写操作
fd = open(DEVICE_FILE, O_RDWR);
if (fd == -1) { // 检查打开是否成功
perror("Failed to open the device"); // 如果失败,打印错误信息
return -1; // 返回错误代码
}
// 设置当前进程为文件描述符的拥有者,以接收信号
fcntl(fd, F_SETOWN, getpid());
// 获取当前文件描述符的状态标志
//@note: F_GETFL获取文件状态标志,F_SETFL设置文件状态标志
// 这里设置文件描述符为异步通知模式,以便接收SIGIO信号
flags = fcntl(fd, F_GETFL);
// 设置文件描述符为异步通知模式,以便接收SIGIO信号
// @note: FASYNC表示异步通知模式,F_SETFL设置文件状态标志
// @note: 这里使用位或运算符“|”将FASYNC与flags进行或运算,得到新的flags值
// @note: 新的flags值将会应用到文件描述符上,从而使得文件描述符变为异步通知模式
// @note: 异步通知模式下,文件描述符上发生的事件(如读写等)将会触发SIGIO信号
// @note: 信号处理函数sigio_handler将会被调用,处理异步通知事件
fcntl(fd, F_SETFL, flags | FASYNC);
printf("Waiting for SIGIO signal...\n"); // 提示用户正在等待SIGIO信号
while (1) { // 主循环
sleep(1); // 每次循环休眠1秒
}
close(fd); // 关闭设备文件(这部分实际上是不会执行到的,因为上面的循环是无限的)
return 0; // 返回0,表示程序正常退出
}
标签:异步,major,param,fasync,内核,linux,mydevice,设备
From: https://blog.csdn.net/gopher9511/article/details/142317099