异步通知实验(信号)
目錄
- 異步通知
- 異步通知簡(jiǎn)介
- 驅(qū)動(dòng)中的信號(hào)處理
- 應(yīng)用程序?qū)Ξ惒酵ㄖ奶幚?/li>
- 硬件原理圖分析
- 實(shí)驗(yàn)程序編寫
- 修改設(shè)備樹文件
- 程序編寫
- 編寫測(cè)試APP
- 運(yùn)行測(cè)試
- 編譯驅(qū)動(dòng)程序和測(cè)試APP
- 運(yùn)行測(cè)試
在前面使用阻塞或者非阻塞的方式來讀取驅(qū)動(dòng)中按鍵值都是應(yīng)用程序主動(dòng)讀取的,對(duì)于非阻塞方式來說還需要應(yīng)用程序通過poll 函數(shù)不斷的輪詢。最好的方式就是驅(qū)動(dòng)程序能主動(dòng)向應(yīng)用程序發(fā)出通知,報(bào)告自己可以訪問,然后應(yīng)用程序在從驅(qū)動(dòng)程序中讀取或?qū)懭霐?shù)據(jù),類似于我們?cè)诼銠C(jī)例程中講解的中斷。Linux 提供了異步通知這個(gè)機(jī)制來完成此功能,本章我們就來學(xué)習(xí)一下異步通知以及如何在驅(qū)動(dòng)中添加異步通知相關(guān)處理代碼。
異步通知
異步通知簡(jiǎn)介
我們首先來回顧一下“中斷”,中斷是處理器提供的一種異步機(jī)制,我們配置好中斷以后就可以讓處理器去處理其他的事情了,當(dāng)中斷發(fā)生以后會(huì)觸發(fā)我們事先設(shè)置好的中斷服務(wù)函數(shù),在中斷服務(wù)函數(shù)中做具體的處理。比如我們?cè)诼銠C(jī)篇里面編寫的GPIO 按鍵中斷實(shí)驗(yàn),我們通過按鍵去開關(guān)蜂鳴器,采用中斷以后處理器就不需要時(shí)刻的去查看按鍵有沒有被按下,因?yàn)榘存I按下以后會(huì)自動(dòng)觸發(fā)中斷。
Linux 應(yīng)用程序可以通過阻塞或者非阻塞這兩種方式來訪問驅(qū)動(dòng)設(shè)備;
- 通過阻塞方式訪問的話應(yīng)用程序會(huì)處于休眠態(tài)(掛起),等待驅(qū)動(dòng)設(shè)備可以使用
- 非阻塞方式的話會(huì)通過poll 函數(shù)來不斷的輪詢,查看驅(qū)動(dòng)設(shè)備文件是否可以使用。
這兩種方式都需要應(yīng)用程序主動(dòng)的去查詢?cè)O(shè)備的使用情況,如果能提供一種類似中斷的機(jī)制,當(dāng)驅(qū)動(dòng)程序可以訪問的時(shí)候主動(dòng)告訴應(yīng)用程序那就最好了,“信號(hào)”為此應(yīng)運(yùn)而生。
信號(hào)(異步通知):類似于我們硬件上使用的“中斷”,只不過信號(hào)是軟件層次上的。算是在軟件層次上對(duì)中斷的一種模擬,驅(qū)動(dòng)可以通過主動(dòng)向應(yīng)用程序發(fā)送信號(hào)的方式來報(bào)告自己可以訪問了,應(yīng)用程序獲取到信號(hào)以后就可以從驅(qū)動(dòng)設(shè)備中讀取或者寫入數(shù)據(jù)了。整個(gè)過程就相當(dāng)于應(yīng)用程序收到了驅(qū)動(dòng)發(fā)送過來了的一個(gè)中斷,然后應(yīng)用程序去響應(yīng)這個(gè)中斷,在整個(gè)處理過程中應(yīng)用程序并沒有去查詢驅(qū)動(dòng)設(shè)備是否可以訪問,一切都是由驅(qū)動(dòng)設(shè)備自己告訴給應(yīng)用程序的。
阻塞、非阻塞、異步通知,這三種是針對(duì)不同的場(chǎng)合提出來的不同的解決方法,沒有優(yōu)劣之分,在實(shí)際的工作和學(xué)習(xí)中,根據(jù)自己的實(shí)際需求選擇合適的處理方法即可。
異步通知的核心就是信號(hào),在arch/xtensa/include/uapi/asm/signal.h 文件中定義了Linux 所支持的所有信號(hào),這些信號(hào)如下所示:
34 #define SIGHUP 1 /* 終端掛起或控制進(jìn)程終止*/ 35 #define SIGINT 2 /* 終端中斷(Ctrl+C組合鍵) */ 36 #define SIGQUIT 3 /* 終端退出(Ctrl+\組合鍵) */ 37 #define SIGILL 4 /* 非法指令*/ 38 #define SIGTRAP 5 /* debug使用,有斷點(diǎn)指令產(chǎn)生*/ 39 #define SIGABRT 6 /* 由abort(3)發(fā)出的退出指令*/ 40 #define SIGIOT 6 /* IOT指令*/ 41 #define SIGBUS 7 /* 總線錯(cuò)誤*/ 42 #define SIGFPE 8 /* 浮點(diǎn)運(yùn)算錯(cuò)誤*/ 43 #define SIGKILL 9 /* 殺死、終止進(jìn)程*/ 44 #define SIGUSR1 10 /* 用戶自定義信號(hào)1 */ 45 #define SIGSEGV 11 /* 段違例(無效的內(nèi)存段) */ 46 #define SIGUSR2 12 /* 用戶自定義信號(hào)2 */ 47 #define SIGPIPE 13 /* 向非讀管道寫入數(shù)據(jù)*/ 48 #define SIGALRM 14 /* 鬧鐘*/ 49 #define SIGTERM 15 /* 軟件終止*/ 50 #define SIGSTKFLT 16 /* 棧異常*/ 51 #define SIGCHLD 17 /* 子進(jìn)程結(jié)束*/ 52 #define SIGCONT 18 /* 進(jìn)程繼續(xù)*/ 53 #define SIGSTOP 19 /* 停止進(jìn)程的執(zhí)行,只是暫停*/在示例代碼53.1.1.1 中的這些信號(hào)中,除了SIGKILL(9)和SIGSTOP(19)這兩個(gè)信號(hào)不能被忽略外,其他的信號(hào)都可以忽略。這些信號(hào)就相當(dāng)于中斷號(hào),不同的中斷號(hào)代表了不同的中斷,不同的中斷所做的處理不同,因此,驅(qū)動(dòng)程序可以通過向應(yīng)用程序發(fā)送不同的信號(hào)來實(shí)現(xiàn)不同的功能。
我們使用中斷的時(shí)候需要設(shè)置中斷處理函數(shù),同樣的,如果要在應(yīng)用程序中使用信號(hào),那么就必須設(shè)置信號(hào)所使用的信號(hào)處理函數(shù),在應(yīng)用程序中使用signal 函數(shù)來設(shè)置指定信號(hào)的處理函數(shù),signal 函數(shù)原型如下所示:
sighandler_t signal(int signum, sighandler_t handler)函數(shù)參數(shù)和返回值含義如下:
signum:要設(shè)置處理函數(shù)的信號(hào)。
handler:信號(hào)的處理函數(shù)。
返回值:設(shè)置成功的話返回信號(hào)的前一個(gè)處理函數(shù),設(shè)置失敗的話返回SIG_ERR。
信號(hào)處理函數(shù)原型如下所示:
我們前面講解的使用“kill -9 PID”殺死指定進(jìn)程的方法就是向指定的進(jìn)程(PID)發(fā)送SIGKILL 這個(gè)信號(hào)。當(dāng)按下鍵盤上的CTRL+C 組合鍵以后會(huì)向當(dāng)前正在占用終端的應(yīng)用程序發(fā)出SIGINT 信號(hào),SIGINT 信號(hào)默認(rèn)的動(dòng)作是關(guān)閉當(dāng)前應(yīng)用程序。這里我們修改一下SIGINT 信號(hào)的默認(rèn)處理函數(shù),當(dāng)按下CTRL+C 組合鍵以后先在終端上打印出“SIGINT signal!”這行字符串,然后再關(guān)閉當(dāng)前應(yīng)用程序。新建signaltest.c 文件,然后輸入如下所示內(nèi)容:
1 #include "stdlib.h" 2 #include "stdio.h" 3 #include "signal.h" 4 5 void sigint_handler(int num) 6 { 7 printf("\r\nSIGINT signal!\r\n"); 8 exit(0);在示例代碼53.1.1.2 中我們?cè)O(shè)置SIGINT 信號(hào)的處理函數(shù)為sigint_handler,當(dāng)按下CTRL+C向signaltest 發(fā)送SIGINT 信號(hào)以后sigint_handler 函數(shù)就會(huì)執(zhí)行,此函數(shù)先輸出一行“SIGINT signal!”字符串,然后調(diào)用exit 函數(shù)關(guān)閉signaltest 應(yīng)用程序。
使用如下命令編譯signaltest.c:
然后輸入“./signaltest”命令打開signaltest 這個(gè)應(yīng)用程序,然后按下鍵盤上的CTRL+C 組合鍵,結(jié)果如圖53.1.1.1 所示:
從圖53.1.1.1 可以看出,當(dāng)按下CTRL+C 組合鍵以后sigint_handler 這個(gè)SIGINT 信號(hào)處理函數(shù)執(zhí)行了,并且輸出了“SIGINT signal!”這行字符串。
驅(qū)動(dòng)中的信號(hào)處理
1、fasync_struct 結(jié)構(gòu)體
首先我們需要在驅(qū)動(dòng)程序中定義一個(gè)fasync_struct 結(jié)構(gòu)體指針變量,fasync_struct 結(jié)構(gòu)體內(nèi)容如下:
struct fasync_struct { spinlock_t fa_lock; int magic; int fa_fd; struct fasync_struct *fa_next; struct file *fa_file; struct rcu_head fa_rcu; };一般將fasync_struct 結(jié)構(gòu)體指針變量定義到設(shè)備結(jié)構(gòu)體中,比如在上一章節(jié)的imx6uirq_dev結(jié)構(gòu)體中添加一個(gè)fasync_struct 結(jié)構(gòu)體指針變量,結(jié)果如下所示:
1 struct imx6uirq_dev { 2 struct device *dev; 3 struct class *cls; 4 struct cdev cdev; ...... 14 struct fasync_struct *async_queue; /* 異步相關(guān)結(jié)構(gòu)體*/ 15 };第14 行就是在imx6uirq_dev 中添加了一個(gè)fasync_struct 結(jié)構(gòu)體指針變量。
2、fasync 函數(shù)
如果要使用異步通知,需要在設(shè)備驅(qū)動(dòng)中實(shí)現(xiàn)file_operations 操作集中的fasync 函數(shù),此函數(shù)格式如下所示:
fasync 函數(shù)里面一般通過調(diào)用fasync_helper 函數(shù)來初始化前面定義的fasync_struct 結(jié)構(gòu)體指針,fasync_helper 函數(shù)原型如下:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)fasync_helper 函數(shù)的前三個(gè)參數(shù)就是fasync 函數(shù)的那三個(gè)參數(shù),第四個(gè)參數(shù)就是要初始化的fasync_struct 結(jié)構(gòu)體指針變量。當(dāng)應(yīng)用程序通過“fcntl(fd, F_SETFL, flags | FASYNC)”改變fasync 標(biāo)記的時(shí)候,驅(qū)動(dòng)程序file_operations 操作集中的fasync 函數(shù)就會(huì)執(zhí)行。
驅(qū)動(dòng)程序中的fasync 函數(shù)參考示例如下:
1 struct xxx_dev { 2 ...... 3 struct fasync_struct *async_queue; /* 異步相關(guān)結(jié)構(gòu)體*/ 4 }; 5 6 static int xxx_fasync(int fd, struct file *filp, int on) 7 { 8 struct xxx_dev *dev = (xxx_dev)filp->private_data; 9 10 if (fasync_helper(fd, filp, on, &dev->async_queue) < 0) 11 return -EIO; 12 return 0; 13 } 14 15 static struct file_operations xxx_ops = { 16 ...... 17 .fasync = xxx_fasync, 18 ...... 19 };在關(guān)閉驅(qū)動(dòng)文件的時(shí)候需要在file_operations 操作集中的release 函數(shù)中釋放fasync_struct,fasync_struct 的釋放函數(shù)同樣為fasync_helper,release 函數(shù)參數(shù)參考實(shí)例如下:
1 static int xxx_release(struct inode *inode, struct file *filp) 2 { 3 return xxx_fasync(-1, filp, 0); /* 刪除異步通知*/ 4 } 5 6 static struct file_operations xxx_ops = { 7 ...... 8 .release = xxx_release, 9 };第3 行通過調(diào)用示例代碼53.1.2.3 中的xxx_fasync 函數(shù)來完成fasync_struct 的釋放工作,但是,其最終還是通過fasync_helper 函數(shù)完成釋放工作。
1、kill_fasync 函數(shù)
當(dāng)設(shè)備可以訪問的時(shí)候,驅(qū)動(dòng)程序需要向應(yīng)用程序發(fā)出信號(hào),相當(dāng)于產(chǎn)生“中斷”。kill_fasync函數(shù)負(fù)責(zé)發(fā)送指定的信號(hào),kill_fasync 函數(shù)原型如下所示:
函數(shù)參數(shù)和返回值含義如下:
fp:要操作的fasync_struct。
sig:要發(fā)送的信號(hào)。
band:可讀時(shí)設(shè)置為POLL_IN,可寫時(shí)設(shè)置為POLL_OUT。
返回值:無。
應(yīng)用程序?qū)Ξ惒酵ㄖ奶幚?/h2>
應(yīng)用程序?qū)Ξ惒酵ㄖ奶幚戆ㄒ韵氯?#xff1a;
1、注冊(cè)信號(hào)處理函數(shù)
應(yīng)用程序根據(jù)驅(qū)動(dòng)程序所使用的信號(hào)來設(shè)置信號(hào)的處理函數(shù),應(yīng)用程序使用signal 函數(shù)來設(shè)置信號(hào)的處理函數(shù)。前面已經(jīng)詳細(xì)的講過了,這里就不細(xì)講了。
2、將本應(yīng)用程序的進(jìn)程號(hào)告訴給內(nèi)核
使用fcntl(fd, F_SETOWN, getpid())將本應(yīng)用程序的進(jìn)程號(hào)告訴給內(nèi)核。
3、開啟異步通知
使用如下兩行程序開啟異步通知:
重點(diǎn)就是通過fcntl 函數(shù)設(shè)置進(jìn)程狀態(tài)為FASYNC,經(jīng)過這一步,驅(qū)動(dòng)程序中的fasync 函數(shù)就會(huì)執(zhí)行。
硬件原理圖分析
本章實(shí)驗(yàn)硬件原理圖參考15.2 小節(jié)即可。
實(shí)驗(yàn)程序編寫
本實(shí)驗(yàn)對(duì)應(yīng)的例程路徑為:開發(fā)板光盤-> 2、Linux 驅(qū)動(dòng)例程-> 16_asyncnoti。
本章實(shí)驗(yàn)我們?cè)谏弦徽聦?shí)驗(yàn)“15_noblockio”的基礎(chǔ)上完成,在其中加入異步通知相關(guān)內(nèi)容即可,當(dāng)按鍵按下以后驅(qū)動(dòng)程序向應(yīng)用程序發(fā)送SIGIO 信號(hào),應(yīng)用程序獲取到SIGIO 信號(hào)以后讀取并且打印出按鍵值。
修改設(shè)備樹文件
因?yàn)槭窃趯?shí)驗(yàn)“15_noblockio”的基礎(chǔ)上完成的,因此不需要修改設(shè)備樹。
程序編寫
新建名為“16_asyncnoti”的文件夾,然后在16_asyncnoti 文件夾里面創(chuàng)建vscode 工程,工作區(qū)命名為“asyncnoti”。將“15_noblockio”實(shí)驗(yàn)中的noblockio.c 復(fù)制到16_asyncnoti 文件夾中,并重命名為asyncnoti.c。接下來我們就修改asyncnoti.c 這個(gè)文件,在其中添加異步通知關(guān)的代碼,完成以后的asyncnoti.c 內(nèi)容如下所示(因?yàn)槭窃谏弦徽聦?shí)驗(yàn)的noblockio.c 文件的基礎(chǔ)
上修改的,因?yàn)榱藴p少篇幅,下面的代碼有省略):
第20 行,添加fcntl.h 頭文件,因?yàn)橐玫较嚓P(guān)的API 函數(shù)。
第64 行,在設(shè)備結(jié)構(gòu)體imx6uirq_dev 中添加fasync_struct 指針變量。
第109~112 行,如果是一次完整的按鍵過程,那么就通過kill_fasync 函數(shù)發(fā)送SIGIO 信號(hào)。
第114~120 行,屏蔽掉以前的喚醒進(jìn)程相關(guān)程序。
第269~273 行,imx6uirq_fasync 函數(shù),為file_operations 操作集中的fasync 函數(shù),此函數(shù)內(nèi)容很簡(jiǎn)單,就是調(diào)用一下fasync_helper。
第281~284 行,release 函數(shù),應(yīng)用程序調(diào)用close 函數(shù)關(guān)閉驅(qū)動(dòng)設(shè)備文件的時(shí)候此函數(shù)就會(huì)執(zhí)行,在此函數(shù)中釋放掉fasync_struct 指針變量。
第292~293 行,設(shè)置file_operations 操作集中的fasync 和release 這兩個(gè)成員變量。
編寫測(cè)試APP
測(cè)試APP 要實(shí)現(xiàn)的內(nèi)容很簡(jiǎn)單,設(shè)置SIGIO 信號(hào)的處理函數(shù)為sigio_signal_func,當(dāng)驅(qū)動(dòng)程序向應(yīng)用程序發(fā)送SIGIO 信號(hào)以后sigio_signal_func 函數(shù)就會(huì)執(zhí)行。sigio_signal_func 函數(shù)內(nèi)容很簡(jiǎn)單,就是通過read 函數(shù)讀取按鍵值。新建名為asyncnotiApp.c 的文件,然后輸入如下所示內(nèi)容:
#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" #include "poll.h" #include "sys/select.h" #include "sys/time.h" #include "linux/ioctl.h" #include "signal.h" /*************************************************************** Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : asyncnotiApp.c 作者 : 左忠凱 版本 : V1.0 描述 : 異步通知測(cè)試APP 其他 : 無 使用方法 :./asyncnotiApp /dev/asyncnoti 打開測(cè)試App 論壇 : www.openedv.com 日志 : 初版V1.0 2019/8/13 左忠凱創(chuàng)建 ***************************************************************/static int fd = 0; /* 文件描述符 *//** SIGIO信號(hào)處理函數(shù)* @param - signum : 信號(hào)值* @return : 無*/ static void sigio_signal_func(int signum) {int err = 0;unsigned int keyvalue = 0;err = read(fd, &keyvalue, sizeof(keyvalue));if(err < 0) {/* 讀取錯(cuò)誤 */} else {printf("sigio signal! key value=%d\r\n", keyvalue);} }/** @description : main主程序* @param - argc : argv數(shù)組元素個(gè)數(shù)* @param - argv : 具體參數(shù)* @return : 0 成功;其他 失敗*/ int main(int argc, char *argv[]) {int flags = 0;char *filename;if (argc != 2) {printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);if (fd < 0) {printf("Can't open file %s\r\n", filename);return -1;}/* 設(shè)置信號(hào)SIGIO的處理函數(shù) */signal(SIGIO, sigio_signal_func);fcntl(fd, F_SETOWN, getpid()); /* 設(shè)置當(dāng)前進(jìn)程接收SIGIO信號(hào) */flags = fcntl(fd, F_GETFL); /* 獲取當(dāng)前的進(jìn)程狀態(tài) */fcntl(fd, F_SETFL, flags | FASYNC); /* 設(shè)置進(jìn)程啟用異步通知功能 */ while(1) {sleep(2);}close(fd);return 0; }第32~43 行,sigio_signal_func 函數(shù),SIGIO 信號(hào)的處理函數(shù),當(dāng)驅(qū)動(dòng)程序有效按鍵按下以后就會(huì)發(fā)送SIGIO 信號(hào),此函數(shù)就會(huì)執(zhí)行。此函數(shù)通過read 函數(shù)讀取按鍵值,然后通過printf 函數(shù)打印在終端上。
第69 行,通過signal 函數(shù)設(shè)置SIGIO 信號(hào)的處理函數(shù)為sigio_signal_func。
第71~73 行,設(shè)置當(dāng)前進(jìn)程的狀態(tài),開啟異步通知的功能。
第75~77 行,while 循環(huán),等待信號(hào)產(chǎn)生。
運(yùn)行測(cè)試
編譯驅(qū)動(dòng)程序和測(cè)試APP
1、編譯驅(qū)動(dòng)程序
編寫Makefile 文件,本章實(shí)驗(yàn)的Makefile 文件和第四十章實(shí)驗(yàn)基本一樣,只是將obj-m 變量的值改為asyncnoti.o,Makefile 內(nèi)容如下所示:
第4 行,設(shè)置obj-m 變量的值為asyncnoti.o。
輸入如下命令編譯出驅(qū)動(dòng)模塊文件:
編譯成功以后就會(huì)生成一個(gè)名為“asyncnoti.ko”的驅(qū)動(dòng)模塊文件。
2、編譯測(cè)試APP
輸入如下命令編譯測(cè)試asyncnotiApp.c 這個(gè)測(cè)試程序:
編譯成功以后就會(huì)生成asyncnotiApp 這個(gè)應(yīng)用程序。
運(yùn)行測(cè)試
將上一小節(jié)編譯出來asyncnoti.ko 和asyncnotiApp 這兩個(gè)文件拷貝到
rootfs/lib/modules/4.1.15 目錄中,重啟開發(fā)板,進(jìn)入到目錄lib/modules/4.1.15 中,輸入如下命令加載asyncnoti.ko 驅(qū)動(dòng)模塊:
驅(qū)動(dòng)加載成功以后使用如下命令來測(cè)試中斷:
./asyncnotiApp /dev/asyncnoti按下開發(fā)板上的KEY0 鍵,終端就會(huì)輸出按鍵值,如圖53.4.2.1 所示:
從圖53.4.2.1 可以看出,捕獲到SIGIO 信號(hào),并且按鍵值獲取成功,大家可以自行以后臺(tái)模式運(yùn)行asyncnotiApp,查看一下這個(gè)應(yīng)用程序的CPU 使用率。如果要卸載驅(qū)動(dòng)的話輸入如下命令即可:
總結(jié)
以上是生活随笔為你收集整理的异步通知实验(信号)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab求出拟合曲线的方程,已知数据
- 下一篇: ios开发之.pch文件的使用