1.select和poll
IO多路轉換技術, select, poll的原理是: 通過將待監聽文件描述符(fd)加入集合, 然后通過查詢其調用返回值, 取得數據有動靜的fd數量, 再輪詢集合中每個fd, 如果有數據, 就直接讀取; 如果沒有數據, 就等待下一次查詢.
select和poll實現了異步形式通知, 但本質上還是需要主動輪詢.
2. BSD異步IO
System V和BSD都有一套各自的異步IO, 原理類似, 這里只介紹BSD異步IO.
BSD異步IO是信號SIGIO, SIGURG的組合: SIGIO 通用異步IO信號; SIGURG 用來通知進程網絡連接上的帶外數據(data of band, 緊急數據)已經達到.
進程接收SIGIO信號, 需要執行的步驟:
- 調用signal/sigaction為SIGIO信號建立處理程序;
- 調用fcntl, 命令參數F_SETOWN, 設置進程ID或進程組ID, 用於告訴驅動程序/內核, 指定進程接收SIGIO信號;
- 調用fcntl, 命令參數F_SETFL, 設置O_ASYNC文件狀態標識, 以便在該fd上可以進行異步IO;
進程接收SIGURG, 只需只需第1,2步. 信號僅對引用支持帶外數據的網絡連接描述符 產生, 如TCP連接(UDP不支持).
什么是帶外數據?
請參考帶外數據 | 博客園
BSD異步IO例程
完整源代碼, 請參見 async.c
關鍵步驟代碼
void sig_fun() {
int data = 0;
int n = read(mousefd, &data, sizeof(data));
if (n < 0) {
printf("read mouse error\n");
}
else {
printf("%d\n", data);
}
}
struct sigaction sa;
struct sigaction od_sa;
sa.sa_handler = sig_fun;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGIO); // SIGIO添加進信號集
sa.sa_flags = 0;
// 1. 調用sigaction為SIGIO信號建立信號處理程序
sigaction(SIGIO, &sa, &od_sa); // 捕獲SIGIO后, 處理信號時, 阻塞信號; 處理完畢后恢復
// 2. 以命令F_SETOWN調用fcntl來設置進程ID, 用於接收對該描述符的信號(SIGIO)
if (fcntl(mousefd, F_SETOWN, getpid()) < 0) {
perror("fcntl F_SETOWN error");
exit(1);
}
// 3. 以命令F_SETFL調用fcntl, 設置O_ASYNC文件狀態標識, 使得在該描述符上可以進行異步IO
int flag = fcntl(mousefd, F_GETFL);
if (flag < 0) {
perror("fcntl F_GETFL error");
exit(1);
}
flag |= O_ASYNC;
ret = fcntl(mousefd, F_SETFL, flag);
if (ret < 0) {
perror("fcntl F_SETFL error");
exit(1);
}
while (1) {
usleep(50);
}
3. POSIX異步IO(AIO)
BSD對不同的設備文件進行異步IO方法不一樣, 如終端設備是產生SIGIO信號, 僅支持帶外數據的設備才能產生SIGURG信號.
POSIX對不同類型文件進行異步IO提供一套一致的方法, SUSv4中, 這些接口被移到了基本部分中, 所以現在所有的平台都被要求支持這些接口.
3.1 AIO控制塊
異步IO接口使用AIO控制塊來描述IO操作.
AIO控制塊由aiocb結構定義:
#include <aiocb.h>
struct aiocb {
/* The order of these fields is implementation-dependent */
int aio_fildes; /* File descriptor */
off_t aio_offset; /* File offset */
volatile void *aio_buf; /* Location of buffer */
size_t aio_nbytes; /* Length of transfer */
int aio_reqprio; /* Request priority */
struct sigevent aio_sigevent; /* Notification method */
int aio_lio_opcode; /* Operation to be performed;
lio_listio() only */
/* Various implementation-internal fields not shown */
};
字段說明
aio_fildes 表示已打開的文件描述符, 用於讀/寫;
aio_offset 讀寫操作從aio_offset指定的偏移量開始;
aio_buf 用於讀寫操作轉存數據的緩沖區;
aio_nbytes 緩沖區aio_buf的大小;
aio_reqprio 應用程序使用該字段為異步IO請求提示順序. 值必須介於0和sysconf(_SC_AIO_PRIO_DELTA_MAX)返回值之間. 文件同步操作忽略該字段;
aio_lio_opcode 應當進行的操作類型, 只能用於lio_listio (基於列表的異步IO), 值描述見lio_listio章節;
aio_sigevent 指明IO事件完成后, 如何通知應用程序.
sigevent結構:
union sigval { /* Data passed with notification */
int sival_int; /* Integer value */
void *sival_ptr; /* Pointer value */
};
struct sigevent {
int sigev_notify; /* Notification method */
int sigev_signo; /* Notification signal */
union sigval sigev_value; /* Data passed with
notification */
void (*sigev_notify_function) (union sigval);
/* Function used for thread notification (SIGEV_THREAD) */
void *sigev_notify_attributes;
/* Attributes for notification thread (SIGEV_THREAD) */
pid_t sigev_notify_thread_id;
/* ID of thread to signal (SIGEV_THREAD_ID) */
};
sigevent字段說明:
-sigev_notify 通知類型, 其取值只能是這3個之一: SIGEV_NONE, SIGEV_SIGNAL, SIGEV_THREAD.
1)SIGEV_NONE 異步IO請求完成后, 不通知進程;
2)SIGEV_SIGNAL 異步IO請求完成后, 產生由sigev_signo字段指定的信號. 也就是說, 需要應用程序捕捉sigev_signo表示的信號, 並在信號處理程序中完成IO數據操作.
3)SIGEV_THREAD 異步IO請求完成時, 調用sigev_notify_function指定的函數, sigev_value作為唯一參數被傳入. 除非sigev_notify_attributes字段被設定為pthread屬性結構的地址, 且該結構指定了一個另外的線程屬性, 否則該函數將在線程分離狀態的一個單獨的線程中執行.
3.2 aio_read & aio_write
使用異步IO前, 應先對AIO控制塊(struct aiocb對象)進行初始化.
aio_read - 異步讀, aio_write - 異步寫:
#include <aio.h>
int aio_read(struct aiocb *aiocb);
int aio_write(struct aiocb *aiocb);
描述
將異步IO請求放入等待處理的隊列中(函數提出請求, 由OS放入). 函數返回值與實際IO操作結果沒有關系. IO操作等待時, 需確保AIO控制塊和數據緩沖區保持穩定, 下面對應的內容也必須始終合法, 不能被釋放, 也不能被復用, 除非IO操作完成.
aio_read是read的異步模擬, aio_write是write的異步模擬.
read(fd, buf, count);
write(fd, buf, n);
返回值
成功返回0; 失敗-1
3.3 aio_fsync
aio_fsync - 異步文件同步:
強制所有(等待隊中)等待的異步操作不等待, 而直接寫入持久化的存儲中(通常指磁盤, emmc等), 可以設置一個AIO控制塊並調用aio_fsync.
#include <aio.h>
int aio_fsync(int ap, struct aiocb *aiocb);
描述
aiocb->aio_fildes字段(文件描述符)指定異步寫操作被同步的文件.
如果op = O_DSYNC, 那么操作執行像調用fdatasync, 函數立即返回, 但IO操作完成前, 文件數據不會被持久化;
如果op = O_SYNC, 那么操作執行像調用fsync, 函數立即返回, 但IO操作完成前, 文件數據和屬性不會被持久化;
sync, fsync, fdatasync, fflush是什么?
參考sync、fsync、fdatasync、fflush函數區別和使用舉例 | CSDN
| 函數名稱 | 作用描述 |
|---|---|
| sync | 將所有修改過的(內核)快緩存區排隊進寫隊列, 然后返回, 並不等待實際寫磁盤操作結束 |
| fsync | 只對由fd指定單一文件起作用, 並且等待磁盤操作結束, 然后返回 |
| fdatasync | 類似於fsync, 但只影響文件的數據部分, 不像fsync還會同步更新文件的屬性 |
| fflush | 沖刷IO庫緩存, 將庫緩存內容寫入內核緩沖區 |
3.4 aio_error
aio_error - 獲取異步IO操作(異步讀、寫或同步)的完成狀態
#include <aio.h>
int aio_error(const struct aiocb *aiocb);
描述
函數返回異步IO請求的錯誤狀態, aiocb指向AIO控制塊, 代表了異步IO請求信息.
返回值
0 異步操作成功, 需要調用aio_return 函數獲取操作返回值;
-1 對aio_error調用失敗, errno被設置;
EINPROGRESS 異步讀、寫或同步操作仍在等待;
其他值 相關異步操作失敗返回的錯誤碼(errno);
3.5 aio_return
aio_error提到, 返回0時表示異步操作成功, 可以調用aio_return獲取操作返回值.
aio_return - 獲取異步IO操作返回值
int <aio.h>
int aio_return(const struct aiocb *aiocb);
描述
注意:
- 異步操作完成之前, 不要調用aio_return, 其行為是未定義的;
- 對每個異步操作調用一次aio_return, 因為一旦調用了, OS就能釋放包含了IO操作返回值的記錄;
返回值
失敗返回-1, errno被設置; 成功時, 返回異步操作結果, 即返回(同步版本)read、write或fsync在被成功調用時可能返回的結果.
3.6 aio_suspend
aio_suspend - 等待異步IO操作完成, 或超時
#include <aio.h>
int aio_suspend(const struct aiocb *const list[], int nent, const strct timespec *timeout);
描述
執行IO操作時, 如果有其他事務處理而不想被IO操作阻塞, 可以使用異步IO. 如果事務執行完畢后, 還有異步操作尚未完成時, 可調用aio_suspend函數阻止進程, 直到操作完成.
參數
list 指向AIO控制塊數組的指針
nent 表明數組的元素個數
timeout 超時時間
返回值
3種情況:
- 如果被一個信號中斷, 返回-1, errno設置為EINTR;
- 如果沒有任何IO操作完成, 阻塞時間超時, 返回-1, errno設置為EAGAIN;
- 如果有任何IO操作完成, 返回0; 如果所有的異步IO操作都已完成, aio_suspend將在不阻塞的情況下直接返回;
3.7 aio_cancel
aio_cancel - 取消未完成的異步IO請求
#include <aio.h>
int aio_cancel(int fd, struct aiocb *aiocb);
描述
如果不想完成還在等待中的異步IO操作時, 可以調用aio_cancel嘗試取消. 描述為嘗試, 是因為系統無法保證一定能取消正在進行的任何操作.
如果異步IO操作成功取消, 相應AIO控制塊調用aio_error將返回錯誤ECANCELED; 如果操作不能被取消, 那么相應的AIO控制塊不會被修改
參數
fd 指定未完成的異步IO操作的文件描述符
aiocb 如果aiocb = NULL, 系統會嘗試取消所有該文件上未完成的異步IO操作; 其他情況, 系統將嘗試取消aiocb指向的單個AIO控制塊描述的單個異步IO操作.
返回值
4個值之一:
AIO_ALLDONE 所有操作在嘗試取消前, 已經完成;
AIO_CANCELED 所有要求的操作已被取消;
AIO_NOTCANCELED 至少有一個要求的操作沒有被取消;
-1 對aio_cancel調用失敗, 設置errno;
3.8 lio_listio
lio_listio - 初始化io請求列表
#include <aio.h>
int lio_listio(int mode, struct aiocb *const aiocb_list[], int nitems, struct sigevent *sevp);
描述
既能以同步方式使用, 也能以異步的方式使用. 函數提交一系列由一個AIO控制塊列表描述的IO請求.
每個AIO控制塊中, aio_lio_opcode字段指定了該操作是一個讀操作(LIO_READ), 寫操作(LIO_WRITE), 還是將忽略的空操作(LIO_NOP). 讀操作, 會按照對應的AIO控制塊被傳給aio_read來處理; 寫操作, 會被傳給aio_write處理.
參數
mode 決定IO釋放真的是異步的. 取值說明:
- LIO_WAIT 調用塊將等到所有操作完成, sevp參數將會被忽略;
- LIO_NOWAIT IO請求入隊后, 立即返回, 進程在所有IO操作完成后, 按sigev指定的, 被異步通知. 如果不想被通知, sigev可設置為NULL. 被sigev指定的異步通知, 是在每個AIO控制塊本身的異步通知之外的.
aiocb_list 指向AIO控制塊列表, 指定了要運行的IO操作.
nitems 指定了aiocb_list數組元素格式.
實現限制
實現一般會限制一些參數的實際取值
POSIX.1中異步IO運行時不變量的值
| 名稱 | 描述 | 可接受的最小值 |
|---|---|---|
| AIO_LISTIO_MAX | 單個列表IO調用中的最大IO操作數 | _POSIX_AOI_LISTIO_MAX |
| AIO_MAX | 未完成的異步IO操作的最大數目 | _POSIX_AIO_MAX |
| AIO_PRIO_DELTA_MAX | 進程可以減少的異步IO優先級的最大值 | 0 |
4. AIO的使用例程
以從一個文件讀取數據, 然后寫到另外一個文件為例.
4.1 同步IO操作
流程

4.2 異步IO操作 (AIO)
流程

