非同步通知實驗

Bathwind_W發表於2024-06-21

非同步通知實驗

Linux 應用程式可以透過阻塞或者非阻塞這兩種方式來訪問驅動裝置,透過阻塞方式訪問的話應用程式會處於休眠態,等待驅動裝置可以使用,非阻塞方式的話會透過 poll 函式來不斷的輪詢.檢視驅動裝置檔案是否可以使用。這兩種方式都需要應用程式主動的去查詢裝置的使用情況,
“訊號”為此應運而生,訊號類似於我們硬體上使用的“中斷”,只不過訊號是軟體層次上的。算是在軟體層次上對中斷的一種模擬,驅動可以透過主動向應用程式傳送訊號的方式來報告自己可以訪問了,應用程式獲取到訊號以後就可以從驅動裝置中讀取或者寫入資料了。整個過程就相當於應用程式收到了驅動傳送過來了的一箇中斷,然後應用程式去響應這個中斷,在整個處理過程中應用程式並沒有去查詢驅動裝置是否可以訪問,一切都是由驅動裝置自己告訴給應用程式的。
非同步通知的核心就是訊號,在 arch/xtensa/include/uapi/asm/signal.h 檔案中定義了 Linux 所支
持的所有訊號,這些訊號如下所示:

示例程式碼 53.1.1.1 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 /* 未使用訊號 */

在 Linux 和其他類 Unix 系統中,訊號是一種程序間通訊機制,用於通知程序某些事件的發生。大多數訊號可以被程序捕獲、忽略或預設處理,但有兩個特殊的訊號 —— SIGKILLSIGSTOP —— 不能被程序忽略或捕獲。這兩個訊號的特殊性主要基於它們在系統中的作用和設計目的:

SIGKILL (訊號 9)

  • 目的: 強制終止程序。
  • 特性: SIGKILL 訊號用於立即終止程序,不給程序任何清理或儲存資料的機會。系統管理員和使用者可以使用這個訊號來終止那些無響應或不能正常關閉的程序。
  • 為什麼不能被忽略或捕獲: 如果程序能夠忽略或捕獲 SIGKILL,它們可能會阻止自身被終止,從而導致殭屍程序或無法控制的程序。這會使系統管理員難以管理系統,尤其是在程序不響應其他終止嘗試時。

SIGSTOP (訊號 19)

  • 目的: 暫停程序的執行。
  • 特性: SIGSTOP 訊號用於暫停(停止)程序的執行,直到收到 SIGCONT 訊號。這對於除錯和系統管理非常有用。
  • 為什麼不能被忽略或捕獲: 如果程序能夠忽略或捕獲 SIGSTOP,它們可能會繼續執行,即使系統管理員或作業系統需要它們停止。這樣的設計確保了系統管理員可以可靠地控制程序的執行,包括必要時暫停程序。
    使用中斷的時候需要設定中斷處理函式,同樣的,如果要在應用程式中使用訊號,那麼就必須設定訊號所使用的訊號處理函式,在應用程式中使用 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 訊號預設的動作是關閉當前應用程式。這裡我們修改一下 SIGINT 訊號的預設處理函式,當按下 CTRL+C 組合鍵以後先在終端上列印出“SIGINT signal!”這行字串,然後再關閉當前應用程式。

驅動中的訊號處理

1、 fasync_struct 結構體

首先我們需要在驅動程式中定義一個 fasync_struct 結構體指標變數, fasync_struct 結構體內容如下:

示例程式碼 53.1.2.1 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 結構體指標變數定義到裝置結構體中,比如在上一章節的 imx6uirq_dev結構體中新增一個 fasync_struct 結構體指標變數,結果如下所示:struct fasync_struct *async_queue; /* 非同步相關結構體 */
如果要使用非同步通知,需要在裝置驅動中實現 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 函式就會執行:
驅動程式中的 fasync 函式參考示例如下:

示例程式碼 53.1.2.3 驅動中 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 函式引數參考例項如下:

