一、IO模型
- I/O 模型在操作系统中用于处理应用程序与设备驱动之间的数据传输。
- I/O 通信模型的核心是解决程序与设备之间如何高效、合理地进行数据通信。不同的模型通过阻塞、非阻塞、同步、异步的方式来控制数据流和处理 I/O 请求。
注:在驱动开发中可以定义一个全局结构体
- 用于全局管理驱动程序的状态和资源。
- 在驱动开发中,有时需要一个全局的数据结构来保存设备的状态、驱动配置、缓存、锁等信息,这样不同的驱动程序函数可以通过该结构体访问和修改共享的资源。
- 作用
- 统一管理驱动状态: 全局结构体通常保存驱动程序的各种状态信息。例如,设备的注册信息、分配的内存、硬件寄存器映射等都可以保存在这个结构体中。
- 共享资源: 驱动程序通常会有多个函数被内核调用,比如初始化函数、读写函数、中断处理函数等。使用全局结构体,可以让这些函数方便地共享和访问同样的数据或资源。
- 简化代码结构: 将驱动程序涉及的所有全局变量封装在一个结构体中,使代码更加清晰、结构化,同时也减少了全局变量命名冲突的可能性。
一、阻塞 I/O 模型
- 阻塞 I/O 是最常见的 I/O 模型。当应用程序发起 I/O 请求时,如果数据没有准备好,应用程序将进入阻塞状态,等待数据准备完毕后,驱动程序会唤醒阻塞的应用程序。此时,应用程序才能继续执行。
- 工作流程
应用程序向驱动发送读取数据的请求。
如果驱动程序中数据尚未准备好,应用程序进入 睡眠状态,等待数据到来。
当数据准备好后,驱动程序会唤醒应用程序,程序从睡眠状态恢复,继续读取数据。
完成 I/O 操作后,应用程序继续执行其他任务。
- 具体步骤
a. 定义并初始化等待队列
wait_queue_head_t wqhead;//在全局结构体中
在结构体 global_struct 中定义了 wqhead,表示等待队列头,用于管理睡眠进程的队列。
在mod_init() 初始化函数中,通过 init_waitqueue_head() 初始化等待队列头。
init_waitqueue_head(&gstruct.wqhead);
b. 进程睡眠机制
在 chdev_read 函数中,当应用程序尝试从驱动读取数据时,程序会检查数据是否可用。如果数据不可用,进程就会进入睡眠状态,挂到等待队列上。
wait_event_interruptible(pt_gstruct->wqhead, pt_gstruct->data_flag);
c. 唤醒机制
当数据到达时,会通过 chdev_write 函数进行处理
pt_gstruct->data_flag = 1;//数据写入后,data_flag 被设置为 1,表示数据已准备好。
wake_up_interruptible(&pt_gstruct->wqhead);
wake_up_interruptible() 函数用于唤醒等待队列 wqhead 上的所有进程,唤醒后,之前处于睡眠状态的进程会继续执行 chdev_read 函数中的代码,读取数据。
二、非阻塞I/O实现
- 非阻塞 I/O 的实现逻辑与阻塞 I/O 类似,但不进入睡眠状态。如果数据尚未准备好,非阻塞 I/O 立即返回错误 -EAGAIN,告诉应用程序稍后重试。
if (file->f_flags & O_NONBLOCK) {
// 检查文件标志是否为非阻塞模式
if (pt_gstruct->data_flag == 0) // 没有数据可用
return -EAGAIN; // 返回 EAGAIN 错误,表示无数据,稍后重试
}
三、异步通知
- 异步通知(Asynchronous Notification)是 Linux 内核提供的一种机制,它允许设备驱动在数据准备好或状态发生变化时,主动向应用程序发送信号(通常是 SIGIO 信号),通知应用程序及时处理。
- 这种机制在应用程序不需要不断轮询设备状态的情况下非常有用。
- 设备驱动中的结构定义
struct global_struct {
struct class *cls;
int major, minor;
struct cdev cdev_obj;
wait_queue_head_t wqhead; // 等待队列头
char sharebuf[128]; // 共享缓冲区
int data_flag; // 标记数据是否可用,0 表示无数据,1 表示有数据
struct fasync_struct *fapp; // 异步通知结构,用于管理异步通知
};
- 驱动程序的 fasync 实现
int chdev_fasync(int fd, struct file *file, int on)
{
struct global_struct *pt_gstruct = file->private_data;
// fasync_helper 负责将文件描述符和进程与异步通知关联或解除关联
return fasync_helper(fd, file, on, &pt_gstruct->fapp);
}
- 实现数据写入时的异步通知
//当驱动程序中有新数据时,通过 kill_fasync 向注册了异步通知的进程发送信号。
ssize_t chdev_write(struct file *file, const char __user *usr, size_t sz, loff_t *loff)
{
struct global_struct *pt_gstruct = file->private_data;
long ret;
// 将用户数据复制到共享缓冲区
ret = copy_from_user(pt_gstruct->sharebuf, usr, sz);
if (ret > 0) {
printk("%s-%d copy_from_user err\n", __func__, __LINE__);
return -3;
}
// 数据写入完成后,标记数据已经准备好
pt_gstruct->data_flag = 1;
// 唤醒所有等待在等待队列上的进程
wake_up_interruptible(&pt_gstruct->wqhead);
// 发送异步通知信号,通知应用程序有数据可读
kill_fasync(&pt_gstruct->fapp, SIGIO<