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 45 2012 vi

d:目录

dr-xr-xr-x.  2 root root  4096 612 2019 bin

l:链接

软链接,硬链接。
一个文件的硬链接下的各个文件与源文件共享inodeid,可以通过stat xx文件查看,并且会在文件本身标识着引用数,当删除源文件时,对其他的硬链接文件没有影响。
一个文件的软链接下的各个文件inodeid是不同的。当删除源文件时,软链接的文件也会无法使用

b:块设备

读取的内容位置可以来回漂移。
例如:硬盘

brw-rw---- 1 root disk 8, 1 723 22:37 sda1

c:字符设备

只能向后读取,不能自由读取前后偏移量的数据,可能会有一些编解码约束,不能被切割的字符数据
键盘,socket

crw--w---- 1 root tty       4,  32 723 22:37 tty32
crw--w---- 1 root tty       4,  33 723 22:37 tty33
crw--w---- 1 root tty       4,  34 723 22:37 tty34
crw--w---- 1 root tty       4,  35 723 22:37 tty35
crw--w---- 1 root tty       4,  36 723 22:37 tty36
crw--w---- 1 root tty       4,  37 723 22:37 tty37
crw--w---- 1 root tty       4,  38 723 22:37 tty38
crw--w---- 1 root tty       4,  39 723 22:37 tty39

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 712 01:30 0 -> /dev/pts/0
lrwx------ 1 root root 64 712 01:31 1 -> /dev/pts/0
lrwx------ 1 root root 64 712 01:31 2 -> /dev/pts/0
lrwx------ 1 root root 64 712 01:31 255 -> /dev/pts/0
lr-x------ 1 root root 64 712 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/0

p:pipeline(底层类型,不能直接看到)

在这里插入图片描述

上面代码管道符左右都会各自启动一个子进程去执行花括号里的内容,而我们知道管道符的作用是将管道符左边的输出作为右边的输入,那它是如何实现的呢?

生成的子进程号分别是4512和4513,此时看下它们的文件描述符:
在这里插入图片描述

可以看到左侧子进程(4512)的输出到pipe管道文件描述符上,而右侧子进程(4513)的输入也为同一个pipe管道文件描述符上,这样就完成了管道符的功能。

通过lsof查看:
在这里插入图片描述
在这里插入图片描述

两者指向的都是一个inodeid 39968的pipe,并且一个是写,一个是读。

[eventpoll]:

内核提供给epoll的内存区域。
因为redis就是基于epoll实现连接的,启动redis,然后看redis的文件描述符可以看到:

lrwx------ 1 root root 64 723 22:37 0 -> /dev/null
lrwx------ 1 root root 64 723 22:37 1 -> /dev/null
lrwx------ 1 root root 64 723 22:37 2 -> /dev/null
lr-x------ 1 root root 64 723 22:37 3 -> pipe:[9002]
l-wx------ 1 root root 64 723 22:37 4 -> pipe:[9002]
lrwx------ 1 root root 64 723 22:37 5 -> [eventpoll]
lrwx------ 1 root root 64 723 22:37 6 -> socket:[9006]

有意思的实验-生成挂载镜像

 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/,发现内容不是空的, 而是已经是挂载的设备的内容了:

[root@dream01 xxoo]# ll
总用量 12
drwx------ 2 root root 12288 719 22:23 lost+found

仿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 719 22:35 bin
-rw-r--r-- 1 root root     6 719 22:47 hello.txt
drwxr-xr-x 2 root root  1024 719 22:41 lib64
drwx------ 2 root root 12288 719 22:23 lost+found

是不是略有一点docker的味道呢?但实际docker肯定不是这么简单的,要复杂的多,但我们做的好像已经有点这种味道了,这样让相当于一个操作系统内出现多个子操作系统,并且每个人都有每个人的根目录。docker也是基于虚拟文件系统的支撑才得以实现的。
而我们操作的其实都是这块挂载设备,卸载之后,发送给他人,他人依旧可以挂载使用,对里面的内容进行修改。 其实回想docker,最开始也是要生成一个img的镜像文件,然后对其里面进行服务注入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值