訂閱
糾錯(cuò)
加入自媒體

Linux:驅(qū)動(dòng)程序如何發(fā)送【信號(hào)】給應(yīng)用程序?

作  者:道哥,10+年嵌入式開(kāi)發(fā)老兵,專(zhuān)注于:C/C++、嵌入式、Linux。

目錄

kill 命令和信號(hào)

使用 kill 命令發(fā)送信號(hào)

多線程中的信號(hào)

信號(hào)注冊(cè)和處理函數(shù)

驅(qū)動(dòng)程序代碼示例:發(fā)送信號(hào)

功能需求

驅(qū)動(dòng)程序代碼

驅(qū)動(dòng)模塊 Makefile

編譯和加載

應(yīng)用程序代碼示例:接收信號(hào)

注冊(cè)信號(hào)處理函數(shù)

測(cè)試驗(yàn)證

別人的經(jīng)驗(yàn),我們的階梯!

大家好,我是道哥,今天我為大伙兒解說(shuō)的技術(shù)知識(shí)點(diǎn)是:【驅(qū)動(dòng)層中,如何發(fā)送信號(hào)給應(yīng)用程序】。

在上一篇文章中,我們討論的是:在應(yīng)用層如何發(fā)送指令來(lái)控制驅(qū)動(dòng)層的 GPIOLinux驅(qū)動(dòng)實(shí)踐:如何編寫(xiě)【 GPIO 】設(shè)備的驅(qū)動(dòng)程序??刂频姆较蚴菑膽(yīng)用層到驅(qū)動(dòng)層:

那么,如果想讓程序的執(zhí)行路徑從下往上,也就是從驅(qū)動(dòng)層傳遞到應(yīng)用層,應(yīng)該如何實(shí)現(xiàn)呢?

最容易、最簡(jiǎn)單的方式,就是通過(guò)發(fā)送信號(hào)!

這篇文章繼續(xù)以完整的代碼實(shí)例來(lái)演示如何實(shí)現(xiàn)這個(gè)功能。

kill 命令和信號(hào)

使用 kill 命令發(fā)送信號(hào)

關(guān)于 Linux 操作系統(tǒng)的信號(hào),每位程序員都知道這個(gè)指令:使用 kill 工具來(lái)“殺死”一個(gè)進(jìn)程:

$ kill -9 <進(jìn)程的 PID>

這個(gè)指令的功能是:向指定的某個(gè)進(jìn)程發(fā)送一個(gè)信號(hào) 9,這個(gè)信號(hào)的默認(rèn)功能是:是停止進(jìn)程。

雖然在應(yīng)用程序中沒(méi)有主動(dòng)處理這個(gè)信號(hào),但是操作系統(tǒng)默認(rèn)的處理動(dòng)作是終止應(yīng)用程序的執(zhí)行。

除了發(fā)送信號(hào) 9,kill 命令還可以發(fā)送其他的任意信號(hào)。

在 Linux 系統(tǒng)中,所有的信號(hào)都使用一個(gè)整型數(shù)值來(lái)表示,可以打開(kāi)文件 /usr/include/x86_64-linux-gnu/bits/signum.h(你的系統(tǒng)中可能位于其它的目錄) 查看一下,比較常見(jiàn)的幾個(gè)信號(hào)是:

Signals.  

#define SIGINT 2 Interrupt (ANSI).  

#define SIGKILL 9 Kill, unblockable (POSIX).  

#define SIGUSR1 10 User-defined signal 1 (POSIX).  

#define SIGSEGV 11 Segmentation violation (ANSI).  

#define SIGUSR2 12 User-defined signal 2 (POSIX).  

...

...

#define SIGSYS 31 Bad system call.  

#define SIGUNUSED 31

#define _NSIG 65 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 __SIGRTMIN 32

#define __SIGRTMAX (_NSIG - 1)

信號(hào) 9 對(duì)應(yīng)著 SIGKILL,而信號(hào)11(SIGSEGV)就是最令人討厭的Segmentfault!

這里還有一個(gè)地方需要注意一下:實(shí)時(shí)信號(hào)和非實(shí)時(shí)信號(hào),它倆的主要區(qū)別是:

1. 非實(shí)時(shí)信號(hào):操作系統(tǒng)不確保應(yīng)用程序一定能接收到(即:信號(hào)可能會(huì)丟失);

2. 實(shí)時(shí)信號(hào):操作系統(tǒng)確保應(yīng)用程序一定能接收到;

如果我們的程序設(shè)計(jì),通過(guò)信號(hào)機(jī)制來(lái)完成一些功能,那么為了確保信號(hào)不會(huì)丟失,肯定是使用實(shí)時(shí)信號(hào)的。

從文件 signum.h 中可以看到,實(shí)時(shí)信號(hào)從 __SIGRTMIN(數(shù)值:32) 開(kāi)始。

