Hasen的linux设备驱动开发学习之旅--异步通知

2024-06-08 03:32

本文主要是介绍Hasen的linux设备驱动开发学习之旅--异步通知,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

/*** Author:hasen* 参考 :《linux设备驱动开发详解》* 简介:android小菜鸟的linux* 	         设备驱动开发学习之旅* 主题:异步通知* Date:2014-11-05*/
一、异步通知的概念和作用
阻塞和非阻塞访问、poll()函数提供了较好地解决设备访问的机制,但是如果有了异步通知整套机制就更
加完整了。
异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这
一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步I/O”。信号时在软件层次上对中断机制
的一种模拟,进程收到信号和处理器收到中断可以说是一样的。信号是异步的,进程不知道信号什么时候到达。
阻塞I/O意味着一直等待设备可访问后再访问,非阻塞I/O使用poll()意味着查询设备是否可访问,而异步
通知则意味着设备通知自身可访问,实现了异步I/O。由此可见,这几种方式I/O可以互为补充。

二、Linux异步通知编程
1、Linux信号
            使用信号进行进程间通信(IPC)是UNIX中一种传统机制,LInux也支持这种机制。在Linux中,异步通知
使用信号来实现,Linux中可用的信号及其定义如下表:

Linux信号
信号 含义
SIGHUP 1 挂起
SIGINT 2 终端中断
SIGQUIT 3 终端退出
SIGILL 4 无效命令
SIGTRAP 5 跟踪陷阱
SIGIOT 6 IOT陷阱
SIGBUS 7 BUS错误
SIGFPE 8 浮点异常
SIGKILL 9 强行终止(不能被捕捉或忽略)
SIGSR1 10 用户定义的信号1
SIGSEGV 11 无效的内存段处理
SIGUSR2 12 用户定义的信号2
SIGPIPE 13 半关闭管道的写操作已经发生
SIGALRM 14 计时器到期
SIGTERM 15 终止
SIGSTKFLT 16 堆栈错误
SIGCHLD 17 子进程已经停止或退出
SIGCONT 18 如果停止了,继续执行
SIGSTOP 19 停止执行(不能被捕获或忽略)
SIGTSTP 20 终端停止信号
SIGTTIN 21 后台进程需要从终端读取输入
SIGTTOU 22 后台进程需要向从终端写出
SIGURG 23 紧急的套接字事件
SIGXCPU 24 超额使用CPU分配的时间
SIGXFSZ 25 文件尺寸超额
SIGVTALRM 26 虚拟时钟信号
SIGPROF 27 时钟信号描述
SIGWINCH 28 窗口尺寸变化
SIGIO 29 I/O
SIGPWR 30 断电重启

2、信号的接收
           在用户程序中,为了捕获信号,可以使用signal()函数来设置对应信号的处理函数:
           void (*signal (int signum,void(*handler))(int))(int) ;
           该函数原型较难理解,它可以分解为:
           typedef void (*sighandler_t) (int) ;
           sighandler_t signal(int signum,sighandler_t sighandler) ;
第一个参数指定信号的值,第二个参数指定针对前面信号值的处理函数,若为SIG_IGN,表示忽略该信号;
若为SIG_DFL,表示采用系统默认方式处理信号;若为用户自定义函数,信号捕捉到后,将会执行该函数。
           如果signal()调用成功,它返回最后一为信号signum绑定的处理函数handler的值,失败返回SIG_ERR。
           在进程执行时,按下“Ctrl+c”将向其发出SIGINT信号,kill正在运行的进程将向其发送SIGTERM信号。
示例:进程捕捉SIGINT和SIGTERM信号并输出信号值

void sigterm_handler(int signo)
{printf("Have caught sig NO.%d\n",signo) ;exit(0) ;
}
int main(void)
{signal(SIGINT,sigterm_handler) ;signal(SIGTERM,sigterm_handler) ;while(1) ;return 0 ;
}
除了signal()函数之外,sigaction()函数也可用于改变进程接收到特点信号的行为,它的原型是:
int sigaction(int signum,const struct sigaction *act,struct sigaction oldact) ;
           该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP之外的任何一个特定有效的信号。第二个
参数是指向结构体sigaction的一个实例指针,在结构体sigaction的实例中,指定了对特定信号的处理函数,若为
空,则进程会以缺省方式对信号进行处理。第三个参数指向的对象用来保存原来对相应信号的处理函数,可指定
oldact为NULL,当后面两个参数都为NULL时,那么该函数可用以检查信号的有效性。
           下面是使用信号实现异步通知的实例,它通过signal(SIGIO,input_handler)对标准输入文件描述符