示例程式碼 53.1.2.4 釋放 fasync_struct 參考示例
1 static int xxx_release(struct inode *inode, struct file *filp)
2 {
3 return xxx_fasync(-1, filp, 0); /* 刪除非同步通知 */
4 }

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 函式就會執行:

驅動程式編寫

先要在裝置結構體中新增

struct imx6uirq_dev{
	dev_t devid;			/* 裝置號 	 */	
	struct cdev cdev;		/* cdev 	*/                 
	struct class *class;	/* 類 		*/
	struct device *device;	/* 裝置 	 */
	int major;				/* 主裝置號	  */
	int minor;				/* 次裝置號   */
	struct device_node	*nd; /* 裝置節點 */	
	atomic_t keyvalue;		/* 有效的按鍵鍵值 */
	atomic_t releasekey;	/* 標記是否完成一次完成的按鍵,包括按下和釋放 */
	struct timer_list timer;/* 定義一個定時器*/
	struct irq_keydesc irqkeydesc[KEY_NUM];	/* 按鍵init述陣列 */
	unsigned char curkeynum;				/* 當前init按鍵號 */
	wait_queue_head_t r_wait;				/* 讀等待佇列頭 */
	struct fasync_struct *async_queue;		/* 非同步相關結構體 */
};

接下來就是處理非同步通知函式:

/*
 * @description     : fasync函式,用於處理非同步通知
 * @param - fd		: 檔案描述符
 * @param - filp    : 要開啟的裝置檔案(檔案描述符)
 * @param - on      : 模式
 * @return          : 負數表示函式執行失敗
 */
static int imx6uirq_fasync(int fd, struct file *filp, int on)
{
	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
	return fasync_helper(fd, filp, on, &dev->async_queue);
}
/*
 * @description     : release函式,應用程式呼叫close關閉驅動檔案的時候會執行
 * @param - inode	: inode節點
 * @param - filp    : 要開啟的裝置檔案(檔案描述符)
 * @return          : 負數表示函式執行失敗
 */
static int imx6uirq_release(struct inode *inode, struct file *filp)
{
	return imx6uirq_fasync(-1, filp, 0);
}

裝置操作函式也要將這兩個函式繫結:

/* 裝置操作函式 */
static struct file_operations imx6uirq_fops = {
	.owner = THIS_MODULE,
	.open = imx6uirq_open,
	.read = imx6uirq_read,
	.poll = imx6uirq_poll,
	.fasync = imx6uirq_fasync,
	.release = imx6uirq_release,
};

接下來是測試APP函式:

#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
描述	   	: 非同步通知測試APP
其他	   	: 無
使用方法	:./asyncnotiApp /dev/asyncnoti 開啟測試App
論壇 	   	: www.openedv.com
日誌	   	: 初版V1.0 2019/8/13 左忠凱建立
***************************************************************/

static int fd = 0;	/* 檔案描述符 */

/*
 * SIGIO訊號處理函式
 * @param - signum 	: 訊號值
 * @return 			: 無
 */
static void sigio_signal_func(int signum)
{
	int err = 0;
	unsigned int keyvalue = 0;

	err = read(fd, &keyvalue, sizeof(keyvalue));
	if(err < 0) {
		/* 讀取錯誤 */
	} else {
		printf("sigio signal! key value=%d\r\n", keyvalue);
	}
}

/*
 * @description		: main主程式
 * @param - argc 	: argv陣列元素個數
 * @param - argv 	: 具體引數
 * @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;
	}

	/* 設定訊號SIGIO的處理函式 */
	signal(SIGIO, sigio_signal_func);
	
	fcntl(fd, F_SETOWN, getpid());		/* 設定當前程序接收SIGIO訊號 	*/
	flags = fcntl(fd, F_GETFL);			/* 獲取當前的程序狀態 			*/
	fcntl(fd, F_SETFL, flags | FASYNC);	/* 設定程序啟用非同步通知功能 	*/	

	while(1) {
		sleep(2);
	}

	close(fd);
	return 0;
}

編譯測試即可。

相關文章