文件描述符:一個非負整數,范圍是0~OPEN_MAX-1
。內核用它來標識進程正在訪問的文件。當進程創建時,默認為它打開了3個文件描述符,它們都鏈接向終端:
通常我們應該使用STDIN_FILENO
,STDOUT_FILENO
和 STDERR_FILENO
來替代這三個幻數,從而提高可讀性。這三個常量位於
中。
2. open
和openat
函數:打開文件
```
#include
int open(const char* path,int oflag,.../*mode_t mode*/);
int openat(int fd,const char*path,int oflag,.../*mode_t mode */);
```
- 參數:
- `path`:要打開或者創建文件的名字
- `oflag`:用於指定函數的操作行為,定義在``中。
- `mode`:文件訪問權限。文件訪問權限常量在 `` 中定義,有下列九個:
- `S_IRUSR`:用戶讀
- `S_IWUSR`:用戶寫
- `S_IXUSR`:用戶執行
- `S_IRGRP`:組讀
- `S_IWGRP`:組寫
- `S_IXGRP`:組執行
- `S_IROTH`:其他讀
- `S_IWOTH`:其他寫
- `S_IXOTH`:其他執行
- 對於`openat`函數,被打開的文件名由`fd`和`path`共同決定:
**- 如果`path`指定的是絕對路徑,此時`fd`被忽略。`openat`等價於`open`**
**- 如果`path`指定的是相對路徑名,則`fd`是一個目錄打開的文件描述符。被打開的文件的絕對路徑由該`fd`描述符對應的目錄加上`path`組合而成**
**- 如果`path`是一個相對路徑名,而`fd`是常量`AT_FDCWD`,則`path`相對於當前工作目錄。被打開文件在當前工作目錄中查找。**
- 返回值:
- 成功:返回文件描述符。
- 失敗:返回 -1
由 `open/openat` 返回的文件描述符一定是最小的未使用的描述符數字。
3. creat
函數:創建一個新文件
```
#include
int creat(const char*path,mode_t mode);
```
該函數等價於`open(path,O_WRONLY|O_CREAT|O_TRUNC,mode)`。注意:
- 它以只寫方式打開,因此若要讀取該文件,則必須先關閉,然後重新以讀方式打開。
- 若文件已存在則將文件截斷為0。
-
4. close
函數:關閉文件
```
#include
int close(int fd);
```
- 參數:
- `fd`:待關閉文件的文件描述符
- 返回值:
- 成功:返回 0
- 失敗:返回 -1
注意:
- 進程關閉一個文件會釋放它加在該文件上的所有記錄鎖。
- 當一個進程終止時,內核會自動關閉它所有的打開的文件。
lseek
函數:設置打開文件的偏移量
#include
off_t lseek(int fd, off_t offset,int whence);
參數:fd
:打開的文件的文件描述符 whence
:必須是 SEEK_SET
、SEEK_CUR
、SEEK_END
三個常量之一 offset
:whence
是SEEK_SET
,則將該文件的偏移量設置為距離文件開始處offset
個字節 如果 whence
是 SEEK_CUR
,則將該文件的偏移量設置為當前值加上offset
個字節,offset
可正,可負 如果 whence
是 SEEK_END
,則將該文件的偏移量設置為文件長度加上offset
個字節,offset
可正,可負 返回值:每個打開的文件都有一個與其關聯的“當前文件偏移量”。它通常是個非負整數,用於度量從文件開始處計算的字節數。通常讀、寫操作都從當前文件偏移量處開始,並且使偏移量增加所讀寫的字節數。注意:
打開一個文件時,除非指定O_APPEND
選項,否則系統默認將該偏移量設為0 如果文件描述符指定的是一個管道、FIFO、或者網絡套接字,則無法設定當前文件偏移量,則lseek
將返回 -1 ,並且將 errno
設置為 ESPIPE
。 對於普通文件,其當前文件偏移量必須是非負值。但是某些設備運行負的偏移量出現。因此比較lseek
的結果時,不能根據它小於0 就認為出錯。要根據是否等於 -1 來判斷是否出錯。 lseek
並不會引起任何 I/O 操作,lseek
僅僅將當前文件的偏移量記錄在內核中。 當前文件偏移量可以大於文件的當前長度。此時對該文件的下一次寫操作將家常該文件,並且在文件中構成一個空洞。空洞中的內容位於文件中但是沒有被寫過,其字節被讀取時都被讀為0
/*
test lseek function for std file io
*/
#include "apue.h"
int main(void)
{
if (lseek(STDIN_FILENO,0,SEEK_CUR) == -1) /*there is must be is equal operation*/
printf("cannot seek\n");
else
printf("seek ok\n");
exit(0);
}
read
函數:讀取文件內容
#include
ssize_t read(int fd,void *buf,size_t nbytes);
參數:fd
:打開的文件的文件描述符 buf
:存放讀取內容的緩沖區的地址(由程序員手動分配) nbytes
:期望讀到的字節數 返回值:讀操作從文件的當前偏移量開始,在成功返回之前,文件的當前偏移量會增加實際讀到的字節數。有多種情況可能導致實際讀到的字節數少於期望讀到的字節數:
讀普通文件時,在讀到期望字節數之前到達了文件尾端 當從終端設備讀時,通常一次最多讀取一行(終端默認是行緩沖的) 當從網絡讀時,網絡中的緩存機制可能造成返回值小於期望讀到的字節數 當從管道或者FIFO
讀時,若管道包含的字節少於所需的數量,則 read
只返回實際可用的字節數 當從某些面向記錄的設備(如磁帶)中讀取時,一次最多返回一條記錄 當一個信號造成中斷,而已讀了部分數據時。
write
函數:想文件寫數據
#include
ssize_t write(int fd,const void *buf,size_t nbytes);
參數:fd
:打開的文件的文件描述符 buf
:存放待寫的數據內容的緩沖區的地址(由程序員手動分配) nbytes
:期望寫入文件的字節數 返回值:write
的返回值通常都是與nbytes
相同。否則表示出錯。write
出錯的一個常見原因是磁盤寫滿,或者超過了一個給定進行的文件長度限制
對於普通文件,寫操作從文件的當前偏移量處開始。如果打開文件時指定了O_APPEND
選項,則每次寫操作之前,都會將文件偏移量設置在文件的當前結尾處。在一次成功寫之後,該文件偏移量增加實際寫的字節數。
內核使用三種數據結構描述打開文件。它們之間的關系決定了一個進程與另一個進程在打開的文件之間的相互影響。
內核為每個進程分配一個進程表項(所有進程表項構成進程表),進程表中都有一個打開的文件描述符表。每個文件描述符占用一項,其內容為:這些信息都是在打開文件時從磁盤讀入內存的。如 i 結點包含了文件的所有者、文件長度、指向文件實際數據在磁盤上所在位置的指針等等。 v 結點結構和 i 結點結構實際上代表了文件的實體。
現在假設進程 A 打開文件 file1
,返回文件描述符 3;進程 B 也打開文件 file2
,返回文件描述符 4:
write
之後,進程 A 對應的文件表項的當前文件偏移量即增加所寫入的字節數。O_APPEND
標志打開一個文件,在相應標志也設置到進程 B 對於的文件表項的文件狀態標志中。lseek
定位到文件當前的尾端,則進程 B 對應的文件表項的當前文件偏移量設置為 i 結點中的當前長度 lseek
函數只是修改文件表項中的當前文件偏移量,不進行任何 I/O 操作
可能一個進程中有多個文件描述符指向同一個文件表項。<喎?http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPtSt19Oy2df3o7o8L3A+DQrXt7zT0ru49s7EvP7KsaOssrvE3M2ouf08Y29kZT5sc2VlazwvY29kZT61vcSpzrLIu7rzPGNvZGU+d3JpdGU8L2NvZGU+oaPSqtPDPGNvZGU+T19BUFBFTkQ8L2NvZGU+0aHP7rTyv6rOxLz+o6zIu7rz1rG90zxjb2RlPndyaXRlPC9jb2RlPqGjPGJyIC8+DQrNqLn9PGNvZGU+bHNlZWs8L2NvZGU+tb3Eqc6yyLu68zxjb2RlPndyaXRlPC9jb2RlPsqxo6zI57n7tuC49r34s8zNrMqx1rTQ0NXiwb249rLZ1/ejrNTyu+HS/cbwvrrV+cz1vP4gzai5/SA8Y29kZT5PX0FQUEVORDwvY29kZT7Roc/utPK/qs7EvP6jrMi7uvPWsb3TPGNvZGU+d3JpdGU8L2NvZGU+yrGjrMTausvDv9K7tM7U2tC0stnX99aux7CjrLa8u+G9q734s8y1xLWxx7DGq9LGwb/J6NbDtb3OxLz+tcTEqc6yo6zT2srHvs2yu9Do0qrWtNDQPGNvZGU+bHNlZWs8L2NvZGU+tqjOu7LZ1/cgPGNvZGU+cHJlYWQvcHdyaXRlPC9jb2RlPr/J0tTWtNDQ1K3X09DUtcS2qM67tsEvtqjOu9C0IDxjb2RlPk9fQ1JFQVQ="O_EXCL選項打開文件時,可以原子性的檢查文件是否存在和創建文件這兩個操作。
pread/pwrite
:原子定位讀和原子定位寫
#include
ssize_t pread(int fd,void*buf,size_t nbytes,off_t offset);
ssize_t pwrite(int fd,const void*buf,size_t nbytes,off_t offset);
參數:fd
:打開的文件描述符 buf
:讀出數據存放的緩沖區/ 寫到文件的數據的緩沖區 nbytes
:預期讀出/寫入文件的字節數 offset
:從文件指定偏移量開始執行read/write
返回:調用pread
相當於先調用lseek
再調用read
.但是調用pread
時,無法中斷其定位和讀操作,並且不更新當前文件偏移量;調用pwrite
相當於先調用lseek
再調用write
.但是調用pwrite
時,無法中斷其定位和寫操作,並且不更新當前文件偏移量
dup/dup2
:復制一個現有的文件描述符:
#include
int dup(int fd);
int dup2(int fd,int fd2);
參數:fd
:被復制的文件描述符(已被打開) fd2
:指定的新的文件描述符(待生成)對於dup
函數,返回的新的文件描述符一定是當前可用的文件描述符中最小的數字。對於dup2
函數:
fd2
已經是被打開的文件描述符且不等於fd
,則先將其關閉,然後再打開(注意關閉再打開是一個原子操作) 如果 fd2
等於fd
,則直接返回fd2
(也等於fd
),而不作任何操作
任何情況下,這個返回的新的文明描述符與參數fd
共享同一個文件表項(因此文件狀態標志以及文件偏移量都會共享)。 任何情況下,這個返回的新的文明描述符的close-on-exec
標志總是被清除
UNIX操作系統在內核中設有緩沖區,大多數磁盤 I/O 都通過緩沖區進行。當我們想文件寫入數據時,內核通常都首先將數據復制到緩沖區中,然後排入隊列,晚些時候再寫入磁盤。這種方式稱為延遲寫。
當內核需要重用緩沖區來存方其他數據時,它會把所有延遲寫的數據庫寫入磁盤 你也可以調用下列函數來顯式的將延遲寫的數據庫寫入磁盤
#include
int fsync(int fd);
int fdatasync(int fd);
void sync(void);
參數(前兩個函數):
fd
:指定的打開的文件描述符
返回值(前兩個函數):
成功:返回 0 失敗: 返回 -1區別:
sync
:將所有修改過的塊緩沖區排入寫隊列,然後返回,它並不等待時機寫磁盤結束 fsync
:只對由fd
指定的單個文件起作用,等待寫磁盤操作結束才返回 fdatasync
:只對由fd
指定的單個文件起作用,等待寫磁盤操作結束才返回,但是它只影響文件的數據部分(fsync
會同步更新文件的屬性)
update
守護進程會周期性的調用sync
函數。命令sync
也會調用sync
函數
fcntl
函數:改變已經打開的文件的屬性
#include
int fcntl(int fd,int cmd,.../* int arg */);
參數:
fd
:已打開文件的描述符 cmd
:有下列若干種:F_DUPEF
常量:復制文件描述符 fd
。新文件描述符作為函數值返回。它是尚未打開的個描述符中大於或等於arg
中的最小值。新文件描述符與fd
共享同一個文件表項,但是新描述符有自己的一套文件描述符標志,其中FD_CLOEXEC
文件描述符標志被清除 F_DUPFD_CLOEXEC
常量:復制文件描述符。新文件描述符作為函數值返回。它是尚未打開的個描述符中大於或等於arg
中的最小值。新文件描述符與fd
共享同一個文件表項,但是新描述符有自己的一套文件描述符標志,其中FD_CLOEXEC
文件描述符標志被設置 F_GETFD
常量:對應於fd
的文件描述符標志作為函數值返回。當前只定義了一個文件描述符標志FD_CLOEXEC
F_SETFD
常量:設置fd
的文件描述符標志為arg
F_GETFL
常量:返回fd
的文件狀態標志。文件狀態標志必須首先用屏蔽字 O_ACCMODE
取得訪問方式位,O_RDONLY
、O_WRONLY
、O_RDWR
、O_EXEC
、O_SEARCH
比較O_APPEND
、O_NONBLOCK
、O_SYNC
O_DSYNC
、O_RSYNC
、F_ASYNC
、O_ASYNC
F_SETFL
常量:設置fd
的文件狀態標志為 arg
。可以更改的標志是:O_APPEND
、O_NONBLOCK
、O_SYNC
、O_DSYNC
、O_RSYNC
、F_ASYNC
、O_ASYNC
F_GETOWN
常量:獲取當前接收 SIGIO
和SIGURG
信號的進程 ID
或者進程組 ID
F_SETOWN
常量:設置當前接收 SIGIO
和SIGURG
信號的進程 ID
或者進程組 ID
為arg
。若 arg
是個正值,則設定進程 ID
;若 arg
是個負值,則設定進程組ID
F_GETLK
、F_SETLK
、F_SETLKW
:獲取/設置文件記錄鎖 arg
:依賴於具體的命令
返回值:
成功: 依賴於具體的命令 失敗: 返回 -1
#include
#include
#include
#include
#include
void print_error(int fd,const char* action,int result)
{
if(result==-1)
{
printf("\t %s on fd(%d) error:beause %s!\n",action,fd,strerror(errno));
}
}
void test_get_fd(int fd)
{
printf("\tget_fd on fd(%d):",fd);
int result;
result=fcntl(fd,F_GETFD);
print_error(fd,"F_GETFD",result);
if(result!=-1)
printf("return:%d !\n",result);
}
void test_set_fd(int fd, int flag)
{
printf("\tset_fd on fd(%d) of flag(%d):",fd,flag);
int result;
result=fcntl(fd,F_SETFD,flag);
print_error(fd,"F_SETFD",result);
if(result!=-1)
printf("set_fd ok !\n");
}
int test_dup_fd(int fd,int min_fd)
{
printf("\tdup_fd on fd(%d),set min_fd(%d),:",fd,min_fd);
int result;
result=fcntl(fd,F_DUPFD,min_fd);
print_error(fd,"F_DUPFD",result);
if(result!=-1)
printf("return:%d !\n",result);
return result;
}
int test_dup_exec_fd(int fd,int min_fd)
{
printf("\tdup_exec_fd on fd(%d),set min_fd(%d),:",fd,min_fd);
int result;
result=fcntl(fd,F_DUPFD_CLOEXEC,min_fd);
print_error(fd,"F_DUPFD_CLOEXEC",result);
if(result!=-1)
printf("return:%d !\n",result);
return result;
}
void test_get_fl(int fd)
{
printf("\tget_fl on fd(%d):",fd);
int result;
result=fcntl(fd,F_GETFL);
print_error(fd,"F_GETFL",result);
if(result!=-1)
{
printf("F_GETFL on fd(%d) has ",fd);
if(result&O_APPEND) printf("\tO_APPEND;");
if(result&O_NONBLOCK) printf("\tO_NONBLOCK;");
if(result&O_SYNC) printf("\tO_SYNC;");
if(result&O_DSYNC) printf("\tO_DSYNC;");
if(result&O_RSYNC) printf("\tO_RSYNC;");
if(result&O_FSYNC) printf("\tO_FSYNC;");
if(result&O_ASYNC) printf("\thas O_ASYNC;");
if((result&O_ACCMODE)==O_RDONLY)printf("\tO_RDONLY;");
if((result&O_ACCMODE)==O_WRONLY)printf("\thas O_WRONLY;");
if((result&O_ACCMODE)==O_RDWR)printf("\tO_RDWR;");
printf("\n");
}
}
void test_set_fl(int fd,int flag)
{
printf("\tset_fl on fd(%d) of flag(%d):",fd,flag);
int result;
result=fcntl(fd,F_SETFL,flag);
print_error(fd,"F_SETFL",result);
if(result!=-1)
printf("set_fl ok !\n");
}
void test_get_own(int fd)
{
printf("\tget_own on fd(%d):",fd);
int result;
result=fcntl(fd,F_GETOWN);
print_error(fd,"F_GETOWN",result);
if(result!=-1)
printf("return:%d !\n",result);
}
void test_set_own(int fd,int pid)
{
printf("\tset_own on fd(%d) of pid(%d):",fd,pid);
int result;
result=fcntl(fd,F_SETOWN,pid);
print_error(fd,"F_SETOWN",result);
if(result!=-1)
printf("set_own ok !\n");
}
int main(int argc, char *argv[])
{
int fd;
fd=openat(AT_FDCWD,"test.txt",O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR);
printf("Test dup:\n");
test_get_fd(test_dup_fd(fd,10));
test_get_fd(test_dup_fd(fd,0));
test_get_fd(test_dup_exec_fd(fd,10));
test_get_fd(test_dup_exec_fd(fd,0));
printf("Test set_get_fd:\n");
test_get_fd(fd);
test_set_fd(fd,~FD_CLOEXEC);
test_get_fd(fd);
test_set_fd(fd,FD_CLOEXEC);
test_get_fd(fd);
printf("Test set_get_fl:\n");
test_get_fl(fd);
test_set_fl(fd,O_RDWR);
test_get_fl(fd);
test_set_fl(fd,O_RDONLY|O_NONBLOCK);
test_get_fl(fd);
printf("Test set_get own:\n");
test_get_own(fd);
test_set_fl(fd,1);
test_get_own(fd);
return 0;
}
注意:
Linux 下,不支持文件狀態標志:F_EXEC與
, F_SEARCH
(result&O_ACCMODE)==O_RDONLY
表達式中, &
優先級較低 F_SETFL
命令:當文件讀打開時,你無法將文件狀態標志修改為O_WRONLY
、O_WRWR
這兩種中任何一個。你只能修改:O_APPEND
、O_NONBLOCK
、O_SYNC
、O_DSYNC
、O_RSYNC
、F_ASYNC
、O_ASYNC
等標志
/dev/fd
目錄:該目錄下是名為0、1、2
等的文件。打開文件/dev/fd/n
等效於復制描述符(假定描述符n
是打開的)
fd=open("/dev/fd/0",mod)
:fd
和文件描述符0
共享同一個文件表項。 大多數系統忽略mod
參數 在 Linux 操作系統上, /dev/fd/0
是個例外,它是個底層物理文件的符號鏈接。因此在它上面調用creat
會導致底層文件被截斷