Linux:驅動程序如何發(fā)送【信號】給應用程序?
作 者:道哥,10+年嵌入式開發(fā)老兵,專注于:C/C++、嵌入式、Linux。
目錄
kill 命令和信號
使用 kill 命令發(fā)送信號
多線程中的信號
信號注冊和處理函數(shù)
驅動程序代碼示例:發(fā)送信號
功能需求
驅動程序代碼
驅動模塊 Makefile
編譯和加載
應用程序代碼示例:接收信號
注冊信號處理函數(shù)
測試驗證
別人的經(jīng)驗,我們的階梯!
大家好,我是道哥,今天我為大伙兒解說的技術知識點是:【驅動層中,如何發(fā)送信號給應用程序】。
在上一篇文章中,我們討論的是:在應用層如何發(fā)送指令來控制驅動層的 GPIOLinux驅動實踐:如何編寫【 GPIO 】設備的驅動程序??刂频姆较蚴菑膽脤拥津寗訉樱
那么,如果想讓程序的執(zhí)行路徑從下往上,也就是從驅動層傳遞到應用層,應該如何實現(xiàn)呢?
最容易、最簡單的方式,就是通過發(fā)送信號!
這篇文章繼續(xù)以完整的代碼實例來演示如何實現(xiàn)這個功能。
kill 命令和信號
使用 kill 命令發(fā)送信號
關于 Linux 操作系統(tǒng)的信號,每位程序員都知道這個指令:使用 kill 工具來“殺死”一個進程:
$ kill -9 <進程的 PID>
這個指令的功能是:向指定的某個進程發(fā)送一個信號 9,這個信號的默認功能是:是停止進程。
雖然在應用程序中沒有主動處理這個信號,但是操作系統(tǒng)默認的處理動作是終止應用程序的執(zhí)行。
除了發(fā)送信號 9,kill 命令還可以發(fā)送其他的任意信號。
在 Linux 系統(tǒng)中,所有的信號都使用一個整型數(shù)值來表示,可以打開文件 /usr/include/x86_64-linux-gnu/bits/signum.h(你的系統(tǒng)中可能位于其它的目錄) 查看一下,比較常見的幾個信號是:
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)
信號 9 對應著 SIGKILL,而信號11(SIGSEGV)就是最令人討厭的Segmentfault!
這里還有一個地方需要注意一下:實時信號和非實時信號,它倆的主要區(qū)別是:
1. 非實時信號:操作系統(tǒng)不確保應用程序一定能接收到(即:信號可能會丟失);
2. 實時信號:操作系統(tǒng)確保應用程序一定能接收到;
如果我們的程序設計,通過信號機制來完成一些功能,那么為了確保信號不會丟失,肯定是使用實時信號的。
從文件 signum.h 中可以看到,實時信號從 __SIGRTMIN(數(shù)值:32) 開始。
多線程中的信號
我們在編寫應用程序時,雖然沒有接收并處理 SIGKILL 這個信號,但是一旦別人發(fā)送了這個信號,我們的程序就被操作系統(tǒng)停止掉了,這是默認的動作。
那么,在應用程序中,應該可以主動聲明接收并處理指定的信號,下面就來寫一個最簡單的實例。
在一個應用程序中,可能存在多個線程;
當有一個信號發(fā)送給此進程時,所有的線程都可能接收到,但是只能有一個線程來處理;
在這個示例中,只有一個主線程來接收并處理信號;
信號注冊和處理函數(shù)
按照慣例,所有應用程序文件都創(chuàng)建在 ~/tmp/App 目錄中。
這個示例程序接收的信號是 SIGUSR1 和 SIGUSR2,也就是數(shù)值 10 和 12。
編譯、執(zhí)行:
$ gcc app_handle_signal.c -o app_handle_signal
$ ./app_handle_signal
此時,應用程序開始執(zhí)行,等待接收信號。
在另一個終端中,使用kill指令來發(fā)送信號SIGUSR1或者 SIGUSR2。
kill 發(fā)送信號,需要知道應用程序的 PID,可以通過指令: ps -au | grep app_handle_signal 來查看。
其中的15428就是進程的 PID。
執(zhí)行發(fā)送信號SIGUSR1指令:
$ kill -10 15428
此時,在應用程序的終端窗口中,就能看到下面的打印信息:
說明應用程序接收到了 SIGUSR1 這個信號!
注意:我們是使用kill命令來發(fā)送信號的,kill 也是一個獨立的進程,程序的執(zhí)行路徑如下:
在這個執(zhí)行路徑中,我們可控的部分是應用層,至于操作系統(tǒng)是如何接收kill的操作,然后如何發(fā)送信號給 app_handle_signal 進程的,我們不得而知。
下面就繼續(xù)通過示例代碼來看一下如何在驅動層主動發(fā)送信號。
驅動程序代碼示例:發(fā)送信號功能需求
在剛才的簡單示例中,可以得出下面這些信息:
1. 信號發(fā)送方:必須知道向誰[PID]發(fā)送信號,發(fā)送哪個信號;
2. 信號接收方:必須定義信號處理函數(shù),并且向操作系統(tǒng)注冊:接收哪些信號;
發(fā)送方當然就是驅動程序了,在示例代碼中,繼續(xù)使用 SIGUSR1 信號來測試。
那么,驅動程序如何才能知道應用程序的PID呢?可以讓應用程序通過oictl函數(shù),把自己的PID主動告訴驅動程序:
驅動程序
這里的示例代碼,是在上一篇文章的基礎上修改的,改動部分的內(nèi)容,使用宏定義 MY_SIGNAL_ENABLE 控制起來,方便查看和比較。
以下所有操作的工作目錄,都是與上一篇文章相同的,即:~/tmp/linux-4.15/drivers/。
這里大部分的代碼,在上一篇文章中已經(jīng)描述的比較清楚了,這里把重點關注放在這兩個函數(shù)上:gpio_ioctl 和 send_signal。
(1)函數(shù) gpio_ioctl
當應用程序調(diào)用 ioctl() 的時候,驅動程序中的 gpio_ioctl 就會被調(diào)用。
這里定義一個簡單的協(xié)議:當應用程序調(diào)用參數(shù)中 cmd 為 100 的時候,就表示用來告訴驅動程序自己的 PID。
驅動程序定義了一個全局變量 g_pid,用來保存應用程序傳入的參數(shù)PID。
需要調(diào)用函數(shù) copy_from_user(&g_pid, pArg, sizeof(int)),把用戶空間的參數(shù)復制到內(nèi)核空間中;
成功取得PID之后,就調(diào)用函數(shù) send_signal 向應用程序發(fā)送信號。
這里僅僅是用于演示目的,在實際的項目中,可能會根據(jù)接收到硬件觸發(fā)之后再發(fā)送信號。
(2)函數(shù) send_signal
這個函數(shù)主要做了3件事情:
構造一個信號結構體變量:struct siginfo info;
通過應用程序傳入的 PID,獲取任務信息:pid_task(find_vpid(g_pid), PIDTYPE_PID);
發(fā)送信號:send_sig_info(sig_no, &info, my_task);
驅動模塊 Makefile
$ touch Makefile
內(nèi)容如下:
編譯驅動模塊
$ make
得到驅動程序: my_driver_signal.ko 。
加載驅動模塊$ sudo insmod my_driver_signal.ko
通過 dmesg 指令來查看驅動模塊的打印信息:
因為示例代碼是在上一篇GPIO的基礎上修改的,因此創(chuàng)建的設備節(jié)點文件,與上篇文章是一樣的:
應用程序代碼示例:接收信號注冊信號處理函數(shù)
應用程序仍然放在 ~/tmp/App/ 目錄下。
$ mkdir ~/tmp/App/app_mysignal
$ cd ~/tmp/App/app_mysignal
$ touch mysignal.c
文件內(nèi)容如下:
可以看到,應用程序主要做了兩件事情:
(1)首先通過函數(shù) sigaction() 向操作系統(tǒng)注冊了信號 SIGUSR1 和 SIGUSR2,它倆的信號處理函數(shù)是同一個:signal_handler()。
除了 sigaction 函數(shù),應用程序還可以使用 signal 函數(shù)來注冊信號處理函數(shù);
(2)然后通過 ioctl(fd, 100, &pid); 向驅動程序設置自己的 PID。
編譯應用程序:
$ gcc mysignal.c -o mysignal
執(zhí)行應用程序:
$ sudo ./mysignal
根據(jù)剛才驅動程序的代碼,當驅動程序接收到設置PID的命令之后,會立刻發(fā)送兩個信號:
先來看一下 dmesg 中驅動程序的打印信息:
可以看到:驅動把這兩個信號(10 和 12),發(fā)送給了應用程序(PID=6259)。
應用程序的輸出信息如下:
可以看到:應用程序接收到信號 10 和 12,并且正確打印出信號中攜帶的一些信息!
請輸入評論內(nèi)容...
請輸入評論/評論長度6~500個字
最新活動更多
-
10月31日立即下載>> 【限時免費下載】TE暖通空調(diào)系統(tǒng)高效可靠的組件解決方案
-
即日-11.13立即報名>>> 【在線會議】多物理場仿真助跑新能源汽車
-
11月28日立即報名>>> 2024工程師系列—工業(yè)電子技術在線會議
-
12月19日立即報名>> 【線下會議】OFweek 2024(第九屆)物聯(lián)網(wǎng)產(chǎn)業(yè)大會
-
即日-12.26火熱報名中>> OFweek2024中國智造CIO在線峰會
-
即日-2025.8.1立即下載>> 《2024智能制造產(chǎn)業(yè)高端化、智能化、綠色化發(fā)展藍皮書》
推薦專題
- 高級軟件工程師 廣東省/深圳市
- 自動化高級工程師 廣東省/深圳市
- 光器件研發(fā)工程師 福建省/福州市
- 銷售總監(jiān)(光器件) 北京市/海淀區(qū)
- 激光器高級銷售經(jīng)理 上海市/虹口區(qū)
- 光器件物理工程師 北京市/海淀區(qū)
- 激光研發(fā)工程師 北京市/昌平區(qū)
- 技術專家 廣東省/江門市
- 封裝工程師 北京市/海淀區(qū)
- 結構工程師 廣東省/深圳市