您的位置:首页 >科技 >

Linux:驱动程序如何发送【信号】给应用程序?

时间:2021-12-06 16:16:23 来源:

作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++、嵌入式、Linux。

目录

kill 命令和信号

使用 kill 命令发送信号

多线程中的信号

信号注册和处理函数

驱动程序代码示例:发送信号

功能需求

驱动程序代码

驱动模块 Makefile

编译和加载

应用程序代码示例:接收信号

注册信号处理函数

测试验证

别人的经验,我们的阶梯!

大家好,我是道哥,今天我为大伙儿解说的技术知识点是:【驱动层中,如何发送信号给应用程序】。

在上一篇文章中,我们讨论的是:在应用层如何发送指令来控制驱动层的 GPIOLinux驱动实践:如何编写【 GPIO 】设备的驱动程序?。控制的方向是从应用层到驱动层:

那么,如果想让程序的执行路径从下往上,也就是从驱动层传递到应用层,应该如何实现呢?

最容易、最简单的方式,就是通过发送信号!

这篇文章继续以完整的代码实例来演示如何实现这个功能。

kill 命令和信号

使用 kill 命令发送信号

关于 Linux 操作系统的信号,每位程序员都知道这个指令:使用 kill 工具来“杀死”一个进程:

$ kill -9 <进程的 PID>

这个指令的功能是:向指定的某个进程发送一个信号 9,这个信号的默认功能是:是停止进程。

虽然在应用程序中没有主动处理这个信号,但是操作系统默认的处理动作是终止应用程序的执行。

除了发送信号 9,kill 命令还可以发送其他的任意信号。

在 Linux 系统中,所有的信号都使用一个整型数值来表示,可以打开文件 /usr/include/x86_64-linux-gnu/bits/signum.h(你的系统中可能位于其它的目录) 查看一下,比较常见的几个信号是:

Signals.

#defineSIGINT2 Interrupt (ANSI).

#defineSIGKILL9 Kill, unblockable (POSIX).

#defineSIGUSR110 User-defined signal 1 (POSIX).

#defineSIGSEGV11 Segmentation violation (ANSI).

#defineSIGUSR212 User-defined signal 2 (POSIX).

...

...

#define SIGSYS31 Bad system call.

#define SIGUNUSED31

#define_NSIG65 Biggest signal number + 1 (including real-time signals).

These are the hard limits of the kernel. These values should not be

used directly at user level.

#define __SIGRTMIN32

#define __SIGRTMAX(_NSIG - 1)

信号 9 对应着 SIGKILL,而信号11(SIGSEGV)就是最令人讨厌的Segmentfault!

这里还有一个地方需要注意一下:实时信号和非实时信号,它俩的主要区别是:

1. 非实时信号:操作系统不确保应用程序一定能接收到(即:信号可能会丢失);

2. 实时信号:操作系统确保应用程序一定能接收到;

如果我们的程序设计,通过信号机制来完成一些功能,那么为了确保信号不会丢失,肯定是使用实时信号的。

从文件 signum.h 中可以看到,实时信号从 __SIGRTMIN(数值:32) 开始。

多线程中的信号

我们在编写应用程序时,虽然没有接收并处理 SIGKILL 这个信号,但是一旦别人发送了这个信号,我们的程序就被操作系统停止掉了,这是默认的动作。

那么,在应用程序中,应该可以主动声明接收并处理指定的信号,下面就来写一个最简单的实例。

在一个应用程序中,可能存在多个线程;

当有一个信号发送给此进程时,所有的线程都可能接收到,但是只能有一个线程来处理;

在这个示例中,只有一个主线程来接收并处理信号;

信号注册和处理函数

按照惯例,所有应用程序文件都创建在 ~/tmp/App 目录中。

Linux:驱动程序如何发送【信号】给应用程序?

这个示例程序接收的信号是 SIGUSR1 和 SIGUSR2,也就是数值 10 和 12。

编译、执行:

$ gcc app_handle_signal.c -o app_handle_signal

$ ./app_handle_signal

此时,应用程序开始执行,等待接收信号。

在另一个终端中,使用kill指令来发送信号SIGUSR1或者 SIGUSR2。

kill 发送信号,需要知道应用程序的 PID,可以通过指令: ps -au | grep app_handle_signal 来查看。

其中的15428就是进程的 PID。

执行发送信号SIGUSR1指令:

$ kill -10 15428

此时,在应用程序的终端窗口中,就能看到下面的打印信息:

说明应用程序接收到了 SIGUSR1 这个信号!

注意:我们是使用kill命令来发送信号的,kill 也是一个独立的进程,程序的执行路径如下:

