普通文件類型
理解了文件系統(tǒng)的結(jié)構之后,我們來看一下文件的類型。
Linux以文件的形式對計算機中的數(shù)據(jù)和硬件資源進行管理,也就是徹底的一切皆文件,反映在Linux的文件類型上就是:**普通文件、目錄文件(也就是文件夾)、設備文件、鏈接文件、管道文件、套接字文件(數(shù)據(jù)通信的接口)**等等。而這些種類繁多的文件被Linux使用目錄樹進行管理, 所謂的目錄樹就是以根目錄(/)為主,向下呈現(xiàn)分支狀的一種文件結(jié)構。
普通文件
從Linux的角度來說,類似mp4、pdf、html這樣應用層面上的文件類型都屬于普通文件,Linux用戶可以根據(jù)訪問權限對普通文件進行查看、更改和刪除。我們知道,文件的屬性,權限,大小,占用那些數(shù)據(jù)塊是存在inode當中的。所以,這里注意一點,inode 當中并沒有存放文件名,至于為什么,我們接下來看目錄文件。
目錄文件
本質(zhì)上來說,目錄頁是文件,目錄文件inode除了存放一些目錄的權限,等屬性之外,目錄文件的內(nèi)容則是該目錄文件下文件名和其inode編號的一個映射關系。最簡單的保存格式就是列表,就是一項一項地將目錄下的文件信息(如文件名、文件 inode、文件類型等)列在表里。
文件目錄塊:
通常,第一項是「.」,表示當前目錄,第二項是「…」,表示上一級目錄,接下來就是一項一項的文件名和 inode。
如果一個目錄有超級多的文件,我們要想在這個目錄下找文件,按照列表一項一項地找,效率就不高了。
于是,保存目錄的格式改成哈希表,對文件名進行哈希計算,把哈希值保存起來,如果我們要查找一個目錄下面的文件名,可以通過名稱取哈希。如果哈希能夠匹配上,就說明這個文件的信息在相應地塊里面。
Linux 系統(tǒng)的 ext 文件系統(tǒng)就是采用了哈希表,來保存目錄的內(nèi)容,這種方法的優(yōu)點是查找非常迅速,插入和刪除也比較簡單,不過需要一些預備措施來避免哈希沖突。
目錄查詢是通過在磁盤上反復搜索完成,需要不斷地進行 I/O 操作,開銷較大。所以,為了減少 I/O 操作,把當前使用的文件目錄緩存在內(nèi)存,以后要使用該文件時只要在內(nèi)存中操作,從而降低了磁盤操作次數(shù),提高了文件系統(tǒng)的訪問速度。
文件inode
文件操作
文件鏈接
- 硬鏈接
一般情況下,文件名和inode號碼是"一一對應"關系,每個inode號碼對應一個文件名。但是,Unix/Linux系統(tǒng)允許,多個文件名指向同一個inode號碼。
這意味著,可以用不同的文件名訪問同樣的內(nèi)容;對文件內(nèi)容進行修改,會影響到所有文件名;但是,刪除一個文件名,不影響另一個文件名的訪問。這種情況就被稱為"硬鏈接"(hard link)。
其實原理很簡單,我們會在某個目錄下創(chuàng)建一個文件名,這個文件名和硬鏈接的文件inode 相同,并且會在這個inode的記錄中增加鏈接數(shù)量。
我們看到的就是兩個鏈接到同一個inode 的文件其實是一個文件和inode的映射。
ln命令可以創(chuàng)建硬鏈接:
root@CentOS7 lnDemo]# ln source source_ln
[root@CentOS7 lnDemo]# ls -ali
總用量 8
33575032 drwxr-xr-x. 2 root root 37 9月 30 17:35 .
33574977 dr-xr-x---. 4 root root 161 9月 30 17:34 ..
33575033 -rw-r--r--. 2 root root 7 9月 30 17:31 source
33575033 -rw-r--r--. 2 root root 7 9月 30 17:31 source_ln
[root@CentOS7 lnDemo]# stat source
文件:"source"
大小:7 塊:8 IO 塊:4096 普通文件
設備:fd00h/64768d Inode:33575033 硬鏈接:2
權限:(0644/-rw-r--r--) Uid:( 0/ root) Gid:( 0/ root)
環(huán)境:unconfined_u:object_r:admin_home_t:s0
最近訪問:2020-09-30 17:34:54.975711266 +0800
最近更改:2020-09-30 17:31:49.124074625 +0800
最近改動:2020-09-30 17:35:05.665345071 +0800
創(chuàng)建時間:-
[root@CentOS7 lnDemo]# stat source_ln
文件:"source_ln"
大小:7 塊:8 IO 塊:4096 普通文件
設備:fd00h/64768d Inode:33575033 硬鏈接:2
權限:(0644/-rw-r--r--) Uid:( 0/ root) Gid:( 0/ root)
環(huán)境:unconfined_u:object_r:admin_home_t:s0
最近訪問:2020-09-30 17:34:54.975711266 +0800
最近更改:2020-09-30 17:31:49.124074625 +0800
最近改動:2020-09-30 17:35:05.665345071 +0800
創(chuàng)建時間:-
根據(jù)我們上面的研究,我們發(fā)現(xiàn),硬鏈接有以下幾個問題:
- 目錄不允許硬鏈接
如果目錄允許硬鏈接,那么我們完全可以將兩個目錄鏈接起來,那么操作系統(tǒng)則在找文件的時候,就會在兩個目錄跳來跳去,形成死循環(huán)。
[root@CentOS7 ~]# ln lnDemo/ ./lnDemo2
ln: "lnDemo/": 不允許將硬鏈接指向目錄
[root@CentOS7 ~]#
- 不同分區(qū)不允許硬鏈接
由于硬鏈接是在本分區(qū)指向相同的inode,那么就意味著inode的命名空間需要一致,但是不同的分區(qū),inode的編號將會重置,所有不能通過inode映射同一個文件。
- 軟連接
而軟連接則不同,當創(chuàng)建軟連接的時候,linux確實已經(jīng)創(chuàng)建了一個inode 和起對應來的data block,只不過,在data block存放的是字符串,字符串的內(nèi)容則是 鏈接文件的地址。
[root@CentOS7 lnDemo]# ln -s source source_sln
[root@CentOS7 lnDemo]# ls
source source_sln
[root@CentOS7 lnDemo]# ls -alt
總用量 4
drwxr-xr-x. 2 root root 38 9月 30 18:08 .
lrwxrwxrwx. 1 root root 6 9月 30 18:08 source_sln -> source
dr-xr-x---. 5 root root 172 9月 30 17:55 ..
-rw-r--r--. 1 root root 7 9月 30 17:31 source
[root@CentOS7 lnDemo]# ls -ailt
總用量 4
33575032 drwxr-xr-x. 2 root root 38 9月 30 18:08 .
33575034 lrwxrwxrwx. 1 root root 6 9月 30 18:08 source_sln -> source
33574977 dr-xr-x---. 5 root root 172 9月 30 17:55 ..
33575033 -rw-r--r--. 1 root root 7 9月 30 17:31 source
我們發(fā)現(xiàn) source_sln 的文件類型為 l ,且inode和source 不同。大小很小,原因就是我們存放的是地址字符。
【文章福利】小編推薦自己的Linux內(nèi)核技術交流群:【865977150】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!!
文件新建(復制)
- (1).讀取GDT,找到各個(或部分)塊組imap中未使用的inode號,并為待存儲文件分配inode號;
- ((2).在inode table中完善該inode號所在行的記錄;
- ((3).在目錄的data block中添加一條該文件的相關記錄;
- ((4).將數(shù)據(jù)填充到data block中。
注意,填充到data block中的時候會調(diào)用block分配器:一次分配4KB大小的block數(shù)量,當填充完4KB的data block后會繼續(xù)調(diào)用block分配器分配4KB的block,然后循環(huán)直到填充完所有數(shù)據(jù)。也就是說,如果存儲一個100M的文件需要調(diào)用block分配器100*1024/4=25600次。 另一方面,在block分配器分配block時,block分配器并不知道真正有多少block要分配,只是每次需要分配時就分配,在每存儲一個data block前,就去bmap中標記一次該block已使用,它無法實現(xiàn)一次標記多個bmap位。這一點在ext4中進行了優(yōu)化。
- (5)填充完之后,去inode table中更新該文件inode記錄中指向data block的尋址指針。
文件刪除
刪除文件分為普通文件和目錄文件,知道了這兩種類型的文件的刪除原理,就知道了其他類型特殊文件的刪除方法。
對于刪除普通文件:
- (1)找到文件的inode和data block(根據(jù)前一個小節(jié)中的方法尋找);
- (1.5) 如果inode的硬鏈接是數(shù)量不是1 ,則將硬鏈接的數(shù)量-1, 否則執(zhí)行真正的刪除。
- (2)將inode table中該inode記錄中的data block指針刪除;
- (3)在imap中將該文件的inode號標記為未使用;
- (4)在其所在目錄的data block中將該文件名所在的記錄行刪除,刪除了記錄就丟失了指向inode的指針(實際上不是真的刪除,直接刪除的話會在目錄data block的數(shù)據(jù)結(jié)構中產(chǎn)生空洞,所以實際的操作是將待刪除文件的inode號設置為特殊的值0,這樣下次新建文件時就可以重用該行記錄);
- (5)將bmap中data block對應的block號標記為未使用。
對于刪除目錄文件:
- 找到目錄和目錄下所有文件、子目錄、子文件的inode和data block;
- 在imap中將這些inode號標記為未使用;將bmap中將這些文件占用的 block號標記為未使用;
- 在該目錄的父目錄的data block中將該目錄名所在的記錄行刪除。需要注意的是,刪除父目錄data block中的記錄是最后一步,如果該步驟提前,將報目錄非空的錯誤,因為在該目錄中還有文件占用。
文件搜索
當執(zhí)行"cat /var/log/messages"命令在系統(tǒng)內(nèi)部進行了什么樣的步驟呢?
- 找到根文件系統(tǒng)的塊組描述符表所在的blocks,讀取GDT(已在內(nèi)存中)找到inode table的block號。
根文件系統(tǒng)是不需被引用的,因為在操作系統(tǒng)加載到內(nèi)存當中的時候,跟文件系統(tǒng)已經(jīng)存在,其中第inode編號也已經(jīng)注冊到了操作系統(tǒng)內(nèi)核當中。根文件系統(tǒng)的GDT早已經(jīng)在內(nèi)存中了,在系統(tǒng)開機的時候會掛載根文件系統(tǒng),掛載的時候就已經(jīng)將所有的GDT放進內(nèi)存中。
- 在inode table的block中定位到根"/“的inode,找出”/"指向的data block。
- 在"/"的datablock中記錄了var目錄名和var的inode號,找到該inode記錄,inode記錄中存儲了指向var的block指針,所以也就找到了var目錄文件的data block。
- 通過var目錄的inode號,可以尋找到var目錄的inode記錄,但是在尋找的過程中,還需要知道該inode記錄所在的塊組以及所在的inode table,所以需要讀取GDT,同樣,GDT已經(jīng)緩存到了內(nèi)存中。
- 在var的data block中記錄了log目錄名和其inode號,通過該inode號定位到該inode所在的塊組及所在的inode table,并根據(jù)該inode記錄找到log的data block。
- 在log目錄文件的data block中記錄了messages文件名和對應的inode號,通過該inode號定位到該inode所在的塊組及所在的inode table,并根據(jù)該inode記錄找到messages的data block。
- 最后讀取messages對應的datablock。
- 當然,在每次定位到inode記錄后,都會先將inode記錄加載到內(nèi)存中,然后查看權限,如果權限允許,將根據(jù)block指針找到對應的data block。
文件移動
同文件系統(tǒng)下移動文件實際上是修改目標文件所在目錄的data block,向其中添加一行指向inode table中待移動文件的inode指針,如果目標路徑下有同名文件,則會提示是否覆蓋,實際上是覆蓋目錄data block中沖突文件的記錄,由于同名文件的inode記錄指針被覆蓋,所以無法再找到該文件的data block,也就是說該文件被標記為刪除
所以在同文件系統(tǒng)內(nèi)移動文件相當快,僅僅在所在目錄data block中添加或覆蓋了一條記錄而已。也因此,移動文件時,文件的inode號是不會改變的。
對于不同文件系統(tǒng)內(nèi)的移動,相當于先復制再刪除的動作。
文件掛載
Linux 系統(tǒng)下,文件是虛擬文件系統(tǒng),當我們ls / 的時候,linux 會吧所有磁盤,所有分區(qū)下的且掛載在根目錄下的所有目錄列出來。 掛載文件系統(tǒng)到某個目錄下,例如"mount /dev/cdrom /mnt",掛載成功后/mnt目錄中的文件全都暫時不可見了,且掛載后權限和所有者(如果指定允許普通用戶掛載)等的都改變了,知道為什么嗎?
下面就以通過"mount /dev/cdrom /mnt"為例,詳細說明掛載過程中涉及的細節(jié)。
在將文件系統(tǒng)/dev/cdrom(此處暫且認為它是文件系統(tǒng))掛載到掛載點/mnt之前,掛載點/mnt是根文件系統(tǒng)中的一個目錄,"/"的data block中記錄了/mnt的一些信息,其中包括inode號inode_n,而在inode table中,/mnt對應的inode記錄中又存儲了block指針block_n,此時這兩個指針還是普通的指針。
當文件系統(tǒng)/dev/cdrom掛載到/mnt上后,/mnt此時就已經(jīng)成為另一個文件系統(tǒng)的入口了,因此它需要連接兩邊文件系統(tǒng)的inode和data block。
- 在根文件系統(tǒng)的inode table中,為/mnt重新分配一個inode記錄m,該記錄的block指針block_m指向文件系統(tǒng)/dev/cdrom中的data block。
- /mnt分配了新的inode記錄m,那么在"/"目錄的data block中,也需要修改其inode指針為inode_m以指向m記錄。
- 同時,原來inode table中的inode記錄n就被標記為暫時不可用。
block_m指向的是文件系統(tǒng)/dev/cdrom的data block,所以嚴格說起來,除了/mnt的元數(shù)據(jù)信息即inode記錄m還在根文件系統(tǒng)上,/mnt的data block已經(jīng)是在/dev/cdrom中的了。這就是掛載新文件系統(tǒng)后實現(xiàn)的跨文件系統(tǒng),它將掛載點的元數(shù)據(jù)信息和數(shù)據(jù)信息分別存儲在不同的文件系統(tǒng)上。
掛載完成后,將在/proc/self/{mounts,mountstats,mountinfo}這三個文件中寫入掛載記錄和相關的掛載信息,并會將/proc/self/mounts中的信息同步到/etc/mtab文件中,當然,如果掛載時加了-n參數(shù),將不會同步到/etc/mtab。
而卸載文件系統(tǒng),其實質(zhì)是移除臨時新建的inode記錄(當然,在移除前會檢查是否正在使用)及其指針,并將指針指回原來的inode記錄,這樣inode記錄中的block指針也就同時生效而找回對應的data block了。由于卸載只是移除inode記錄,所以使用掛載點和文件系統(tǒng)都可以實現(xiàn)卸載,因為它們是聯(lián)系在一起的。
下面是分析或結(jié)論。
(1).掛載點掛載時的inode記錄是新分配的。
掛載前掛載點/mnt的inode號
[root@server2 tmp]# ll -id /mnt
100663447 drwxr-xr-x. 2 root root 6 Aug 12 2015 /mnt
[root@server2 tmp]# mount /dev/cdrom /mnt
# 掛載后掛載點的inode號
[root@server2 tmp]# ll -id /mnt
1856 dr-xr-xr-x 8 root root 2048 Dec 10 2015 mnt
由此可以驗證,inode號確實是重新分配的。
(2).掛載后,掛載點的內(nèi)容將暫時不可見、不可用,卸載后文件又再次可見、可用。
在掛載前,向掛載點中創(chuàng)建幾個文件
[root@server2 tmp]# touch /mnt/a.txt
[root@server2 tmp]# mkdir /mnt/abcdir
# 掛載
[root@server2 tmp]# mount /dev/cdrom /mnt
掛載后,掛載點中將找不到剛創(chuàng)建的文件
[root@server2 tmp]# ll /mnt
total 636
-r--r--r-- 1 root root 14 Dec 10 2015 CentOS_BuildTag
dr-xr-xr-x 3 root root 2048 Dec 10 2015 EFI
-r--r--r-- 1 root root 215 Dec 10 2015 EULA
-r--r--r-- 1 root root 18009 Dec 10 2015 GPL
dr-xr-xr-x 3 root root 2048 Dec 10 2015 images
dr-xr-xr-x 2 root root 2048 Dec 10 2015 isolinux
dr-xr-xr-x 2 root root 2048 Dec 10 2015 LiveOS
dr-xr-xr-x 2 root root 612352 Dec 10 2015 Packages
dr-xr-xr-x 2 root root 4096 Dec 10 2015 repodata
-r--r--r-- 1 root root 1690 Dec 10 2015 RPM-GPG-KEY-CentOS-7
-r--r--r-- 1 root root 1690 Dec 10 2015 RPM-GPG-KEY-CentOS-Testing-7
-r--r--r-- 1 root root 2883 Dec 10 2015 TRANS.TBL
卸載后,掛載點/mnt中的文件將再次可見
[root@server2 tmp]# umount /mnt
[root@server2 tmp]# ll /mnt
total 0
drwxr-xr-x 2 root root 6 Jun 9 08:18 abcdir
-rw-r--r-- 1 root root 0 Jun 9 08:18 a.txt
之所以會這樣,是因為掛載文件系統(tǒng)后,掛載點原來的inode記錄暫時被標記為不可用,關鍵是沒有指向該inode記錄的inode指針了。在卸載文件系統(tǒng)后,又重新啟用掛載點原來的inode記錄,"/"目錄下的mnt的inode指針又重新指向該inode記錄。
(3).掛載后,掛載點的元數(shù)據(jù)和data block是分別存放在不同文件系統(tǒng)上的。
(4).掛載點即使在掛載后,也還是屬于源文件系統(tǒng)的文件。
文件描述符
先看一段最文件描述符的官方說明
維基百科:文件描述符在形式上是一個非負整數(shù)。實際上,它是一個索引值,指向內(nèi)核為每一個進程所維護的該進程打開文件的記錄表。當程序打開一個現(xiàn)有文件或者創(chuàng)建一個新文件時,內(nèi)核向進程返回一個文件描述符。在程序設計中,一些涉及底層的程序編寫往往會圍繞著文件描述符展開。
- 作用
Linux 系統(tǒng)中,把一切都看做是文件,當進程打開現(xiàn)有文件或創(chuàng)建新文件時,內(nèi)核向進程返回一個文件描述符,文件描述符就是內(nèi)核為了高效管理已被打開的文件所創(chuàng)建的索引,用來指向被打開的文件,所有執(zhí)行I/O操作的系統(tǒng)調(diào)用都會通過文件描述符。
- 概念定義文件描述符 是 用來訪問資源(文件,輸入輸出設備等)的一種抽象指示符。文件描述符 是POSIX(Portable Operating System Interface)規(guī)范的組成部分文件描述符 通常是非負整數(shù),C 語言中使用int類型。
- FD 具體可以指向什么文件/目錄 files/directories輸入輸出源 input/output管道 pipes套接字 sockets其他 Unix 文件類型 other Unix files
- 默認的fds每一個 Unix 進程中,通常會有三個預制的 FD。它們分別是標準輸入 Standard input 標準輸入 用于程序接受數(shù)據(jù)標準輸出 Standard output 標準輸出 用于程序輸出數(shù)據(jù)標準錯誤(輸出) Standard error 標準錯誤 用于程序輸出錯誤或者診斷信息
- 文件描述符的意義 一個 Linux 進程啟動后,會在內(nèi)核空間中創(chuàng)建一個 PCB 控制塊,PCB 內(nèi)部有一個文件描述符表(File descriptor table),記錄著當前進程所有可用的文件描述符,也即當前進程所有打開的文件。
除了文件描述符表,系統(tǒng)還需要維護另外兩張表:
打開文件表(Open file table)
i-node 表(i-node table)
文件描述符表每個進程都有一個,打開文件表和 i-node 表整個系統(tǒng)只有一個,它們?nèi)咧g的關系如下圖所示。
文件描述符的意義
首先,為什么不把文件位置干脆存放在索引節(jié)點中,而要多此一舉,設一個新的數(shù)據(jù)結(jié)構呢?我們知道,Linux中的文件是能夠共享的,假如把文件位置存放在索引節(jié)點中,則如果有兩個或更多個進程同時打開同一個文件時,它們將去訪問同一個索引節(jié)點,于是一個進程的LSEEK操作將影響到另一個進程的讀操作,這顯然是不允許也是不可想象的。
另一個想法是既然進程是通過文件描述符訪問文件的,為什么不用一個與文件描述符數(shù)組相平行的數(shù)組來保存每個打開文件的文件位置?這個想法也是不能實現(xiàn)的,原因就在于在生成一個新進程時,子進程要共享父進程的所有信息,包括文件描述符數(shù)組。
我們知道,一個文件不僅可以被不同的進程分別打開,而且也可以被同一個進程先后多次打開。一個進程如果先后多次打開同一個文件,則每一次打開都要分配一個新的文件描述符,并且指向一個新的file結(jié)構,盡管它們都指向同一個索引節(jié)點,但是,如果一個子進程不和父進程共享同一個file結(jié)構,而是也如上面一樣,分配一個新的file結(jié)構,會出現(xiàn)什么情況了?讓我們來看一個例子:
假設有一個輸出重定位到某文件A的shell script(shell腳本),我們知道,shell是作為一個進程運行的,當它生成第一個子進程時,將以0作為A的文件位置開始輸出,假設輸出了2K的數(shù)據(jù),則現(xiàn)在文件位置為2K。然后,shell繼續(xù)讀取腳本,生成另一個子進程,它要共享shell的file結(jié)構,也就是共享文件位置,所以第二個進程的文件位置是2K,將接著第一個進程輸出內(nèi)容的后面輸出。如果shell不和子進程共享文件位置,則第二個進程就有可能重寫第一個進程的輸出了,這顯然不是希望得到的結(jié)果。
查看文件描述符
lsof(list open files)是一個查看當前系統(tǒng)文件的工具。在linux環(huán)境下,任何事物都以文件的形式存在,通過文件不僅僅可以訪問常規(guī)數(shù)據(jù),還可以訪問網(wǎng)絡連接和硬件。如傳輸控制協(xié)議 (TCP) 和用戶數(shù)據(jù)報協(xié)議 (UDP) 套接字等,系統(tǒng)在后臺都為該應用程序分配了一個文件描述符,該文件描述符提供了大量關于這個應用程序本身的信息。
lsof打開的文件可以是:
- 普通文件
- 目錄
- 網(wǎng)絡文件系統(tǒng)的文件
- 字符或設備文件
- (函數(shù))共享庫
- 管道,命名管道
- 符號鏈接
- 網(wǎng)絡文件(例如:NFS file、網(wǎng)絡socket,unix域名socket)
- 還有其它類型的文件,等等
我們用java 新寫一段代碼:
public static void main(String[] args) throws Exception {
String s ="/tmp/file.test";
FileOutputStream fileOutputStream = new FileOutputStream(new File(s));
System.in.read();
}
運行上述代碼,并用jps找到其對應的pid
利用lsof -i 命令來查看:
...
java 36562 lizhipeng mem REG 253,0 142144 50547 /usr/lib64/libpthread-2.17.so
java 36562 lizhipeng mem REG 253,0 163312 42066 /usr/lib64/ld-2.17.so
java 36562 lizhipeng mem REG 253,0 32768 51151094 /tmp/hsperfdata_lizhipeng/36562
java 36562 lizhipeng 0u CHR 136,4 0t0 7 /dev/pts/4
java 36562 lizhipeng 1u CHR 136,4 0t0 7 /dev/pts/4
java 36562 lizhipeng 2u CHR 136,4 0t0 7 /dev/pts/4
java 36562 lizhipeng 3r REG 253,0 73861866 33613070 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.262.b10-0.el7_8.x86_64/jre/lib/rt.jar
java 36562 lizhipeng 4r REG 253,0 1027597 33613060 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.262.b10-0.el7_8.x86_64/jre/lib/jfr.jar
java 36562 lizhipeng 5w REG 253,0 0 33671497 /tmp/file.test
PCB 進程控制塊
為了描述控制進程的運行,系統(tǒng)中存放進程的管理和控制信息的數(shù)據(jù)結(jié)構稱為進程控制塊(PCB Process Control Block),它是進程實體的一部分,是操作系統(tǒng)中最重要的記錄性數(shù)據(jù)結(jié)構。它是進程管理和控制的最重要的數(shù)據(jù)結(jié)構,每一個進程均有一個PCB,在創(chuàng)建進程時,建立PCB,伴隨進程運行的全過程,直到進程撤消而撤消。 在linux中 PCB 用task_struct 數(shù)據(jù)結(jié)構來表示
PCB
fs_struct
1、與進程相關的文件
首先,文件必須由進程打開,每個進程都有它自己當前的工作目錄和它自己的根目錄。task_struct的fs字段指向進程的fs_struct結(jié)構,files字段指向進程的files_struct結(jié)構。
struct fs_struct {
atomic_t count;
rwlock_t lock;
int umask;
struct dentry * root, * pwd, * altroot;
struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
};
- count:共享這個表的進程個數(shù)
- lock:用于表中字段的讀/寫自旋鎖
- umask:當打開文件設置文件權限時所使用的位掩碼
- root:根目錄的目錄項
- pwd:當前工作目錄的目錄項
files_struct
每個進程用一個 files_struct 結(jié)構來記錄文件描述符的使用情況, 這個 files_struct結(jié)構稱為用戶打開文件表, 它是進程的私有數(shù)據(jù)。 files_struct 結(jié)構在include/linux/sched.h 中定義如下:
struct files_struct {
atomic_t count;
struct fdtable *fdt;
struct fdtable fdtab;
int next_fd;
struct embedded_fd_set close_on_exec_init;
struct embedded_fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT];
};
ulimit
ulimit命令可以查看當前shell下的文件描述符的數(shù)量。
ulimit 用于限制 shell 啟動進程所占用的資源,支持以下各種類型的限制:所創(chuàng)建的內(nèi)核文件的大小、進程數(shù)據(jù)塊的大小、Shell 進程創(chuàng)建文件的大小、內(nèi)存鎖住的大小、常駐內(nèi)存集的大小、打開文件描述符的數(shù)量、分配堆棧的最大大小、CPU 時間、單個用戶的最大線程數(shù)、Shell 進程所能使用的最大虛擬內(nèi)存。同時,它支持硬資源和軟資源的限制。
作為臨時限制,ulimit 可以作用于通過使用其命令登錄的 shell 會話,在會話終止時便結(jié)束限制,并不影響于其他 shell 會話。而對于長期的固定限制,ulimit 命令語句又可以被添加到由登錄 shell 讀取的文件中,作用于特定的 shell 用戶。
語法:
ulimit (選項)
選項:
-a:顯示目前資源限制的設定;
-c :設定core文件的最大值,單位為區(qū)塊;
-d <數(shù)據(jù)節(jié)區(qū)大小>:程序數(shù)據(jù)節(jié)區(qū)的最大值,單位為KB;
-f <文件大小>:shell所能建立的最大文件,單位為區(qū)塊;
-H:設定資源的硬性限制,也就是管理員所設下的限制;
-m <內(nèi)存大小>:指定可使用內(nèi)存的上限,單位為KB;
-n <文件數(shù)目>:指定同一時間最多可開啟的文件數(shù);
-p <緩沖區(qū)大小>:指定管道緩沖區(qū)的大小,單位512字節(jié);
-s <堆疊大小>:指定堆疊的上限,單位為KB;
-S:設定資源的彈性限制;
-t :指定CPU使用時間的上限,單位為秒;
-u <程序數(shù)目>:用戶最多可開啟的程序數(shù)目;
-v <虛擬內(nèi)存大小>:指定可使用的虛擬內(nèi)存上限,單位為KB。
實例:
來看一下具體的用法:
[root@Centos ~]# ulimit -a
core file size (blocks, -c) 0 #core文件的最大值為100 blocks。
data seg size (kbytes, -d) unlimited #進程的數(shù)據(jù)段可以任意大。
scheduling priority (-e) 0
file size (blocks, -f) unlimited #文件可以任意大。
pending signals (-i) 3794 #最多有98304個待處理的信號。
max locked memory (kbytes, -l) 64 #一個任務鎖住的物理內(nèi)存的最大值為32KB。
max memory size (kbytes, -m) unlimited #一個任務的常駐物理內(nèi)存的最大值。
open files (-n) 1024 #一個任務最多可以同時打開1024的文件。
pipe size (512 bytes, -p) 8 #管道的最大空間為4096字節(jié)。
POSIX message queues (bytes, -q) 819200 #POSIX的消息隊列的最大值為819200字節(jié)。
real-time priority (-r) 0
stack size (kbytes, -s) 10240 #進程的棧的最大值為10240字節(jié)。
cpu time (seconds, -t) unlimited #進程使用的CPU時間。
max user processes (-u) 1024 #當前用戶同時打開的進程(包括線程)的最大個數(shù)為98304。
virtual memory (kbytes, -v) unlimited #沒有限制進程的最大地址空間。
file locks (-x) unlimited #所能鎖住的文件的最大個數(shù)沒有限制。
Linux默認的文件打開數(shù)是1024,現(xiàn)在設置打開數(shù)為2048.
[root@Centos ~]# ulimit -n --查看打開數(shù)為1024
1024
[root@Centos ~]# ulimit -n 2048 --設置打開數(shù)為2048
[root@Centos ~]# ulimit -n --再次查看
2048
特殊文件類型
- Linux設備驅(qū)動程序工作原理
系統(tǒng)調(diào)用是操作系統(tǒng)內(nèi)核和應用程序之間的接口,設備驅(qū)動程序是操作系統(tǒng)內(nèi)核和機器硬件之間的接口。設備驅(qū)動程序為應用程序屏蔽了硬件的細節(jié),這樣在應用程序看來,硬件設備只是一個設備文件, 應用程序可以象操作普通文件一樣對硬件設備進行操作。設備驅(qū)動程序是內(nèi)核的一部分,運行在核心態(tài),它完成以下的功能:
1.對設備初始化和釋放.
2.把數(shù)據(jù)從內(nèi)核傳送到硬件和從硬件讀取數(shù)據(jù).
3.讀取應用程序傳送給設備文件的數(shù)據(jù)和回送應用程序請求的數(shù)據(jù).
4.檢測和處理設備出現(xiàn)的錯誤.
在Linux操作系統(tǒng)下有三類主要的設備文件類型:字符設備、塊設備和網(wǎng)絡接口。 字符設備和塊設備的主要區(qū)別是:在對字符設備發(fā)出讀/寫請求時,實際的硬件I/O一般就緊接著發(fā)生了塊設備則不然,它利用一塊系統(tǒng)內(nèi)存作緩沖區(qū),當用戶進程對設備請求能滿足用戶的要求,就返回請求的數(shù)據(jù),如果不能,就調(diào)用請求函數(shù)來進行實際的I/O操作。這也就是進程管理的Page cache的作用,塊設備是主要針對磁盤等慢速設備設計的,以免耗費過多的CPU時間來等待。
換句話說, 當發(fā)生塊設備的IO的時候, 操作系統(tǒng)實際是先寫到Page cache上,而 pageCashe 會有一個映射規(guī)則,映射到某個塊設備的具體地址,在發(fā)生操作以系統(tǒng)的IO的時候,比如說 寫某個文件 當我們點擊保存的時候,實際是寫到了Page cache上, 此時操作系統(tǒng)將當前pageCash標記為臟頁,之后如何將臟頁刷新會磁盤就要看各個操作系統(tǒng)策略了。
字符設備、塊設備
每個設備文件都有其文件屬性(c/b),表示是字符設備還是塊設備, 另外每個文件都有兩個設備號,第一個是主設備號,標識驅(qū)動程序,第二個是從設備號,標識使用同一個設備驅(qū)動程序的不同的硬件設備,比如有兩個軟盤,就可以用 從設備號來區(qū)分他們。設備文件的的主設備號必須與設備驅(qū)動程序在登記時申請的主設備號一致,否則用戶進程將無法訪問到驅(qū)動程序。
我可以通過 ls 來看一下
命令:ls -alti
輸出詳解:
# ===================
10605 (inode 編號)
brw-rw----. b 塊設備, c 字符設備
1
root (所屬用戶)
cdrom (用戶組)
11,(主設備號)
0(次設備號)
10月 12 17:41
sr0 設備名稱
# ===================
ex:
10445 brw-rw----. 1 root disk 8, 1 10月 12 17:41 sda1
10446 brw-rw----. 1 root disk 8, 2 10月 12 17:41 sda2
10444 brw-rw----. 1 root disk 8, 0 10月 12 17:41 sda
10449 brw-rw----. 1 root disk 8, 16 10月 12 17:41 sdb
8535 crw-------. 1 root root 247, 1 10月 12 17:41 usbmon1
10104 crw-------. 1 root root 246, 0 10月 12 17:41 hidraw0
8550 crw-------. 1 root root 247, 2 10月 12 17:41 usbmon2
- 文件操作的關鍵結(jié)構
由于用戶進程是通過設備文件同硬件打交道,對設備文件的操作方式不外乎就是一些系統(tǒng)調(diào)用,如 open,read,write,close…, 注意,不是fopen, fread,但是如何把系統(tǒng)調(diào)用和驅(qū)動程序關聯(lián)起來呢?這需要了解一個非常關鍵的數(shù)據(jù)結(jié)構 file_operations:
struct file_operations {
int (*seek) (struct inode * ,struct file *, off_t ,int);
int (*read) (struct inode * ,struct file *, char ,int);
int (*write) (struct inode * ,struct file *, off_t ,int);
int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);
int (*select) (struct inode * ,struct file *, int ,select_table *);
int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);
int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);
int (*open) (struct inode * ,struct file *);
int (*release) (struct inode * ,struct file *);
int (*fsync) (struct inode * ,struct file *);
int (*fasync) (struct inode * ,struct file *,int);
int (*check_media_change) (struct inode * ,struct file *);
int (*revalidate) (dev_t dev);
}
這個結(jié)構的每一個成員的名字都對應著一個系統(tǒng)調(diào)用。用戶進程利用系統(tǒng)調(diào)用在對設備文件進行諸如read/write操作時,系統(tǒng)調(diào)用通過設備文件的主設備號找到相應的設備驅(qū)動程序,然后讀取這個數(shù)據(jù)結(jié)構相應的函數(shù)指針,接著把控制權交給該函數(shù)。這是linux的設備驅(qū)動程序工作的基本原理 這里不再詳細闡述。
鏈接文件
軟連接文件,詳情請看上文 文件鏈接
管道文件
- 什么是管道?
管道,英文為pipe。這是一個我們在學習Linux命令行的時候就會引入的一個很重要的概念。它的發(fā)明人是道格拉斯.麥克羅伊,這位也是UNIX上早期shell的發(fā)明人。他在發(fā)明了shell之后,發(fā)現(xiàn)系統(tǒng)操作執(zhí)行命令的時候,經(jīng)常有需求要將一個程序的輸出交給另一個程序進行處理,這種操作可以使用輸入輸出重定向加文件搞定,比如:
[lizhipeng@CentOS7 ~]$ ls /etc/ > etc.txt
[lizhipeng@CentOS7 ~]$ wc -l etc.txt
但是這樣未免顯得太麻煩了。所以,管道的概念應運而生。目前在任何一個shell中,都可以使用“|”連接兩個命令,shell會將前后兩個進程的輸入輸出用一個管道相連,以便達到進程間通信的目的:
[lizhipeng@CentOS7 ~]$ ls -l /etc/ | wc -l
對比以上兩種方法,我們也可以理解為,管道本質(zhì)上就是一個文件,前面的進程以寫方式打開文件,后面的進程以讀方式打開。這樣前面寫完后面讀,于是就實現(xiàn)了通信。實際上管道的設計也是遵循UNIX的“一切皆文件”設計原則的,它本質(zhì)上就是一個文件。Linux系統(tǒng)直接把管道實現(xiàn)成了一種文件系統(tǒng),借助VFS給應用程序提供操作接口。
雖然實現(xiàn)形態(tài)上是文件,但是管道本身并不占用磁盤或者其他外部存儲的空間。在Linux的實現(xiàn)上,它占用的是內(nèi)存空間。所以,Linux上的管道就是一個操作方式為文件的內(nèi)存緩沖區(qū)。
Linux上的管道分兩種類型:
- 匿名管道
- 命名管道
這兩種管道也叫做有名或無名管道。匿名管道最常見的形態(tài)就是我們在shell操作中最常用的”|”。它的特點是只能在父子進程中使用,父進程在產(chǎn)生子進程前必須打開一個管道文件,然后fork產(chǎn)生子進程,這樣子進程通過拷貝父進程的進程地址空間獲得同一個管道文件的描述符,以達到使用同一個管道通信的目的。此時除了父子進程外,沒人知道這個管道文件的描述符,所以通過這個管道中的信息無法傳遞給其他進程。這保證了傳輸數(shù)據(jù)的安全性,當然也降低了管道了通用性,于是系統(tǒng)還提供了命名管道。
我們可以使用mkfifo或mknod命令來創(chuàng)建一個命名管道,這跟創(chuàng)建一個文件沒有什么區(qū)別:
[lizhipeng@CentOS7 ~]$ mkfifo pip
[lizhipeng@CentOS7 ~]$ ls
prw-rw-r--. 1 lizhipeng lizhipeng 0 11月 17 13:24 pip
可以看到創(chuàng)建出來的文件類型比較特殊,是p類型。表示這是一個管道文件。有了這個管道文件,系統(tǒng)中就有了對一個管道的全局名稱,于是任何兩個不相關的進程都可以通過這個管道文件進行通信了。比如我們現(xiàn)在讓一個進程寫這個管道文件:
[lizhipeng@CentOS7 ~]$ echo xxxxxxxxxxxxxx > pip
此時這個寫操作會阻塞,因為管道另一端沒有人讀。這是內(nèi)核對管道文件定義的默認行為。此時如果有進程讀這個管道,那么這個寫操作的阻塞才會解除:
[lizhipeng@CentOS7 ~]$ cat pip
xxxxxxxxxxxxxx
大家可以觀察到,當我們cat完這個文件之后,另一端的echo命令也返回了。這就是命名管道
接下來我們來看一下匿名管道,我們需要用到 shell 的代碼塊 命令如下
[lizhipeng@CentOS7 ~]$ { echo $BASHPID; read x ; } | { cat ; echo $BASHPID; read y; }
37057
{} 花括號的代碼會先執(zhí)行,遇到管道后,會開啟另外一個進程,兩個進程實現(xiàn)通訊。此時父進程輸出了父進程的pid
且阻塞在了read x 這個代碼塊中,此時我們可以通過結(jié)果拿到父進程的 pid 37057
我們通過pstree來驗證一下我們的關系
[lizhipeng@CentOS7 ~]$ pstree -p
...
─sshd(36387)───bash(36388)─┬─bash(37057+
│ │ └─bash(37058+
│ └─sshd(36713)───sshd(36717)───bash(36718)───pstree(370+
...
我們看到了 37057 進程生出了 37058的子進程。我們來看一下管道的文件描述符:
[lizhipeng@CentOS7 fd]$ ls -alt /proc/37057/fd
總用量 0
lrwx------. 1 lizhipeng lizhipeng 64 11月 17 13:33 0 -> /dev/pts/4
l-wx------. 1 lizhipeng lizhipeng 64 11月 17 13:33 1 -> pipe:[253711]
lrwx------. 1 lizhipeng lizhipeng 64 11月 17 13:33 2 -> /dev/pts/4
lrwx------. 1 lizhipeng lizhipeng 64 11月 17 13:33 255 -> /dev/pts/4
dr-x------. 2 lizhipeng lizhipeng 0 11月 17 13:33 .
dr-xr-xr-x. 9 lizhipeng lizhipeng 0 11月 17 13:30 ..
[lizhipeng@CentOS7 fd]$ ls -alt /proc/37058/fd
總用量 0
lr-x------. 1 lizhipeng lizhipeng 64 11月 17 13:34 0 -> pipe:[253711]
lrwx------. 1 lizhipeng lizhipeng 64 11月 17 13:34 1 -> /dev/pts/4
lrwx------. 1 lizhipeng lizhipeng 64 11月 17 13:34 2 -> /dev/pts/4
lrwx------. 1 lizhipeng lizhipeng 64 11月 17 13:34 255 -> /dev/pts/4
dr-x------. 2 lizhipeng lizhipeng 0 11月 17 13:34 .
dr-xr-xr-x. 9 lizhipeng lizhipeng 0 11月 17 13:30 ..
由此我們可以看到,37057 通過重定向 1 號文件描述符來講管道 重定向到了 37058 的0號描述符。
這就是匿名管道。
評論
查看更多