1.非阻塞IO
- 設置方法
- 1.open()時指定O_NONBLOCK標誌
- 2.已經打開的可用fcntl()打開O_NONBLOCK標誌
說明:POSIX標準規定無數據可讀時read()返回-1,,errno=EAGAIN,文件結束返回0
2.記錄鎖(字節範圍鎖)
商用UNIX系統提供了記錄鎖機制(使用數據庫的前提),POSIX標準的基礎是fcntl()方法,linux3.2.0支持fcntl()、lockf()、flock()
- fcntl()記錄鎖
int fcntl(int fd, int cmd, ... /* arg */ );
- cmd是F_GETLK、F_SETLK、F_SETLKW
- 對三個參數是指向flock的指針
struct flock { ... short l_type; /* Type of lock: F_RDLCK,F_WRLCK, F_UNLCK */ short l_whence; /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */ off_t l_start; /* Starting offset for lock */ off_t l_len; /* Number of bytes to lock */ pid_t l_pid; /* PID of process blocking our lock (F_GETLK only) */ ... };
- l_len爲0表示鎖的範圍可擴大最大可能偏移量
- 加讀鎖時描述符必須是讀打開,加寫鎖時必須是寫打開
- 同一進程不適用兼容性規則(會直接覆蓋),故不能用F_GETLK來測試自己是否在文件的某一部分持有一把鎖
- 在設置或釋放文件上的一把鎖時,系統按要求組合或分裂相鄰區
- 死鎖
- 兩個進程相互等待且不釋放
- 一個進程已經控制了文件中的一個加鎖區域,又試圖對另一個進程控制的區域加鎖
- 鎖的隱含繼承和釋放
- 鎖與進程和文件兩者相關聯:
- 進程終止時,建立的鎖全部釋放
- 描述符關閉時,通過該描述符引用的文件上的任何一把鎖都會釋放
- fork產生的子進程不會繼承父進程的鎖
- exec()後,新進程基礎原執行程序的鎖(未設置O_CLOEXEC)
- 鎖與進程和文件兩者相關聯:
- FreeBSD實現
- 主要就是struct lockf結構是作爲v節點的一個表項(v又是i_node的表項),這樣就可以解釋自動繼承和釋放的規則,並可以看出文件鎖是非強制性鎖
- 前面提到的可以用文件鎖保證守護進程只有一個實例在運行
#define lockfile(fd) write_lock((fd),0,SEEK_SET,0)
- 在文件尾端加鎖
- 需要注意的是因爲還沒有獲得鎖,所以不能用fstat來得到當前文件的長度
- 內核會將相對偏移量(SEEK_CUR或SEEK_END)轉換爲絕對偏移量
- 需要注意的是因爲還沒有獲得鎖,所以不能用fstat來得到當前文件的長度
- 建議性鎖和強制性鎖
- 使用一致函數訪問數據庫的進程集叫合作進程(建議性鎖是可行的),但不能阻止對數據庫文件有寫權限的其他非合作進程。
在linux上想使用,需要在各個文件系統的基礎上使用mount命令的-o mand選項來打開
- SVR3:對一個特定的文件打開其設置組ID位、關閉其組執行位便開啓了對該文件的強制性鎖
- open()一個具有強制性記錄鎖的文件時指定了O_TRUNC或O_CREAT,會失敗,errno=EAGAIN
- 具有強制性記錄鎖的文件可以被刪除(所有鎖對某些編輯器沒用)
- 使用一致函數訪問數據庫的進程集叫合作進程(建議性鎖是可行的),但不能阻止對數據庫文件有寫權限的其他非合作進程。
3.I/O多路轉接
3.1 函數select和pselect
/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
#include <sys/select.h>
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask);
- timeout不爲空且超時事件未到時linux3.2.0將用剩餘時間更新該值
說明:一個描述符阻塞與否並不影響select是否阻塞
- 除一下幾點外,pselect與select相同
- pselect使用timespec結構(秒和納秒),時間更精確
- timeout爲const
- 調用時以原子的方式安裝信號屏蔽字,返回時恢復
3.2 函數select和pselect
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <poll.h>
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts, const sigset_t *sigmask);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
3.3 異步I/O
- POSIX異步IO有下面這些侷限
- 三個可能產生錯誤的地方:
- 操作提交部分
- 操作本身的結果
- 決定異步操作的狀態函數中
- 三個可能產生錯誤的地方:
- System V異步I/O
- 是streams的一部分(只對streams設備和管道起作用),信號是SIGPOLL
- 啓動需要調用ioctl,第二個參數爲I_SETSIG,第三個參數見pdf421頁或stropts.h
- streams在SUSv4被廢棄了,這裏就不討論了
- BSD異步I/O
- 我不寫IOS系統程序這裏就不討論了
- POSIX異步
- 對不同類型的文件提供了一套一致的方法,現在所有的平臺都被要求支持
- 使用AIO控制塊
/* Asynchronous I/O control block. */ struct aiocb { int aio_fildes; /* File desriptor. */ int aio_lio_opcode; /* Operation to be performed. */ int aio_reqprio; /* Request priority offset. */ volatile void *aio_buf; /* Location of buffer. */ size_t aio_nbytes; /* Length of transfer. */ struct sigevent aio_sigevent; /* Signal number and value. */ /* Internal members. */ struct aiocb *__next_prio; … };
- 異步IO必須顯示指定偏移量(不會影響系統指定的偏移量),但是追加模式寫數據偏移量會被忽略
- 系統並不一定遵循aio_reqprio字段請求的提示順序
- aio_sigevent設置如何通知應用程序
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) */ };
- …
- 工作中用不到我就先不看啦,看了也記不住
4.函數readv()和writev()
也叫散步讀和聚集寫
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
ssize_t preadv(int fd, const struct iovec *iov, int iovcnt,
off_t offset);
ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt,
off_t offset);
struct iovec {
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
- 對於431頁的性能測試,雖然結果上緩衝區複製後接一次write時間少於一次writev(),那是因爲對於這種少量數據,使用writev的固定成本大於收益。
- 總之,還是用盡量少的系統調用來完成任務
8.存儲映射I/O
將一個磁盤文件映射到存儲空間的一個緩衝區上
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *addr, size_t length);
- prot參數可以下面參數的按位與,不能超過open()時的權限:
- PROT_READ
- PROT_WRITE
- PROT_EXEC
- PROT_NONE
- addr爲0可獲得最大的可移植性,
- 基於上面的特性flag最好不要用MAP_FIXED
- MAP_SHARED:
- MAP_PRIVATE:創建文件的一個私有副本
- off和addr通常是0
- 這些在網絡編程卷2有更詳細的討論,這裏就不再贅述了
需要注意的是:
不能用mmap()將數據添加到文件中,必須先加長該文件,fork可以繼承存儲映射區但exec不能- 更改映射權限
int mprotect(const void *addr, size_t len, int prot);
- addr必須是頁長的整數倍?page435
- 即使指定了MAP_SHARED,髒頁面何時寫回由內核守護進程決定
int msync(void *addr, size_t length, int flags);
- flags
- MS_SYNC:同步方式
- MS_ASYNC:異步方式,返回後不一定同步了
- flags
- 關於性能,在page437頁linux 3.2.0對比中,可以看到使用read/write 總時長比mmap/memcpy要快將近4倍,但是CPU時間差不多(也可能CPU計算方式與我們理解的有誤差),而在Solaris 10中第二種方式更快