在这个执行路径中,我们可控的部分是应用层,至于操作系统是如何接收kill的操作,然后如何发送信号给 app_handle_signal 进程的,我们不得而知。

下面就继续通过示例代码来看一下如何在驱动层主动发送信号。

驱动程序代码示例:发送信号功能需求

在刚才的简单示例中,可以得出下面这些信息:

1. 信号发送方:必须知道向谁[PID]发送信号,发送哪个信号;

2. 信号接收方:必须定义信号处理函数,并且向操作系统注册:接收哪些信号;

发送方当然就是驱动程序了,在示例代码中,继续使用 SIGUSR1 信号来测试。

那么,驱动程序如何才能知道应用程序的PID呢?可以让应用程序通过oictl函数,把自己的PID主动告诉驱动程序:

驱动程序

这里的示例代码,是在上一篇文章的基础上修改的,改动部分的内容,使用宏定义 MY_SIGNAL_ENABLE 控制起来,方便查看和比较。

以下所有操作的工作目录,都是与上一篇文章相同的,即:~/tmp/linux-4.15/drivers/。

Linux:驱动程序如何发送【信号】给应用程序?

Linux:驱动程序如何发送【信号】给应用程序?

Linux:驱动程序如何发送【信号】给应用程序?

Linux:驱动程序如何发送【信号】给应用程序?

Linux:驱动程序如何发送【信号】给应用程序?

Linux:驱动程序如何发送【信号】给应用程序?

Linux:驱动程序如何发送【信号】给应用程序?

这里大部分的代码,在上一篇文章中已经描述的比较清楚了,这里把重点关注放在这两个函数上:gpio_ioctl 和 send_signal。

(1)函数 gpio_ioctl

当应用程序调用 ioctl() 的时候,驱动程序中的 gpio_ioctl 就会被调用。

这里定义一个简单的协议:当应用程序调用参数中 cmd 为 100 的时候,就表示用来告诉驱动程序自己的 PID。

驱动程序定义了一个全局变量 g_pid,用来保存应用程序传入的参数PID。

需要调用函数 copy_from_user(&g_pid, pArg, sizeof(int)),把用户空间的参数复制到内核空间中;

成功取得PID之后,就调用函数 send_signal 向应用程序发送信号。

这里仅仅是用于演示目的,在实际的项目中,可能会根据接收到硬件触发之后再发送信号。

(2)函数 send_signal

这个函数主要做了3件事情:

构造一个信号结构体变量:struct siginfo info;

通过应用程序传入的 PID,获取任务信息:pid_task(find_vpid(g_pid), PIDTYPE_PID);

发送信号:send_sig_info(sig_no, &info, my_task);

驱动模块 Makefile

$ touch Makefile

内容如下:

Linux:驱动程序如何发送【信号】给应用程序?

编译驱动模块

$ make

得到驱动程序: my_driver_signal.ko 。

加载驱动模块$ sudo insmod my_driver_signal.ko

通过 dmesg 指令来查看驱动模块的打印信息:

因为示例代码是在上一篇GPIO的基础上修改的,因此创建的设备节点文件,与上篇文章是一样的:

应用程序代码示例:接收信号注册信号处理函数

应用程序仍然放在 ~/tmp/App/ 目录下。

$ mkdir ~/tmp/App/app_mysignal

$ cd ~/tmp/App/app_mysignal

$ touch mysignal.c

文件内容如下:

Linux:驱动程序如何发送【信号】给应用程序?

Linux:驱动程序如何发送【信号】给应用程序?

可以看到,应用程序主要做了两件事情:

(1)首先通过函数 sigaction() 向操作系统注册了信号 SIGUSR1 和 SIGUSR2,它俩的信号处理函数是同一个:signal_handler()。

除了 sigaction 函数,应用程序还可以使用 signal 函数来注册信号处理函数;

(2)然后通过 ioctl(fd, 100, &pid); 向驱动程序设置自己的 PID。

编译应用程序:

$ gcc mysignal.c -o mysignal

执行应用程序:

$ sudo ./mysignal

根据刚才驱动程序的代码,当驱动程序接收到设置PID的命令之后,会立刻发送两个信号:

先来看一下 dmesg 中驱动程序的打印信息:

可以看到:驱动把这两个信号(10 和 12),发送给了应用程序(PID=6259)。

应用程序的输出信息如下:

可以看到:应用程序接收到信号 10 和 12,并且正确打印出信号中携带的一些信息!


郑重声明:文章仅代表原作者观点,不代表本站立场;如有侵权、违规,可直接反馈本站,我们将会作修改或删除处理。