一、異步通知簡介
我們首先來回顧一下“中斷”,中斷是處理器提供的一種異步機制,我們配置好中斷以後就可以讓處理器去處理其他的事情了,當中斷髮生以後會觸發我們事先設置好的中斷服務函數,在中斷服務函數中做具體的處理。比如我們在裸機篇裏面編寫的 GPIO 按鍵中斷實驗,我們通過按鍵去開關蜂鳴器,採用中斷以後處理器就不需要時刻的去查看按鍵有沒有被按下,因爲按鍵按下以後會自動觸發中斷。同樣的,Linux 應用程序可以通過阻塞或者非阻塞這兩種方式來訪問驅動設備,通過阻塞方式訪問的話應用程序會處於休眠態,等待驅動設備可以使用,非阻塞方式的話會通過 poll 函數來不斷的輪詢,查看驅動設備文件是否可以使用。這兩種方式都需要應用程序主動的去查詢設備的使用情況,如果能提供一種類似中斷的機制,當驅動程序可以訪問的時候主動告訴應用程序那就最好了。
“信號”爲此應運而生,信號類似於我們硬件上使用的“中斷”,只不過信號是軟件層次上的。算是在軟件層次上對中斷的一種模擬,驅動可以通過主動向應用程序發送信號的方式來報告自己可以訪問了,應用程序獲取到信號以後就可以從驅動設備中讀取或者寫入數據了。整個過程就相當於應用程序收到了驅動發送過來了的一箇中斷,然後應用程序去響應這個中斷,在整個處理過程中應用程序並沒有去查詢驅動設備是否可以訪問,一切都是由驅動設備自己告訴給應用程序的。
異步通知的核心就是信號,在 arch/xtensa/include/uapi/asm/signal.h
文件中定義了 Linux 所支持的所有信號,這些信號如下所示:
34 #define SIGHUP 1 /* 終端掛起或控制進程終止 */
35 #define SIGINT 2 /* 終端中斷(Ctrl+C 組合鍵) */
36 #define SIGQUIT 3 /* 終端退出(Ctrl+\組合鍵) */
37 #define SIGILL 4 /* 非法指令 */
38 #define SIGTRAP 5 /* debug 使用,有斷點指令產生 */
39 #define SIGABRT 6 /* 由 abort(3)發出的退出指令 */
40 #define SIGIOT 6 /* IOT 指令 */
41 #define SIGBUS 7 /* 總線錯誤 */
42 #define SIGFPE 8 /* 浮點運算錯誤 */
43 #define SIGKILL 9 /* 殺死、終止進程 */
44 #define SIGUSR1 10 /* 用戶自定義信號 1 */
45 #define SIGSEGV 11 /* 段違例(無效的內存段) */
46 #define SIGUSR2 12 /* 用戶自定義信號 2 */
47 #define SIGPIPE 13 /* 向非讀管道寫入數據 */
48 #define SIGALRM 14 /* 鬧鐘 */
49 #define SIGTERM 15 /* 軟件終止 */
50 #define SIGSTKFLT 16 /* 棧異常 */
51 #define SIGCHLD 17 /* 子進程結束 */
52 #define SIGCONT 18 /* 進程繼續 */
53 #define SIGSTOP 19 /* 停止進程的執行,只是暫停 */
54 #define SIGTSTP 20 /* 停止進程的運行(Ctrl+Z 組合鍵) */
55 #define SIGTTIN 21 /* 後臺進程需要從終端讀取數據 */
56 #define SIGTTOU 22 /* 後臺進程需要向終端寫數據 */
57 #define SIGURG 23 /* 有"緊急"數據 */
58 #define SIGXCPU 24 /* 超過 CPU 資源限制 */
59 #define SIGXFSZ 25 /* 文件大小超額 */
60 #define SIGVTALRM 26 /* 虛擬時鐘信號 */
61 #define SIGPROF 27 /* 時鐘信號描述 */
62 #define SIGWINCH 28 /* 窗口大小改變 */
63 #define SIGIO 29 /* 可以進行輸入/輸出操作 */
64 #define SIGPOLL SIGIO
65 /* #define SIGLOS 29 */
66 #define SIGPWR 30 /* 斷點重啓 */
67 #define SIGSYS 31 /* 非法的系統調用 */
68 #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 信號默認的動作是關閉當前應用程序。
二、驅動中的信號處理
1、fasync_struct 結構體
首先我們需要在驅動程序中定義一個 fasync_struct 結構體指針變量,fasync_struct 結構體內容如下:
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 結構體指針變量定義到設備結構體中:
1 struct imx6uirq_dev {
2 struct device *dev;
3 struct class *cls;
4 struct cdev cdev;
......
14 struct fasync_struct *async_queue; /* 異步相關結構體 */
15 };
2、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 函數就會執行。
1 struct xxx_dev {
2 ......
3 struct fasync_struct *async_queue; /* 異步相關結構體 */
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 };
在關閉驅動文件的時候需要在 file_operations 操作集中的 release 函數中釋放 fasync_struct,fasync_struct 的釋放函數同樣爲 fasync_helper,release 函數參數參考實例如下:
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 行通過調用 xxx_fasync 函數來完成 fasync_struct 的釋放工作,但是,其最終還是通過 fasync_helper 函數完成釋放工作。
3、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。
返回值:無
三、應用程序對異步通知的處理
應用程序對異步通知的處理包括以下三步:
1、註冊信號處理函數
應用程序根據驅動程序所使用的信號來設置信號的處理函數,使用 signal 函數。
2、將本應用程序的進程號告訴給內核
使用如下語句將本應用程序的進程號告訴給內核。
fcntl(fd, F_SETOWN, getpid())
3、開啓異步通知
使用如下兩行程序開啓異步通知:
flags = fcntl(fd, F_GETFL); /* 獲取當前的進程狀態 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 開啓當前進程異步通知功能 */
重點就是通過 fcntl 函數設置進程狀態爲 FASYNC,經過這一步,驅動程序中的 fasync 函數就會執行。
四、實例程序(僞代碼)
- 驅動程序
34 #define IMX6UIRQ_CNT 1 /* 設備號個數 */
35 #define IMX6UIRQ_NAME "asyncnoti" /* 名字 */
36 #define KEY0VALUE 0X01 /* KEY0 按鍵值 */
37 #define INVAKEY 0XFF /* 無效的按鍵值 */
38 #define KEY_NUM 1 /* 按鍵數量 */
......
49 /* imx6uirq 設備結構體 */
50 struct imx6uirq_dev{
......
64 struct fasync_struct *async_queue; /* 異步相關結構體 */
65 };
66
67 struct imx6uirq_dev imx6uirq; /* irq 設備 */
68
......
84
90 void timer_function(unsigned long arg)
91 {
92 unsigned char value;
93 unsigned char num;
94 struct irq_keydesc *keydesc;
95 struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
......
109 if(atomic_read(&dev->releasekey)) { /* 一次完整的按鍵過程 */
110 if(dev->async_queue)
111 kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
112 }
269 static int imx6uirq_fasync(int fd, struct file *filp, int on)
270 {
271 struct imx6uirq_dev *dev = (struct imx6uirq_dev *) filp->private_data;
272 return fasync_helper(fd, filp, on, &dev->async_queue);
273 }
281 static int imx6uirq_release(struct inode *inode, struct file *filp)
282 {
283 return imx6uirq_fasync(-1, filp, 0);
284 }
285
286 /* 設備操作函數 */
287 static struct file_operations imx6uirq_fops = {
288 .owner = THIS_MODULE,
289 .open = imx6uirq_open,
290 .read = imx6uirq_read,
291 .poll = imx6uirq_poll,
292 .fasync = imx6uirq_fasync,
293 .release = imx6uirq_release,
294 };
301 static int __init imx6uirq_init(void)
302 {
......
328
329 /* 5、始化按鍵 */
330 atomic_set(&imx6uirq.keyvalue, INVAKEY);
331 atomic_set(&imx6uirq.releasekey, 0);
332 keyio_init();
333 return 0;
334 }
341 static void __exit imx6uirq_exit(void)
342 {
343 unsigned i = 0;
......
354 class_destroy(imx6uirq.class);
355 }
356
357 module_init(imx6uirq_init);
358 module_exit(imx6uirq_exit);
359 MODULE_LICENSE("GPL");
第 64 行,在設備結構體 imx6uirq_dev 中添加 fasync_struct 指針變量。
第 109~112 行,如果是一次完整的按鍵過程,那麼就通過 kill_fasync 函數發送 SIGIO 信
號。
第 269-273 行,imx6uirq_fasync 函數,爲 file_operations 操作集中的 fasync 函數,此函數內容很簡單,就是調用一下 fasync_helper。 第 281~284 行,release 函數,應用程序調用 close 函數關閉驅動設備文件的時候此函數就會執行,在此函數中釋放掉fasync_struct 指針變量。
第 292~293 行,設置 file_operations 操作集中的 fasync 和 release 這兩個成員變量。
- 應用程序
32 static void sigio_signal_func(int signum)
33 {
34 int err = 0;
35 unsigned int keyvalue = 0;
36
37 err = read(fd, &keyvalue, sizeof(keyvalue));
38 if(err < 0) {
39 /* 讀取錯誤 */
40 } else {
41 printf("sigio signal! key value=%d\r\n", keyvalue);
42 }
43 }
51 int main(int argc, char *argv[])
52 {
53 int flags = 0;
54 char *filename;
55
56 if (argc != 2) {
57 printf("Error Usage!\r\n");
58 return -1;
59 }
60
61 filename = argv[1];
62 fd = open(filename, O_RDWR);
63 if (fd < 0) {
64 printf("Can't open file %s\r\n", filename);
65 return -1;
66 }
67
68 /* 設置信號 SIGIO 的處理函數 */
69 signal(SIGIO, sigio_signal_func);
70
71 fcntl(fd, F_SETOWN, getpid()); /* 將當前進程的進程號告訴給內核 */
72 flags = fcntl(fd, F_GETFD); /* 獲取當前的進程狀態 */
73 fcntl(fd, F_SETFL, flags | FASYNC);/* 設置進程啓用異步通知功能 */
74
75 while(1) {
76 sleep(2);
77 }
78
79 close(fd);
80 return 0;
81 }
第 32~43 行,sigio_signal_func 函數,SIGIO 信號的處理函數,當驅動程序有效按鍵按下以後就會發送 SIGIO 信號,此函數就會執行。此函數通過 read 函數讀取按鍵值,然後通過printf 函數打印在終端上。
第 69 行,通過 signal 函數設置 SIGIO 信號的處理函數爲 sigio_signal_func。 第 71~73 行,設置當前進程的狀態,開啓異步通知的功能。
第 75~77 行,while 循環,等待信號產生。