1.文件描述符
所有執(zhí)行I/O操作的系統(tǒng)調(diào)用都以文件描述符(一個(gè)非負(fù)整數(shù))來指代打開的文件。文件描述符用以表示所有類型的已打開文件,包括管道、FIFO、socket、終端、設(shè)備、普通文件。它是一個(gè)索引值,指向內(nèi)核為每一個(gè)進(jìn)程所維護(hù)的該進(jìn)程打開文件的記錄表。當(dāng)程序打開一個(gè)現(xiàn)有文件或者創(chuàng)建一個(gè)新文件時(shí),內(nèi)核向進(jìn)程返回一個(gè)文件描述符。每個(gè)進(jìn)程,文件描述符都自成一套。
標(biāo)準(zhǔn)流(標(biāo)準(zhǔn)文件描述符)
3中標(biāo)準(zhǔn)的文件描述符:
當(dāng)linux啟動(dòng)后,會(huì)自動(dòng)打開三個(gè)文件,就是標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤。標(biāo)準(zhǔn)輸入流默認(rèn)是鍵盤,標(biāo)準(zhǔn)輸出流默認(rèn)是終端,向錯(cuò)誤流寫數(shù)據(jù),終端的默認(rèn)做法是打印出錯(cuò)誤內(nèi)容,當(dāng)然這些流可以更改的。
-
fprintf(stdout, "input someting") <=> printf("input someting")- 向標(biāo)準(zhǔn)輸出流(終端程序)輸出一個(gè)字符串
-
fscanf(stdin, "%d",&a) <=> scanf("%d", &a)- 向標(biāo)準(zhǔn)輸入流(鍵盤)讀入一個(gè)數(shù)據(jù)
-
fprintf(stderr, "a error occur")- 向標(biāo)準(zhǔn)錯(cuò)誤流寫入一個(gè)錯(cuò)誤信息
重定向標(biāo)準(zhǔn)流
-
./demo.out 1>>a.txt輸出流重定向- 將1代表的標(biāo)準(zhǔn)輸出流重定向(>>)到a.txt文件
-
./demo.out 1>>a.txt等價(jià)于./demo.out >>a.txt -
./demo.out >>a.txt輸出流中的內(nèi)容是追加的,追加到結(jié)尾 -
./demo.out >a.txt輸出流中的內(nèi)容是覆蓋的,再次寫入會(huì)覆蓋之前的內(nèi)容
-
./demo.out <a.txt輸入流重定向
2.I/O模型
I/O的4個(gè)主要系統(tǒng)調(diào)用:
-
fd = open(pathname,flags,mode)打開或創(chuàng)建一個(gè)新文件- flags標(biāo)志
- image
- image
- 位掩碼參數(shù)mode指定了新創(chuàng)建文件的權(quán)限,若open()并未指定O_CREAT標(biāo)志,則忽略該參數(shù)
- S_IRUSER
- S_IWUSER
- 返回文件描述符值
- SUSv3規(guī)定,如果open()成功,必須保證其返回值為進(jìn)程未用文件描述符中數(shù)值最小者,如果文件描述符0未使用,那么open一定會(huì)使用此文件描述符打開文件。
- 錯(cuò)誤處理
- open()返回-1,錯(cuò)誤號(hào)errno標(biāo)識(shí)錯(cuò)誤原因
- EACCES
- EISDIR
- EMFILE
- ENFILE
- ENOENT
- EROFS
- ETXTBSY
- flags標(biāo)志
-
numread = read(fd,buffer,count)讀取fd所指代的文件中之多count字節(jié)的數(shù)據(jù),并存儲(chǔ)到buffer中- count參數(shù)指定最多能讀取的字節(jié)數(shù)
- buffer參數(shù)提供用來存放輸入數(shù)據(jù)的內(nèi)存緩存地址
- 返回
- 遇到文件結(jié)束(EOF)則返回0
- 出錯(cuò)返回 -1
- 正確返回存放讀取的字節(jié)數(shù)
numwritten = write(fd,buffer,count)-
status = close(fd)- 文件描述符屬于有限資源,因此文件描述符關(guān)閉失敗可能會(huì)導(dǎo)致一個(gè)進(jìn)程將文件描述符資源消耗殆盡。
3.改變文件偏移量:lseek()
off_t lseek(int fd, off_t offset, int whence)
- offset參數(shù)指定了一個(gè)以字節(jié)為單位的數(shù)值
- whence參數(shù)則表明贏參照哪個(gè)基點(diǎn)來解釋offset參數(shù),應(yīng)為下列其中之一:
- SEEK_SET:文件頭部開始
- SEEK_CUR:當(dāng)前文件偏移量處
- SEEK_END:文件結(jié)尾
- image
4.通用I/O模型以外的操作:ioctl()、fcntl()
ioctl()
ioctl()系統(tǒng)調(diào)用又為執(zhí)行文件和設(shè)備操作提供了一種多用途機(jī)制。
-
int ioctl(int fd, int request,...);- request指定了將在fd上執(zhí)行的控制操作
- 第三個(gè)參數(shù)...(argp)可以是任意數(shù)據(jù)類型,根據(jù)request的參數(shù)值來確定argp所期望的類型。通常情況,argp指向整數(shù)或結(jié)構(gòu)的指針
fcntl()
fcntl()系統(tǒng)調(diào)用對(duì)一個(gè)打開的文件描述符執(zhí)行一些列控制操作
-
int fcntl(intn fd, int cmd, ...)- cmd參數(shù)所支持的操作范圍很廣
5.原子操作和競(jìng)爭(zhēng)條件
原子操作:將某一系統(tǒng)調(diào)用所要完成的各個(gè)動(dòng)作作為不可中斷的操作,一次性加以執(zhí)行,期間不會(huì)為其他進(jìn)程或線程所中斷。所有的系統(tǒng)調(diào)用都是以原子操作方式執(zhí)行的。
舉例:當(dāng)同時(shí)制定O_EXCL與O_CREAT作為open()標(biāo)志位時(shí),如果要打開已經(jīng)存在的文件,就會(huì)返回一個(gè)錯(cuò)誤,這提供了一種機(jī)制,對(duì)文件是否存在的檢查和創(chuàng)建文件屬于同一原子操作。區(qū)別于先檢查文件再創(chuàng)建可能會(huì)造成其他進(jìn)程在這個(gè)過程中搶占資源。
O_EXCL確保調(diào)用者就是文件的創(chuàng)建者。
O_APPEND標(biāo)志,確保多個(gè)進(jìn)程在對(duì)同一文件追加數(shù)據(jù)時(shí)不會(huì)覆蓋彼此的輸出。
6.打開文件的狀態(tài)標(biāo)志
獲取訪問模式和狀態(tài)標(biāo)志
fcntl()的用途之一是針對(duì)一個(gè)打開的文件,獲取或修改其訪問模式和狀態(tài)標(biāo)志(這些值是通過open()調(diào)用的flag參數(shù)設(shè)置的),應(yīng)將fcntl()的cmd參數(shù)設(shè)置為F_GETTFL,并且獲取的標(biāo)志中總是包含O_LARGEFILE標(biāo)志
flags = fcntl(fd, F_GETFL);
要判斷是否包含某一標(biāo)志位,只需要將flags于其相&即可。如下可以判斷文件是否以同步方式打開:
if (flags & O_SYNC)
判定文件的訪問模式稍微復(fù)雜一點(diǎn),因?yàn)镺_RDONLY(0) O_WRONLY(1) O_RDWR(2)這三個(gè)常量并不與打開文件狀態(tài)標(biāo)志中的單個(gè)比特位對(duì)應(yīng),需使用掩碼O_ACCMODE與flag相與
accessMode = flags & O_ACCMODE
if (accessMode == O_WRONLY) {...}
修改訪問模式和狀態(tài)標(biāo)志
使用fcntl()的F_SETFL來修改,允許更改的標(biāo)志有:
- O_APPEND
- O_NONBLOCK
- O_NOATIME
- A_ASYNC
- O_DIRECT
適用的場(chǎng)景:
- 文件不是由調(diào)用程序打開的,所以無法使用open來控制這些標(biāo)志(文件是3個(gè)標(biāo)準(zhǔn)描述符,這些描述符在程序啟動(dòng)之前就被打開)
- 文件描述符獲取是通過open之外的系統(tǒng)調(diào)用,比如pipe()、socket()
修改標(biāo)志位代碼如下:
flags = fcntl(fd, F_GETFL)
flags |= O_APPEND
if(fcntl(fd, F_SETFL, flags) == -1) { errExit()}
7.文件描述符和打開文件之間的關(guān)系
文件描述符和打開的文件不一定是一一對(duì)應(yīng)的關(guān)系,多個(gè)文件描述符可以指向同一打開文件。這些文件描述符可在相同或不同的進(jìn)程中打開。
內(nèi)核維護(hù)的3個(gè)數(shù)據(jù)結(jié)構(gòu):
- 進(jìn)程級(jí)的文件描述符表
- 系統(tǒng)級(jí)的打開文件表
- 文件系統(tǒng)的i-node表
針對(duì)每個(gè)進(jìn)程,內(nèi)核為其維護(hù)打開的文件描述符表,每一條記錄的相關(guān)信息:
- 控制文件描述符操作的一組標(biāo)志
- 對(duì)打開文件句柄的引用
內(nèi)核對(duì)所有打開的文件維護(hù)一個(gè)系統(tǒng)級(jí)的描述表格(打開文件表),并將表中各條目稱為打開文件句柄,一個(gè)打開文件句柄存儲(chǔ)了與一個(gè)打開文件相關(guān)的全部信息:
- 當(dāng)前文件偏移量
- 打開文件時(shí)所使用的狀態(tài)標(biāo)識(shí)(flags參數(shù))
- 文件訪問模式(只讀只寫等)
- 與信號(hào)驅(qū)動(dòng)I/O相關(guān)的設(shè)置
- 對(duì)該文件i-node對(duì)象的引用
文件系統(tǒng)會(huì)為駐留其上的所有文件建立一個(gè)i-node表:
- 文件類型(例如,普通文件、套接字、FIFO)和訪問權(quán)限
- 一個(gè)指針,指向該文件所持有的鎖的列表
- 文件的各種屬性,包括文件大小以及與不同類型操作相關(guān)的時(shí)間戳
文件描述符、打開的文件句柄、i-node的關(guān)系:
總結(jié)以下要點(diǎn):
- 不同文件描述符(1和2)可以指向同一打開文件句柄,可能是通過調(diào)用dup() dup2()或fcntl()形成的
- 不同進(jìn)程文件描述符可以指向同一打開文件句柄,可能調(diào)用fork()出現(xiàn)
- 不同的文件句柄指向同一i-node表?xiàng)l目,換言之,指向同一文件,可能因?yàn)槊總€(gè)進(jìn)程各自對(duì)同一文件發(fā)起了open調(diào)用
- 兩個(gè)不同的文件描述符,若指向同一打開文件句柄,將共享同一文件偏移量
- 文件描述符標(biāo)志(close_on_exec標(biāo)志)為進(jìn)程和文件描述符所私有
8.復(fù)制文件描述符
int dup(int oldfd)
dup()調(diào)用復(fù)制一個(gè)打開的文件描述符oldfd,并返回一個(gè)新描述符,二者都指向同一打開文件句柄。系統(tǒng)會(huì)保證新描述一定是編號(hào)值最低的未用文件描述符
int dup2(int oldfd, int newfd)
dup2()系統(tǒng)調(diào)用會(huì)為oldfd參數(shù)所指定的文件描述符創(chuàng)建副本,其編號(hào)由newfd參數(shù)指定,如果newfd已經(jīng)打開,那么dup2會(huì)將其先關(guān)閉
newfd = fcntl(oldfd, FU_DUPFD, startfd)
該調(diào)用為oldfd創(chuàng)建一個(gè)副本,且將使用大于等于startfd的最小未使用值作為描述符編號(hào)。
文件描述符的正、副本之間共享同一打開文件句柄所含的文件偏移量和狀態(tài)標(biāo)志,新的文件描述符有其自己一套文件描述符標(biāo)志,且其close-onexec標(biāo)志(FD_CLOEXEC)總是處于關(guān)閉
int dup3(int oldfd, int newfd, int flags)
dup3與dup2相同,只是增加了一個(gè)附加參數(shù)flag
9.在文件特定偏移量出的I/O:pread()和pwrite()
ssize_t pread(int fd, void *buf, size_t count, off_t offset)
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset)
相比于read()和write(),會(huì)直接設(shè)置offset參數(shù),是一個(gè)原子操作,且性能更好
10.分散輸入和集中輸出:readv()和writev()
readv()和writev()系統(tǒng)調(diào)用分別實(shí)現(xiàn)了分散輸入和集中輸出的功能
ssize_t readv(int fd, const struct iovec *iov, int iovcnt)
ssize_t writev(int fd, const struct iovec *iov, int iovcnt)
這些系統(tǒng)調(diào)用并非只對(duì)單個(gè)緩沖區(qū)進(jìn)行讀寫操作,而是一次即可傳輸多個(gè)緩存區(qū)的數(shù)據(jù)。數(shù)組iov定義了一組用來傳輸數(shù)據(jù)的緩沖區(qū)。iovcnt指定了iov的成員個(gè)數(shù),iov中的數(shù)據(jù)結(jié)構(gòu):
struct iovec {
void *iov_base;
size_t iov_len;
}
下圖展示關(guān)系:
分散輸入
從文件描述符fd所指代的文件中讀取一片連續(xù)的字節(jié),然后將其散置于iov指定的緩沖區(qū)中,這一散置動(dòng)作從iov[0]開始依次填滿每個(gè)緩沖區(qū)。是原子性操作。
集中輸出
將iov所指定的緩沖區(qū)中的數(shù)據(jù)拼接起來,然后寫入fd中。
在指定offset處分散輸入和集中輸出
preadv()、pwirtev()
11.截?cái)辔募簍runcate()和ftruncate()
truncate()和ftruncate()系統(tǒng)調(diào)用將文件大小設(shè)置為length指定長(zhǎng)度
int truncate(const char *pathname, off_t length)
int ftruncate(int fd, off_t length)
若長(zhǎng)度大于length則丟棄超出部分,若小于length,則在文件尾追加一系列字節(jié)或一個(gè)文件空洞。
12.非阻塞I/O
打開文件時(shí)指定O_NONBLOCK標(biāo)志的作用:
- 若open()未能立即打開文件,則返回錯(cuò)誤,而非陷入阻塞
- 調(diào)用open()成功后,后續(xù)I/O操作也是非阻塞的
由于管道、FIFO、套接字、設(shè)備都支持非阻塞模式,因無法通過open()設(shè)置標(biāo)志,只能通過fcntl()的F_SETFL命令來修改。
由于內(nèi)核緩沖區(qū)保證了普通文件I/O陷入阻塞,故而打開普通文件會(huì)忽略O(shè)_NONBLOCK標(biāo)志
13.大文件I/O
LFS規(guī)范定義了一套擴(kuò)展功能,允許在32位系統(tǒng)中運(yùn)行的進(jìn)程來操作無法以32位表示的大文件。
14./dev/fd 目錄
對(duì)于每個(gè)進(jìn)程,內(nèi)核都提供一個(gè)特殊的虛擬目錄/dev/fd/n,n是與進(jìn)程中的打開文件描述符相對(duì)應(yīng)的編號(hào)。
打開/dev/fd目錄中的一個(gè)文件等同于復(fù)制相應(yīng)的文件描述符:
fd = open("/dev/fd/1", O_WRONLY)
fd = dup(1)
/dev/fd實(shí)際上是一個(gè)符號(hào)鏈接,鏈接到linux所專有的/proc/self/fd目錄
15.創(chuàng)建臨時(shí)文件
mkstemp()、tmpfile()