柚子快報激活碼778899分享:運維 Linux:基礎(chǔ)IO
柚子快報激活碼778899分享:運維 Linux:基礎(chǔ)IO
一、文件fd
1.1 共識原理?
1、文件=內(nèi)容+屬性?
2、文件分為打開的文件和沒打開的文件 (如c中的fopen和fclose)
? ? ? ? ? ?可以用以下的例子去理解:快遞(文件) ?有被人(進程)取走的快遞(打開的文件)和沒被取走的快遞(沒打開的文件),被人取走的快遞研究的是人和快遞的關(guān)系(進程和文件的關(guān)系) ,而沒被人取走的快遞,他會被暫時安防在菜鳥驛站(磁盤) ? 他的數(shù)量很多(文件非常多) 所以我們打算去取的時候其實我們是會收到一個取件碼的(查找該文件的信息) ?然后我們根據(jù)這個號碼比方說3-1113 ? 我們會找到這個區(qū)域 然后再去找號碼 ?所以最關(guān)鍵的是快遞如何被按照區(qū)域劃分好(對文件分門別類地存儲) ?這樣才能方便人去?。ǚ奖阄覀兛焖僬业轿募ξ募脑鰟h查改)
3、打開的文件是由進程打開的,所以研究打開的文件本質(zhì)上就是研究進程和文件的關(guān)系??!
4、沒打開的文件有特別多,并且在磁盤上放著,所以研究沒打開的文件關(guān)鍵在于文件如何被分門別類地放置好從而方便用戶快速找到文件并進行相關(guān)的增刪查改工作??!
1.2 被打開的文件
?由于:
(1)文件要被打開,必然要先被加載到內(nèi)存中。
(2)一個進程可能打開多個文件,一個文件也可能被多個進程打開。
——>在操作系統(tǒng)內(nèi)部一定存在大量被打開的文件 ??!
——>操作系統(tǒng)必須按照先描述再組織的方式把被打開的文件管理起來!!
?1.3 回憶C的文件操作接口
1.3.1 文件的打開和關(guān)閉
?問題1:為什么我們默認會新建在當(dāng)前路徑,憑什么???
——>當(dāng)前路徑,其實就是進程的路徑,因為進程在執(zhí)行的過程中,他需要知道自己從哪來,也要知道如果自己產(chǎn)生一些臨時性的文件時應(yīng)該放在哪里,所以他需要一個默認路徑cwd。表明的是他當(dāng)前的工作目錄。
——>因為進程PCB結(jié)構(gòu)體內(nèi)部有一個cwd屬性,如果我們更改了進程的,cwd屬性,就可以將文件新建到別的地方!!
問題2: 先被加載到內(nèi)存的是文件的屬性還是文件的內(nèi)容??
——>當(dāng)你fopen的時候,其實就需要創(chuàng)建一個文件的內(nèi)核數(shù)據(jù)結(jié)構(gòu),里面包含了文件的一些必要屬性,所以是屬性先被加載進去了?。?至于內(nèi)容需不需要被加載,關(guān)鍵看你有沒有通過一些接口來對該文件進程增刪查改!!
?1.3.2 文件的增刪查改
?
?w:在寫入之前,會對文件進行清空處理??! 所以我們可以知道echo重定向方式寫入也會先清空文件,所以底層必然也是w形式?。?/p>
?strlen默認是不加/0,如果我們+1帶上/0,此時會打出一個亂碼,但是這個亂碼是什么并不重要,重要的是我們發(fā)現(xiàn)/0也是一個可以被寫進去的字符!!
為什么???——>因為字符串以/0結(jié)尾,是C語言的規(guī)定,跟文件、跟操作系統(tǒng)沒有任何關(guān)系?。?/p>
a:在文件的結(jié)尾追加寫??!?
1.4 過渡文件系統(tǒng)調(diào)用
? ? ? ?因為文件在硬盤上,所以我們想要訪問文件其實就是訪問硬件,因此幾乎所有的庫想要訪問硬件設(shè)備,就必須封裝系統(tǒng)調(diào)用??!??
? ? ? ??參數(shù)pathname是文件名,參數(shù)flags是打開的模式,而mode是權(quán)限設(shè)置? ? ? 因此第一個open是用來打開一個已經(jīng)存在的文件,而第二個open打開的是新建的文件(因為我們需要給新建的文件設(shè)置權(quán)限?。?/p>
如果用第一個open去新建不存在的文件,會出現(xiàn)文件的權(quán)限錯誤!!所以必須用第二個open!
? ? 關(guān)于文件權(quán)限的傳遞,我們要記得因為有粘滯位umask 所以我們想要設(shè)置的權(quán)限可能并不是我們最終想要的,所以我們要向用umask把該進程的粘滯位變成0!!
???1.4.1 比特位方式的標志位傳遞原理
狀態(tài)的組合方式有很多種,但是為什么操作系統(tǒng)只用一個int類型就可以表明這些情況??
——>以往我們需要很多標記位的時候我們本能想到的是多創(chuàng)建幾個參數(shù)來表示,但當(dāng)位圖出現(xiàn)后,我們想到了可以用int類型的32個bit位來表示各種不同的組合。?
模擬實現(xiàn):
通過位圖的方式一次向一個調(diào)用傳遞多個標記位,這是操作系統(tǒng)傳遞參數(shù)的一種方式!!
——>本質(zhì)上是在外部用 | 的方式組合? 在內(nèi)部的方式用& 的方式檢測??!?
1.4.2 訪問文件的本質(zhì)
問題1:以前我們學(xué)C語言的時候,fopen的返回值是一個FILE*? 那個時候我們知道這個是C庫封裝的一個結(jié)構(gòu)體,但是為什么系統(tǒng)調(diào)用解決ooen的返回值是一個整形呢???
? ? ?因為一個進程可能打開多個文件,那么我們想要快速地找到任意一個文件,如果僅僅是用鏈表的方式組織,確實太慢了?。?/p>
? ? ?所以在PCB結(jié)構(gòu)體內(nèi)部,其實有一個file_struct*指針,該指針指向一個file_struct結(jié)構(gòu)體,該結(jié)構(gòu)體就是操作系統(tǒng)給該進程提供的一個文件描述符表,里面除了一些必要的字段信息,還有一個存放file*指針的指針數(shù)組,這些file*指針分別指向一個個被該進程打開的文件!!
——>所以fd我們稱之為文件描述符,他的本質(zhì)就是文件描述符表的下標,我們可以通過這個下標里存儲的file指針找到我們想操作的被打開的文件??!?
問題2:file結(jié)構(gòu)體里面有什么呢???
——>肯定直接或者間接(間接的意思是可能內(nèi)部還有別的結(jié)構(gòu)對象)包含如下屬性:
(1)在磁盤的什么位置
(2)基本的屬性:權(quán)限、大小、讀寫位置、誰打開的)
(3)文件的內(nèi)核緩沖區(qū)
(4)引用計數(shù)(因為一個文件可能會被多個進程打開,所以當(dāng)一個進程關(guān)閉該文件的時候不代表這個文件的結(jié)構(gòu)體就被釋放了,而是要引用計數(shù)為0時才釋放)
(5)file* next:鏈表節(jié)點結(jié)構(gòu),可以跟其他的文件鏈接成雙鏈表的形式做管理!
……
?1.4.3 理解文件描述符fd
? ? ? ? 一個進程在打開的時候默認會打開3個文件:標準輸入文件(鍵盤)、標準輸出文件(顯示器)、標準錯誤文件(顯示器)…… 他們的fd分別為 0? 1? 2? ?
問題1:有沒有覺得很眼熟??因為我們在學(xué)C語言的時候也知道C程序會默認打開3個流!有什么關(guān)系??
——> 標準輸入流、標準輸出流、標準錯誤流其實并不是C語言的特性!!而是操作系統(tǒng)的特性!!
?問題2:FILE* 是什么??如何理解?
——>FILE* 是一個C庫自己封裝的結(jié)構(gòu)體,由于系統(tǒng)調(diào)用接口用的是fd文件描述符來對指定的文件進行操作,所以我們可以知道FILE結(jié)構(gòu)體內(nèi)部必然封裝著文件描述符fd!
問題3:為什么一定要打開這三個流呢??
——>?因為我們的電腦開機的時候,我們的操作系統(tǒng)就默認檢測到了顯示器、鍵盤這類的設(shè)備,所以進程打開的時候就必然需要有這些,因為我們程序員天然需要通過鍵盤、顯示器來觀察結(jié)果。
1.4.4 文件描述符的使用?
close是在3號手冊的系統(tǒng)調(diào)用接口,可以把傳進去的文件描述符對應(yīng)的文件給關(guān)掉!!
?write是系統(tǒng)調(diào)用接口,用來向指定的文件描述符文件輸出內(nèi)容,其中
buf:表示我們要寫入的內(nèi)容,是void* 也就是說他并不限制你傳什么內(nèi)容
count:期望(最多)寫多少字節(jié)的內(nèi)容
返回值ssize_t:實際寫了多少字節(jié)的內(nèi)容
?
問題1:為什么要在后面+個/0??
——> 因為我們使用的是系統(tǒng)調(diào)用接口,并且參數(shù)buf也是void*指針,所以他并不知道你傳的是什么,只知道默認把一個個字符放在緩沖區(qū)里,所以如果我們想讓他按照C語言字符串的形式去讀取出來,那么就需要加個/0 因為這是C語言的規(guī)則。
1.4.5 三個流的理解?
? ? ? printf是C庫函數(shù) 底層封裝的時候默認向 stdout的1號描述符里寫入,所以如果你把1號給關(guān)了,printf底層調(diào)用write這個函數(shù)會失敗,但是printf本身并不知道,所以他是有返回值的。而fprintf的優(yōu)點是可以指定我們想要輸出的流,所以當(dāng)我們想stderr的2號描述符寫入的時候,恰好也是指向顯示器文件,所以就會被打印出來?。?/p>
?——>側(cè)面可以證明? 文件的file結(jié)構(gòu)體里必然存在引用計數(shù)?。?因為在1號描述符關(guān)閉之后,顯示器文件并沒有被關(guān)閉,所以close底層的操作就是對count計數(shù)--,然后將文件描述符表的指針置空,但是顯示器文件還是打開著的,因為2號描述符還指向顯示器文件!!
?總結(jié):任何一門語言對文件描述符的封裝不一樣,是千變?nèi)f化的,比如在C++中可能還會有繼承和多態(tài)的體系。但是萬變不離其中,在底層都是使用的操作系統(tǒng)提供的系統(tǒng)調(diào)用接口,對fd文件描述符進行封裝。 所以底層理解了,其實任何語言都是學(xué)習(xí)他的應(yīng)用而已??!
1.4.6 內(nèi)核的源碼下載
??
二、重定向再理解
2.1 輸出重定向和追加重定向?
? ? ? ?但是我們把他1號關(guān)了,然后又打開了一個新的文件,?發(fā)現(xiàn)本來應(yīng)該寫到1號的標準輸出文件里! 這說明在close將1號位置置空后,該文件就補上了這個位置!! 說明文件描述符的放置規(guī)則是從0下標開始,尋找最小的沒使用的數(shù)組位置,他的下標就是新的文件描述符??!
? ? ? 這不就有點像 我們之前學(xué)的輸出重定向,所以我們可以發(fā)現(xiàn)其實輸出重定向的本質(zhì)就是將文件的輸出描述符對應(yīng)的指針信息替換成文件的指針信息??!
?問題1:難道我們必須要先把1號文件關(guān)閉了再打開新的文件才能完成重定向嗎??
——>其實本質(zhì)上就是將新文件的指針覆蓋掉原來1號位置的指針就行了,系統(tǒng)提供了一個接口叫dup來幫助我們解決這個問題??!所以輸出重定向和追加重定向底層肯定使用了dup接口!
?他是用oldfd覆蓋掉newfd
問題2:進程替換會影響文件的重定向嗎??
——>進程歷史打開的文件與進行的各種重定向關(guān)系都和未來進行的程序替換無關(guān)??! 他們是兩個模塊,程序替換并不影響文件訪問?。?/p>
?2.2 輸入重定向
?read的使用:
輸入重定向:?
2.3 一個測試
? ? ? ?我們知道printf默認是向1號文件寫入,但如果我們將1號的顯示器文件關(guān)閉后,然后用一個新的文件去替換該位置,于是printf就變成了向文件打印。
? ? ? ?但是我們會發(fā)現(xiàn),我們用fprintf的時候傳參寫入的是stdout,可是該文件卻也寫到了文件里面而不是寫到了顯示器上。
——>這說明了,其實不管是stdin、stdout、stderr都是一個結(jié)構(gòu)體,底層都是封裝文件描述符對應(yīng)的是0、1、2,? ?——>說明上層只知道要向文件描述符位置寫入。即使你通過系統(tǒng)調(diào)用將該位置的文件信息改了,他也并不知情。
2.4 shell的重定向封裝?
? ? ?所以往后可能我們會遇到這樣的指令,所以我們需要在字符串拆分的部分去做一些特殊處理,來判斷究竟是輸出重定向、追加重定向還是輸入重定向!?
?
先對這些情況做一個宏的定義,為了后期在普通命令執(zhí)行的時候做區(qū)分?
?
封裝一個函數(shù)檢查一下字符串是否涉及到重定向的問題(往后檢測看看會不會遇到>或者<),如果是的話判斷是哪種類型。?
在普通命令的執(zhí)行這邊根據(jù)宏進行判斷?
?
2.5 重定向的本質(zhì)寫法(為什么要有stderr)
??????
?1、將程序的運行結(jié)果分別重定向到兩個不同的文件(這樣我們可以把運行結(jié)果放到我們的正常文件里,然后把錯誤的一些信息放到我們的錯誤文件里,方便我們觀察——>這就是為什么要有stderr的原因)。
這才是重定向的正確寫法,只不過我們平常不寫fd的話默認就是將1號文件的內(nèi)容重定向。?
2、將兩個文件的結(jié)果都寫到一個文件
? ? ? ? 這個意思是1號文件的地址變成了all.log 然后2也被寫入到原來1的位置,所以最后其實都被寫到了all.log文件里面
三、如何理解一切皆文件
1、計算機上進行的所有操作,所有任務(wù)都會被系統(tǒng)檢測成進程,所以進程是操作系統(tǒng)幫助用戶完成任務(wù)的主要渠道,幾乎沒有之一?
——>因此我們目前對文件的所有操作其實都依賴于進程操作??!
2、而我們所有的外設(shè)都需要提供相應(yīng)的讀寫方法 (跟文件有點類似)!所以我們會嘗試把外設(shè)也當(dāng)成是文件來處理,在使用的時候在內(nèi)核層面搞一個file結(jié)構(gòu)體, 但是這樣會遇到一個問題就是,并不是所有的外設(shè)都有讀寫方法的??!比如說鍵盤只有寫方法,而顯示器只有讀方法,那我們的file要如何做區(qū)分呢??
——>所以操作系統(tǒng)還給這些文件提供了一個? 方法? 結(jié)構(gòu)體? 里面保存了讀方法和寫方法的指針??
?這倆函數(shù)指針指向的是各個外設(shè)的讀寫方法。(就比如說我當(dāng)前想要打開顯示器,在創(chuàng)建file結(jié)構(gòu)體的時候順便創(chuàng)建了方法結(jié)構(gòu)體,里面的讀寫的函數(shù)指針分別指向顯示器的讀方法和寫方法,所以因為顯示器只有寫方法,讀方法是空,于是在調(diào)用的時候就自然區(qū)分得出來了)
3、其實這就是VFS 虛擬文件系統(tǒng),所以可以理解Linux一切皆文件。
——>其實我們還可以發(fā)現(xiàn)? 這個文件其實就是基類,而外設(shè)就是派生類,然后指針指向什么就調(diào)用什么對象,這就是多態(tài),只不過Linux必須用C語言寫,所以只能用函數(shù)指針來完成這個工作??!
4、理解了Linux的一切皆文件后,懂得了文件操作的底層,即使以后在使用其他語言的文件操作時對接口不熟,但只要給時間查一下,很快就會懂得怎么用了?。。]有太多的恐懼),這就是理論知識所帶給我們的力量和底氣??!?
四、理解為什么會有面向?qū)ο?
? ? ? ? 如果面向?qū)ο笫荂++的專屬,那么他只能算是一種特性,但是當(dāng)你發(fā)現(xiàn)大部分主流語言都是支持面向?qū)ο蟮?,那么這就說明面向?qū)ο蟛皇且粌砷T語言的特性,而是歷史的必然?。?/p>
? ? ?為什么會有人能憑空想出來面向?qū)ο蟮恼Z言呢??
——>因為人們在經(jīng)過大量的工程實驗后,發(fā)現(xiàn)我們總是或多或少要使用一些多態(tài)的特性,比如說寫操作系統(tǒng)的人必然也是有可能開發(fā)語言的人,他在寫的時候就意識到Linux里面很多虛擬化的東西,要不是你必須拿C去寫,我早就發(fā)明出一門面向?qū)ο蟮恼Z言了,直接搞個基類派生類出來就很快了??! ——>因為很多地方需要對軟件做分層,設(shè)置出各種虛擬化的場景(比如剛剛提到的文件虛擬系統(tǒng)就是,只不過Linux必須用C寫,否則肯定用C++寫更方便)?——>封裝、繼承、多態(tài)!
五、緩沖區(qū)深入理解?
5.1 引入一些奇怪的現(xiàn)象
現(xiàn)象1:為什么向文件寫入的時候 write會先被打印出來???
現(xiàn)象2、為什么加了fork之后,向文件寫入時C接口會被調(diào)了兩次??且向文件寫入時write先被打印?
?
?現(xiàn)象3、close1號文件后,為什么就沒有結(jié)果了??
?5.2 緩沖區(qū)不在操作系統(tǒng)內(nèi)部!
? ? ?通過現(xiàn)象3,我們發(fā)現(xiàn)一旦close之后,內(nèi)容就不見了??! 因為現(xiàn)代操作系統(tǒng)不做浪費空間和時間的問題,所以close作為系統(tǒng)調(diào)用接口不可能不在關(guān)閉文件之前刷新緩沖區(qū),所以這說明他根本看不到這個緩沖區(qū)!!
?所以我們的庫函數(shù)接口是先把內(nèi)容放到一個C提供的緩沖區(qū),當(dāng)需要刷新的時候,才會去調(diào)用write函數(shù)進行寫入??!
——>所以close后刷新不出來的原因就是:進程退出后想要刷新的時候,文件描述符被關(guān)了,所以即使調(diào)了write也寫不進去,緩沖區(qū)的數(shù)據(jù)被丟棄了?
(注意:進程退出的時候是會刷新緩沖區(qū)的?。。?/p>
5.3 緩沖區(qū)的刷新策略?
1、無緩沖——>直接刷新——>fflush函數(shù)
2、 行緩沖——>遇到/n刷新——>顯示器文件
3、全緩存——>寫滿才刷新——>普通文件
?問題1:為什么要有這些不同的方案??
——>一般來說寫滿再刷新效率高,因為這樣可以減少調(diào)用系統(tǒng)接口的次數(shù),而顯示器之所以是行刷新,因為顯示器是要給人給的,按行看符合我們的習(xí)慣,而文件采用的就是全緩存策略,因為用戶不需要馬上看到這些信息,所以這樣可以提高效率。 而對于一些特殊情況我們就可以用fllush之前強制刷新出來。? ?——>所以方案是根據(jù)不同的需求來的!
?問題2:解釋現(xiàn)象1
——>當(dāng)我們從向顯示器寫入轉(zhuǎn)變?yōu)橄蚱胀ㄎ募蛴r,此時刷新策略從行刷新轉(zhuǎn)變?yōu)槿⑿?,所以前三個C接口并沒有直接寫入,而是暫時保存在了緩沖區(qū)里面,而write是系統(tǒng)調(diào)用接口優(yōu)先被打印了出來,之后當(dāng)進程退出的時候,緩沖區(qū)的內(nèi)容才被刷新出來。
問題3:解釋現(xiàn)象2
——>跟現(xiàn)象1一樣,前三個C接口的數(shù)據(jù)暫時被存在了緩沖區(qū),而write的調(diào)用先被打了出來。當(dāng)fork的時候,子進程會和父進程指向相同的代碼和數(shù)據(jù),當(dāng)其中一方打算刷新緩沖區(qū)時,其實就相當(dāng)于要修改數(shù)據(jù),操作系統(tǒng)檢測到之后就會發(fā)生寫時拷貝,于是緩沖區(qū)的數(shù)據(jù)被多拷貝了一份,而后該進程退出時就會再刷新一次,因此C接口寫入的數(shù)據(jù)被調(diào)了2次?。?
5.4 為什么要有緩沖區(qū)呢???
? ? ? ?舉個例子,比方說你和你的好朋友相隔千里,而你想要給他送個鍵盤,如果沒有快遞公司和菜鳥驛站(緩沖區(qū))的話,那么你可能得坐車好幾天才能到他那里,但如果你的樓下有菜鳥驛站和快遞公司,那么你只需要下樓付點錢填個單子就行了,接著你可以去忙你自己的事情,當(dāng)旁邊的人問你鍵盤去哪里的時候,你會說已經(jīng)寄給朋友了,其實這個時候你的鍵盤可能還在快遞公司放著。
——>
1、從總體來看東西是你送還是快遞公司送其實都差不多,區(qū)別就是你不需要操太多心。因此緩沖區(qū)方便了用戶?。?/p>
2、快遞公司可以有不同的策略來提高整體的效率,比方說你這個快遞不急,那么我就等快遞車裝滿了再送(全刷新)? ,如果比較急,我就裝滿一個袋子就送(行刷新),如果你特別急,可以通過加錢(fllus強制刷新)來加急。 所以緩沖區(qū)解決了效率問題?
3、 配合格式化!比方說C語言經(jīng)常需要%d這樣的格式化,我們的數(shù)字123 最后被打印的時候也是要轉(zhuǎn)化成字符串的123 才能調(diào)用write寫入,因此我們可以將這個解格式化的工作放在緩沖區(qū)去完成??!
?5.5 用戶緩沖區(qū)在哪?
我們回憶一下exit和_exit 區(qū)別就是exit會先調(diào)用一次fllush把緩沖區(qū)的數(shù)據(jù)刷新出來。
我們會注意到他傳遞的參數(shù)是FILE* 類型?
?——>FILE* 不僅封裝了fd的信息,還維護了對應(yīng)文件的緩沖區(qū)字段和文件信息?。?/p>
——>FILE*是用戶級別的緩沖區(qū)(任何語言都屬于用戶層),當(dāng)我們打開一個文件的時候語言層給我們malloc(FILE),同時也會維護一個專屬于該文件的緩沖區(qū)?。?所以如果有10個文件就會有10個緩沖區(qū)?。?/p>
?5.6 內(nèi)核緩沖區(qū)在哪?
? ? ? ? ?內(nèi)核緩沖區(qū)也是由操作系統(tǒng)的file結(jié)構(gòu)體維護的一段空間,和語言的緩沖區(qū)模式是類似的,作為用戶我們不需要太關(guān)心操作系統(tǒng)什么時候會刷新,我們只需要認為數(shù)據(jù)只要刷新到了內(nèi)核,就必然可以到達硬件,因為現(xiàn)代操作系統(tǒng)不做任何浪費空間和時間的事情。?。。?!?
六、模擬實現(xiàn)C庫文件接口
6.1 Mystdio.h?
//#pragma once
#ifndef __MYSTDIO_H__
#define __MYSTDIO_H__ //類似#pragma once的功能 防止頭文件被重復(fù)包含
#include
#define SIZE 1024 //緩沖區(qū)的大小
#define FLUSH_NOW 1 //立刻刷新
#define FLUSH_LINE 2 //行刷新
#define FLUSH_ALL 4 //全刷新
typedef struct IO_FILE{ //自定義的FILE對象
int fileno;//文件描述符
int flag; //當(dāng)前的刷新策略
//char inbuffer[SIZE];//輸入緩沖區(qū)
//int in_pos;//當(dāng)前用了多少位置
char outbuffer[SIZE]; //輸出緩存區(qū)
int out_pos;//當(dāng)前用了多少位置
}_FILE;
_FILE * _fopen(const char*filename, const char *flag);
int _fwrite(_FILE *fp, const char *s, int len);
void _fclose(_FILE *fp);
#endif
6.2 Mystdio.c
#include "Mystdio.h"
#include
#include
#include
#include
#include
#include
#define FILE_MODE 0666//默認新建文件的權(quán)限
// "w", "a", "r"
_FILE * _fopen(const char*filename, const char *flag)
{
assert(filename); //防止你傳空
assert(flag);//防止你傳空
int f = 0;
int fd = -1;
if(strcmp(flag, "w") == 0) {
f = (O_CREAT|O_WRONLY|O_TRUNC);//新建、只寫、清空
fd = open(filename, f, FILE_MODE);
}
else if(strcmp(flag, "a") == 0) {
f = (O_CREAT|O_WRONLY|O_APPEND);//新建、只寫、追加
fd = open(filename, f, FILE_MODE);
}
else if(strcmp(flag, "r") == 0) {
f = O_RDONLY;//只讀
fd = open(filename, f);
}
else
return NULL;//防止你傳一些亂七八糟的東西
if(fd == -1) return NULL;//說明打開失敗
_FILE *fp = (_FILE*)malloc(sizeof(_FILE));
if(fp == NULL) return NULL;//說明malloc失敗
//初始化
fp->fileno = fd;
//fp->flag = FLUSH_LINE;
fp->flag = FLUSH_ALL;
fp->out_pos = 0;
return fp;
}
// FILE中的緩沖區(qū)的意義是什么????
int _fwrite(_FILE *fp, const char *s, int len)
{
// "abcd\n"
memcpy(&fp->outbuffer[fp->out_pos], s, len); // 沒有做異常處理, 也不考慮局部問題
fp->out_pos += len;
if(fp->flag&FLUSH_NOW)//立刻刷新策略
{
write(fp->fileno, fp->outbuffer, fp->out_pos);
fp->out_pos = 0;
}
else if(fp->flag&FLUSH_LINE) //行刷新策略
{
if(fp->outbuffer[fp->out_pos-1] == '\n'){ // 不考慮其他情況
write(fp->fileno, fp->outbuffer, fp->out_pos);
fp->out_pos = 0;
}
}
else if(fp->flag & FLUSH_ALL) //全刷新策略
{
if(fp->out_pos == SIZE){
write(fp->fileno, fp->outbuffer, fp->out_pos);
fp->out_pos = 0;
}
}
return len;
}
void _fflush(_FILE *fp) //立即刷新
{
if(fp->out_pos > 0){
write(fp->fileno, fp->outbuffer, fp->out_pos);
fp->out_pos = 0;
}
}
void _fclose(_FILE *fp)
{
if(fp == NULL) return;
_fflush(fp);//文件關(guān)閉前都刷新一次
close(fp->fileno);
free(fp);
}
6.3 main.c
#include "Mystdio.h"
#include
#define myfile "test.txt"
int main()
{
_FILE *fp = _fopen(myfile, "a");
if(fp == NULL) return 1;
const char *msg = "hello world\n";
int cnt = 10;
while(cnt){
_fwrite(fp, msg, strlen(msg));
// fflush(fp);
sleep(1);
cnt--;
}
_fclose(fp);
return 0;
}
6.4 反思
1、 不同操作系統(tǒng),比如Linux和windows底層的系統(tǒng)調(diào)用接口肯定是不一樣的,但是我們在語言層面使用的庫函數(shù)是一樣的!說明在C庫底層實現(xiàn)這些接口的時候,不僅寫了Linux系統(tǒng)接口的C函數(shù),也寫了windows系統(tǒng)接口的C函數(shù),然后通過條件編譯的方式在不同的平臺裁掉另外一部分,這樣我們無論在什么平臺都可以使用被封裝好的C接口!!
——>C語言具有跨平臺性、可移植性(對底層做了封裝,雖然不同平臺底層都不一樣,但是上層的接口一模一樣的 ,庫幫我們解決了移植性的問題)
2、通過系統(tǒng)調(diào)用把數(shù)據(jù)拷貝到操作系統(tǒng)是有時間成本的,所以緩沖區(qū)的存在可以讓我們盡量減少和操作系統(tǒng)的交互,就能提高我們使用C庫函數(shù)的效率,因為很多時候只是單純地把數(shù)據(jù)拷貝到自己目前的緩沖區(qū)。
柚子快報激活碼778899分享:運維 Linux:基礎(chǔ)IO
精彩鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。