以下是正文:
一、Linux 的 5 種 IO 模型
二、如何使用信號驅(qū)動式 I/O,?
三,、內(nèi)核何時會發(fā)送 "IO 就緒" 信號?
四,、最簡單的示例
五,、擴(kuò)展知識
一、Linux 的 5 種 IO 模型
阻塞式 I/O:
系統(tǒng)調(diào)用可能因?yàn)闊o法立即完成而被操作系統(tǒng)掛起,,直到等待的事件發(fā)生為止,。
非阻塞式 I/O (O_NONBLOCK):
系統(tǒng)調(diào)用則總是立即返回,而不管事件是否已經(jīng)發(fā)生,。
I/O 復(fù)用 (select,、poll、epoll):
通過 I/O 復(fù)用函數(shù)向內(nèi)核注冊一組事件,內(nèi)核通過 I/O 復(fù)用函數(shù)把其中就緒的事件通知給應(yīng)用程序,。
信號驅(qū)動式 I/O (SIGIO):
為一個目標(biāo)文件描述符指定宿主進(jìn)程,,當(dāng)文件描述符上有事件發(fā)生時,SIGIO 的信號處理函數(shù)將被觸發(fā),,然后便可對目標(biāo)文件描述符執(zhí)行 I/O 操作,。
異步 I/O (POSIX 的 aio_ 系列函數(shù)):
異步 I/O 的讀寫操作總是立即返回,而不論 I/O 是否是阻塞的,,真正的讀寫操作由內(nèi)核接管,。
思考一下,什么時候應(yīng)該選擇何種 I/O 模型,?為何要這么選擇,?
下面重點(diǎn)關(guān)注信號驅(qū)動式 I/O 這一模型,其他模型可查閱文末參考書籍,。
二,、如何使用信號驅(qū)動式 I/O?
一般通過如下 6 個步驟來使用信號驅(qū)動式 I/O 模型,。
1> 為通知信號安裝處理函數(shù),。
通過 sigaction() 來完成:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
默認(rèn)情況下,這個通知信號為 SIGIO,。
2> 為文件描述符的設(shè)置屬主,。
通過 fcntl() 的 F_SETOWN 操作來完成:
fcntl(fd, F_SETOWN, pid)
屬主是當(dāng)文件描述符上可執(zhí)行 I/O 時,會接收到通知信號的進(jìn)程或進(jìn)程組,。
pid 為正整數(shù)時,,代表了進(jìn)程 ID 號。
pid 為負(fù)整數(shù)時,,它的絕對值就代表了進(jìn)程組 ID 號,。
3> 使能非阻塞 I/O。
通過 fcntl() 的 F_SETFL 操作來完成:
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
4> 使能信號驅(qū)動 I/O,。
通過 fcntl() 的 F_SETFL 操作來完成:
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_ASYNC);
5> 進(jìn)程等待 "IO 就緒" 信號的到來,。
當(dāng) I/O 操作就緒時,內(nèi)核會給進(jìn)程發(fā)送一個信號,,然后調(diào)用在第 1 步中安裝好的信號處理函數(shù),。
6> 進(jìn)程盡可能多地執(zhí)行 I/O 操作。
循環(huán)執(zhí)行 I/O 系統(tǒng)調(diào)用直到失敗為止,,此時錯誤碼為 EAGAIN 或 EWOULDBLOCK,。
原因:
信號驅(qū)動 I/O 提供的是邊緣觸發(fā)通知,即只有當(dāng) I/O 事件發(fā)生時我們才會收到通知,,
且當(dāng)文件描述符收到 I/O 事件通知時,,并不知道要處理多少 I/O 數(shù)據(jù),。
三、內(nèi)核何時會發(fā)送 "IO 就緒" 信號,?
對于不同類型的文件描述符,,情況不一樣。
1> 終端
對于終端,,當(dāng)有新的輸入時會會產(chǎn)生信號,。
2> 管道和 FIFO
對于讀端,下列情況會產(chǎn)生信號:
數(shù)據(jù)寫入到管道中;管道的寫端關(guān)閉;
對于寫端,,下列情況會產(chǎn)生信號:
對管道的讀操作增加了管道中的空余空間大小,。管道的讀端關(guān)閉;
3> 套接字
對于 UDP 套接字,下列情況會產(chǎn)生信號:
數(shù)據(jù)報到達(dá)套接字,;套接字上發(fā)生異步錯誤;
對于 TCP 套接字,,信號驅(qū)動式 I/O 近乎無用。
太多情況都會產(chǎn)生信號,,而我們又無法得知事件類型,,因此這里就不再列舉其產(chǎn)生信號的情況。
四,、最簡單的示例
信號處理函數(shù):
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()\n");
exit(1);
}
/* Set owner process */
if (fcntl(STDIN_FILENO, F_SETOWN, getpid()) == -1) {
perror("fcntl() / F_SETOWN\n");
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\n");
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\n", cnt, ch);
done = ch == '#';
}
}
}
exit(0);
}
運(yùn)行效果:
./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 #
該程序會先使能信號驅(qū)動 IO,,然后循環(huán)執(zhí)行計(jì)數(shù)操作。
當(dāng)有 IO 就緒信號到來時,,會去終端讀取數(shù)據(jù)并打印出來,,然后繼續(xù)執(zhí)行計(jì)數(shù)操作。
五,、擴(kuò)展知識
I/O 多路復(fù)用 ,、信號驅(qū)動 I/O 以及 epoll 機(jī)制可用于監(jiān)視多個文件描述符。
它們并不實(shí)際執(zhí)行 I/O 操作,,當(dāng)某個文件描述符處于就緒態(tài),,仍需采用傳統(tǒng)的 I/O 系統(tǒng)調(diào)用來完成 I/O 操作。
相比 I/O 多路復(fù)用,,當(dāng)監(jiān)視大量的文件描述符時信號驅(qū)動 I/O 有著顯著的性能優(yōu)勢,原因是內(nèi)核能夠幫進(jìn)程記錄了正在監(jiān)視的文件描述符列表,。
信號驅(qū)動 I/O 的缺點(diǎn):
信號的處理流程較為復(fù)雜;
無法指定需要監(jiān)控的事件類型,。
Linux 特有的 epoll 是一個更好的選擇。
六,、相關(guān)參考
UNIX 網(wǎng)絡(luò)編程卷1
6.2 I/O模型25 信號驅(qū)動式I/O
Linux-UNIX 系統(tǒng)編程手冊
63 其他備選的I/O模型
Linux 高性能服務(wù)器編程
8.3 I/O 模型
Linux 多線程服務(wù)端編程_使用muduo C++網(wǎng)絡(luò)庫
7.4.1 muduo的IO模型
更多信息可以來這里獲取==>>電子技術(shù)應(yīng)用-AET<<