Linux中的文件IO
1.什么是文件IO
(1)IO就是input/output,輸入/輸出。文件IO的意思就是讀寫文件。
2.linux常用文件IO接口
(1)open、close、write、read、lseek
3.文件操作的一般步驟
(1)一般先open打開文件,得到文件描述符,然后對文件進(jìn)行讀寫(其他)操作,最后close關(guān)閉文件即可
(2)如果文件存在塊設(shè)備中的文件系統(tǒng),稱為靜態(tài)文件。當(dāng)open打開一個文件時,linux內(nèi)核做的操作包括內(nèi)核在進(jìn)程中建立了一個打開文件的數(shù)據(jù)結(jié)構(gòu),記錄下我們打開的這個文件;內(nèi)核在內(nèi)存中申請一段內(nèi)存,并且將靜態(tài)文件的內(nèi)容從塊設(shè)備中讀取到內(nèi)存中特定地址管理存放(叫動態(tài)文件)。
(3)打開文件對這個文件的讀寫操作,都是針對內(nèi)存中這份動態(tài)文件,而不是靜態(tài)文件。此時動態(tài)文件和靜態(tài)文件不同步,需要close關(guān)閉動態(tài)文件時,close內(nèi)部內(nèi)核將內(nèi)存中的動態(tài)文件的內(nèi)容去更新到塊設(shè)備中的靜態(tài)文件。
4.為什么這么設(shè)計讀寫文件
(1)塊設(shè)備本身有讀寫限制,對塊設(shè)備進(jìn)行操作非常不靈活,而內(nèi)存可以按字節(jié)為單位來操作,而且可以隨機操作,靈活。
5.文件描述符
(1)文件描述符實質(zhì)是一個數(shù)字,這個數(shù)組在一個進(jìn)程中表示一個特定的含義,當(dāng)我們open打開一個文件時,操作系統(tǒng)在內(nèi)存中構(gòu)建了一些數(shù)據(jù)結(jié)構(gòu)來表示這個動態(tài)文件,然后返回給應(yīng)用程序一個數(shù)字作為文件描述符,這個數(shù)字就和我們內(nèi)存中維護(hù)這個動態(tài)文件的這些數(shù)據(jù)結(jié)構(gòu)掛鉤綁定上了,以后我們應(yīng)用程序如果要操作這一個動態(tài)文件,只需要用這個文件描述符進(jìn)行區(qū)分
(2)作用域:當(dāng)前進(jìn)程。
(3)文件描述符用來區(qū)分一個程序打開的多個文件。
(4)文件描述符的合法范圍是0或者一個正數(shù),不可能是負(fù)數(shù)。
6.實時查man手冊
(1)man 1xxx 查linux shell命令,man 2 xxx 查API,man 3 xxx 查庫函數(shù)
7.讀取文件內(nèi)容
ssize_t read(int fd, void* buf, size_t count);
d表示要讀取哪個文件,fd一般由前面的open返回得到
buf是應(yīng)用程序自己提供的一段內(nèi)存緩沖區(qū),用來存儲讀出的內(nèi)容
count是我們要讀取的字節(jié)數(shù)
返回值ssize_t類型是linux內(nèi)核用typedef重定義的一個類型(其實就是int),返回值表示成功讀取的字節(jié)數(shù)。如果在調(diào)用read之前到達(dá)文件末尾,則返回0。
8.向文件文件寫入
ssize_t write(int fd, const void *buf, size_t count);
fd:文件描述符
buf:通常一個字符串,需要寫入的字符串
count:每次寫入的字節(jié)數(shù)
返回值:
成功:返回寫入的字節(jié)數(shù)
失敗:返回-1并設(shè)置為errno
9.open函數(shù)flag詳解1
int open(const char *pathname, int flags);
(1)linux中文件有讀寫權(quán)限,當(dāng)我們在open打開文件也需要附帶權(quán)限說明。O_RDONLY(只讀方式打開),O_WRONLY(只寫方式打開),O_RDWR(可讀可寫方式打開)
(2)O_TRUNC屬性去打開文件時,如果這個文件中本來是有內(nèi)容的,則原來的內(nèi)容會被丟棄。
(3)O_APPEND屬性去打開文件時,如果這個文件中本來是有內(nèi)容的,則新寫入的內(nèi)容會接續(xù)到原來內(nèi)容的后面。
(4)默認(rèn)不使用O_APPEND和O_TRUNC屬性時,不讀不寫的時候,原來的文件中的內(nèi)容保持不變。
10.open函數(shù)flag詳解2
(1)O_CREAT,為了打開一個并不存在的文件,當(dāng)前文件不存在則會創(chuàng)建并打開它。
(2)問題:我們本來是想去創(chuàng)建一個新文件的,但是把文件名搞錯了弄成了一個老文件名,結(jié)果老文件就被意外修改了。我們希望的效果是:如果我CREAT要創(chuàng)建的是一個已經(jīng)存在的名字的文件,則給我報錯,不要去創(chuàng)建。
解決方案:O_EXCL標(biāo)志和O_CREAT標(biāo)志來結(jié)合使用,當(dāng)這連個標(biāo)志一起的時候,則沒有文件時創(chuàng)建文件,有這個文件時會報錯提醒我們。
(3)open函數(shù)在使用O_CREAT標(biāo)志去創(chuàng)建文件時,可以使用第三個參數(shù)mode來指定要創(chuàng)建的文件的權(quán)限。mode使用4個數(shù)字來指定權(quán)限的,其中后面三個很重要,對應(yīng)我們要創(chuàng)建的這個文件的權(quán)限標(biāo)志。譬如一般創(chuàng)建一個可讀可寫不可執(zhí)行的文件就用0666
11.O_NONBLOCK
(1)阻塞與非阻塞。如果一個函數(shù)是阻塞式的,則我們調(diào)用這個函數(shù)時當(dāng)前進(jìn)程有可能被卡住(阻塞住,實質(zhì)是這個函數(shù)內(nèi)部要完成的事情條件不具備,當(dāng)前沒法做,要等待條件成熟),函數(shù)被阻塞住了就不能立刻返回;如果一個函數(shù)是非阻塞式的那么我們調(diào)用這個函數(shù)后一定會立即返回,但是函數(shù)有沒有完成任務(wù)不一定。
(2)阻塞和非阻塞是兩種不同的設(shè)計思路,并沒有好壞。總的來說,阻塞式的結(jié)果有保障但是時間沒保障;非阻塞式的時間有保障但是結(jié)果沒保障。
(3)打開一個文件默認(rèn)是阻塞式,如果希望是非阻塞式打開文件,則flag中要加O_NONBLOCK
(4)只用于設(shè)備文件,而不用于普通文件。
12.O_SYNC
(1)write阻塞等待底層完成寫入才返回到應(yīng)用層。
(2)無O_SYNC時write只是將內(nèi)容寫入底層緩沖區(qū)即可返回,然后底層(操作系統(tǒng)中負(fù)責(zé)實現(xiàn)open、write這些操作的那些代碼,也包含OS中讀寫硬盤等底層硬件的代碼)在合適的時候會將buf中的內(nèi)容一次性的同步到硬盤中。這種設(shè)計是為了提升硬件操作的性能和銷量,提升硬件壽命;但是有時候我們希望硬件不用等待,直接將我們的內(nèi)容寫入硬盤中,這時候就可以用O_SYNC標(biāo)志。
12.exit、_exit、_Exit退出進(jìn)程
我們?nèi)绾瓮顺龀绦?#xff1f;
第一種;在main用return,一般原則是程序正常終止return 0,如果程序異常終止則return -1。
第一種:正式終止進(jìn)程(程序)應(yīng)該使用exit或者_(dá)exit或者_(dá)Exit之一。
從圖中可以看出,_exit 函數(shù)的作用是:直接使進(jìn)程停止運行,清除其使用的內(nèi)存空間,并清除其在內(nèi)核的各種數(shù)據(jù)結(jié)構(gòu);exit 函數(shù)則在這些基礎(chǔ)上做了一些小動作,在執(zhí)行退出之前還加了若干道工序。exit() 函數(shù)與 _exit() 函數(shù)的最大區(qū)別在于exit()函數(shù)在調(diào)用exit 系統(tǒng)調(diào)用前要檢查文件的打開情況,把文件緩沖區(qū)中的內(nèi)容寫回文件。也就是圖中的“清理I/O緩沖”。
exit: void exit(int status)
_exit: void _exit(int status)
函數(shù)傳入值:status 是一個整型的參數(shù),可以利用這個參數(shù)傳遞進(jìn)程結(jié)束時的狀態(tài)。一般來說,0表示正常結(jié)束;其他的數(shù)值表示出現(xiàn)了錯誤,進(jìn)程非正常結(jié)束。在實際編程時,父進(jìn)程可以利用wait 系統(tǒng)調(diào)用接收子進(jìn)程的返回值,從而針對不同的情況進(jìn)行不同的處理。
傳入的參數(shù)是程序退出時的狀態(tài)碼,0表示正常退出,其他表示非正常退出,一般都用-1或者1
printf(const char *fmt,…)函數(shù)使用的是緩沖I/O方式,該函數(shù)在遇到 “\n” 換行符時自動從緩沖區(qū)中將記錄讀出。
13.文件讀寫的一些細(xì)節(jié)
errno和perror
(1)errno就是error number,意思就是錯誤號碼。linux系統(tǒng)對錯誤做了編號,當(dāng)函數(shù)執(zhí)行錯誤時,函數(shù)會返回一個特定的errno編號來告訴這個函數(shù)哪里錯了。
(2)linux系統(tǒng)本身提供了一個函數(shù)perror(printf error),perror函數(shù)內(nèi)部會讀取errno并且將這個不好認(rèn)的數(shù)字直接給轉(zhuǎn)成對應(yīng)的錯誤信息字符串,然后print打印出來。
14.read和write的count
(1)count和返回值的關(guān)系。count表示想要讀取或?qū)懙淖止?jié)數(shù),返回值表示實際完成讀取或?qū)懙淖止?jié)數(shù)。函數(shù)返回值有可能小于count。
(2)count和阻塞非阻塞結(jié)合起來。
(3)如果要讀取或者寫入一個很龐大的文件,不可能一次把count設(shè)置完,應(yīng)該設(shè)置為一個合適的大小,分開讀取。
15.文件io效率和標(biāo)準(zhǔn)IO
(1)文件IO就指的是我們當(dāng)前在講的open、close、write、read等API函數(shù)構(gòu)成的一套用來讀寫文件的體系,這套體系可以很好的完成文件讀寫,但是效率并不是最高的。
(2)應(yīng)用層C語言庫函數(shù)提供了一些用來做文件讀寫的函數(shù)列表,叫標(biāo)準(zhǔn)IO。標(biāo)準(zhǔn)IO由一系列的C庫函數(shù)構(gòu)成(fopen、fclose、fwrite、fread),這些標(biāo)準(zhǔn)IO函數(shù)其實是由文件IO封裝而來的(fopen內(nèi)部其實調(diào)用的還是open,fwrite內(nèi)部還是通過write來完成文件寫入的)。標(biāo)準(zhǔn)IO加了封裝之后主要是為了在應(yīng)用層添加一個緩沖機制,這樣我們通過fwrite寫入的內(nèi)容不是直接進(jìn)入內(nèi)核中的buf,而是先進(jìn)入應(yīng)用層標(biāo)準(zhǔn)IO庫自己維護(hù)的buf中,然后標(biāo)準(zhǔn)IO庫自己根據(jù)操作系統(tǒng)單次write的最佳count來選擇好的時機來完成write到內(nèi)核中的buf(內(nèi)核中的buf再根據(jù)硬盤的特性來選擇好的實際去最終寫入硬盤中)。
16.lseek詳解
(1)文件指針:當(dāng)我們要對一個文件進(jìn)行讀寫時,一定需要先打開這個文件,動態(tài)文件在內(nèi)存中的形態(tài)就是文件流的形式。
(2)在動態(tài)文件中,我們會通過文件指針來表征這個正在操作的位置。所謂文件指針,就是我們文件管理表這個結(jié)構(gòu)體里面的一個指針。所以文件指針其實是vnode中的一個元素。這個指針表示當(dāng)前我們正在操作文件流的哪個位置。這個指針不能被直接訪問,linux系統(tǒng)用lseek函數(shù)來訪問這個文件指針。
(3)打開一個空文件時,默認(rèn)文件指針指向文件流的開始。
17.lseek計算文件長度
off_t lseek(int fd, off_t offset, int whence);
參數(shù) whence 為下列其中一種:
SEEK_SET 參數(shù)offset 即為新的讀寫位置.
SEEK_CUR 以目前的讀寫位置往后增加offset 個位移量.
SEEK_END 將讀寫位置指向文件尾后再增加offset 個位移量. 當(dāng)whence 值為SEEK_CUR 或
SEEK_END 時, 參數(shù)offet 允許負(fù)值的出現(xiàn).
返回值:當(dāng)調(diào)用成功時則返回目前的讀寫位置, 也就是距離文件開頭多少個字節(jié). 若有錯誤則返回-1, errno 會存放錯誤代碼.
18.用lseek構(gòu)建空洞文件
(1)使用lseek往后跳過一段,再write寫入一段。
ret = lseek(fd, 10, SEEK_SET);
19.多次打開同一文件與O_APPEND
(1)重復(fù)打開同一文件讀取
一個進(jìn)程中兩次打開同一文件,結(jié)果是fd1和fd2分別讀。
原因:使用open兩次打開同一文件,fd1和fd2所對應(yīng)的文件指針是不同的2兩個獨立的指針。文件指針是包含在動態(tài)文件的文件管理表中的,所以linux系統(tǒng)的進(jìn)程中不同的fd對應(yīng)不同的獨立的文件管理表。
(2).重復(fù)打開同一文件寫入
默認(rèn)情況下,一個進(jìn)程兩次打開同一文件,結(jié)果是fd1和fd2分別寫。
(3)加O_APPEND解決覆蓋問題
如果希望接續(xù)寫而不是分別寫,方法是在open時加O_APPEND標(biāo)志即可。
(4)O_APPEND實現(xiàn)原理和其原子操作性說明
1.O_APPEND為什么能夠?qū)⒎謩e寫改為接續(xù)寫?關(guān)鍵的核心的東西是文件指針。分別寫的內(nèi)部原理就是2個fd擁有不同的文件指針,并且彼此只考慮自己的位移。但是O_APPEND標(biāo)志可以讓write和read函數(shù)內(nèi)部多做一件事情,就是移動自己的文件指針的同時也去把別人的文件指針同時移動。(也就是說即使加了O_APPEND,fd1和fd2還是各自擁有一個獨立的文件指針,但是這兩個文件指針關(guān)聯(lián)起來了,一個動了會通知另一個跟著動)
2.O_APPEND對文件指針的影響,對文件的讀寫是原子的。
3.原子操作的含義是:整個操作一旦開始是不會被打斷的,必須直到操作結(jié)束其他代碼才能得以調(diào)度運行,這就叫原子操作。每種操作系統(tǒng)中都有一些機制來實現(xiàn)原子操作,以保證那些需要原子操作的任務(wù)可以運行。
20.文件共享的實現(xiàn)方式
(1)什么是文件共享
文件共享是同一個文件(同一個inode,同一個pathname)被多個獨立的讀寫體(幾乎可以理解為多個文件描述符)去同時(一個打開尚未關(guān)閉的同時另一個去操作)操作。
(2)意義:可以通過文件共享來實現(xiàn)多線程同時操作同一個大文件,以減少文件讀寫時間,提升效率。
21.文件共享的3種實現(xiàn)方式
(1)文件共享的核心是多個文件描述符指向同一個文件。
(2)常見的3種文件共享的情況:第一種是同一個進(jìn)程多次使用open打開同一文件;第二種是在不同進(jìn)程中去分別open打開同一文件(因為兩個fd在不同的進(jìn)程中,所以兩個fd的數(shù)字可以相同也可以不同);第三種是linux系統(tǒng)提供了dup和dup2兩個API來讓進(jìn)程復(fù)制文件描述符。
22.剖析文件描述符
(1)文件描述符的本質(zhì)是一個數(shù)字,這個數(shù)字本質(zhì)上是進(jìn)程表中文件描述符表的一個表項,進(jìn)程通過文件描述符作為index去索引查表得到文件表指針,再間接訪問得到這個文件對應(yīng)的文件表。
(2)文件描述符這個數(shù)字是open系統(tǒng)調(diào)用內(nèi)部由操作系統(tǒng)自動分配的,操作系統(tǒng)分配這個fd時也不是隨意分配,也是遵照一定的規(guī)律的,我們現(xiàn)在就要研究這個規(guī)律。
(3)操作系統(tǒng)規(guī)定,fd從0開始依次增加。fd也是有最大限制的,在linux的早期版本中(0.11)fd最大是20,所以當(dāng)時一個進(jìn)程最多允許打開20個文件。linux中文件描述符表是個數(shù)組(不是鏈表),所以這個文件描述符表其實就是一個數(shù)組,fd是index,文件表指針是value
(4)當(dāng)我們?nèi)pen時,內(nèi)核會從文件描述符表中挑選一個最小的未被使用的數(shù)字給我們返回。也就是說如果之前fd已經(jīng)占滿了0-9,那么我們下次open得到的一定是10.(但是如果上一個fd得到的是9,下一個不一定是10,這是因為可能前面更小的一個fd已經(jīng)被close釋放掉了)
(5)fd中0、1、2已經(jīng)默認(rèn)被系統(tǒng)占用了,因此用戶進(jìn)程得到的最小的fd就是3了。
(6)linux內(nèi)核占用了0、1、2這三個fd是有用的,當(dāng)我們運行一個程序得到一個進(jìn)程時,內(nèi)部就默認(rèn)已經(jīng)打開了3個文件,這三個文件對應(yīng)的fd就是0、1、2。這三個文件分別叫stdin、stdout、stderr。也就是標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯誤。
(7)標(biāo)準(zhǔn)輸入一般對應(yīng)的是鍵盤(可以理解為:0這個fd對應(yīng)的是鍵盤的設(shè)備文件),標(biāo)準(zhǔn)輸出一般是LCD顯示器(可以理解為:1對應(yīng)LCD的設(shè)備文件)
(8)printf函數(shù)其實就是默認(rèn)輸出到標(biāo)準(zhǔn)輸出stdout上了。stdio中還有一個函數(shù)叫fprintf,這個函數(shù)就可以指定輸出到哪個文件描述符中。
參數(shù):
stream – 這是指向 FILE 對象的指針,該 FILE 對象標(biāo)識了流。
format – 這是 C 字符串,包含了要被寫入到流 stream 中的文本。它可以包含嵌入的 format 標(biāo)簽,format 標(biāo)簽可被隨后的附加參數(shù)中指定的值替換,并按需求進(jìn)行格式化。format 標(biāo)簽屬性是 %[flags][width][.precision][length]specifier,
返回值
如果成功,則返回寫入的字符總數(shù),否則返回一個負(fù)數(shù)。
23.文件描述符的復(fù)制(dup和dup2)
24.使用dup進(jìn)行文件描述符復(fù)制1
(1)dup系統(tǒng)調(diào)用對fd進(jìn)行復(fù)制,會返回一個新的文件描述符(譬如原來的fd是3,返回的就是4)
(2)dup系統(tǒng)調(diào)用有一個特點,就是自己不能指定復(fù)制后得到的fd的數(shù)字是多少,而是由操作系統(tǒng)內(nèi)部自動分配的,分配的原則遵守fd分配的原則。
(3)dup返回的fd和原來的oldfd都指向oldfd打開的那個動態(tài)文件,操作這兩個fd實際操作的都是oldfd打開的那個文件。實際上構(gòu)成了文件共享。
(4)dup返回的fd和原來的oldfd同時向一個文件寫入時,結(jié)果是接續(xù)寫
缺陷:dup不能指定分配的新的文件描述符的數(shù)字,dup2系統(tǒng)調(diào)用修復(fù)了這個缺陷。
當(dāng)調(diào)用dup函數(shù)時,內(nèi)核在進(jìn)程中創(chuàng)建一個新的文件描述符,此描述符是當(dāng)前可用文件描述符的最小數(shù)值,這個文件描述符指向oldfd所擁有的文件表項。
練習(xí):通過使用close和dup配合進(jìn)行文件的重定位
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>#define FILENAME "1.txt"int main(void) {int fd1 = -1, fd2 = -1;fd1 = open(FILENAME, O_RDWR | O_CREAT | O_TRUNC, 0644);if (fd1 < 0){perror("open");return -1;}printf("fd1 = %d.\n", fd1);close(1); // 1就是標(biāo)準(zhǔn)輸出stdout// 復(fù)制文件描述符fd2 = dup(fd1); // fd2一定等于1,因為前面剛剛關(guān)閉了1,這句話就把// 1.txt文件和標(biāo)準(zhǔn)輸出就綁定起來了,所以以后輸出到標(biāo)準(zhǔn)輸出的信息就// 可以到1.txt中查看了。printf("fd2 = %d.\n", fd2);printf("this is for test");close(fd1);return -1; }25.使用dup2進(jìn)行文件描述符復(fù)制
(1)dup2和dup的作用是一樣的,但是dup2允許用戶指定新的文件描述符的數(shù)字。
dup2與dup區(qū)別是dup2可以用參數(shù)newfd指定新文件描述符的數(shù)值。若參數(shù)newfd已經(jīng)被程序使用,則系統(tǒng)就會將newfd所指的文件關(guān)閉,若newfd等于oldfd,則返回newfd,而不關(guān)閉newfd所指的文件。dup2所復(fù)制的文件描述符與原來的文件描述符共享各種文件狀態(tài)。共享所有的鎖定,讀寫位置和各項權(quán)限或flags等.
(2)練習(xí):dup2共享文件交叉寫入測試
(1)dup2復(fù)制的文件描述符,和原來的文件描述符雖然數(shù)字不一樣,但是這連個指向同一個打開的文件
(2)交叉寫入的時候,結(jié)果是接續(xù)寫
26.命令行中重定位命令 >
(1)linux中的shell命令執(zhí)行后,打印結(jié)果都是默認(rèn)進(jìn)入stdout,(本質(zhì)上這些命令ls,pwd等都是調(diào)用printf進(jìn)行打印),所以可以在linux的終端shell中直接看到命令執(zhí)行的結(jié)果。
(2)能夠讓ls、pwd,等命令的輸出給重定位到一個文件中,實際上linux終端支持一個重定位的符號>可以完成這個功能。
(3)>的實現(xiàn)原理:利用open+close+dup,open打開一個文件1.txt,然后close關(guān)閉stdout,然后dup將1和2.txt文件關(guān)聯(lián)起來即可。
程序如上面23所示.
27.fcntl函數(shù)介紹
int fcntl(int fd, int cmd, ... /* arg */ );(1)fcntl函數(shù)是一個多功能文件管理的工具箱,接收2個參數(shù)+1個變參。第一個參數(shù)是fd表示要操作哪個文件,第二個參數(shù)是cmd表示要進(jìn)行哪個命令操作。變參是用來傳遞參數(shù)的,要配合cmd來使用。
(2)fcntl的常用cmd:F_DUPFD這個cmd的作用是復(fù)制文件描述符(作用類似于dup和dup2),這個命令的功能是從可用的fd數(shù)字列表中找一個比arg大或者和arg一樣大的數(shù)字作為oldfd的一個復(fù)制的fd,和dup2有點像但是不同。dup2返回的就是我們指定的那個newfd否則就會出錯,但是F_DUPFD命令返回的是>=arg的最小的那一個數(shù)字。
28.標(biāo)準(zhǔn)IO庫介紹
(1)庫函數(shù)比API還有一個優(yōu)勢就是:API在不同的操作系統(tǒng)之間是不能通用的,但是C庫函數(shù)在不同操作系統(tǒng)中幾乎是一樣的。所以C庫函數(shù)具有可移植性而API不具有可移植性。
(2)性能上和易用性上看,C庫函數(shù)一般要好一些。譬如IO,文件IO是不帶緩存的,而標(biāo)準(zhǔn)IO是帶緩存的,因此標(biāo)準(zhǔn)IO比文件IO性能要更高。
(3)簡單的標(biāo)準(zhǔn)IO讀寫文件實例
總結(jié)
以上是生活随笔為你收集整理的Linux中的文件IO的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 毕设中常出现的定性检验与定量检验的方法及
- 下一篇: mac u盘linux 双系统安装教程,