多線程中的信號(hào)

我們?cè)诰帉?xiě)應(yīng)用程序時(shí),雖然沒(méi)有接收并處理 SIGKILL 這個(gè)信號(hào),但是一旦別人發(fā)送了這個(gè)信號(hào),我們的程序就被操作系統(tǒng)停止掉了,這是默認(rèn)的動(dòng)作。

那么,在應(yīng)用程序中,應(yīng)該可以主動(dòng)聲明接收并處理指定的信號(hào),下面就來(lái)寫(xiě)一個(gè)最簡(jiǎn)單的實(shí)例。

在一個(gè)應(yīng)用程序中,可能存在多個(gè)線程;

當(dāng)有一個(gè)信號(hào)發(fā)送給此進(jìn)程時(shí),所有的線程都可能接收到,但是只能有一個(gè)線程來(lái)處理;

在這個(gè)示例中,只有一個(gè)主線程來(lái)接收并處理信號(hào);

信號(hào)注冊(cè)和處理函數(shù)

按照慣例,所有應(yīng)用程序文件都創(chuàng)建在 ~/tmp/App 目錄中。

image.png

這個(gè)示例程序接收的信號(hào)是 SIGUSR1 和 SIGUSR2,也就是數(shù)值 10 和 12。

編譯、執(zhí)行:

$ gcc app_h(yuǎn)andle_signal.c -o app_h(yuǎn)andle_signal

$ ./app_h(yuǎn)andle_signal

此時(shí),應(yīng)用程序開(kāi)始執(zhí)行,等待接收信號(hào)。

在另一個(gè)終端中,使用kill指令來(lái)發(fā)送信號(hào)SIGUSR1或者 SIGUSR2。

kill 發(fā)送信號(hào),需要知道應(yīng)用程序的 PID,可以通過(guò)指令: ps -au | grep app_h(yuǎn)andle_signal 來(lái)查看。

其中的15428就是進(jìn)程的 PID。

執(zhí)行發(fā)送信號(hào)SIGUSR1指令:

$ kill -10 15428

此時(shí),在應(yīng)用程序的終端窗口中,就能看到下面的打印信息:

說(shuō)明應(yīng)用程序接收到了 SIGUSR1 這個(gè)信號(hào)!

注意:我們是使用kill命令來(lái)發(fā)送信號(hào)的,kill 也是一個(gè)獨(dú)立的進(jìn)程,程序的執(zhí)行路徑如下:

在這個(gè)執(zhí)行路徑中,我們可控的部分是應(yīng)用層,至于操作系統(tǒng)是如何接收kill的操作,然后如何發(fā)送信號(hào)給 app_h(yuǎn)andle_signal 進(jìn)程的,我們不得而知。

下面就繼續(xù)通過(guò)示例代碼來(lái)看一下如何在驅(qū)動(dòng)層主動(dòng)發(fā)送信號(hào)。

驅(qū)動(dòng)程序代碼示例:發(fā)送信號(hào)功能需求

在剛才的簡(jiǎn)單示例中,可以得出下面這些信息:

1. 信號(hào)發(fā)送方:必須知道向誰(shuí)[PID]發(fā)送信號(hào),發(fā)送哪個(gè)信號(hào);

2. 信號(hào)接收方:必須定義信號(hào)處理函數(shù),并且向操作系統(tǒng)注冊(cè):接收哪些信號(hào);

發(fā)送方當(dāng)然就是驅(qū)動(dòng)程序了,在示例代碼中,繼續(xù)使用 SIGUSR1 信號(hào)來(lái)測(cè)試。

那么,驅(qū)動(dòng)程序如何才能知道應(yīng)用程序的PID呢?可以讓?xiě)?yīng)用程序通過(guò)oictl函數(shù),把自己的PID主動(dòng)告訴驅(qū)動(dòng)程序:

驅(qū)動(dòng)程序

這里的示例代碼,是在上一篇文章的基礎(chǔ)上修改的,改動(dòng)部分的內(nèi)容,使用宏定義 MY_SIGNAL_ENABLE 控制起來(lái),方便查看和比較。

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

image.png

image.png

image.png

image.png

image.png

image.png

image.png

這里大部分的代碼,在上一篇文章中已經(jīng)描述的比較清楚了,這里把重點(diǎn)關(guān)注放在這兩個(gè)函數(shù)上:gpio_ioctl 和 send_signal。

(1)函數(shù) gpio_ioctl

當(dāng)應(yīng)用程序調(diào)用 ioctl() 的時(shí)候,驅(qū)動(dòng)程序中的 gpio_ioctl 就會(huì)被調(diào)用。

這里定義一個(gè)簡(jiǎn)單的協(xié)議:當(dāng)應(yīng)用程序調(diào)用參數(shù)中 cmd 為 100 的時(shí)候,就表示用來(lái)告訴驅(qū)動(dòng)程序自己的 PID。

