1.1 檔案描述符
檔案描述符(fd)相當於windows編程中的檔案控制代碼,使一個非負整數,引用一個開啟的檔案。
Unix的慣例是檔案描述符0(STDIN_FILENO)是標準輸出,1(STDOUT_FILENO)是標準輸出,2(STDERR_FILENO)是標準錯誤輸出。
1.2 檔案的開啟與關閉
1.2.1 相關函數
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);
int close(int fd);
1.2.2 flag的選項
O_RDONLY、O_WRONLY和O_RDWR,這三個標誌中只能指定一個。另外還有以下常數可以選擇:O_APPEND、O_CREAT、O_EXEL(與O_CREAT同時指定,而檔案已存在則出錯,否則建立)、O_TRUNC、O_NONBLOCK(非阻塞方式)、O_SYNC(同步寫,每次write都等到物理I/O完成)等常用選項。
1.2.3 建立新檔案
當flags指定O_CREAT時,需要指定第三個參數mode,說明新檔案的存取許可權。也可以用creat函數,creat等價於:
open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode)
1.3 改變檔案的位移量
1.3.1 相關函數
off_t lseek(int fildes, off_t offset, int whence);
1.3.2 設定檔案位移量
參數offset指定位移量,參數whence可指定為SEEK_SET(相對於檔案頭)、SEEK_CUR(相對於當前位置)、SEEK_END(相對於從檔案尾)。
因為位移量可能是負值,比較lseek的傳回值時必須謹慎,不要測試它是否小於0,而要測試它是否等於-1。
1.3.3 檔案空洞
檔案的位移量可以大於檔案當前長度,這種情況下,對該檔案的下一次寫操作將延長該檔案。形成一個空洞,空洞中的位元組都為0。
1.4 檔案的讀寫
1.4.1 相關函數
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
1.4.2 讀檔案
read函數的傳回值是實際讀到的位元組數,他可能少於要求讀的位元組數count:讀到檔案尾了;或者讀的是終端裝置、網路裝置或者某些面向記錄的裝置時。
1.4.3 寫檔案
寫操作從當前位移開始。若開啟檔案時設定了O_APPEND選項,則每次寫之前,都會將當前位移量設為檔案尾。
1.4.4 效率
使用不同的BUFFSIZE將資料從標準輸入讀入然後寫到標準輸出。在下面的測試中,標準輸入重新導向到一個7175K的檔案,標準輸出重新導向到/dev/null。
BUFFSIZE為1:
real 0m15.945s
user 0m5.420s
sys 0m10.500s
BUFFSIZE為4:
real 0m4.025s
user 0m1.570s
sys 0m2.460s
BUFFSIZE為16:
real 0m1.057s
user 0m0.200s
sys 0m0.860s
BUFFSIZE為256:
real 0m0.153s
user 0m0.030s
sys 0m0.120s
BUFFSIZE為1024:
real 0m0.093s
user 0m0.000s
sys 0m0.100s
BUFFSIZE為4096:
real 0m0.081s
user 0m0.010s
sys 0m0.080s
BUFFSIZE為16384:
real 0m0.078s
user 0m0.000s
sys 0m0.080s
BUFFSIZE為65536:
real 0m0.080s
user 0m0.000s
sys 0m0.080s
繼續增大BUFFSIZE對系統時間並沒有影響。
1.5 複製檔案描述符
1.5.1 相關函數
int dup(int oldfd);
int dup2(int oldfd, int newfd);
1.5.2 說明
這些函數返回一個和oldfd共用同一檔案表項的新fd。不同之處是dup2可以指定新fd的值,並且如果newfd已經開啟,則先將其關閉。他們可以用fcntl(oldfd,F_DUPFD, 0/newfd)實現,不同的只是errno,並且dup2是一個原子操作。
1.6 fcntl函數
1.6.1 相關函數
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
1.6.2 功能
- 獲得/設定檔案描述符標誌(cmd = F_GETFD或F_SETFD)。
- 獲得/設定檔案狀態標誌(cmd d = F_GETFL或F_SETFL)。
- 獲得/設定非同步I / O有權(cmd = F_GETOWN或F_SETOWN)。
- 獲得/設定記錄鎖(cmd = F_GETLK, F_SETLK或F_SETLKW)。
1.6.3 F_GETFL或F_SETFL
對於這兩種操作,可以獲得或者設定檔案的狀態標誌。F_GETFL時可以獲得O_RDONLY、O_WRONLY、O_RDWR、O_APPEND、O_NONBLOCK、O_SYNC和O_ASYNC。對於前三種標誌,必須使用屏蔽字O_ACCMODE,然後將結果與這三種標誌一一比較。F_SETFL時只能設定後四種標誌。
accmode = val & O_ACCMODE;
if (accmode == O_RDONLY) …
else if (accmode == O_WRONLY) …
else if (accmode == O_RDWR) …
else err_dump("unknown access mode");
if (val & O_APPEND) …
if (val & O_NONBLOCK) …
……
1.7 記錄鎖
1.7.1 相關函數:
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, struct flock *lock);
1.7.2 相關結構:
struct flock {
short l_type; /* F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* 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 */
};
l_start和l_whence決定加鎖或解鎖地區的起始位置,l_len決定其長度。該地區可以越過檔案尾端,但不能越過檔案起始位置。l_len為0,則表示鎖的地區從開始位置直至檔案尾,而不管檔案如何增長。鎖整個檔案的通常方法是:l_start為0,l_whence為SEEK_SET,l_len為0。
1.7.3 共用鎖定和獨佔鎖
共用鎖定,又叫讀鎖(RDLCK);獨佔鎖,又叫寫鎖(WRLCK)。
表格 1 (讀鎖和寫鎖)
當前所有/要求 |
讀鎖 |
寫鎖 |
讀鎖(一把或多把) |
可以 |
拒絕 |
寫鎖 |
拒絕 |
拒絕 |
1.7.4 加鎖和解鎖
根據fcntl函數的cmd參數進行不同操作:
- F_GETLK:檢查是否可以建立參數lock描述的鎖
- F_SETLK:設定lock描述的鎖,出錯立刻返回
- F_SETLKW:F_SETLK的阻塞版,若不能加鎖則等待
1.7.5 鎖的繼承和釋放
進程終止時,它建立的鎖全部釋放。
關閉一個檔案描述符時,則與該描述符相關的檔案上的所有由這個進程設定的鎖都被釋放,即使進程還有開啟的描述符指向該檔案。
fork產生的子進程不繼承父進程設定的鎖。而exec調用後,新程式可以繼承原執行程式的鎖。
1.7.6 建議性鎖和強制性鎖
建議性鎖(Advisory lock)並不能保證其他對檔案有寫入權限的進程讀寫檔案,使用建議性鎖的程式必須以一致的方法處理記錄鎖。使用強制性鎖(Mandatory lock),則核心對每個open、read和write都要檢查調用進程是否違背了某一把鎖的作用。
要使用強制性鎖,首先檔案所在的檔案系統必須開啟此功能(mount的時候加上-o mand參數);然後開啟其set-guid,關閉其組執行位(chmod命令中的g-x和g+s)即可。強制性鎖的使用和建議性鎖基本相同。]
強制性鎖不是POSIX標準。
1.8 I/O多路轉接
1.8.1 相關函數:
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
int pselect(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask)
int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
FD_CLR(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_SET(int fd, fd_set *set);
FD_ZERO(fd_set *set);
1.8.2 相關結構
fd_set是一個fd的集合,有四個宏可對其進行操作:清空一個fd即(FD_ZERO),檢測指定 fd是否在集中(FD_ISSET),加入(FD_SET)和移除(FD_CLR)一個fd。
select使用結構timeval:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
pselect使用結構timespec:
struct timespec {
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
1.8.3 說明
select等待指定的fd對應的檔案準備就緒。對於readfds中的檔案,等待起可讀;對於writefds中的檔案,等待其可寫;對於exceptfds中的檔案等待其發生異常。而n則是參數中最大的fd加1,可以設為FD_SETSIZE,但會影響效率。timeout則是等待的時間,為NULL則無限等待。傳回值是就緒的fd的個數。
在select函數中,如果在一個fd上碰到了檔案結束,則select認為該fd是可讀的,而不是指示一個異常。
pselect和select類似,只是使用了timespec結構指定時間,另外和sigsuspend類似,在等待時會將訊號屏蔽改為sigmask。
poll是select函數的另一版本,不同之處是它構造一格pollfd結構數組,對每個元素指定一個fd及對其關心的條件。
1.8.4 使用
select可以作為提供更精確的sleep函數使用。
當必須讀寫多個fd時,使用select函數,可以避免長時間阻塞在一個fd上而另一個fd的資料卻得不到及時處理的情況。
1.9 讀寫多個緩衝
1.9.1 相關函數
ssize_t readv(int fd, const struct iovec *vector, int count);
ssize_t writev(int fd, const struct iovec *vector, int count);
1.9.2 相關結構
struct iovec {
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes */
};
1.9.3 說明
這兩個函數用於讀寫多個緩衝。讀寫的順序是按數組的順序。
1.9.4 效能
兩個緩衝,一個512,一個1024,分別用三種方法將其寫入檔案10000遍,第一種是調用writev,第二種是每個迴圈調用兩次write,第三種是聲名一個1536的緩衝,將前兩個緩衝的內容都拷貝入這個緩衝中然後調用一次write,結果如下。右邊是apue上的測試結果,他的緩衝大小為100、200,機器是80386。
one writev:
real 0m0.318s 8.2s
user 0m0.000s 0.3s
sys 0m0.320s 7.8s
two write:
real 0m0.990s 13.7s
user 0m0.000s 0.5s
sys 0m0.330s 13.1s
memcpy and one write
real 0m0.255s 8.1s
user 0m0.010s 0.7s
sys 0m0.250s 7.3s
很奇怪,調用2次write所用系統時間應該是調用一次write或writev的2倍,但我的出的結果卻是調用一次writev和兩次write所用系統時間差不多,而memcpy後調用一次write反而比一次writev還快。
1.10 I/O儲存映射
1.10.1 概念
儲存映射使一個磁碟檔案或裝置同儲存空間的一個緩衝區相映射。於是從緩衝中存取資料就相當於檔案讀寫。
1.10.2 相關函數
void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
int munmap(void *start, size_t length);
int msync(void *start, size_t length, int flags);
1.10.3 說明
start參數一般設為0,即讓系統選擇映射緩衝取的起始地址,這個地址通過傳回值獲得。fd,length和offset指定檔案地區。prot指定對映射區的保護方式,flags則是一些標識位。MAP_SHARED表示和其他進程共用此映射,存取緩衝取回直接反映到檔案中;MAP_PRIVATE表示獨享此進程,寫入映射緩衝區並不影響原檔案。這兩個標識只能指定一個。
munmap解除儲存映射。要注意的是,關閉檔案描述符並不解除對此檔案的映射。調用munmap也並不使映射區的內容寫到檔案中,同步檔案和映射區可以調用msync函數。
1.10.4 使用
使用mmap/memcpy進行檔案操作比read/write快,因為前者直接對映射取緩衝進行操作,而對於後者,核心需要將資料在使用者緩衝和它自己的緩衝之間進行拷貝。
但是記憶體映射不能用在某些裝置(如網路和終端裝置)之間進行複製,並且操作時必須注意檔案的長度是否改變。