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;
}
相关文章
|
1天前
|
Linux Shell
Linux操作系统下查找大文件或目录的技巧
Linux操作系统下查找大文件或目录的技巧
|
1天前
|
Linux 编译器 调度
xenomai内核解析--双核系统调用(二)--应用如何区分xenomai/linux系统调用或服务
本文介绍了如何将POSIX应用程序编译为在Xenomai实时内核上运行的程序。
16 1
xenomai内核解析--双核系统调用(二)--应用如何区分xenomai/linux系统调用或服务
|
1天前
|
算法 Ubuntu Linux
为什么Linux不是实时操作系统
本文探讨了Linux为何不是实时操作系统(RTOS)。实时性关乎系统对事件的确定性响应时间,而Linux虽能保证调度执行的实时任务,但无法确保中断响应时间、中断处理时间和任务调度时间的确定性。中断响应时间受中断屏蔽时间影响,Linux中无法确保;中断处理时间因不支持中断嵌套而不确定;任务调度时间虽快,但调度点的限制影响实时性。Linux的定位是通用操作系统,追求平均性能而非绝对实时性。为改善实时性,Linux提供了不同抢占模型,如可抢占内核(Low-Latency Desktop)和PREEMPT-RT补丁,后者接近硬实时但牺牲了吞吐量。PREEMPT-RT正逐渐成为Linux实时增强的标准。
8 1
为什么Linux不是实时操作系统
|
1天前
|
存储 Ubuntu Linux
xenomai3+linux构建linux实时操作系统-基于X86_64和arm
Xenomai是一个实时性解决方案,通过在Linux上添加实时内核Cobalt来增强实时性能。它有三个主要部分:libcobalt(用户空间实时库)、Cobalt(内核空间实时内核)和硬件架构特定层(ipipe-core或dovetail)。ipipe-core适用于Linux 5.4以下版本,而dovetail用于5.4及以上版本。本文介绍了在X86 Ubuntu环境下,如何编译Xenomai内核,搭建应用环境,包括配置、编译、安装和实时性测试。对于其他硬件架构,如ARM和ARM64,步骤类似。文章还提到了Xenomai与Linux内核版本的兼容性和实时性测试结果。
10 0
xenomai3+linux构建linux实时操作系统-基于X86_64和arm
|
1天前
|
消息中间件 测试技术 Linux
linux实时操作系统xenomai x86平台基准测试(benchmark)
本文是关于Xenomai实时操作系统的基准测试,旨在评估其在低端x86平台上的性能。测试模仿了VxWorks的方法,关注CPU结构、指令集等因素对系统服务耗时的影响。测试项目包括信号量、互斥量、消息队列、任务切换等,通过比较操作前后的时戳来测量耗时,并排除中断和上下文切换的干扰。测试结果显示了各项操作的最小、平均和最大耗时,为程序优化提供参考。注意,所有数据基于特定硬件环境,测试用例使用Alchemy API编写。
8 0
linux实时操作系统xenomai x86平台基准测试(benchmark)
|
1天前
|
消息中间件 存储 Linux
linux实时应用如何printf输出不影响实时性?
本文探讨了Linux实时任务中为何不能直接使用`printf(3)`,并介绍了实现不影响实时性的解决方案。实时任务的执行时间必须确定且短,但`printf(3)`的延迟取决于多个因素,包括用户态glibc缓冲、内核态TTY驱动和硬件。为确保实时性,通常将非实时IO操作交给低优先级任务处理,通过实时进程间通信传递信息。然而,即使这样,`printf(3)`在glibc中的实现仍可能导致高优先级任务阻塞。Xenomai 3提供了一个实时的`printf()`实现,通过libcobalt库在应用编译链接时自动处理,预分配内存,使用共享内存和线程特有数据来提高效率和实时性。
11 0
linux实时应用如何printf输出不影响实时性?
|
1天前
|
存储 Linux C语言
Linux:冯·诺依曼结构 & OS管理机制
Linux:冯·诺依曼结构 & OS管理机制
8 0
|
4天前
|
安全 Linux Shell
Linux常用命令详解与实战应用
【5月更文挑战第7天】本文详述了Linux常用命令,包括文件与目录操作(ls, cd, pwd, cp, mv, rm)、文本处理(cat, grep, sed)及系统管理(top, df, du)命令。通过实例演示了如何使用这些命令,帮助读者理解和掌握Linux操作,提升系统管理效率。学习和熟练运用这些基础命令,是高效使用Linux的关键。
|
3月前
|
Ubuntu Linux Shell
【Linux操作系统】探秘Linux奥秘:shell 编程的解密与实战
【Linux操作系统】探秘Linux奥秘:shell 编程的解密与实战
70 0
|
3月前
|
Ubuntu Linux Shell
【Linux操作系统】探秘Linux奥秘:用户、组、密码及权限管理的解密与实战
【Linux操作系统】探秘Linux奥秘:用户、组、密码及权限管理的解密与实战
59 0