驅(qū)動(dòng)程序定義了一個(gè)全局變量 g_pid,用來(lái)保存應(yīng)用程序傳入的參數(shù)PID。

需要調(diào)用函數(shù) copy_from_user(&g_pid, pArg, sizeof(int)),把用戶(hù)空間的參數(shù)復(fù)制到內(nèi)核空間中;

成功取得PID之后,就調(diào)用函數(shù) send_signal 向應(yīng)用程序發(fā)送信號(hào)。

這里僅僅是用于演示目的,在實(shí)際的項(xiàng)目中,可能會(huì)根據(jù)接收到硬件觸發(fā)之后再發(fā)送信號(hào)。

(2)函數(shù) send_signal

這個(gè)函數(shù)主要做了3件事情:

構(gòu)造一個(gè)信號(hào)結(jié)構(gòu)體變量:struct siginfo info;

通過(guò)應(yīng)用程序傳入的 PID,獲取任務(wù)信息:pid_task(find_vpid(g_pid), PIDTYPE_PID);

發(fā)送信號(hào):send_sig_info(sig_no, &info, my_task);

驅(qū)動(dòng)模塊 Makefile

$ touch Makefile

內(nèi)容如下:

image.png

編譯驅(qū)動(dòng)模塊

$ make

得到驅(qū)動(dòng)程序: my_driver_signal.ko 。

加載驅(qū)動(dòng)模塊$ sudo insmod my_driver_signal.ko

通過(guò) dmesg 指令來(lái)查看驅(qū)動(dòng)模塊的打印信息:

因?yàn)槭纠a是在上一篇GPIO的基礎(chǔ)上修改的,因此創(chuàng)建的設(shè)備節(jié)點(diǎn)文件,與上篇文章是一樣的:

應(yīng)用程序代碼示例:接收信號(hào)注冊(cè)信號(hào)處理函數(shù)

應(yīng)用程序仍然放在 ~/tmp/App/ 目錄下。

$ mkdir ~/tmp/App/app_mysignal

$ cd ~/tmp/App/app_mysignal

$ touch mysignal.c

文件內(nèi)容如下:

image.png

image.png

可以看到,應(yīng)用程序主要做了兩件事情:

(1)首先通過(guò)函數(shù) sigaction() 向操作系統(tǒng)注冊(cè)了信號(hào) SIGUSR1 和 SIGUSR2,它倆的信號(hào)處理函數(shù)是同一個(gè):signal_h(yuǎn)andler()。

除了 sigaction 函數(shù),應(yīng)用程序還可以使用 signal 函數(shù)來(lái)注冊(cè)信號(hào)處理函數(shù);

(2)然后通過(guò) ioctl(fd, 100, &pid); 向驅(qū)動(dòng)程序設(shè)置自己的 PID。

編譯應(yīng)用程序:

$ gcc mysignal.c -o mysignal

執(zhí)行應(yīng)用程序:

$ sudo ./mysignal

根據(jù)剛才驅(qū)動(dòng)程序的代碼,當(dāng)驅(qū)動(dòng)程序接收到設(shè)置PID的命令之后,會(huì)立刻發(fā)送兩個(gè)信號(hào):

先來(lái)看一下 dmesg 中驅(qū)動(dòng)程序的打印信息:

可以看到:驅(qū)動(dòng)把這兩個(gè)信號(hào)(10 和 12),發(fā)送給了應(yīng)用程序(PID=6259)。

應(yīng)用程序的輸出信息如下:

可以看到:應(yīng)用程序接收到信號(hào) 10 和 12,并且正確打印出信號(hào)中攜帶的一些信息!

聲明: 本文由入駐維科號(hào)的作者撰寫(xiě),觀點(diǎn)僅代表作者本人,不代表OFweek立場(chǎng)。如有侵權(quán)或其他問(wèn)題,請(qǐng)聯(lián)系舉報(bào)。

發(fā)表評(píng)論

0條評(píng)論,0人參與

請(qǐng)輸入評(píng)論內(nèi)容...

請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字

您提交的評(píng)論過(guò)于頻繁,請(qǐng)輸入驗(yàn)證碼繼續(xù)

  • 看不清,點(diǎn)擊換一張  刷新

暫無(wú)評(píng)論

暫無(wú)評(píng)論

    掃碼關(guān)注公眾號(hào)
    OFweek人工智能網(wǎng)
    獲取更多精彩內(nèi)容
    文章糾錯(cuò)
    x
    *文字標(biāo)題:
    *糾錯(cuò)內(nèi)容:
    聯(lián)系郵箱:
    *驗(yàn) 證 碼:

    粵公網(wǎng)安備 44030502002758號(hào)