STDIN_FILENO启动信号机制。用户输入后,应用程序将接收到SIGNO信号,处理函数input_handler()将被调用。
示例:使用信号实现异步通知的应用程序
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#define MAX_LEN 100 ;void input_handler(int num)
{char data[MAX_LEN] ;int len ;/*读取并输出STDIN_FILENO上的输入*/len = read(STDIN_FILENO,&data,MAX_LEN) ;data[len] = 0 ;printf("input available:%s\n,data") ;
}
main()
{int oflags ;/*启动信号驱动机制*/signal(SIGIO,input_handler) ;//input_handler为信号处理函数fcntl(STDIN_FILENO,F_SETOWN,getpid()) ;//设备本进程为文件拥有者oflags = fcntl(STDIN_FILENO,F_GETFL) ;//得到文件标志fcntl(STDIN_FILENO,FSETFL,oflags|FASYNC) ;//为文件添加FASYNC标志(异步通知机制)/*最后进入一个死循环,仅为保持进程不终止,如果程序中没有这个死循环会立即执行完毕*/while(1) ;
}
           为了在用户空间中能处理一个设备释放的信号,需要完成下面三个步骤:
           (1)通过F_SETOWN IO控制命令设置文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进
程接收到。
           (2)通过F_SETFL IO控制命令设置设备文件支持FASYNC,即异步通知模式。
           (3)通过signal()函数连接信号和信号处理函数。
3、信号的释放
           在设别驱动和应用程序的异步通知交互中,仅仅在应用程序端捕获信号是不够的,因为信号的源头
在设备驱动端,因此,在设备驱动中添加信号释放的代码。
           为了使设别支持异步通知机制,驱动程序设计3项工作:
           (1)支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID,不过,内核已
经处理了此项工作,设备驱动无需处理。
           (2)支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。因
此,驱动中应该实现fasync()函数。
           (3)在设备资源可获得时,调用kill_fasync()函数激发相应的信号。


           驱动中的工作与用户空间中的是一一对应的,下图是异步通信过程中用户空间和设备驱动的交互。

           设备驱动中的异步通知编程,主要用到两个函数和一个结构体
结构体:fasync_struct 
方法:(1)处理FASYNC标志变更的int fasync_helper(int fd,struct file *flip,int mode,struct fasync_struct **fa);(2)释放信号用的函数void kill_fasync(struct fasync_struct **fa ,int sig,int band);和其他的设备驱动一样,将fasync_struct结构体指针放在设备结构体中最合适。
示例:支持异步通知的设备结构体模板
struct xxx_dev{struct cdev cdev ;/*cdev结构体*/...struct fasync_struct *fasync_queue ;/*异步结构体指针*/
}
           在设备驱动的fasync()函数中,只要简单地将该函数的3个参数以及fasync_struct结构体指针的指针
作为第4个参数传入fasync_helper()函数即可。
示例:支持异步通知的设备驱动fasync()函数模板
static int xxx_fasync()
{struct xxx_dev *dev = filp->private_data;return fasync_helper(fd,filp,mode,&dev->async_queue) ;
}
           在设备资源可以获得时,应该调用kill_fasync()释放SIGIO信号,可读时第三个参数设置为POLL_IN,
可写时第三个参数设置为POLL_OUT 。 
示例:支持异步通知的设备驱动信号释放。
static ssize_t xxx_write(struct file *filp,const char __user buf,size_t count,loff_t *f_pos)
{struct xxx_dev *dev = filp->private_data ;.../*产生异步读信号*/if(dev->async_queue)kill_fasync(&dev->async_queue,SIGIO,POLL_IN) ;...
}
           最后在文件关闭时,即在设备的release()函数中,应调用设备驱动的fasync()函数将文件从异步通
知的列表中删除。
示例:支持异步通知的设备驱动release()函数模板    
static ssize_t xxx_release(struct inode *inode,struct file filp)
{struct xxx_dev *dev = filp->private_data ;.../*产生异步读信号*/if(dev->async_queue)kill_fasync(&dev->async_queue,SIGIO,POLL_IN) ;...
}
           下面是增加异步通知机制的设备驱动和验证代码

在globalfifo驱动中增加异步通知

