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

相关文章

Linux脚本(shell)的使用方式

《Linux脚本(shell)的使用方式》:本文主要介绍Linux脚本(shell)的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述语法详解数学运算表达式Shell变量变量分类环境变量Shell内部变量自定义变量:定义、赋值自定义变量:引用、修改、删

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

使用Python开发一个现代化屏幕取色器

《使用Python开发一个现代化屏幕取色器》在UI设计、网页开发等场景中,颜色拾取是高频需求,:本文主要介绍如何使用Python开发一个现代化屏幕取色器,有需要的小伙伴可以参考一下... 目录一、项目概述二、核心功能解析2.1 实时颜色追踪2.2 智能颜色显示三、效果展示四、实现步骤详解4.1 环境配置4.

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Python使用smtplib库开发一个邮件自动发送工具

《Python使用smtplib库开发一个邮件自动发送工具》在现代软件开发中,自动化邮件发送是一个非常实用的功能,无论是系统通知、营销邮件、还是日常工作报告,Python的smtplib库都能帮助我们... 目录代码实现与知识点解析1. 导入必要的库2. 配置邮件服务器参数3. 创建邮件发送类4. 实现邮件

详解Linux中常见环境变量的特点与设置

《详解Linux中常见环境变量的特点与设置》环境变量是操作系统和用户设置的一些动态键值对,为运行的程序提供配置信息,理解环境变量对于系统管理、软件开发都很重要,下面小编就为大家详细介绍一下吧... 目录前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变

Linux系统中的firewall-offline-cmd详解(收藏版)

《Linux系统中的firewall-offline-cmd详解(收藏版)》firewall-offline-cmd是firewalld的一个命令行工具,专门设计用于在没有运行firewalld服务的... 目录主要用途基本语法选项1. 状态管理2. 区域管理3. 服务管理4. 端口管理5. ICMP 阻断

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

Linux中修改Apache HTTP Server(httpd)默认端口的完整指南

《Linux中修改ApacheHTTPServer(httpd)默认端口的完整指南》ApacheHTTPServer(简称httpd)是Linux系统中最常用的Web服务器之一,本文将详细介绍如何... 目录一、修改 httpd 默认端口的步骤1. 查找 httpd 配置文件路径2. 编辑配置文件3. 保存