zynq操作系统: Linux下异步通知的应用

简介: 在使用阻塞或者非阻塞的方式来读取驱动中某些值都是应用程序主动读取的,对于非阻塞方式来说还需要应用程序通过 poll 函数不断的轮询。最好的方式就是驱动程序能主动向应用程序发出通知,报告自己可以访问,然后应用程序在从驱动程序中读取或写入数据。 Linux 提供了异步通知这个机制来完成此功能

前言

 在使用阻塞或者非阻塞的方式来读取驱动中某些值都是应用程序主动读取的,对于非阻塞方式来说还需要应用程序通过 poll 函数不断的轮询。最好的方式就是驱动程序能主动向应用程序发出通知,报告自己可以访问,然后应用程序在从驱动程序中读取或写入数据。 Linux 提供了异步通知这个机制来完成此功能

异步通知简介

 我们首先来回顾一下“中断”,中断是处理器提供的一种异步机制,我们配置好中断以后就可以让处理器去处理其他的事情了,当中断发生以后会触发我们事先设置好的中断服务函数,在中断服务函数中做具体的处理。我们不再循环读取1553B的中断寄存器数据获取中断状态状态,而是采用中断方式,采用中断方式以后处理器就不需要时刻的去查看最低位是否是1,因为一旦置1以后会自动触发中断。同样的,Linux 应用程序可以通过阻塞或者非阻塞这两种方式来访问驱动设备,通过阻塞方式访问的话应用程序会处于休眠态,等待驱动设备可以使用,非阻塞方式的话会通过 poll 函数来不断的轮询,查看驱动设备文件是否可以使用。这两种方式都需要应用程序主动的去查询设备的使用情况,如果能提供一种类似中断的机制,当驱动程序可以访问的时候主动告诉应用程序那就最好了。

 “信号”为此应运而生,信号类似于我们硬件上使用的“中断”,只不过信号是软件层次上的。算是在软件层次上对中断的一种模拟,驱动可以通过主动向应用程序发送信号的方式来报告自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据了。整个过程就相当于应用程序收到了驱动发送过来了的一个中断,然后应用程序去响应这个中断,在整个处理过程中应用程序并没有去查询驱动设备是否可以访问,一切都是由驱动设备自己告诉给应用程序的。

 阻塞、非阻塞、异步通知,这三种是针对不同的场合提出来的不同的解决方法,没有优劣之分,在实际的工作和学习中,根据自己的实际需求选择合适的处理方法即可。

 异步通知的核心就是信号,在 include/uapi/asm-generic/signal.h 头文件中定义了Linux 所支持的信号,这些信号如下所示

