Linux设备驱动开发-linux驱动中的异步通知

简介: 前面已经说了阻塞与非阻塞的访问方式,这里我们就继续说下异步通知的机制。 什么是异步通知呢?异步通知的意思就是,一旦设备就绪,则主动通知应用程序,应用程序 根本就不需要查询设备状态,类似于中断的概念,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。

前面已经说了阻塞非阻塞的访问方式,这里我们就继续说下异步通知的机制。

什么是异步通知呢?异步通知的意思就是,一旦设备就绪,则主动通知应用程序,应用程序 根本就不需要查询设备状态,类似于中断的概念,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来 等待信号的到达。下面我们就看一下在linux中机制的实现方式。

在linux中,异步通知是使用信号来实现的,而在linux,大概有30种信号,比如大家熟悉的ctrl+c的SIGINT信号,进程能够忽略或者捕获除过SIGSTOP和SIGKILL的全部信号,当信号背捕获以后,有相应的函数来处理它。

在linux中,使用signal()函数来捕获信号,它的函数运行如下:

  1. #include   
  2.   
  3. typedef void (*sighandler_t)(int);  
  4.   
  5. sighandler_t signal(int signum, sighandler_t handler);  
第 一个参数就是指定的信号的值,而第二个参数便是此信号的信号处理函数,当为SIG_IGN,表示信号被忽略,当为SIG_DFL时,表示采用系统的默认方 式来处理该信号。当然,信号处理函数也可以自己定义。当signal()调用成功后,返回处理函数handler值,调用失败后返回SIG_ERR。下面 的函数就是当ctrl+c发生以后,进程捕获别且指定处理函数:
  1. signal(SIGINT,sigterm_handler);  
这里面SIGINT便是接受的信号,而sigterm_handler就是针对此信号的处理函数,当然处理函数可以自己定义。

下面我们来看一个异步通知的实例:

  1. #include  
  2. #include  
  3. #include  
  4. #include  
  5. #include  
  6. #include  
  7. #define MAX_LEN 100  
  8.   
  9. void input_handler(int num)  
  10. {  
  11.     char data[MAX_LEN];  
  12.     int len;  
  13.   
  14.     len=read(STDIN_FILENO,&data,MAX_LEN);  
  15.     data[len]=0;  
  16.     printf("input available:%s\n",data);  
  17. }  
  18.   
  19. main()  
  20. {  
  21.     int oflags;  
  22.       
  23.     signal(SIGIO,input_handler);  
  24.     fcntl(STDIN_FILENO,F_SETOWN,getpid());  
  25.     oflags=fcntl(STDIN_FILENO,F_GETFL);  
  26.     fcntl(STDIN_FILENO,F_SETFL,oflags|FASYNC);  
  27.   
  28.     while(1);  
  29. }  
首 先这里面将要进行捕获的信号是SIGIO信号,这里面通过signal()对标准输入文本描述符STDIN_FILENO启动异步信号机制。当用户进行输 入以后,进程将捕获到SIGIO信号,然后调用处理函数input_handler()进行处理,当然这个函数是自己定义的。而 fcntl(STDIN_FILENO,F_SETOWN,getpid())设置进程为STDIN_FILENO文件的拥有者,这一步其实是将该信号与 进程进行绑定,下面oflags=fcntl(STDIN_FILENO,F_GETFL)和 fcntl(STDIN_FILENO,F_SETFL,oflags|FASYNC)是启动异步机制,对设备设置FASYNC标志,程序执行后效果如 下:
  1. wanchres@wanchres-desktop:~/Linux/test/c code$ ./signal_test   
  2. wanchres  
  3. input available:wanchres  
当输入一串字符以后,标准输入设备释放SIGIO信号,然后这个信号驱使对应的应用程序中的处理函数得以执行,将输入显示出来。上面的例子很好的说明了一个在用户空间中处理一个释放信号的具体的过程。

而在驱动程序和应用程序的异步通知的交互中,这种仅仅在应用程序端的,因为信号的源头在设备驱动端。因此,应该再合适的时候让设备驱动释放信号,其相关的步骤如下:

1、支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID,此工作已由内核完成。

2、支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动中的fasync()函数将得以执行,所以驱动中应实现fasync()函数。

3、在设备资源可获得时,调用kill_fasync()函数激发相应的信号。

设备驱动中异步通知的编程实现中,主要用到一个结构体fasync_struct,其定义为:

  1. struct fasync_struct {  
  2.                int     magic;  
  3.                int     fa_fd;  
  4.                struct  fasync_struct   *fa_next; /* singly linked list */  
  5.                struct  file            *fa_file;             
  6. };  
fa_file即进程打开设备文件时创建的file结构file.owner即为进程的pid。

处理FASYNC标志变更的函数:

  1. int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)  
释放信号的函数:
  1. void kill_fasync(struct fasync_struct **fp, int sig, int band)  
下面我们来看下支持异步通知的模板。

