内存与IO

虚拟目录树

VFS:虚拟文件系统
树上不同节点可以映射到不同的物理位置

程序和硬件(磁盘、网卡…)之间隔了一个内核,里面有pagecache维护想读的数据,如果修改了会标记为dirty
标记为dirty的有一个flush过程来书写到磁盘中去。

如果系统中有两个程序,想打开同一个文件。并不是两个独立加载的过 程,而是访问VFS,如果发现加载过这个文件,想读的文件pagecache已经命中了,这时候已经可以返回所要的pagecache的数据了。
注:页缓存(pagecache)默认4k

FD:文件描述符
不同程序对内核中文件的inode各自有各自的fd【它的seek(指针)偏移量不同】
所以他们通过偏移找到自己该读写的pagecache。如果大家修改的同一文件,需要加锁。

文件内存

硬链接
磁盘中有这么一个文件。这个文件在虚拟文件系统中、在目录中有各自唯一的path(从开始的根目录一直映射到这个文件名,这个描述路径是唯一的),但指向的是同一个物理文件(inode相同)

1
2
$ vi msb.txt
$ ln /root/msb.txt /root/xxoo.txt

在这里插入图片描述

  • 硬链接后,计数值升为2
  • 改动msb.txt,进入xxoo.txt会发现内容也发生了改变
  • 删除其中一个,另一个仍存在但计数值降为1
    计数值其实就是硬链接被引用的数量

软链接

1
$ ln -s /root/msb.txt /root/ooxx.txt

在这里插入图片描述

  • 改动msb.txt,进入ooxx.txt会发现内容也发生了改变
  • 删除msb.txt ls -l会发现 -> /root/ooxx.txt红 报错

在这里插入图片描述

  1. 磁盘上得到一个被空填充的100M的文件
    if=inputfile; of=outputfile; bs=blocksize
    (104876为1M)

  2. 把镜像文件映射到虚拟的设备(loop0)上

  3. 把loop0格式化为ext2类型
    在这里插入图片描述

这时候loop0是一个设备,只不过这个设备是一个虚拟的映射关系,不是一个物理设备。df中没有它

  1. 进入/mnt,建立并进入一个空目录/ooxx,将loop0挂到/ooxx这个虚拟目录上。所以访问目录的时候,其实就是在访问mydisk.img虚拟硬盘
    在这里插入图片描述

  2. ldd bash 分析bash程序动态链接库有哪些。
    返回上一级,创建一个/lib目录,模仿我们根目录里面的目录结构里面程序的摆放位置。拷贝动态链接库中的链接文件。
    在这里插入图片描述

  3. echo是bash自带的打印程序,打印“hello” 到abc.txt
    在这里插入图片描述

  4. 切回\ooxx目录
    在这里插入图片描述

  5. 卸载/ooxx, 再去访问/ooxx,就是空了。不过/root/mashibing下mydisk.img还存在

    在这里插入图片描述

lsof命令: 查看一个进程打开了哪些文件
任何程序都有0、1、2这种输入输出流的文件描述符
在这里插入图片描述

  1. 1
    2
    3
    $ exec 8< xxoo.txt
    $ cd /proc/$$/fd
    $ ls -l

    利用8去读取xxoo.txt
    到根目录的/proc/$$/fd

  2. read标准输入0来自于8,把8指向的文件内容赋给a
    不过read只读到换行符就不读了。所以read通过8只读取到xxoo.txt里面的第一行
    $ read a 0<& 8

  3. 打开新的程序,进入/proc/$$/fd
    随便用比如一个 6 也去读取xxoo.txt
    lsof -op $$ 查看bash进程(id号为$$),6为文件描述符,偏移量为0。
    这时候用read a <& 6读的时候来自6 也是读ooxx.txt第一行。如果再读一次,读了第二行。这时候偏移量已经移动到10。返回第一个程序,它的偏移量依旧是5。
    内核为每一个进程各自维护一套数据,这个数据里包含了fd。fd里面维护了一些指向的文件的偏移、inode号等信息。不过缓存pagecache是公用的

任何程序都有0:标准输入,1:标准输出,2:报错删除
cd /proc/$$/fd
/proc:内核映射目录,开机后内核的变量属性和对象等都会在里面挂载。进程被内核管理,进程或网络的一些属性在/proc里面被映射成文件。
$$:当前bash的pid(当前交互进程的id号) $BASHPID
/proc/$$/fd 命令lsof -op $$ 可以看到文件描述符的细节。