#define SIGHUP 1 /* 终端挂起或控制进程终止 */
 #define SIGINT 2 /* 终端中断(Ctrl+C 组合键) */
 #define SIGQUIT 3 /* 终端退出(Ctrl+\组合键) */
 #define SIGILL 4 /* 非法指令 */
 #define SIGTRAP 5 /* debug 使用,有断点指令产生 */
 #define SIGABRT 6 /* 由 abort(3)发出的退出指令 */
 #define SIGIOT 6 /* IOT 指令 */
 #define SIGBUS 7 /* 总线错误 */
 #define SIGFPE 8 /* 浮点运算错误 */
 #define SIGKILL 9 /* 杀死、终止进程 */
 #define SIGUSR1 10 /* 用户自定义信号 1 */
 #define SIGSEGV 11 /* 段违例(无效的内存段) */
 #define SIGUSR2 12 /* 用户自定义信号 2 */
 #define SIGPIPE 13 /* 向非读管道写入数据 */
 #define SIGALRM 14 /* 闹钟 */
 #define SIGTERM 15 /* 软件终止 */
 #define SIGSTKFLT 16 /* 栈异常 */
 #define SIGCHLD 17 /* 子进程结束 */
 #define SIGCONT 18 /* 进程继续 */
 #define SIGSTOP 19 /* 停止进程的执行,只是暂停 */
 #define SIGTSTP 20 /* 停止进程的运行(Ctrl+Z 组合键) */
 #define SIGTTIN 21 /* 后台进程需要从终端读取数据 */
 #define SIGTTOU 22 /* 后台进程需要向终端写数据 */
 #define SIGURG 23 /* 有"紧急"数据 */
 #define SIGXCPU 24 /* 超过 CPU 资源限制 */
 #define SIGXFSZ 25 /* 文件大小超额 */
 #define SIGVTALRM 26 /* 虚拟时钟信号 */
 #define SIGPROF 27 /* 时钟信号描述 */
 #define SIGWINCH 28 /* 窗口大小改变 */
 #define SIGIO 29 /* 可以进行输入/输出操作 */
 #define SIGPOLL SIGIO
 /* #define SIGLOS 29 */
 #define SIGPWR 30 /* 断点重启 */
 #define SIGSYS 31 /* 非法的系统调用 */
 #define SIGUNUSED 31 /* 未使用信号 */

 除了SIGKILL(9)和 SIGSTOP(19)这两个信号不能被忽略外,其他的信号都可以忽略。这些信号就相当于中断号,不同的中断号代表了不同的中断,不同的中断所做的处理不同,因此,驱动程序可以通过向应用程序发送不同的信号来实现不同的功能。我们使用中断的时候需要设置中断处理函数,同样的,如果要在应用程序中使用信号,那么就必须设置信号所使用的信号处理函数,在应用程序中使用 signal 函数来设置指定信号的处理函数,signal 函数原型如下所示:

sighandler_t signal(int signum, sighandler_t handler)

 函数参数和返回值含义如下:

 signum:要设置处理函数的信号。

 handler: 信号的处理函数。

 返回值: 设置成功的话返回信号的前一个处理函数,设置失败的话返回 SIG_ERR。

 信号处理函数原型如下所示

typedef void (*sighandler_t)(int)

 使用“ kill -9 PID”杀死指定进程的方法就是向指定的进程(PID)发送SIGKILL 这个信号。当按下键盘上的 CTRL+C 组合键以后会向当前正在占用终端的应用程序发出 SIGINT 信号, SIGINT 信号默认的动作是关闭当前应用程序。这里我们修改一下 SIGINT 信号的默认处理函数,当按下 CTRL+C 组合键以后先在终端上打印出“ SIGINT signal!”这行字符串,然后再关闭当前应用程序。 在 Linux系统 中新建一个 signaltest.c 文件,然后输入如下所示内容:


#include <stdlib.h>
 #include <stdio.h>
 #include <signal.h>
void sigint_handler(int num)
 {
  printf("\r\nSIGINT signal!\r\n");
  exit(0);
 }
 int main(void)
 {
  signal(SIGINT, sigint_handler);
  while(1);
 return 0;
 }

 执行后很明显可以看到程序终止并打印出了我们预期的SIGINT signal!

驱动中的信号处理

fasync_struct 结构体

 首先我们需要在驱动程序中定义一个 fasync_struct 结构体指针变量, 该结构体定义在内核源码目录 include/linux/fs.h 头文件中, fasync_struct 结构体内容如下:


struct fasync_struct {
  spinlock_t            fa_lock;
  int                   magic;
        int                   fa_fd;
        struct fasync_struct *fa_next; /* singly linked list */
  struct file          *fa_file;
        struct rcu_head       fa_rcu;
};

 一般将 fasync_struct 结构体指针变量定义到设备结构体中

struct fasync_struct *RT_async;

fasync 函数

 如果要使用异步通知,需要在设备驱动中实现 file_operations 操作集中的 fasync 函数,此函数格式如下所示:

int (*fasync) (int fd, struct file *filp, int on)

 fasync 函数里面一般通过调用 fasync_helper 函数来初始化前面定义的 fasync_struct结构体指针, fasync_helper 函数原型如下:

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)

 fasync_helper 函数的前三个参数就是 fasync 函数的那三个参数,第四个参数就是要初始化的 fasync_struct 结构体指针变量。当应用程序通过“ fcntl(fd, F_SETFL, flags |FASYNC)”改变 fasync 标记的时候,驱动程序 file_operations 操作集中的 fasync 函数就会执行。

 驱动程序中的 fasync 函数参考示例如下:

