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