设备结构体:

  1. struct xxx_dev{  
  2.         struct cdev cdev;  
  3.     ......  
  4.     struct fasync_struct *async_queue;  
  5. };  
fasync()函数:
  1. static int xxx_fasync(int fd,struct file *filp,int mode)  
  2. {  
  3.     struct xxx_dev *dev = filp->private_data;  
  4.     return fasync_helper(fd,filp,mode,&dev->async_queue);  
  5. }  
在设备资源可以获得时,应该调用kill_fasync()释放SIGIO信号,可读时第三个参数是POLL_IN,可写时为POLL_OUT.
  1. static ssize_t xxx_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)  
  2. {  
  3.     struct xxx_dev *dev = filp->private_data;  
  4.     ......  
  5.     if(dev->async_queue)  
  6.         kill_fasync(&dev->async_queue,SIGIO,POLL_IN);  
  7.         ......  
  8. }  
在release函数中,应调用fasync()函数将文件从异步通知的列表中删除。
  1. int xxx_release(struct inode *inode,struct file *filp)  
  2. {  
  3.     xxx_fasync(-1,filp,0);  
  4.     return 0;  
  5. }  
上面是异步通知在设备驱动中的模板,下面我们来看看在globalmem中添加异步通知后各模块的代码:
  1. struct globalmem_dev{  
  2.         struct cdev cdev;  
  3.     unsigned int current_len;  
  4.         unsigned char mem[GLOBALMEM_SIZE];  
  5.     struct semaphore sem;  
  6.     wait_queue_head_t r_wait;  
  7.     wait_queue_head_t w_wait;  
  8.     struct fasync_struct *async_queue;  
  9. };  

  1. static int globalmem_fasync(int fd,struct file *filp,int mode)  
  2. {  
  3.     struct globalmem_dev *dev = filp->private_data;  
  4.     return fasync_helper(fd,filp,mode,&dev->async_queue);  
  5. }  

  1. static ssize_t globalmem_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)  
  2. {  
  3.     unsigned long p = *ppos;  
  4.     int ret = 0;  
  5.     struct globalmem_dev *dev = filp->private_data;  
  6.     DECLARE_WAITQUEUE(wait,current);      
  7.       
  8.     down(&dev->sem);  
  9.     add_wait_queue(&dev->w_wait,&wait);  
  10.   
  11.     while(dev->current_len==GLOBALMEM_SIZE){  
  12.         if(filp->f_flags & O_NONBLOCK){  
  13.             ret = -EAGAIN;  
  14.             goto out;         
  15.         }  
  16.         __set_current_state(TASK_INTERRUPTIBLE);  
  17.         up(&dev->sem);  
  18.   
  19.         schedule();  
  20.         if(signal_pending(current)){  
  21.             ret = -ERESTARTSYS;  
  22.             goto out2;    
  23.         }     
  24.           
  25.         down(&dev->sem);  
  26.     }  
  27.       
  28.     if(count > GLOBALMEM_SIZE-dev->current_len)  
  29.         count = GLOBALMEM_SIZE-dev->current_len;  
  30.     if(copy_from_user(dev->mem+dev->current_len,buf,count)){  
  31.         ret = -EFAULT;  
  32.         goto out;     
  33.     }else{  
  34.         dev->current_len += count;  
  35.         printk(KERN_INFO "written %d bytes(s),current_len:%d\n",count,dev->current_len);  
  36.   
  37.         wake_up_interruptible(&dev->r_wait);  
  38.           
  39.         if(dev->async_queue)  
  40.             kill_fasync(&dev->async_queue,SIGIO,POLL_IN);  
  41.   
  42.         ret = count;              
  43.     }  
  44.     out:up(&dev->sem);  
  45.     out2:remove_wait_queue(&dev->w_wait,&wait);  
  46.     set_current_state(TASK_RUNNING);  
  47.     return ret;  
  48. }  

  1. int globalmem_release(struct inode *inode,struct file *filp)  
  2. {  
  3.     globalmem_fasync(-1,filp,0);  
  4.     return 0;  
  5. }  

代码写好以后,我们还需编写一个在用户空间验证globalmem异步通知的程序,程序的功能是接收到globalmem发出的信号后输出信号值,代码如下:
  1. #include  
  2. #include  
  3. #include  
  4. #include  
  5. #include  
  6. #include  
  7.   
  8. void input_handler(int signum)  
  9. {  
  10.     printf("receive a signal from globalmem,signalnum:%d\n",signum);  
  11. }  
  12.   
  13. main()  
  14. {  
  15.     int fd,oflags;  
  16.     fd = open("/dev/globalmem_sync",O_RDWR,S_IRUSR|S_IWUSR);  
  17.     if(fd != -1){  
  18.         signal(SIGIO,input_handler);  
  19.         fcntl(fd,F_SETOWN,getpid());  
  20.         oflags = fcntl(fd,F_GETFL);  
  21.         fcntl(fd,F_SETFL,oflags|FASYNC);  
  22.         while(1){  
  23.             sleep(100);  
  24.         }  
  25.     }else{  
  26.         printf("device open failure\n");  
  27.     }  
  28. }  
