前言
什么是緩沖區?
- 緩沖區是內存空間上的一小段內存,我們平常在寫程序的時候,其實是很難感知到緩沖區的存在的,接下來看一段代碼,可以很好地體現緩沖區的存在。
#include<stdio.h> #include<unistd.h> int main() {printf("這是我關于緩沖區的一次小測試");sleep(2);return 0; }
運行后
2秒后
按照我們對于平常代碼執行順序的理解,我們應該先打印printf中的文本信息,在執行sleep函數休眠2秒,但實際并不是這樣,事實恰恰相反。
對
對上述代碼做一次改動,再次運行
#include<stdio.h> #include<unistd.h> int main() {printf("這是我關于緩沖區的一次小測試\n");sleep(2);return 0; }
我們添加在文本信息后添加了換行符"\n",
結果
? 這一次,符合我們心中的預期,先打印文本信息,在進行休眠。
? 這其實就是緩沖區在發揮作用,有人說,緩沖區讓我對代碼執行產生誤解,我摸不清代碼怎么執行了,為什么要存在它?
? 為什么會有緩沖區,緩沖區的作用是什么?這就是今天我們所探究的。
緩沖區
緩沖區是什么?
- 緩沖區是內存空間上的一段內存,大小由操作系統分配。
- 一個暫時存儲數據的區域?
緩沖區存在于哪里?
- 緩沖區存在于內存空間
1.為什么要有緩沖區呢?
? 緩沖區是一個暫時存儲數據的地方,當數據足夠,就會向目標地點進行輸送,我們現實生活中也存在這樣的地方,就是我們日常的快遞站。
? 當你需要將禮物送給你的朋友,而你們相距甚遠的時候,比如一個在江西,一個在黑龍江,你會親自將禮物送到他手上嗎?
?
? 答案顯然是否定的,那樣成本不僅高,還會耗費你大量的時間,這時候,我們都會選擇——寄快遞,讓快遞員替我們將禮物送出,當你將快遞交給快遞站,他們也不會立刻將你的快遞發出,送一個快遞,對他們來說和讓你自己送沒有區別,不過耗費的不是你的時間和成本,快遞站也有他們自己的寄送規則。
快遞站寄送規則
- 立即寄送
- 等到一定數量寄送
- 存滿快遞寄送
以上三種規則,第一種時間快,但成本太大,收益幾乎沒有,甚至倒貼;
第二種,不僅能有收益,還能保障效率;
第三種,犧牲時間換取收益,收益提高了,但用戶等待的時間很長。
快遞站的這三種寄快遞規則,也就相當于我們緩沖區的推送(刷新)數據規則
- 實時刷新
- 行刷新
- 存滿刷新
通過上述,我們理解的緩沖區的作用
- 提高效率,暫時存儲數據,待到數據數量達到一定時,刷新推送數據到磁盤文件當中,避免了多次讀寫帶來的時間損耗,降低效率? ? ? ? ? ? ? ? ? ? ? ? ?
緩沖區的運作過程
? ?當一個文件被進程打開的時候,它就不存在于磁盤之上,而是存在于內存之上,這是因為操作系統會將其屬性和內容均加載到內存上,屬性我們知道,操作系統內核會形成一個struct file類型的結構體來保存屬性。
那文件內容呢?
- ? 被操作系統拷貝到緩沖區當中,內核文件管理不僅會創建文件結構體,還會為文件內容分配一段內存空間,也就是我們的緩沖區,將文件內容放入其中,并為文件結構體添加相關指針,用來管理這一段內存空間。
- ? 當進程對文件內容進行操作的時候,實際并不是對磁盤上的文件進行操作,而是對內存上的文件進行操作,修改的,增加的,或者刪除的內容,都是在對內存上緩沖區里面的內容操作,當操作結束,關閉文件,結束進程,會自動刷新緩沖區,將緩沖區內的內容拷貝到磁盤當中。
? 所以,緩沖區的運作過程
- 文件被打開,文件內容從磁盤上拷貝到緩沖區當中
- 用戶進程對文件內容操作,實際是對內存中緩沖區中的內容操作
- 關閉文件,用戶進程結束,緩沖區刷新,其中的數據被重新拷貝到磁盤文件中。
總結:對文件內容的操作,本質就是文件內容數據的來回拷貝。
緩沖區的刷新規則
通過上面的學習
我們知道緩沖區的刷新的三個規則
- 即時刷新
- 行刷新
- 全滿刷新
在我們開頭的代碼中,為什么加換行符之前不刷新,加了換行符后立刻刷新呢?
除了等待進程退出,自動刷新緩沖區
我們還有兩種方式刷新緩沖區
- 使用換行符號強制刷新
- 使用flush函數強制刷新
什么時候行刷新,什么時候全滿刷新呢?
- 一般對于顯示器文件,我們采用行刷新的策略
- 對于磁盤文件,我們一般全滿刷新。
來看一個樣例
#include<stdio.h> #include<unistd.h> #include<string.h> int main() {fprintf(stdout,"C: hello fprintf\n");printf("C: hello printf\n");fputs("C: hello fputs\n",stdout);const char*str="system call:hello write";write(1,str,strlen(str));fork();return 0; }
當我們向顯示器當中打印的時候
結果:
當我們向文件當中打印的時候
結果:
這是為何?
- 當我們向顯示器中打印信息的時候,為了照顧用戶體驗,顯示器的刷新方式默認為行刷新,并且每個打印語句都有\n,當執行到fork的時候,這時候的緩沖區已經被刷新完了,緩沖區當中已經沒有數據了。
- 當我們重定向到文件中的時候,刷新方式變成了全緩沖,即等到緩沖區數據到達一定體量再刷新數據,切刷新方式為全緩沖的時候,緩沖區也會變大,這時候代碼執行到fork語句的時候,緩沖區內還有數據。
- 子進程和父進程共享代碼和數據,如果執行到fork函數的時候,父進程緩沖區中還有數據,子進程也會一并將其繼承下來。
? 所以,當父子進程都退出的時候,父進程緩沖區的數據被刷新,子進程緩沖區的數據被刷新,子進程緩沖區的數據是繼承父進程的,所以會重復打印文本信息。
C式緩沖區和內核緩沖區
?當我們重定向,將信息輸出到指定文件中的時候
? 我們可以發現,C語言函數接口的語句,每個都打印了兩次,而系統接口函數的文本信息僅僅只打印了一次,這是為什么?
- ? ? ?利用子進程會繼承父進程數據的特點,我們也可以讓子進程繼承緩沖區中的數據,實現兩次打印的效果,但這都是針對C語言函數接口而言的,對于系統函數接口write,它寫入的文本信息其實并不在父進程的緩沖區當中,而是在內核緩沖區,直接和操作系統打交道的緩沖區,而父進程和子進程的緩沖區,則是C語言自己維護的緩沖區。
- ? ? 也就是說,此時共有三個緩沖區,父進程一個C語言緩沖區,子進程一個C語言緩沖區,一個內核緩沖區,父子進程的緩沖區中的數據是一樣的,重復的,內核緩沖區的數據則是系統調用接口write寫入的,獨一份的,當進程退出的時候,父子進程緩沖區數據被刷入內核緩沖區,再和內核緩沖區的數據一同被刷新拷貝到磁盤文件當中。
? 緩沖區的作用就是提高效率,解決各個設備讀寫速度不匹配帶來的低速拖累高速的情況,C式緩沖區存在的目的也是為了減少用戶進程與內核緩沖區的交互,希望數據積攢到一定體量再寫入內核緩沖區,就像內核緩沖區和磁盤文件的存儲關系一樣。
? 如果想直接寫入內核緩沖區,就使用操作系統提供的系統調用接口,它們會直接對內核緩沖區讀寫,如read和write;如果想寫入C式緩沖區,則使用C語言提供的調用接口,如printf和scanf。
總結
- 無論是C式緩沖區還是內核緩沖區,它們的存在目的都是為了避免頻繁讀寫,從而提高整體效率
- 使用C調用接口,讀寫經過C式緩沖區;使用系統調用接口,讀寫跳過C式緩沖區,直接進入內核緩沖區。
磁盤上的文件管理
文件分為被打開、未被打開兩種狀態,被打開的文件加載到內存空間當中,由內核操作系統的文件系統同一管理,那未被打開的文件呢?
- 未被打開的文件存在于磁盤之上,由磁盤的文件系統管理?
接下來,進入磁盤的文件管理學習!
?磁盤物理結構
首先來認識一下,磁盤
磁盤由許多盤片組成,每個盤片由有正反兩個面,每個面分配一個磁頭,也就是說,有多少個盤面,就有多少個磁頭。
俯瞰盤片
- 盤片分成許多空心圓,每個空心圓的邊緣都是一圈磁道。
- 磁道平均分,每一段磁道就是扇區,扇區是磁盤存儲數據的基本單位。
- 越內的扇區越小,但存儲的數據量不變,因此,它的數據密度更大。
注:扇區是磁盤存儲數據的基本單位,大小一般為512個字節。
?CHS方法
- 定位磁道
- 定位盤片
- 定位扇區
磁盤邏輯結構
如圖,將卷成圓形的磁帶抽出來,就是一條長帶。
同樣,我們也可以將磁盤看成卷起來的磁帶,將其扯出來,也是一條長帶。
這是一種線性結構,因此,我們可以將磁盤看成一個數組,一個元素就是一個扇區,對文件的查找,就可以轉換成對數組元素的查找!
磁盤的大小很大,而一個扇區的大小僅僅只有512字節,如果真正按照以扇區作為一個數組元素的方式劃分,那么數組元素的數量將達到恐怖的數字,這個數量也是我們不愿意看到的。
借用現實生活中,我們分省分市的劃分方法,為了更好地管理文件,磁盤管理系統對文件采用分區分組的管理方式。
先將磁盤分成幾個大區,再在一個大區中分組,最后組中存儲文件。
組中又分為幾個塊,這些塊就是磁盤文件管理的核心,我們認識一下
- Data blocks:存儲文件內容的塊區,該塊區中有許多數據塊,數據塊用來存儲對應文件的內容,每個數據塊大小為4KB(8個扇區)
- inode Table:存儲的是struct inode結構體,每個結構體大小為128字節,該結構體中保存的是文件的inode編號,屬性,數據塊指針(指向Data blocks中的數據塊,找到文件內容)
- inode Bitmap:文件位圖,來判斷一個文件是不是存在,如果該文件存在,對應比特位為1,否則為0。
- Block Bitmap:用來判斷對應的數據塊是否被占用,如果被占用,對應比特位為1,否則為0.
- Group Descriptor Table:存儲的是當前組的信息,如屬性,數據塊使用量,文件量,組大小。
- Super Block:存放文件系統本身的結構信息。記錄的信息主要有:大區中block和inode的總量,未使用的block和inode的量,一個block和inode的大小,最近一次掛載的時間,最近一次寫入數據的時間,最近一次檢驗磁盤的時間等其他文件系統的相關信息,Super Block的信息被破壞,該大區的文件系統結構就被破壞了
- 一般一個分區中,有好幾個組都有Super Block,防止分區文件系統結構被破壞后無法被修復。
在組中,我們不止一次見到了inode。那么inode是什么?
- inode是文件編號,一個inode代表一個文件,它是文件的唯一標識。
inode雖然是文件的唯一標識,但我們在平常Linux文件操作中,卻從未見過,一般都是靠文件名來查找,操作文件,這是為什么?
- inode是文件的唯一標識符,這是對內核而言的,內核通過inode對文件進行定位。
- 當文件被創建的時候,內核會為其分配inode,并為其文件名和inode建立映射關系,這個映射關系會被存儲到該文件的目錄文件當中。
- 當用戶層使文件名操作文件,內核操作系統會先在該目錄下找到對應目錄關系,用inode替換文件名,再進行后續操作。
Linux操作系統怎么在磁盤中查找文件?
- 依靠文件路徑查找文件,對于一個文件而言,文件路徑是唯一的。
原理:
- 文件系統管理分區首先要進行掛載才能被使用,掛載點一般是一個目錄,后序將該目錄當成入口訪問某個分區上的文件。
- 查找文件,依靠的是文件路徑,通過目錄進入分區,在通過文件名和inode的映射關系找到文件。
刪除或者修改一個文件的本質是什么?
- 刪除一個文件的本質,就是將其inode Bitmap中的比特位置為0,將其Block?Bitmap中的比特位置0,這樣在邏輯上,該文件就被刪除了,當該inode被重新使用,數據塊內容被覆蓋,才是真正被刪除。這也為恢復文件提供機會,前提是保存了inode。
- 修改一個文件的本質是通過struct inode結構體中的數據塊指針找到對應的數據塊,修改數據塊中的內容。
創建一個文件的過程是怎么樣的?
- 權限檢查:
- 在創建文件之前,內核會檢查當前用戶是否有在目標目錄中創建文件的權限。
- 這通常涉及讀取目錄的權限位和當前用戶的權限。
- 分配inode和數據塊:
- 如果權限檢查通過,文件系統會為新文件分配一個inode,用于存儲文件的屬性(如文件大小、權限、時間戳等)。
- 同時,文件系統還會為新文件分配必要的數據塊,用于存儲文件內容。
- 更新目錄結構:
- 文件系統會在目標目錄中為新文件創建一個目錄項,將文件名與inode關聯起來。
- 這通常涉及更新目錄的數據塊,以包含新文件的條目。
寄語
? ?本次文章介紹了緩沖區和磁盤上的文件管理,希望其中的一些觀點能夠幫助大家學習,緩沖區我個人認為難度較大,難以理解,其中有問題的地方希望大家能夠指出來,在評論區進行討論。