IO那些事01-IO总述和文件描述符
VFS
內核既管內存,又管磁盤IO。
作為LINUX內核來說,它在內存中構建了一個虛擬文件系統VFS,不同于windows上的物理文件系統結構,C盤代表的就是物理的C盤分區,D盤就是D盤的物理分區,
VFS本質就是一顆目錄樹,每個目錄可以映射代表不同的物理設備。
為什么要有VFS?
因為VFS相當于一個中間的解耦層,下層的存儲源的存儲形式可能是各不相同的,可能是來自不同的硬件設備,但需要將這些包成一個統一的對外接口暴露給上層應用使用。
pagecache
在VFS中,每一個文件都有一個inode id作為唯一標識,一個文件首先要被內核讀到內存中,然后開啟一個pagecache(默認4K)作為這個文件在內存中的緩存,這樣的話如果多個應用都需要這同一個文件,只需要來內存中命中這一份文件,而不需要多次讀取,后續對文件的操作都將是基于內存中緩存的操作,將會變得非常快
dirty
pagecache作為文件的緩存,當被進行修改了的時候,實際是對這個緩存內容的修改,此時就會標記成dirty
flush
被標記為dirty,意味著這個緩存的文件是發生了變化的,也就是需要將變化同步更新到磁盤的真實文件中,所以會有一個flush的操作,但flush的觸發時機也是分為很多種,每一種的成本也不同:
1、例如可以修改一次緩存文件,就通過內核刷寫更新一次內容到磁盤文件中(響應快,但效率最差)
2、考慮到dirty的文件并不是只針對某一個文件的, 而是內核中會存在許多dirty的文件,因此可以當內核中的dirty達到一定比例,內核進行刷寫(存在中間數據丟失風險,效率較高)
3、可以周期性的進行刷洗內核中的dirty的文件。(存在中間數據丟失風險,效率較高)
FD(文件描述符)
文件的內存中的pagecache,就意味著多個應用有可能都共享使用一份緩存內容,那么各自的例如對文件的不同位置的讀取,就涉及到一個seek偏移量可能是各不相同的,而這些包起來對外來看就統一用FD來表示。
通過df -h可以看到當前VFS的整個結構
這個結構就是首先掛載了來自/dev/sda3的一個/的根目錄,然后發現還有一個單獨從/dev/sda1掛載的/boot目錄,而實際上/下面也應該有一個/boot目錄,但這個掛載的/boot目錄會覆蓋原本/中的/boot目錄。
這里看到的實際上就是來自 /dev/sda1的/boot
那如果把這個掛載的/boot去掉呢?
發現掛載結構少了/boot,確實卸載掉了/boot:
此時再去/下查看,發現/boot還在,這個/boot就是/原本的/boot,只不過里邊是空的:
此時重新再掛載上剛才卸載的/boot,發現再次查看/boot,內容又回來了:
但這個過程我們可以發現,對于程序而言,這個文件目錄樹結構非常的穩定,不會隨著掛載的變動,目錄發生什么變化
并且這之中其實是有一個映射的過程,通過這個過程,我們可以想到,日后如果某個文件夾例如/abc的大小不滿足需要,完全可以接入一塊新的比較大的外接設備,然后把舊數據遷移過去,然后將外接設備掛載到/abc,也就是覆蓋掉原來的/abc,這樣就完成了一個無結構修改的靜默擴容操作了。
LINUX中的一切皆文件
在馮諾依曼體系結構下,計算器,控制器就相當于我們的CPU,主存儲器就相當于我們的內存, 而輸入輸出設備也就是一切的IO設備。而這一切,在linux之中,全都是用文件進行表示。
文件類型
-:普通文件
可執行,圖片,文本
-rwxr-xr-x. 1 root root 764088 4月 5 2012 vid:目錄
dr-xr-xr-x. 2 root root 4096 6月 12 2019 binl:鏈接
軟鏈接,硬鏈接。
一個文件的硬鏈接下的各個文件與源文件共享inodeid,可以通過stat xx文件查看,并且會在文件本身標識著引用數,當刪除源文件時,對其他的硬鏈接文件沒有影響。
一個文件的軟鏈接下的各個文件inodeid是不同的。當刪除源文件時,軟鏈接的文件也會無法使用
b:塊設備
讀取的內容位置可以來回漂移。
例如:硬盤
c:字符設備
只能向后讀取,不能自由讀取前后偏移量的數據,可能會有一些編解碼約束,不能被切割的字符數據
鍵盤,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(底層類型,不能直接看到)
上面代碼管道符左右都會各自啟動一個子進程去執行花括號里的內容,而我們知道管道符的作用是將管道符左邊的輸出作為右邊的輸入,那它是如何實現的呢?
生成的子進程號分別是4512和4513,此時看下它們的文件描述符:
可以看到左側子進程(4512)的輸出到pipe管道文件描述符上,而右側子進程(4513)的輸入也為同一個pipe管道文件描述符上,這樣就完成了管道符的功能。
通過lsof查看:
兩者指向的都是一個inodeid 39968的pipe,并且一個是寫,一個是讀。
[eventpoll]:
內核提供給epoll的內存區域。
因為redis就是基于epoll實現連接的,啟動redis,然后看redis的文件描述符可以看到:
有意思的實驗-生成掛載鏡像
dd if=/dev/zero of=mydisk.img bs=1048576 count=100輸入是/dev/zero(空),輸出是mydisk.img,一個塊的大小是1048576(1M),一共有100個塊組成,最后生成的就是100M的被0填充的文件
losetup /dev/loop0 mydisk.img讓環衛接口設備/dev/loop0掛載剛剛生成的文件mydisk.img 也就是/dev/loop0不再指向一個物理地址,而是指向了新生成的這個文件。
mke2fs /dev/loop0格式化成ext2的文件格式。
到現在為止,我們已經成功掛載到了一塊虛擬環衛設備,那能不能也類似的讓linux中的某個虛擬文件路徑映射到這個虛擬設備上? 就類似于上面/boot的效果。
我們先生成一個虛擬路徑:
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此時可以看到,文件路徑映射已經形成了這兩者的映射。
此時移動到~/io_test/mnt/xxoo/,發現內容不是空的, 而是已經是掛載的設備的內容了:
仿docker的思想雛形方向體現
先看一下我們的bash程序在哪:
[root@dream01 xxoo]# whereis bash bash: /bin/bash /usr/share/man/man1/bash.1.gz我們在虛擬路徑里照樣創建一個bin的目錄,并將系統的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/接下來,如果我們能讓當前的虛擬掛載點作為根目錄,是不是就可以也可以啟動我們剛剛拷貝過來的bash了呢?
先看一下當前的bash進程號:
[root@dream01 xxoo]# echo $$ 1264把根目錄切換到當前目錄,并啟動當前目錄的bash:
[root@dream01 xxoo]# chroot ./然后發現確實啟動了一個新的bash解釋程序,我們打印一下當前的進程號:
bash-4.1# echo $$ 39999發現是39999
此時如果打印這么一句生成文件的指令:
bash-4.1# echo "hello" > /hello.txt然后退出bash,發現內容被輸出到了我們剛剛指定的新的根目錄,也就是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是不是略有一點docker的味道呢?但實際docker肯定不是這么簡單的,要復雜的多,但我們做的好像已經有點這種味道了,這樣讓相當于一個操作系統內出現多個子操作系統,并且每個人都有每個人的根目錄。docker也是基于虛擬文件系統的支撐才得以實現的。
而我們操作的其實都是這塊掛載設備,卸載之后,發送給他人,他人依舊可以掛載使用,對里面的內容進行修改。 其實回想docker,最開始也是要生成一個img的鏡像文件,然后對其里面進行服務注入。
總結
以上是生活随笔為你收集整理的IO那些事01-IO总述和文件描述符的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Qt设置背景图片方法
- 下一篇: Jacob操作Word文档转换-XXOO