重定向

重定向不是命令,是一种机制。可以选择让你的io指向别的地方去。
流的方向就是输入/输出。输入输出重定向操作符的基本符<>
e.g

1
2
3
$ ls ./  
$ ls ./ 1> ~/ls.out
$ cat 0< ooxx.txt 1> cat.out

第一个命令直接在交互界面输出(显示当前目录),而第二个命令不会在交互界面输出,因为指定让ls的标准输出1不要再指向屏幕了,而是换了一下映射,输出到了ls.out中。
第三条命令从ooxx.txt中标准读入,再标准输出到cat.txt中。
其实我们就把原来程序中硬编码的东西改变掉了。

1
2
3
$ read a
$ echo $a
$ read a 0< cat.txt

执行上面第一个命令后,阻塞等待用户。实际上read的0是键盘输入,直到遇到回车换行结束。并把内容输出到a中。用第二个命令进行查看输入的内容。第三个命令不会阻塞等待用户,因为直接从cat.out读取。不过因为读到换行符就结束了,所以只有第一行。

1
2
3
$ ls ./ /ooxx
$ ls ./ /ooxx 1> ls01.out 2>ls02.out
$ ls ./ /ooxx 1> ls03.out 2>ls03.out

执行上面第一个命令后,会显示当前目录,同时报错找不到ooxx。
执行上面第二个命令后,意味着将当前目录输入到ls01.out,/ooxx的报错输入到ls02.out
执行第三个命令,默认覆盖输出。

1
$ ls ./ /ooxx 1> ls04.out 2>& 1

执行上面命令后,可以将俩个输出都放到一个文件中(不被覆盖)
如果重定向描述符在右边,需要在前面加一个&
且重定向符的绑定是有顺序的。

管道

管道的目的是,前面的输出作为后面的输入

1
2
$ head -n text.txt
$ tail -n text.txt

上面第一个命令,可以连续读出text.txt文件的前n行;上面第二个命令,可以连续读出text.txt文件的后n行,如果要读出确定的第n行,需要用到管道。

1
$ head -8 text.txt | tail -1

先把前8行拿出来,然后交给tail -1,即可显示第8行。

1
2
3
4
5
6
$ x = 100
$ echo $x
100
$ /bin/bash
$ echo $x
$ export x

正常来说,父子进程是会变量隔离的,所以在一个bash里面启动子bash,再读x是读不出来的。
export x 让x具备环境变量导出能力,它的任何子进程再执行echo $x,就可以读取变量x。

所以配置有时候需要环境变量(加上export),否则这些变量只在当前进程中有用。

指令块

{}相当于一个指令,括号需要与其他字符分开,括号内可以放很多东西。

1
2
3
$ { echo "sdfsdf"; echo "123"; }
sdfsdf
123

易错问题

1
2
3
4
5
6
7
$ a=1
$ echo $a
1
$ { a=9; echo "sdfsdf" } | cat
sdfsdf
$ echo $a
1

bash是个解释执行的程序体系,不会先看到管道的。当看到管道的时候,会先在左边启动一个子进程,右边再启动一个子进程, 并让两个子进程输入输出通过管道pipipeline对接。
等到第三个命令结束后,又回到父进程,所以a=9已经没了,还是a=1

1
2
3
4
5
6
$ echo $$
4398
$ echo $$ | cat
4398
$ echo $BASHPID | cat
4496

之前说,$$$BASHPID都可以取出id号。区别:
先看到$$,扩展成父进程id 4398,然后才看到管道。然后左边起一个进程,右边起一个进程。左边的$$已经是4398字符串了,直接输出。
$BASHPID,先看到管道。再看到前面的,所以扩展为了4496被输出。

1
2
3
4
$ echo $$
4398
$ { echo $BASHPID ; read x; } | { cat; echo $BASHPID ; read y; }
4512

另开一页去访问这台服务器,确定4512是管道左边的子进程,是4398的子进程。$ ps -fe | grep 4398
$ cd /proc/4512/fd$ cd /proc/4513/fd$ lsof -op 4512 可以看到4512的1是管道,4513的0是管道。

为什么使用buffer的IO要比不使用的快?

在这里插入图片描述