int GLOBALFIFO_SIZE = 100 ;/*增加异步通知后的globalfifo设备结构体*/
struct globalfifo_dev {struct cdev cdev ;/*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 ;/*异步结构体指针*/	
} ;/*支持异步通知的globalfifo设备驱动的fasync()函数*/
static int globalfifo_fasync(int fd,struct file *flip,int mode)
{struct globalfifo_dev *dev = flip->private_data ;return fasync_helper(fd,flip,mode,&dev->async_queue) ;
}/*支持异步通知的globalfifo设备驱动写函数*/
static ssize_t globalfifo_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
{struct globalfifo_dev *dev = filp->private_data ;/*获得设备结构体指针*/int ret ;DECLARE_WAITQUEUE(wait,current) ;/*定义等待队列*/down(&dev->sem) ;/*获取信号量*/add_wait_queue(&dev->w_wait,&wait) ;/*进入写等待队列头*//*等待fifo非满*/if(dev->current_len == GLOBALFIFO_SIZE) {if(filp->f_flag & O_NONBLOCK ){/*如果是非阻塞访问*/ret = -EAGAIN ;goto out ;}__set_current_state(TASK_INTERRPTIBLE) ;/*改变进程状态为睡眠*/up(&dev->sem) ;schedule() ;/*调度其他进程执行*/if(signal_pending(current)){ /*如果是因为信号唤醒*/ret = -ERESTARTSYS ;goto out2 ;}down(&dev->sem) ;/*获取信号量*/}/*从用户空间拷贝到内核空间*/if(count >GLOBALFIFO_SIZE - dev->current_len)count = GLOBALFIFO_SIZE - dev->current_len ;if(copy_from_user(dev->mem + dev->current_len , buf ,count)){ret = -EFAULT ;goto out ;}else{dev->current_len += count ;printk(KERN_INFO "writeen %d bytes(s),current_len:%d\n",count,dev->current_len) ;wake_up_interruptible(&dev->r_wait) ;/*唤醒读等待队列*//*产生异步读信号*/if(dev->async_queue)/*释放信号,可写时为POLL_OUT,可读时为POLL_IN*/kill_fasync(&dev->async_queue,SIGIO,POLL_IN) ;ret = count ;}out: up(&dev->sem) ;/*释放信号量*/out2 :remove_wait_queue(&dev->w_wait,&wait) ;set_current_state(TASK_RUNNING) ;return ret ;
}/*增加异步通知的globalfifo设备驱动release()函数*/
int globalfifo_release(struct inode *inode ,struct file *filp ) 
{/*将文件从异步通知列表中删除*/globalfifo_fasync(-1,filp,0) ;return 0 ;
}

在用户空间验证globalfifo的异步通知
#include <xxx.h>void input_handler(int signum)
{printf("receive a signal from globalfifo,signum:%d\n",signum) ;
}void main()
{int fd  , oflags ;fd = open("/dev/globalfifo",O_RDWR ,S_IRUSR|S_IWUSR) ;if(fd != -1){/*启动信号驱动机制*/signal(SIGIO,input_handler);/*让input_handler处理SIGIO信号*/fcntl(fd,F_SETOWN,getpid()) ;/*设置当前进程为文件所有者*/oflags = fcntl(fd,F_GETFL) ; /*得到文件的所有标志*/fcntl(fd.F_SETFL,oflags | FASYNC) ;/*给文件加上FASYNC标志,使支持异步通知模式*/while(1){sleep(100) ;}}else{printf("open driver failure\n") ;}
}


这篇关于Hasen的linux设备驱动开发学习之旅--异步通知的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1041094

相关文章

基于 Cursor 开发 Spring Boot 项目详细攻略

《基于Cursor开发SpringBoot项目详细攻略》Cursor是集成GPT4、Claude3.5等LLM的VSCode类AI编程工具,支持SpringBoot项目开发全流程,涵盖环境配... 目录cursor是什么?基于 Cursor 开发 Spring Boot 项目完整指南1. 环境准备2. 创建

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

Python实战之SEO优化自动化工具开发指南

《Python实战之SEO优化自动化工具开发指南》在数字化营销时代,搜索引擎优化(SEO)已成为网站获取流量的重要手段,本文将带您使用Python开发一套完整的SEO自动化工具,需要的可以了解下... 目录前言项目概述技术栈选择核心模块实现1. 关键词研究模块2. 网站技术seo检测模块3. 内容优化分析模

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

Linux搭建ftp服务器的步骤

《Linux搭建ftp服务器的步骤》本文给大家分享Linux搭建ftp服务器的步骤,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录ftp搭建1:下载vsftpd工具2:下载客户端工具3:进入配置文件目录vsftpd.conf配置文件4:

Java+AI驱动实现PDF文件数据提取与解析

《Java+AI驱动实现PDF文件数据提取与解析》本文将和大家分享一套基于AI的体检报告智能评估方案,详细介绍从PDF上传、内容提取到AI分析、数据存储的全流程自动化实现方法,感兴趣的可以了解下... 目录一、核心流程:从上传到评估的完整链路二、第一步:解析 PDF,提取体检报告内容1. 引入依赖2. 封装

Python异步编程之await与asyncio基本用法详解

《Python异步编程之await与asyncio基本用法详解》在Python中,await和asyncio是异步编程的核心工具,用于高效处理I/O密集型任务(如网络请求、文件读写、数据库操作等),接... 目录一、核心概念二、使用场景三、基本用法1. 定义协程2. 运行协程3. 并发执行多个任务四、关键