一、Linux 的 5 種 IO 模型
二、如何使用信號驅動式 I/O?
三、內核何時會發送 “IO 就緒” 信號?
四、最簡單的示例
五、擴展知識
一、Linux 的 5 種 IO 模型
阻塞式 I/O:
系統調用可能因為無法立即完成而被操作系統掛起,直到等待的事件發生為止。
點擊查看大圖
非阻塞式 I/O (O_NONBLOCK):
系統調用則總是立即返回,而不管事件是否已經發生。
點擊查看大圖
I/O 復用 (select、poll、epoll):
通過 I/O 復用函數向內核注冊一組事件,內核通過 I/O 復用函數把其中就緒的事件通知給應用程序。
點擊查看大圖
信號驅動式 I/O (SIGIO):
為一個目標文件描述符指定宿主進程,當文件描述符上有事件發生時,SIGIO 的信號處理函數將被觸發,然后便可對目標文件描述符執行 I/O 操作。
點擊查看大圖
異步 I/O (POSIX 的 aio_ 系列函數):
異步 I/O 的讀寫操作總是立即返回,而不論 I/O 是否是阻塞的,真正的讀寫操作由內核接管。
點擊查看大圖
思考一下,什么時候應該選擇何種 I/O 模型?為何要這么選擇?
下面重點關注信號驅動式 I/O 這一模型,其他模型可查閱文末參考書籍。
二、如何使用信號驅動式 I/O?
一般通過如下 6 個步驟來使用信號驅動式 I/O 模型。
1》 為通知信號安裝處理函數。
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
默認情況下,這個通知信號為 SIGIO。
2》 為文件描述符的設置屬主。
通過 fcntl() 的 F_SETOWN 操作來完成:
fcntl(fd, F_SETOWN, pid)
屬主是當文件描述符上可執行 I/O 時,會接收到通知信號的進程或進程組。
pid 為正整數時,代表了進程 ID 號。
pid 為負整數時,它的絕對值就代表了進程組 ID 號。
3》 使能非阻塞 I/O。
通過 fcntl() 的 F_SETFL 操作來完成:
flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, flags | O_NONBLOCK);
4》 使能信號驅動 I/O。
通過 fcntl() 的 F_SETFL 操作來完成:
flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, flags | O_ASYNC);
5》 進程等待 “IO 就緒” 信號的到來。
當 I/O 操作就緒時,內核會給進程發送一個信號,然后調用在第 1 步中安裝好的信號處理函數。
6》 進程盡可能多地執行 I/O 操作。
循環執行 I/O 系統調用直到失敗為止,此時錯誤碼為 EAGAIN 或 EWOULDBLOCK。
原因:
信號驅動 I/O 提供的是邊緣觸發通知,即只有當 I/O 事件發生時我們才會收到通知,
且當文件描述符收到 I/O 事件通知時,并不知道要處理多少 I/O 數據。
三、內核何時會發送 “IO 就緒” 信號?
對于不同類型的文件描述符,情況不一樣。
1》 終端
對于終端,當有新的輸入時會會產生信號。
2》 管道和 FIFO
對于讀端,下列情況會產生信號:
數據寫入到管道中;
管道的寫端關閉;
對于寫端,下列情況會產生信號:
對管道的讀操作增加了管道中的空余空間大小。
管道的讀端關閉;
3》 套接字
對于 UDP 套接字,下列情況會產生信號:
數據報到達套接字;
套接字上發生異步錯誤;
對于 TCP 套接字,信號驅動式 I/O 近乎無用。
太多情況都會產生信號,而我們又無法得知事件類型,因此這里就不再列舉其產生信號的情況。
四、最簡單的示例
信號處理函數:
static volatile sig_atomic_t gotSigio = 0; static void handler(int sig) { gotSigio = 1; }
主程序:
int main(int argc, char *argv[]) { int flags, j, cnt; struct termios origTermios; char ch; struct sigaction sa; int done; /* Establish handler */ sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = handler; if (sigaction(SIGIO, &sa, NULL) == -1) { perror(“sigaction() ”); exit(1); } /* Set owner process */ if (fcntl(STDIN_FILENO, F_SETOWN, getpid()) == -1) { perror(“fcntl() / F_SETOWN ”); exit(1); } /* Enable “I/O possible” signaling and make I/O nonblocking */ flags = fcntl(STDIN_FILENO, F_GETFL); if (fcntl(STDIN_FILENO, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1) { perror(“fcntl() / F_SETFL ”); exit(1); } for (done = 0, cnt = 0; !done ; cnt++) { sleep(1); if (gotSigio) { gotSigio = 0; /* Read all available input until error (probably EAGAIN) or EOF */ while (read(STDIN_FILENO, &ch, 1) 》 0 && !done) { printf(“cnt=%d; read %c ”, cnt, ch); done = ch == ‘#’; } } } exit(0); }
運行效果:
。/build/sigio a cnt=0; read a cnt=0; read abc cnt=4; read a cnt=4; read b cnt=4; read c cnt=4; read # cnt=7; read #
該程序會先使能信號驅動 IO,然后循環執行計數操作。
當有 IO 就緒信號到來時,會去終端讀取數據并打印出來,然后繼續執行計數操作。
五、擴展知識
I/O 多路復用 、信號驅動 I/O 以及 epoll 機制可用于監視多個文件描述符。
它們并不實際執行 I/O 操作,當某個文件描述符處于就緒態,仍需采用傳統的 I/O 系統調用來完成 I/O 操作。
相比 I/O 多路復用,當監視大量的文件描述符時信號驅動 I/O 有著顯著的性能優勢,原因是內核能夠幫進程記錄了正在監視的文件描述符列表。
信號驅動 I/O 的缺點:
信號的處理流程較為復雜;
無法指定需要監控的事件類型。
Linux 特有的 epoll 是一個更好的選擇。
六、相關參考
6.2 I/O模型
25 信號驅動式I/O
Linux-UNIX 系統編程手冊
63 其他備選的I/O模型
Linux 高性能服務器編程
8.3 I/O 模型
Linux 多線程服務端編程_使用muduo C++網絡庫
原文標題:Linux-C 編程 | 3 分鐘快速了解信號驅動式 IO
文章出處:【微信公眾號:FPGA之家】歡迎添加關注!文章轉載請注明出處。
責任編輯:haq
-
Linux
+關注
關注
87文章
11345瀏覽量
210389 -
信號
+關注
關注
11文章
2804瀏覽量
77100
原文標題:Linux-C 編程 | 3 分鐘快速了解信號驅動式 IO
文章出處:【微信號:zhuyandz,微信公眾號:FPGA之家】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論