IO那些事01-IO总述和文件描述符
VFS
內(nèi)核既管內(nèi)存,又管磁盤IO。
作為LINUX內(nèi)核來說,它在內(nèi)存中構(gòu)建了一個(gè)虛擬文件系統(tǒng)VFS,不同于windows上的物理文件系統(tǒng)結(jié)構(gòu),C盤代表的就是物理的C盤分區(qū),D盤就是D盤的物理分區(qū),
VFS本質(zhì)就是一顆目錄樹,每個(gè)目錄可以映射代表不同的物理設(shè)備。
為什么要有VFS?
因?yàn)閂FS相當(dāng)于一個(gè)中間的解耦層,下層的存儲(chǔ)源的存儲(chǔ)形式可能是各不相同的,可能是來自不同的硬件設(shè)備,但需要將這些包成一個(gè)統(tǒng)一的對外接口暴露給上層應(yīng)用使用。
pagecache
在VFS中,每一個(gè)文件都有一個(gè)inode id作為唯一標(biāo)識,一個(gè)文件首先要被內(nèi)核讀到內(nèi)存中,然后開啟一個(gè)pagecache(默認(rèn)4K)作為這個(gè)文件在內(nèi)存中的緩存,這樣的話如果多個(gè)應(yīng)用都需要這同一個(gè)文件,只需要來內(nèi)存中命中這一份文件,而不需要多次讀取,后續(xù)對文件的操作都將是基于內(nèi)存中緩存的操作,將會(huì)變得非常快
dirty
pagecache作為文件的緩存,當(dāng)被進(jìn)行修改了的時(shí)候,實(shí)際是對這個(gè)緩存內(nèi)容的修改,此時(shí)就會(huì)標(biāo)記成dirty
flush
被標(biāo)記為dirty,意味著這個(gè)緩存的文件是發(fā)生了變化的,也就是需要將變化同步更新到磁盤的真實(shí)文件中,所以會(huì)有一個(gè)flush的操作,但flush的觸發(fā)時(shí)機(jī)也是分為很多種,每一種的成本也不同:
1、例如可以修改一次緩存文件,就通過內(nèi)核刷寫更新一次內(nèi)容到磁盤文件中(響應(yīng)快,但效率最差)
2、考慮到dirty的文件并不是只針對某一個(gè)文件的, 而是內(nèi)核中會(huì)存在許多dirty的文件,因此可以當(dāng)內(nèi)核中的dirty達(dá)到一定比例,內(nèi)核進(jìn)行刷寫(存在中間數(shù)據(jù)丟失風(fēng)險(xiǎn),效率較高)
3、可以周期性的進(jìn)行刷洗內(nèi)核中的dirty的文件。(存在中間數(shù)據(jù)丟失風(fēng)險(xiǎn),效率較高)
FD(文件描述符)
文件的內(nèi)存中的pagecache,就意味著多個(gè)應(yīng)用有可能都共享使用一份緩存內(nèi)容,那么各自的例如對文件的不同位置的讀取,就涉及到一個(gè)seek偏移量可能是各不相同的,而這些包起來對外來看就統(tǒng)一用FD來表示。
通過df -h可以看到當(dāng)前VFS的整個(gè)結(jié)構(gòu)
這個(gè)結(jié)構(gòu)就是首先掛載了來自/dev/sda3的一個(gè)/的根目錄,然后發(fā)現(xiàn)還有一個(gè)單獨(dú)從/dev/sda1掛載的/boot目錄,而實(shí)際上/下面也應(yīng)該有一個(gè)/boot目錄,但這個(gè)掛載的/boot目錄會(huì)覆蓋原本/中的/boot目錄。
這里看到的實(shí)際上就是來自 /dev/sda1的/boot
那如果把這個(gè)掛載的/boot去掉呢?
發(fā)現(xiàn)掛載結(jié)構(gòu)少了/boot,確實(shí)卸載掉了/boot:
此時(shí)再去/下查看,發(fā)現(xiàn)/boot還在,這個(gè)/boot就是/原本的/boot,只不過里邊是空的:
此時(shí)重新再掛載上剛才卸載的/boot,發(fā)現(xiàn)再次查看/boot,內(nèi)容又回來了:
但這個(gè)過程我們可以發(fā)現(xiàn),對于程序而言,這個(gè)文件目錄樹結(jié)構(gòu)非常的穩(wěn)定,不會(huì)隨著掛載的變動(dòng),目錄發(fā)生什么變化
并且這之中其實(shí)是有一個(gè)映射的過程,通過這個(gè)過程,我們可以想到,日后如果某個(gè)文件夾例如/abc的大小不滿足需要,完全可以接入一塊新的比較大的外接設(shè)備,然后把舊數(shù)據(jù)遷移過去,然后將外接設(shè)備掛載到/abc,也就是覆蓋掉原來的/abc,這樣就完成了一個(gè)無結(jié)構(gòu)修改的靜默擴(kuò)容操作了。
LINUX中的一切皆文件
在馮諾依曼體系結(jié)構(gòu)下,計(jì)算器,控制器就相當(dāng)于我們的CPU,主存儲(chǔ)器就相當(dāng)于我們的內(nèi)存, 而輸入輸出設(shè)備也就是一切的IO設(shè)備。而這一切,在linux之中,全都是用文件進(jìn)行表示。
文件類型
-:普通文件
可執(zhí)行,圖片,文本
-rwxr-xr-x. 1 root root 764088 4月 5 2012 vid:目錄
dr-xr-xr-x. 2 root root 4096 6月 12 2019 binl:鏈接
軟鏈接,硬鏈接。
一個(gè)文件的硬鏈接下的各個(gè)文件與源文件共享inodeid,可以通過stat xx文件查看,并且會(huì)在文件本身標(biāo)識著引用數(shù),當(dāng)刪除源文件時(shí),對其他的硬鏈接文件沒有影響。
一個(gè)文件的軟鏈接下的各個(gè)文件inodeid是不同的。當(dāng)刪除源文件時(shí),軟鏈接的文件也會(huì)無法使用
b:塊設(shè)備
讀取的內(nèi)容位置可以來回漂移。
例如:硬盤
c:字符設(shè)備
只能向后讀取,不能自由讀取前后偏移量的數(shù)據(jù),可能會(huì)有一些編解碼約束,不能被切割的字符數(shù)據(jù)
鍵盤,socket
s:socket(底層類型,不能直接看到)
[root@dream01 fd]# exec 8<> /dev/tcp/www.baidu.com/80 [root@dream01 fd]# cd /proc/$$/fd [root@dream01 fd]# ll 總用量 0 lrwx------ 1 root root 64 7月 12 01:30 0 -> /dev/pts/0 lrwx------ 1 root root 64 7月 12 01:31 1 -> /dev/pts/0 lrwx------ 1 root root 64 7月 12 01:31 2 -> /dev/pts/0 lrwx------ 1 root root 64 7月 12 01:31 255 -> /dev/pts/0 lr-x------ 1 root root 64 7月 12 01:31 8 -> socket:[24388] [root@dream01 fd]# lsof -op $$ COMMAND PID USER FD TYPE DEVICE OFFSET NODE NAME bash 9310 root cwd DIR 0,3 24173 /proc/9310/fd bash 9310 root rtd DIR 8,3 2 / bash 9310 root txt REG 8,3 2621442 /bin/bash bash 9310 root mem REG 8,3 3932199 /lib64/libresolv-2.12.so bash 9310 root mem REG 8,3 3932187 /lib64/libnss_dns-2.12.so bash 9310 root mem REG 8,3 3932189 /lib64/libnss_files-2.12.so bash 9310 root mem REG 8,3 1971152 /usr/lib/locale/locale-archive bash 9310 root mem REG 8,3 3932173 /lib64/libc-2.12.so bash 9310 root mem REG 8,3 3932179 /lib64/libdl-2.12.so bash 9310 root mem REG 8,3 3932216 /lib64/libtinfo.so.5.7 bash 9310 root mem REG 8,3 3932163 /lib64/ld-2.12.so bash 9310 root mem REG 8,3 2230781 /usr/share/locale/zh_CN/LC_MESSAGES/libc.mo bash 9310 root mem REG 8,3 1967051 /usr/lib64/gconv/gconv-modules.cache bash 9310 root 0u CHR 136,0 0t0 3 /dev/pts/0 bash 9310 root 1u CHR 136,0 0t0 3 /dev/pts/0 bash 9310 root 2u CHR 136,0 0t0 3 /dev/pts/0 bash 9310 root 8r IPv4 24388 0t0 TCP dream01:59731->39.156.66.14:http (ESTABLISHED) ##這就是socket類型 bash 9310 root 255u CHR 136,0 0t0 3 /dev/pts/0p:pipeline(底層類型,不能直接看到)
上面代碼管道符左右都會(huì)各自啟動(dòng)一個(gè)子進(jìn)程去執(zhí)行花括號里的內(nèi)容,而我們知道管道符的作用是將管道符左邊的輸出作為右邊的輸入,那它是如何實(shí)現(xiàn)的呢?
生成的子進(jìn)程號分別是4512和4513,此時(shí)看下它們的文件描述符:
可以看到左側(cè)子進(jìn)程(4512)的輸出到pipe管道文件描述符上,而右側(cè)子進(jìn)程(4513)的輸入也為同一個(gè)pipe管道文件描述符上,這樣就完成了管道符的功能。
通過lsof查看:
兩者指向的都是一個(gè)inodeid 39968的pipe,并且一個(gè)是寫,一個(gè)是讀。
[eventpoll]:
內(nèi)核提供給epoll的內(nèi)存區(qū)域。
因?yàn)閞edis就是基于epoll實(shí)現(xiàn)連接的,啟動(dòng)redis,然后看redis的文件描述符可以看到:
有意思的實(shí)驗(yàn)-生成掛載鏡像
dd if=/dev/zero of=mydisk.img bs=1048576 count=100輸入是/dev/zero(空),輸出是mydisk.img,一個(gè)塊的大小是1048576(1M),一共有100個(gè)塊組成,最后生成的就是100M的被0填充的文件
losetup /dev/loop0 mydisk.img讓環(huán)衛(wèi)接口設(shè)備/dev/loop0掛載剛剛生成的文件mydisk.img 也就是/dev/loop0不再指向一個(gè)物理地址,而是指向了新生成的這個(gè)文件。
mke2fs /dev/loop0格式化成ext2的文件格式。
到現(xiàn)在為止,我們已經(jīng)成功掛載到了一塊虛擬環(huán)衛(wèi)設(shè)備,那能不能也類似的讓linux中的某個(gè)虛擬文件路徑映射到這個(gè)虛擬設(shè)備上? 就類似于上面/boot的效果。
我們先生成一個(gè)虛擬路徑:
mkdir -p ~/io_test/mnt/xxoo/ mount -t ext2 /dev/loop0 ~/io_test/mnt/xxoo/指定掛載的文件格式為ext2,把它(/dev/loop0)掛載到~/io_test/mnt/xxoo/的虛擬路徑上
[root@dream01 io_test]# df -lh Filesystem Size Used Avail Use% Mounted on /dev/sda3 97G 9.4G 83G 11% / tmpfs 931M 0 931M 0% /dev/shm /dev/sda1 194M 27M 158M 15% /boot /dev/loop0 97M 1.6M 91M 2% /root/io_test/mnt/xxoo此時(shí)可以看到,文件路徑映射已經(jīng)形成了這兩者的映射。
此時(shí)移動(dòng)到~/io_test/mnt/xxoo/,發(fā)現(xiàn)內(nèi)容不是空的, 而是已經(jīng)是掛載的設(shè)備的內(nèi)容了:
仿docker的思想雛形方向體現(xiàn)
先看一下我們的bash程序在哪:
[root@dream01 xxoo]# whereis bash bash: /bin/bash /usr/share/man/man1/bash.1.gz我們在虛擬路徑里照樣創(chuàng)建一個(gè)bin的目錄,并將系統(tǒng)的bin拷貝過來:
[root@dream01 xxoo]# mkdir bin [root@dream01 xxoo]# cp /bin/bash bin/然后查看一下bash需要的第三方依賴是什么:
[root@dream01 bin]# ldd bash linux-vdso.so.1 => (0x00007fffd1bff000)libtinfo.so.5 => /lib64/libtinfo.so.5 (0x00007f9f2ca1f000)libdl.so.2 => /lib64/libdl.so.2 (0x00007f9f2c81b000)libc.so.6 => /lib64/libc.so.6 (0x00007f9f2c486000)/lib64/ld-linux-x86-64.so.2 (0x00007f9f2cc49000)然后把這些依賴也都拷貝過來:
[root@dream01 xxoo]# mkdir lib64 [root@dream01 xxoo]# cp /lib64/{libtinfo.so.5,libdl.so.2,libc.so.6,ld-linux-x86-64.so.2} lib64/接下來,如果我們能讓當(dāng)前的虛擬掛載點(diǎn)作為根目錄,是不是就可以也可以啟動(dòng)我們剛剛拷貝過來的bash了呢?
先看一下當(dāng)前的bash進(jìn)程號:
[root@dream01 xxoo]# echo $$ 1264把根目錄切換到當(dāng)前目錄,并啟動(dòng)當(dāng)前目錄的bash:
[root@dream01 xxoo]# chroot ./然后發(fā)現(xiàn)確實(shí)啟動(dòng)了一個(gè)新的bash解釋程序,我們打印一下當(dāng)前的進(jìn)程號:
bash-4.1# echo $$ 39999發(fā)現(xiàn)是39999
此時(shí)如果打印這么一句生成文件的指令:
bash-4.1# echo "hello" > /hello.txt然后退出bash,發(fā)現(xiàn)內(nèi)容被輸出到了我們剛剛指定的新的根目錄,也就是xxoo目錄下:
[root@dream01 xxoo]# ll 總用量 15 drwxr-xr-x 2 root root 1024 7月 19 22:35 bin -rw-r--r-- 1 root root 6 7月 19 22:47 hello.txt drwxr-xr-x 2 root root 1024 7月 19 22:41 lib64 drwx------ 2 root root 12288 7月 19 22:23 lost+found是不是略有一點(diǎn)docker的味道呢?但實(shí)際docker肯定不是這么簡單的,要復(fù)雜的多,但我們做的好像已經(jīng)有點(diǎn)這種味道了,這樣讓相當(dāng)于一個(gè)操作系統(tǒng)內(nèi)出現(xiàn)多個(gè)子操作系統(tǒng),并且每個(gè)人都有每個(gè)人的根目錄。docker也是基于虛擬文件系統(tǒng)的支撐才得以實(shí)現(xiàn)的。
而我們操作的其實(shí)都是這塊掛載設(shè)備,卸載之后,發(fā)送給他人,他人依舊可以掛載使用,對里面的內(nèi)容進(jìn)行修改。 其實(shí)回想docker,最開始也是要生成一個(gè)img的鏡像文件,然后對其里面進(jìn)行服務(wù)注入。
總結(jié)
以上是生活随笔為你收集整理的IO那些事01-IO总述和文件描述符的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Qt设置背景图片方法
- 下一篇: Jacob操作Word文档转换-XXOO