linux设备驱动的异步通知和异步IO
1. 异步通知驱动函数基础
异步通知三设备就绪后,主动通知应用程序,这样可以不需要应用程序轮询设备状态。信号是软件层面上对中断机制的一种模拟,下表给出了几个Linux信号的例子。
| 信号 | 值 | 含义 |
| SIGHUP | 1 | 挂起 |
| SIGINT | 2 | 终端终端 |
| SIGKILL | 9 | 强行终止 |
| SIGSEGV | 11 | 无效内存段处理 |
1.1 signal
signal 函数接受两个参数,第一个参数是信号的值,第二个参数是该信号对应的处理函数。若处理函数传入 SIG_IGN 表示忽略该信号,若传入 SIG_DFL 表示使用默认方式处理。 signal 函数调用成功则返回最后一次的 handler 值,即上次的信号处理函数,失败则返回 SIG_ERR 。函数原型如下
// signal函数原型 void (*signal(int signum, void (*handler)(int)))(int); // 函数拆解 // 1.返回值 typedef void (*sighandler_t)(int); // 2.函数 sighandler_t signal(int signum, sighandler_t handler));
该函数的简单使用案例如下
#include <stdio.h> #include <signal.h> #include <unistd.h> // 自定义信号处理函数 void sigint_handler(int signum) { printf("收到了 SIGINT (Ctrl+C) 信号 %d\n", signum); } int main() { // 设置 SIGINT 的处理函数为 sigint_handler void (*old_handler)(int); // 保存上次的处理函数 old_handler = signal(SIGINT, sigint_handler); if (old_handler == SIG_ERR) { perror("signal 设置失败"); return 1; } printf("按 Ctrl+C 触发信号,或者等待 10 秒自动退出\n"); sleep(10); // 恢复原来的处理方式 signal(SIGINT, old_handler); return 0; }
1.2 fasync_helper
用户空间中使用 fcntl(fd, F_SETFL, flags | FASYNC) 会调用驱动层的 fasync 函数,下面的函数原型可以根据 mode 值配置
-
如果
mode非零(启用异步通知),则在链表中查找是否已存在对应filp的条目;若不存在,则分配并初始化一个新的fasync_struct,将其插入链表 -
如果
mode为 0(禁用异步通知),则从链表中删除与filp对应的条目,并释放其内存 -
返回
0表示成功,负值表示错误(如内存不足)
/* * fd:文件描述符,由应用层传入 * filp:文件指针,与设备文件关联。 * mode:模式,通常为 FASYNC 或 0。当应用通过 fcntl(fd, F_SETFL, flags | FASYNC) 设置 FASYNC 标志时,mode 非零(启用);当清除标志时,mode 为 0(禁用)。 * fapp:二级指针,指向设备驱动中维护的 struct fasync_struct * 链表头。驱动通常会在设备结构体中定义一个 struct fasync_struct *async_queue 变量,并将它的地址传给 fapp。该函数可以完成fapp变量的初始化
*/ int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fapp);
1.3 kill_fasync
kill_fasync 用于遍历指定的异步通知链表,向链表中每个条目所代表的进程发送信号。它会:
- 遍历
*fapp链表中的每个fasync_struct
- 根据条目中保存的进程信息(
fa_file->f_owner)确定目标进程
- 调用
send_sigio发送信号,并附带band信息
/* * fapp:二级指针,指向异步通知链表头(即驱动中维护的 async_queue 变量地址) * sig:要发送的信号编号,通常为 SIGIO * band:表示就绪事件的类型,取值如 POLL_IN(可读)、POLL_OUT(可写)、POLL_ERR(错误)。这些值与 poll 机制中的事件对应,会作为信号携带的额外信息(通过 si_band 传递) */ void kill_fasync(struct fasync_struct **fapp, int sig, int band);
2.使用案例
2.1 驱动函数
#include <linux/init.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/module.h> #include <linux/ioctl.h> #include <linux/io.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include <linux/slab.h> #include <linux/wait.h> #define GLOBALFIFO_SIZE 1024 #define GLOBALMEM_MAGIC 'M' #define MEM_CLEAR _IO(GLOBALMEM_MAGIC, 0) struct globalfifo_dev { struct cdev m_cdev; /* 字符设备 */ unsigned int current_len; /* fifo有效数据长度 */ unsigned char mem[GLOBALFIFO_SIZE]; /* 全局内存 */ struct semaphore sem; /* 并发控制信号量 */ wait_queue_head_t r_wait; /* 阻塞读等待队列头 */ wait_queue_head_t w_wait; /* 阻塞写等待队列头 */ struct fasync_struct* async_queue; /* 异步结构体指针,用于读 */ }; static int globalfifo_major = 266; // 存放字符设备私有数据 struct globalfifo_dev* globalfifo_devp; /* user open fd */ static int globalmem_open(struct inode* inode, struct file* filp) { struct globalfifo_dev* dev; dev = container_of(inode->i_cdev, struct globalfifo_dev, m_cdev); filp->private_data = dev; return 0; } /* user read fd */ static ssize_t globalmem_read(struct file* filp, char __user* buf, size_t count, loff_t* ppos) { int ret; struct globalfifo_dev* dev = filp->private_data; // 定义等待队列 DECLARE_WAITQUEUE(wait, current); down(&dev->sem); // 1.将等待队列加入到等待队列头 add_wait_queue(&dev->r_wait, &wait); // 2.循环检查等待条件(防止假唤醒,如果唤醒后不满足条件会再次睡眠) while (dev->current_len == 0) { // 3. 检查非阻塞模式直接返回 if (filp->f_flags & O_NONBLOCK) { ret = -EAGAIN; up(&dev->sem); remove_wait_queue(&dev->w_wait, &wait); set_current_state(TASK_RUNNING); return ret; } // 4.改变进程状态为可中断睡眠 __set_current_state(TASK_INTERRUPTIBLE); up(&dev->sem); // 5.调度其他进程执行(真正睡眠) schedule(); // 6.检查如果有信号到达返回上层处理错误(自己的唤醒只将状态转换为TASK_RUNNING,但信号到来也会做这个处理) if (signal_pending(current)) { ret = -ERESTARTSYS; remove_wait_queue(&dev->w_wait, &wait); set_current_state(TASK_RUNNING); return ret; } // 被唤醒后的处理 down(&dev->sem); } if(count > dev->current_len) count = dev->current_len; if(copy_to_user(buf, dev->mem, count)) { ret = -EFAULT; up(&dev->sem); remove_wait_queue(&dev->w_wait, &wait); set_current_state(TASK_RUNNING); return ret; } else { memcpy(dev->mem, dev->mem + count, dev->current_len - count); dev->current_len -= count; wake_up_interruptible(&dev->w_wait); // 读出数据后唤醒写进程 ret = count; } up(&dev->sem); remove_wait_queue(&dev->w_wait, &wait); set_current_state(TASK_RUNNING); return ret; } /* user write fd */ static ssize_t globalmem_write(struct file* filp, const char __user* buf, size_t count, loff_t* ppos) { int ret; struct globalfifo_dev* dev = filp->private_data; // 定义等待队列 DECLARE_WAITQUEUE(wait, current); down(&dev->sem); // 1.将等待队列插入写等待队列头 add_wait_queue(&dev->w_wait, &wait); // 2.循环等待 若FIFO满则应该挂起 while (dev->current_len == GLOBALFIFO_SIZE) { // 3. 若非阻塞则直接返回 if (filp->f_flags & O_NONBLOCK) { up(&dev->sem); ret = -EAGAIN; remove_wait_queue(&dev->w_wait, &wait); set_current_state(TASK_RUNNING); return ret; } // 4.将进程状态改为可打断睡眠 __set_current_state(TASK_INTERRUPTIBLE); up(&dev->sem); // 5.调度其他进程(真正睡眠) schedule(); // 6.若因为信号唤醒,则返回让上层完成错误处理 if (signal_pending(current)) { ret = -ERESTARTSYS; remove_wait_queue(&dev->w_wait, &wait); set_current_state(TASK_RUNNING); return ret; } down(&dev->sem); } if (count > GLOBALFIFO_SIZE - dev->current_len) count = GLOBALFIFO_SIZE - dev->current_len; if (copy_from_user(dev->mem, buf, count)) { ret = -EFAULT; up(&dev->sem); remove_wait_queue(&dev->w_wait, &wait); set_current_state(TASK_RUNNING); return ret; } else { dev->current_len += count; // 唤醒等待队列 wake_up_interruptible(&dev->r_wait); // ***唤醒异步队列,注册该信号的fd会接收到SIGIO信号,此时队列非满 if (dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN); ret = count; } up(&dev->sem); remove_wait_queue(&dev->w_wait, &wait); set_current_state(TASK_RUNNING); return count; } /* user lseek fd */ static loff_t globalmem_llseek(struct file* filp, loff_t offset, int orig) { loff_t ret; switch(orig) { // 从起始位置开始移动指针 case 0: if(offset < 0) { ret = -EINVAL; break; } if((unsigned int)offset > GLOBALFIFO_SIZE) { ret = -EINVAL; break; } filp->f_pos = (unsigned int)offset; ret = filp->f_pos; break; // 从当前位置开始移动指针 case 1: if((filp->f_pos + offset) > GLOBALFIFO_SIZE) { ret = -EINVAL; break; } if((filp->f_pos + offset) < 0) { ret = -EINVAL; break; } filp->f_pos += offset; ret = filp->f_pos; break; default: ret = -EINVAL; } return ret; } /* user ioctl fd */ static long globalfifo_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){ // 获取设备结构体指针 struct globalfifo_dev* dev = filp->private_data; switch(cmd) { case MEM_CLEAR: down(&dev->sem); dev->current_len = 0; memset(dev->mem, 0, GLOBALFIFO_SIZE); up(&dev->sem); break; default: return -EINVAL; } return 0; } /***** 支持异步操作,用户层调用会将fd加入异步通知队列async_queue *****/ static int globalfifo_fasync(int fd, struct file* filp, int mode) { struct globalfifo_dev* dev = filp->private_data; return fasync_helper(fd, filp, mode, &dev->async_queue); } /* user release fd*/ static int globalmem_release(struct inode* inode, struct file* filp) { globalfifo_fasync(-1, filp, 0); return 0; } static const struct file_operations globalfifo_fops = { .owner = THIS_MODULE, .open = globalmem_open, .release = globalmem_release, .llseek = globalmem_llseek, .read = globalmem_read, .write = globalmem_write, .unlocked_ioctl = globalfifo_unlocked_ioctl .fasync = globalfifo_fasync /*** 用户调用异步函数 ***/ }; /* 设备驱动模块insmod加载函数 */ static int globalmem_init(void) { // 向 Linux 内核中注册字符设备编号范围 register_chrdev_region(MKDEV(globalfifo_major, 0), 1, "globalfifo"); // 为设备以及共享内存分配内存 globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev), GFP_KERNEL); memset(globalfifo_devp, 0, sizeof(struct globalfifo_dev)); // 初始化字符设备0的基本字段 cdev_init(&globalfifo_devp->m_cdev, &globalfifo_fops); globalfifo_devp->m_cdev.owner = THIS_MODULE; // 将主设备号globalfifo_major次设备号0,与字符设备驱动的关联 cdev_add(&globalfifo_devp->m_cdev, MKDEV(globalfifo_major, 0), 1); // 初始化信号量 sema_init(&globalfifo_devp->sem, 1); // 初始化读写等待队列头 init_waitqueue_head(&globalfifo_devp->r_wait); init_waitqueue_head(&globalfifo_devp->w_wait); return 0; } static void globalmem_exit(void) { dev_t devno; // 注销cdev cdev_del(&globalfifo_devp->m_cdev); // 释放设备结构体内存 kfree(globalfifo_devp); // 释放设备号 devno = MKDEV(globalfifo_major, 0); unregister_chrdev_region(devno, 1); } MODULE_AUTHOR("cear"); MODULE_LICENSE("GPL"); module_param(globalfifo_major, int, S_IRUGO); module_init(globalmem_init); module_exit(globalmem_exit);
2.2 用户函数
#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <fcntl.h> #include <signal.h> #include <unistd.h> // 信号处理函数 void input_handler(int signum) { printf("receive a signal from globalfifo,signalnum:%d\n",signum); } int main() { int fd, oflags; fd = open("/dev/globalfifo", O_RDWR, S_IRUSR | S_IWUSR); if (fd != - 1) { // 1.绑定信号函数 signal(SIGIO, input_handler); // 2.设置拥有者(内核通过fd发送信号,需要绑定fd和进程的关系) fcntl(fd, F_SETOWN, getpid()); // 3.启用异步通知 oflags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, oflags | FASYNC); while(1) { sleep(100); } } else { printf("device open failure\n"); } return 0; }

浙公网安备 33010602011771号