struct xxx_dev {
    ......
    struct fasync_struct *async_queue; /* 异步相关结构体 */
 };
static int xxx_fasync(int fd, struct file *filp, int on)
 {
  struct xxx_dev *dev = (xxx_dev)filp->private_data;
   if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
     return -EIO;
    return 0;
 }
 static struct file_operations xxx_ops = {
    ......
    .fasync = xxx_fasync,
    ......
 };

 在关闭驱动文件的时候需要在 file_operations 操作集中的 release 函数中释放fasync_struct, fasync_struct 的释放函数同样为 fasync_helper, release 函数参数参考

 实例如下:

static int xxx_release(struct inode *inode, struct file *filp)
  {
  return xxx_fasync(-1, filp, 0); /* 删除异步通知 */
  }
static struct file_operations xxx_ops = {
  ......
  .release = xxx_release,
 };

 第 3 行通过调用示例代码xxx_fasync 函数来完成 fasync_struct 的释放工作,但是,其最终还是通过 fasync_helper 函数完成释放工作。

 体现在之前的代码中:

 先添加 fcntl.h 头文件,因为要用到相关的 API 函数。

 在设备结构体中添加 fasync_struct 指针变量。

 设备操作函数集 file_operations 结构体中的 fasync 函数  RT_fasync_ops,该函数中直接调用 fasync_helper 函数进行相关处理。

 在RT_release 函数中也调用RT_fasync_ops 函数释放 fasync_struct 指针变量。

static int RT_fasync_ops (int fd, struct file *filp, int on)
{
  return fasync_helper(fd, filp, on, &RT_async);
}
static int RT_release(struct inode *inode, struct file *filp)
{
  return RT_fasync_ops(-1, filp, 0);
}
static struct file_operations RT_fops = {
  .read=RT_read,
  // .unlocked_ioctl=RT_ioctl,
  .fasync = RT_fasync_ops,
  .release = RT_release,
};

kill_fasync 函数

 当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生“中断”。kill_fasync 函数负责发送指定的信号, kill_fasync 函数原型如下所示:

void kill_fasync(struct fasync_struct **fp, int sig, int band)

 函数参数和返回值含义如下:

 fp:要操作的 fasync_struct。

 sig: 要发送的信号。

 band: 可读时设置为 POLL_IN,可写时设置为 POLL_OUT。

 返回值: 无。

 参考上篇测试用例:

if (RT_async)
     {
       kill_fasync(&RT_async, SIGIO, POLL_IN);
     } 
     return 0;
  }
  rt1553B_BUMSG.commandWord=-1;

应用层对异步通知的处理:

 应用程序对异步通知的处理包括以下三步:

 1、注册信号处理函数

 应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用 signal 函数来设置信号的处理函数。前面已经详细的讲过了,这里就不细讲了。

 2、将本应用程序的进程号告诉给内核

 使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。

 3、开启异步通知

 使用如下两行程序开启异步通知:

 flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 /

 fcntl(fd, F_SETFL, flags | FASYNC); / 开启当前进程异步通知功能 */

 重点就是通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行。

参考上篇测试用例:

 RT_signal_func函数, SIGIO 信号的处理函数,

 通过 signal 函数设置 SIGIO 信号的处理函数为 RT_signal_func。

 下面三行设置当前进程的状态,开启异步通知的功能

 While1 循环,等待信号产生。