将 驱动的内核模块make完成后,将其插入,然后编译用户程序,采用前面介绍过的加载新的设备驱动别且创建设备文件节点(mknod /dev/globalmem),最后再echo向/dev/globalmenm写入新的数据,例如:echo wanchres > /dev/globalmem。
相关文章
|
5月前
|
存储 网络协议 Ubuntu
【Linux开发实战指南】基于UDP协议的即时聊天室:快速构建登陆、聊天与退出功能
UDP 是一种无连接的、不可靠的传输层协议,位于IP协议之上。它提供了最基本的数据传输服务,不保证数据包的顺序、可靠到达或无重复。与TCP(传输控制协议)相比,UDP具有较低的传输延迟,因为省去了建立连接和确认接收等过程,适用于对实时性要求较高、但能容忍一定数据丢失的场景,如在线视频、语音通话、DNS查询等。 链表 链表是一种动态数据结构,用于存储一系列元素(节点),每个节点包含数据字段和指向下一个节点的引用(指针)。链表分为单向链表、双向链表和循环链表等类型。与数组相比,链表在插入和删除操作上更为高效,因为它不需要移动元素,只需修改节点间的指针即可。但访问链表中的元素不如数组直接,通常需要从
281 2
|
2月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
106 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
3月前
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】
|
4月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
50 6
|
4月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
53 5
|
4月前
|
编解码 安全 Linux
基于arm64架构国产操作系统|Linux下的RTMP|RTSP低延时直播播放器开发探究
这段内容讲述了国产操作系统背景下,大牛直播SDK针对国产操作系统与Linux平台发布的RTMP/RTSP直播播放SDK。此SDK支持arm64架构,基于X协议输出视频,采用PulseAudio和Alsa Lib处理音频,具备实时静音、快照、缓冲时间设定等功能,并支持H.265编码格式。此外,提供了示例代码展示如何实现多实例播放器的创建与管理,包括窗口布局调整、事件监听、视频分辨率变化和实时快照回调等关键功能。这一技术实现有助于提高直播服务的稳定性和响应速度,适应国产操作系统在各行业中的应用需求。
123 3
|
5月前
|
Web App开发 缓存 Linux
FFmpeg开发笔记(三十六)Linux环境安装SRS实现视频直播推流
《FFmpeg开发实战》书中第10章提及轻量级流媒体服务器MediaMTX,适合测试RTSP/RTMP协议,但不适合生产环境。推荐使用SRS或ZLMediaKit,其中SRS是国产开源实时视频服务器,支持多种流媒体协议。本文简述在华为欧拉系统上编译安装SRS和FFmpeg的步骤,包括安装依赖、下载源码、配置、编译以及启动SRS服务。此外,还展示了如何通过FFmpeg进行RTMP推流,并使用VLC播放器测试拉流。更多FFmpeg开发内容可参考相关书籍。
124 2
FFmpeg开发笔记(三十六)Linux环境安装SRS实现视频直播推流
|
5月前
|
Linux
FFmpeg开发笔记(三十四)Linux环境给FFmpeg集成libsrt和librist
《FFmpeg开发实战》书中介绍了直播的RTSP和RTMP协议,以及新协议SRT和RIST。SRT是安全可靠传输协议,RIST是可靠的互联网流传输协议,两者于2017年发布。腾讯视频云采用SRT改善推流卡顿。以下是Linux环境下为FFmpeg集成libsrt和librist的步骤:下载安装源码,配置、编译和安装。要启用这些库,需重新配置FFmpeg,添加相关选项,然后编译和安装。成功后,通过`ffmpeg -version`检查版本信息以确认启用SRT和RIST支持。详细过程可参考书中相应章节。
111 1
FFmpeg开发笔记(三十四)Linux环境给FFmpeg集成libsrt和librist
|
5月前
|
弹性计算 运维 自然语言处理
阿里云OS Copilot测评:重塑Linux运维与开发体验的智能革命
阿里云OS Copilot巧妙地将大语言模型的自然语言处理能力与操作系统团队的深厚经验相结合,支持自然语言问答、辅助命令执行等功能,为Linux用户带来了前所未有的智能运维与开发体验。
|
6月前
|
编解码 Linux
FFmpeg开发笔记(二十八)Linux环境给FFmpeg集成libxvid
XviD是开源的MPEG-4视频编解码器,曾与DivX一起用于早期MP4视频编码,但现在已被H.264取代。要集成XviD到Linux上的FFmpeg,首先下载源码,解压后配置并编译安装libxvid。接着,在FFmpeg源码目录中,重新配置FFmpeg以启用libxvid,然后编译并安装。成功后,通过`ffmpeg -version`检查是否启用libxvid。详细步骤包括下载、解压libxvid,使用`configure`和`make`命令安装,以及更新FFmpeg配置并安装。
96 2
FFmpeg开发笔记(二十八)Linux环境给FFmpeg集成libxvid