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

相关文章

Spring Boot集成/输出/日志级别控制/持久化开发实践

《SpringBoot集成/输出/日志级别控制/持久化开发实践》SpringBoot默认集成Logback,支持灵活日志级别配置(INFO/DEBUG等),输出包含时间戳、级别、类名等信息,并可通过... 目录一、日志概述1.1、Spring Boot日志简介1.2、日志框架与默认配置1.3、日志的核心作用

Oracle数据库定时备份脚本方式(Linux)

《Oracle数据库定时备份脚本方式(Linux)》文章介绍Oracle数据库自动备份方案,包含主机备份传输与备机解压导入流程,强调需提前全量删除原库数据避免报错,并需配置无密传输、定时任务及验证脚本... 目录说明主机脚本备机上自动导库脚本整个自动备份oracle数据库的过程(建议全程用root用户)总结

Linux如何查看文件权限的命令

《Linux如何查看文件权限的命令》Linux中使用ls-R命令递归查看指定目录及子目录下所有文件和文件夹的权限信息,以列表形式展示权限位、所有者、组等详细内容... 目录linux China编程查看文件权限命令输出结果示例这里是查看tomcat文件夹总结Linux 查看文件权限命令ls -l 文件或文件夹

idea的终端(Terminal)cmd的命令换成linux的命令详解

《idea的终端(Terminal)cmd的命令换成linux的命令详解》本文介绍IDEA配置Git的步骤:安装Git、修改终端设置并重启IDEA,强调顺序,作为个人经验分享,希望提供参考并支持脚本之... 目录一编程、设置前二、前置条件三、android设置四、设置后总结一、php设置前二、前置条件

PyQt5 GUI 开发的基础知识

《PyQt5GUI开发的基础知识》Qt是一个跨平台的C++图形用户界面开发框架,支持GUI和非GUI程序开发,本文介绍了使用PyQt5进行界面开发的基础知识,包括创建简单窗口、常用控件、窗口属性设... 目录简介第一个PyQt程序最常用的三个功能模块控件QPushButton(按钮)控件QLable(纯文本

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

Linux系统之lvcreate命令使用解读

《Linux系统之lvcreate命令使用解读》lvcreate是LVM中创建逻辑卷的核心命令,支持线性、条带化、RAID、镜像、快照、瘦池和缓存池等多种类型,实现灵活存储资源管理,需注意空间分配、R... 目录lvcreate命令详解一、命令概述二、语法格式三、核心功能四、选项详解五、使用示例1. 创建逻

Linux下在线安装启动VNC教程

《Linux下在线安装启动VNC教程》本文指导在CentOS7上在线安装VNC,包含安装、配置密码、启动/停止、清理重启步骤及注意事项,强调需安装VNC桌面以避免黑屏,并解决端口冲突和目录权限问题... 目录描述安装VNC安装 VNC 桌面可能遇到的问题总结描js述linux中的VNC就类似于Window

linux下shell脚本启动jar包实现过程

《linux下shell脚本启动jar包实现过程》确保APP_NAME和LOG_FILE位于目录内,首次启动前需手动创建log文件夹,否则报错,此为个人经验,供参考,欢迎支持脚本之家... 目录linux下shell脚本启动jar包样例1样例2总结linux下shell脚本启动jar包样例1#!/bin

Go语言并发之通知退出机制的实现

《Go语言并发之通知退出机制的实现》本文主要介绍了Go语言并发之通知退出机制的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、通知退出机制1.1 进程/main函数退出1.2 通过channel退出1.3 通过cont