int main(void)
{
    //int command;
    int flags = 0;
    int err;
    //pthread_t deal_data;
    rt1553open();
    // fastenable();
    fd = open("/dev/1553drv", O_RDWR);
    if(fd < 0) {
        printf("*********open fail************\n");
        return -1;
    }
    signal(SIGIO, RT_signal_func);
    fcntl(fd, F_SETOWN, getpid()); // 将当前进程的进程号告诉给内核
    flags = fcntl(fd, F_GETFD); // 获取当前的进程状态
    fcntl(fd, F_SETFL, flags | FASYNC); // 设置进程启用异步通知功能
    // err=pthread_create(&deal_data, NULL, &data_fun,NULL);
    //   if(err != 0)
    //   {
    //     printf("pthreadrx_create fail\n");
    //     return -1;
    //   }
    while(1)
    {
        sleep(1);
    }
    return 0;
}
相关文章
|
7月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
6月前
|
存储 Linux iOS开发
【Linux】冯诺依曼体系与操作系统理解
本文深入浅出地讲解了计算机体系的两大核心概念:冯诺依曼体系结构与操作系统。冯诺依曼体系作为现代计算机的基础架构,通过中央处理器、存储器和输入输出设备协同工作,解决了硬件性能瓶颈问题。操作系统则是连接硬件与用户的桥梁,管理软硬件资源,提供运行环境。文章还详细解析了操作系统的分类、意义及管理方式,并重点阐述了系统调用的作用,为学习Linux系统编程打下坚实基础。适合希望深入了解计算机原理和技术内幕的读者。
153 1
|
2月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
126 0
|
4月前
|
Java 关系型数据库 MySQL
在Linux操作系统上设置JDK、Tomcat、MySQL以及J2EE后端接口的部署步骤
让我们总结一下,给你的Linux操作系统装备上最强的军队,需要先后装备好JDK的弓箭,布置好Tomcat的阵地,再把MySQL的物资原料准备好,最后部署好J2EE攻城车,那就准备好进军吧,你的Linux军团,无人可挡!
113 18
|
4月前
|
开发框架 关系型数据库 Java
Linux操作系统中JDK、Tomcat、MySQL的完整安装流程以及J2EE后端接口的部署
然后Tomcat会自动将其解压成一个名为ROOT的文件夹。重启Tomcat,让新“植物”适应新环境。访问http://localhost:8080/yourproject看到你的项目页面,说明“植物”种植成功。
125 10
|
5月前
|
人工智能 安全 Anolis
|
7月前
|
NoSQL Unix Linux
Linux 操作系统的诞生与发展历程
步探索与准备: 1991年初,林纳斯·托瓦兹开始在一台386sx兼容微机上学习minix操作系统。通过学习,他逐渐不能满足于minix系统的现有性能,并开始酝酿开发一个新的免费操作系统。
221 8
Linux 操作系统的诞生与发展历程
|
7月前
|
中间件 关系型数据库 数据库
docker快速部署OS web中间件 数据库 编程应用
通过Docker,可以轻松地部署操作系统、Web中间件、数据库和编程应用。本文详细介绍了使用Docker部署这些组件的基本步骤和命令,展示了如何通过Docker Compose编排多容器应用。希望本文能帮助开发者更高效地使用Docker进行应用部署和管理。
186 19
|
7月前
|
机器学习/深度学习 安全 Linux
Linux 主要应用领域的归纳
服务器领域 Linux在服务器领域的应用是其最为广泛和成熟的领域之一。由于其开源、稳定、高效和安全的特性,Linux成为许多企业服务器的首选操作系统。 Web服务器:Linux是Web服务器的理想选择,因为它支持Apache、Nginx等流行的Web服务器软件,这些软件在Linux上运行稳定且性能卓越。Linux服务器可以高效地处理大量并发请求,提供快速、可靠的Web服务。 数据库服务器:Linux也广泛用于数据库服务器,如MySQL、PostgreSQL和Oracle等数据库管理系统在Linux上运行良好。Linux的稳定性和安全性使得它成为存储和管理敏感数据的理想平台。 邮件服务器:Lin
269 5
|
10月前
|
安全 Linux 数据安全/隐私保护
Vanilla OS:下一代安全 Linux 发行版
【10月更文挑战第30天】
421 0
Vanilla OS:下一代安全 Linux 发行版

热门文章

最新文章