做網(wǎng)站設(shè)計(jì)參考文獻(xiàn)seo兼職論壇
本篇重點(diǎn):文件描述符,重定向,緩沖區(qū),磁盤結(jié)構(gòu),文件系統(tǒng),inode理解文件的增刪查改,查找一個(gè)文件為什么一定要有路徑,動(dòng)靜態(tài)庫,有的時(shí)候?yàn)槭裁凑也坏綆?#xff0c;動(dòng)態(tài)庫的加載。
目錄
需要大概了解的知識(shí)
IO接口簡單介紹
read,write和open的初次使用
文件fd
0&1&2
重定向
struct file
磁盤的物理結(jié)構(gòu)和邏輯結(jié)構(gòu)
軟硬鏈接
動(dòng)靜態(tài)庫
靜態(tài)庫
動(dòng)態(tài)庫
2.生成動(dòng)態(tài)庫
3.使用動(dòng)態(tài)庫
總結(jié)
需要大概了解的知識(shí)
文件=內(nèi)容+屬性
內(nèi)容和屬性本質(zhì)上都是數(shù)據(jù),都存儲(chǔ)在磁盤中
由馮諾依曼可得,文件想要被打開一定要先被加載到內(nèi)存中
所以文件可被分為被打開文件(內(nèi)存中)和未被打開的文件(磁盤中)
文件在被加載到內(nèi)存之前,肯定會(huì)在內(nèi)存中生成對應(yīng)的描述結(jié)構(gòu)體 struct file,和進(jìn)程類似
對于被打開的文件我們也要進(jìn)行管理,怎么管理?先描述,再組織!
一個(gè)進(jìn)程可能打開多個(gè)文件,所以進(jìn)程和被打開文件的關(guān)系是1:n
本篇先要大概了解進(jìn)程和被打開文件的關(guān)系
IO接口簡單介紹
之前我們學(xué)習(xí)過c語言的文件操作,但那是語言級別的。由之前所學(xué)知道中間肯定需要操作系統(tǒng)參與,所以c語言的文件操作必定封裝了系統(tǒng)調(diào)用
read,write和open的初次使用
read和write?
?open
這里注意一下mode的使用即可
重點(diǎn):為什么open的返回值是int???
文件fd
open的返回值int,被成為文件描述符fd
當(dāng)使用open函數(shù)打開文件再將對應(yīng)的fd打印出來我們可以發(fā)現(xiàn),fd是一個(gè)較小的大概率連續(xù)的整數(shù)
這比較符合日常使用的數(shù)組的下標(biāo)的特征
我們之前說過一個(gè)進(jìn)程要打開一個(gè)磁盤中的文件的時(shí)候,要先將磁盤中的文件加載到內(nèi)存中,在這之前我們要先建立對應(yīng)的struct file 結(jié)構(gòu)體(先將里面默認(rèn)為有屬性和struct file*指針和其他字段),除此之外還要建立一個(gè)數(shù)組
對應(yīng)的結(jié)構(gòu)體也要被管理起來?怎么管理?先描述,再組織!
被打開的文件也算一個(gè)小的進(jìn)程了
在進(jìn)程的對應(yīng)的描述對象task_struct 結(jié)構(gòu)體中,我們可以發(fā)現(xiàn)一個(gè)數(shù)組指針struct file_struct *file指針一個(gè)數(shù)組
這個(gè)數(shù)組默認(rèn)0,1,2下標(biāo)被填充。
進(jìn)程每打開一個(gè)文件,對應(yīng)的struct file就會(huì)被生成,然后指向磁盤中的對應(yīng)部分,該數(shù)組從0開始找到未被填充的部分將其填充,這樣就能通過數(shù)組找到對應(yīng)的結(jié)構(gòu)體struct file,對應(yīng)的下標(biāo)作為返回值返回給fd
這樣進(jìn)程就能通過該數(shù)組對struct file實(shí)現(xiàn)管理
文件描述符fd的本質(zhì):就是數(shù)組的下標(biāo)!!!
那c語言的FILE又是什么鬼?
由上可以推測出FILE是一個(gè)結(jié)構(gòu)體,里面必定封裝了文件描述符fd
操作系統(tǒng)訪問文件只認(rèn)文件描述符
0&1&2
為什么該數(shù)組的0,1,2位置會(huì)被默認(rèn)填充呢?填充的是什么呢?
填充的分別為stdin(鍵盤),stdout(顯示器)和stderr(顯示器),目的是為了方便程序員進(jìn)行讀寫操作
stderr是什么鬼?又如何進(jìn)一步理解一切皆文件呢?
struct file里除了屬性,還有對應(yīng)的讀寫等操作方法集和緩沖區(qū)。
因?yàn)橐呀?jīng)默認(rèn)填充好0,1,2位置了并且有了對應(yīng)的struct file,所以當(dāng)我們敲鍵盤的時(shí)候,本質(zhì)就是向?qū)?yīng)的鍵盤設(shè)備寫入數(shù)據(jù);當(dāng)進(jìn)行打印操作的時(shí)候,本質(zhì)就是向顯示器寫入數(shù)據(jù)
stdout和stderr都是顯示器,這是為什么呢?為了將代碼編輯運(yùn)行的錯(cuò)誤信息和普通的打印信息進(jìn)行分類,更好地為程序員定位錯(cuò)誤位置和寫日志?
文件fd的分配規(guī)則:從小到大進(jìn)行尋找沒有被使用數(shù)據(jù)的位置,然后分配給指定的打開的文件
重定向
重定向可以分為清空重定向和追加重定向
1.清空重定向>
第一次寫的hello world被第二次寫的你好呀覆蓋了,這就是清空重定向
2.追加重定向>>
第一次寫的hello沒有被第二次寫的你覆蓋,而是追加在hello后面,這就是追加重定向。
重定向本質(zhì):上層fd不變,fd指向的內(nèi)容進(jìn)行改變
struct file
struct file里主要包含文件的屬性,文件的操作方法集和文件緩沖區(qū)
磁盤中的文件要加載到內(nèi)存中,其實(shí)就是加載到對應(yīng)的文件緩沖區(qū)中,這一工作由操作系統(tǒng)來做
- 讀數(shù)據(jù):要將數(shù)據(jù)讀到內(nèi)存中——將磁盤中數(shù)據(jù)拷貝到對應(yīng)的struct file 里的文件緩沖區(qū)
- 寫數(shù)據(jù):要將數(shù)據(jù)讀到內(nèi)存中——將磁盤中數(shù)據(jù)拷貝到對應(yīng)的struct file 里的文件緩沖區(qū),然后再進(jìn)行修改
我們在應(yīng)用層進(jìn)行數(shù)據(jù)讀寫的本質(zhì)是什么?將文件緩沖區(qū)中的數(shù)據(jù)進(jìn)行來回拷貝
緩沖區(qū)是什么呢?由上圖可得緩沖區(qū)是struct file結(jié)構(gòu)體里的一段空間,所以緩沖區(qū)本質(zhì)上就是內(nèi)存的一部分空間
為什么要有緩沖區(qū)呢?緩沖區(qū)就等于現(xiàn)實(shí)生活中的菜鳥驛站,我們要把一個(gè)東西送給朋友,有了菜鳥驛站就不用親自開車去送,而是到驛站去寄快遞,到驛站寄了以后,就會(huì)說已經(jīng)把東西送了出去了,此時(shí)東西也不再屬于我們了。所以緩沖區(qū)的最主要作用就是提高效率——提高使用者的效率
因?yàn)橛辛司彌_區(qū)的存在,可以積累一部分?jǐn)?shù)據(jù)然后統(tǒng)一發(fā)送,減少了IO的次數(shù),提高了發(fā)送效率
緩沖區(qū)因?yàn)榭梢詴捍鏀?shù)據(jù),必定有一定的刷新方式
- 無緩沖(立即刷新)
- 行緩沖(行刷新,\n)
- 全緩沖(等緩沖區(qū)滿了再進(jìn)行刷新)
一般策略,特殊情況
- 強(qiáng)制刷新
- 進(jìn)程退出的時(shí)候,一般要刷新緩沖區(qū)
一般對于顯示器文件——行緩沖
對于磁盤上的文件—— 全緩沖(重定向后往往由行緩沖變成全緩沖)
一段樣例
#include<stdio.h>
#include<string.h>
#include<unistd.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\n";write(1,str,strlen(str));fork();return 0;
}
測試結(jié)果
1.當(dāng)我們向顯示器文件進(jìn)行打印的時(shí)候,顯示器文件的緩沖區(qū)是行緩沖,每一個(gè)字符串都有\(zhòng)n,fork之前所有數(shù)據(jù)都已經(jīng)被刷新,有第一次的結(jié)果出現(xiàn)
2.當(dāng)進(jìn)行重定向后,磁盤文件的緩沖區(qū)是全緩沖,遇到\n不再刷新,也意味著緩沖區(qū)變大,這些字符串不足以將緩沖區(qū)寫滿讓其刷新,fork執(zhí)行的時(shí)候,數(shù)據(jù)仍然存在緩沖區(qū)中
3.我們目前談的緩沖區(qū)和OS無關(guān),只和c語言有關(guān)
4.當(dāng)進(jìn)程退出的時(shí)候,一般都要刷新緩沖區(qū),哪怕沒有滿足對應(yīng)的條件
5.fork之后立馬退出,當(dāng)一個(gè)進(jìn)程退出的時(shí)候,刷新緩沖區(qū),此時(shí)就要發(fā)生寫時(shí)拷貝
緩沖區(qū)實(shí)際上分為用戶緩沖區(qū)(C語言提供)和文件緩沖區(qū)
上面所談以及日常使用最多的其實(shí)都是C/C++提供的語言級別的緩沖區(qū),上面所說的刷新規(guī)則也都是C語言緩沖區(qū)的刷新規(guī)則
上文所說的刷新,就是把C緩沖區(qū)的數(shù)據(jù)拷貝到文件緩沖區(qū)
假如我們調(diào)用printf的函數(shù),將要打印的數(shù)據(jù)拷貝到C語言緩沖區(qū),這一函數(shù)就返回了
然后進(jìn)行刷新,調(diào)用write函數(shù),將C緩沖區(qū)的數(shù)據(jù)拷貝到文件緩沖區(qū)
如果直接調(diào)用系統(tǒng)調(diào)用write,它沒有語言緩沖區(qū),所以是直接寫到文件緩沖區(qū)的
至于從文件緩沖區(qū)拷貝到磁盤這一刷新,就要視系統(tǒng)而定了
那么C語言的緩沖區(qū)具體是在哪?當(dāng)調(diào)用C的文件操作的時(shí)候,返回值都是FILE,所以FILE結(jié)構(gòu)體里不僅含有fd,還必定含有緩沖區(qū)
以上就是對打開文件的大概介紹,接下來談?wù)勗诖疟P中存儲(chǔ)的文件
雖然磁盤中的文件沒有被打開,那要不要對其進(jìn)行管理呢?答案是肯定需要的
對于這部分文件,最核心的工作就是對其進(jìn)行快遞定位,怎么做到?路徑
在了解如何管理磁盤文件中,我們要先大概了解一下磁盤的物理結(jié)構(gòu)抽象成邏輯結(jié)構(gòu)
磁盤的物理結(jié)構(gòu)和邏輯結(jié)構(gòu)
眾所周知,磁盤是一種硬件結(jié)構(gòu),那么如何存儲(chǔ)數(shù)據(jù)呢?
下面是磁盤的物理結(jié)構(gòu)示意圖
一個(gè)盤面可以有很多的同心磁道,一圈磁道可以有很多相同的扇區(qū)
扇區(qū)是最小的存儲(chǔ)單元,通常為512字節(jié),即4kb
我們想要向一個(gè)扇區(qū)進(jìn)行寫入,那么要怎么尋址呢?
我們可以將磁盤結(jié)構(gòu)進(jìn)行邏輯抽象,這樣對磁盤文件的管理就變成了數(shù)組的管理
所用的尋址方法是CHS
而文件系統(tǒng)通常以文件塊為基本單位,一個(gè)文件塊通常為4kb
對磁盤中數(shù)據(jù)的管理和上面類似
管理工作當(dāng)然沒有那么簡單,我們肯定要對磁盤進(jìn)行分類,就和C盤D盤一樣
假設(shè)總共磁盤有500GB,對其進(jìn)行分區(qū)分為100,100, 100, 200GB的內(nèi)容
各個(gè)分區(qū)中又由2GB大小的group組成以及一個(gè)啟動(dòng)塊Boot Block
這樣只要我們管理好著2GB空間,就管理好一個(gè)分區(qū),進(jìn)一步管理好了整個(gè)磁盤文件
怎么管理好2GB大小的空間呢?當(dāng)然是要了解這個(gè)塊里的組成
- 超級塊(Super Block):存放文件系統(tǒng)本身的結(jié)構(gòu)信息。記錄的信息主要有:bolck 和 inode的總量,未使用的block和inode的數(shù)量,一個(gè)block和inode的大小,最近一次掛載的時(shí)間,最近一次寫入數(shù)據(jù)的時(shí)間,最近一次檢驗(yàn)磁盤的時(shí)間等其他文件系統(tǒng)的相關(guān)信息。Super Block的信息被破壞,可以說整個(gè)文件系統(tǒng)結(jié)構(gòu)就被破壞了
- GDT,Group Descriptor Table:塊組描述符,描述塊組屬性信息,有興趣的同學(xué)可以在了解一下
- 塊位圖(Block Bitmap):Block Bitmap中記錄著Data Block中哪個(gè)數(shù)據(jù)塊已經(jīng)被占用,哪個(gè)數(shù)據(jù)塊沒有被占用
- inode位圖(inode Bitmap):每個(gè)bit表示一個(gè)inode是否空閑可用。
- i節(jié)點(diǎn)表:存放文件屬性 如 文件大小,所有者,最近修改時(shí)間等
- 數(shù)據(jù)區(qū):存放文件內(nèi)容
文件的屬性和數(shù)據(jù)分開存儲(chǔ),屬性存在iNode中,數(shù)據(jù)存在Data Block中
這里以新建一個(gè)文件的過程為例子,講解每個(gè)區(qū)域的大概作用
新建一個(gè)文件,首先要在磁盤中的一個(gè)分區(qū)中找到一個(gè)group,利用iNode Tables查找struct iNode
?的使用情況,使用了對應(yīng)位置為1,沒使用為0,找到一個(gè)或者多個(gè)未被使用的struct iNode進(jìn)行填充屬性信息
關(guān)于存儲(chǔ)的數(shù)據(jù),通過塊位圖知道哪些文件塊未被使用,將數(shù)據(jù)內(nèi)容填充到對應(yīng)的文件塊,并將文件塊下標(biāo)賦值到iNode里的Blocks數(shù)組,這樣通過iNode就能知道哪些文件塊被使用,哪些文件塊沒被使用
雖然Blocks數(shù)組只有15個(gè)內(nèi)容,但是其中下標(biāo)0-12為直接映射,13是間接映射,14為三級映射,所以能記錄的數(shù)據(jù)塊是很多的
iNode編號在一個(gè)分區(qū)內(nèi)具有唯一性,一個(gè)分區(qū)大概有32000個(gè)iNode,文件識(shí)別系統(tǒng)不認(rèn)文件名,只認(rèn)iNode編號
這里有一個(gè)很關(guān)鍵的問題,一開始你怎么知道文件在哪個(gè)分區(qū)呢?通過路徑的前綴判斷在哪個(gè)分區(qū)
上面說的是文件,那要怎么理解目錄呢?
目錄本質(zhì)上也是一個(gè)文件,存儲(chǔ)的是文件名和iNode編號的映射關(guān)系
當(dāng)一個(gè)文件被建立好后,對應(yīng)的目錄也要進(jìn)行記錄
軟硬鏈接
我們先對一個(gè)目錄中的文件進(jìn)行硬軟鏈接操作,得到以下結(jié)果
可以發(fā)現(xiàn),硬鏈接的iNode編號和原本文件的iNode編號是一樣的,軟鏈接的iNode編號和原本文件的iNode編號是一樣的
可以得出硬鏈接不是獨(dú)立的文件,軟鏈接是獨(dú)立的文件
當(dāng)對一個(gè)文件建立硬鏈接的時(shí)候,iNode里的引用計(jì)數(shù)會(huì)增加,刪除的話會(huì)減少,當(dāng)引用計(jì)數(shù)為0時(shí)磁盤對應(yīng)文件內(nèi)容被釋放,即相應(yīng)的iNode BitMap對應(yīng)的位置改為0
軟鏈接則是一個(gè)獨(dú)立文件,因?yàn)閕Node編號不同,內(nèi)容保存的是指向的目標(biāo)文件的路徑,類似Windows的快捷方式
用戶無法對目錄新建硬鏈接操作,因?yàn)榻⒁粋€(gè)目錄并進(jìn)去后,發(fā)現(xiàn)默認(rèn)有.和..兩個(gè)文件表示當(dāng)前目錄和上級目錄,這里的.就相當(dāng)于已經(jīng)對目錄建立硬鏈接了
動(dòng)靜態(tài)庫
在理解什么叫做動(dòng)靜態(tài)庫之前,應(yīng)該先理解什么叫做庫?
庫在日常生活中隨處可見,我們之前學(xué)習(xí)的數(shù)據(jù)結(jié)構(gòu)unordered_map,unoredered_set就需要包含對應(yīng)的庫文件——庫其實(shí)就是寫來給人提供服務(wù)的,里面包含各種函數(shù)從而實(shí)現(xiàn)了強(qiáng)大的功能
根據(jù)不同用法又將庫分為靜態(tài)庫和動(dòng)態(tài)庫
靜態(tài)庫
靜態(tài)庫簡單來說就是將庫里面的代碼拷貝到可執(zhí)行程序的代碼區(qū),程序運(yùn)行的時(shí)候就不再需要靜態(tài)庫了,因?yàn)槌绦蚶镆呀?jīng)有對應(yīng)的代碼了
由于是直接拷貝庫的內(nèi)容,所以靜態(tài)庫就會(huì)很大
要是一個(gè)庫被重復(fù)使用很多次,還是靜態(tài)庫,那么很多個(gè)可執(zhí)行程序里都有相同的代碼,造成空間的浪費(fèi)
動(dòng)態(tài)庫
動(dòng)態(tài)庫簡單來說就是可執(zhí)行程序拿到動(dòng)態(tài)庫的首地址,在可執(zhí)行程序運(yùn)行的時(shí)候再去拿地址去鏈接庫文件進(jìn)行使用
動(dòng)態(tài)鏈接的程序加載的時(shí)候,不僅程序要被加載,被鏈接的庫也要進(jìn)行加載,被映射進(jìn)虛擬地址空間的共享區(qū)
動(dòng)態(tài)庫的本質(zhì):將重復(fù)代碼放到一個(gè)地方,以節(jié)省空間
動(dòng)態(tài)庫因?yàn)椴皇侵苯釉诳蓤?zhí)行程序里的,所以鏈接的時(shí)候就要去找?guī)?/p>
庫的搜索路徑
- 從左到右搜索-L指定的目錄。
- 由環(huán)境變量指定的目錄 (LIBRARY_PATH)。
- 由系統(tǒng)指定的目錄。
- /usr/lib
- /usr/local/lib
本人理解:感覺動(dòng)態(tài)庫就和指針一樣,需要用的時(shí)候通過它去找,代碼只存在一份,不會(huì)占用空間
? ? ? ? ? ? ? ? ? 靜態(tài)庫是直接拷貝的,占用空間更大
靜態(tài)庫把方法加載到可執(zhí)行程序中,加載幾個(gè)可執(zhí)行程序就在內(nèi)存中有幾個(gè)靜態(tài)庫的內(nèi)容,動(dòng)態(tài)庫第一次調(diào)用加載到內(nèi)存中,以后在繼續(xù)使用這個(gè)庫的可執(zhí)行程序可以直接找到
靜態(tài)庫和動(dòng)態(tài)庫的生成
靜態(tài)庫的生成
寫出mylib.h代碼
#ifndef __MYLIB_H__
#define __MYLIB_H__
#include<stdio.h>
void print();#endif
寫出mylib.c代碼
#include"mylib.h"
void print()
{printf("Hello Linux!\n");
}
寫出main.c代碼
#include"mylib.h"
int main()
{print();return 0;
}
生成靜態(tài)庫第一步,先生成目標(biāo)文件:?gcc -c mylib.c -o mylib.o
第二步,用目標(biāo)文件生成靜態(tài)庫:ar -rc libmylib.a mylib.o
????????ar是gnu歸檔工具,rc表示(replace and create)
可以查看靜態(tài)庫中的目錄列表:ar -tv libmylib.a
t:列出靜態(tài)庫中的文件? ? ? v:verbose 詳細(xì)信息
第三步運(yùn)行main.c得到結(jié)果。?gcc main.c -L. -lmylib
-L 指定庫路徑
-l 指定庫名
測試目標(biāo)文件生成后,靜態(tài)庫刪掉,程序照樣可以運(yùn)行。
生成動(dòng)態(tài)庫
還是上面的代碼為例子
生成動(dòng)態(tài)庫第一步,先生成目標(biāo)文件:?gcc -fPIC -c mylib.c
2.生成動(dòng)態(tài)庫
- shared: 表示生成共享庫格式
- fPIC:產(chǎn)生位置無關(guān)碼(position independent code)
- 庫名規(guī)則:libxxx.so
第二步,用目標(biāo)文件生成動(dòng)態(tài)庫:gcc -shared -o libmylib.so *.o
3.使用動(dòng)態(tài)庫
- ?編譯選項(xiàng)
- I:鏈接動(dòng)態(tài)庫,只要庫名即可(去掉lib以及版本號)。
- L:鏈接庫所在得路徑。
第三步運(yùn)行main.c得到結(jié)果。?gcc main.c -o main -L. -lmylib
可以看看這篇文章:https://blog.csdn.net/qq_44918090/article/details/118185830?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170859497116800180637536%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=170859497116800180637536&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-28-118185830-null-null.nonecase&utm_term=linux&spm=1018.2226.3001.4450
一些補(bǔ)充:
能夠在共享區(qū)任意加載的關(guān)鍵:偏移地址
加載進(jìn)內(nèi)存的時(shí)候,每個(gè)指令都有自己的物理地址和虛擬地址(偏移地址)
虛擬地址(偏移地址)初始化頁表左邊,物理地址初始化頁表右邊
cpu用的全是虛擬地址,所以調(diào)用的時(shí)候才會(huì)有虛擬地址到物理地址的轉(zhuǎn)變
我們見到的FILE *文件流指針,其實(shí)就是_IO_FILE的類型重定義,其中封裝包含了文件描述符,因此一個(gè)文件流指針一定對應(yīng)有一個(gè)文件描述符。
總結(jié)
以上就是今天要講的內(nèi)容,本文僅僅簡單介紹了進(jìn)場基礎(chǔ)IO的使用,而系統(tǒng)編程提供了大量能使我們快速便捷地處理數(shù)據(jù)的函數(shù)和方法,我們務(wù)必掌握。這篇文章寫了挺久,如有錯(cuò)誤歡迎討論,也請大家多多支持!!!