虚拟目录树
VFS:虚拟文件系统
树上不同节点可以映射到不同的物理位置
程序和硬件(磁盘、网卡…)之间隔了一个内核,里面有pagecache维护想读的数据,如果修改了会标记为dirty
标记为dirty
的有一个flush
过程来书写到磁盘中去。
如果系统中有两个程序,想打开同一个文件。并不是两个独立加载的过 程,而是访问VFS,如果发现加载过这个文件,想读的文件pagecache已经命中了,这时候已经可以返回所要的pagecache的数据了。
注:页缓存(pagecache)默认4k
FD:文件描述符
不同程序对内核中文件的inode各自有各自的fd【它的seek(指针)偏移量不同】
所以他们通过偏移找到自己该读写的pagecache。如果大家修改的同一文件,需要加锁。
文件内存
硬链接
磁盘中有这么一个文件。这个文件在虚拟文件系统中、在目录中有各自唯一的path(从开始的根目录一直映射到这个文件名,这个描述路径是唯一的),但指向的是同一个物理文件(inode相同)
1 | $ vi msb.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红 报错
磁盘上得到一个被空填充的100M的文件
if=inputfile; of=outputfile; bs=blocksize
(104876为1M)把镜像文件映射到虚拟的设备(loop0)上
把loop0格式化为ext2类型
这时候loop0
是一个设备,只不过这个设备是一个虚拟的映射关系,不是一个物理设备。df
中没有它
进入
/mnt
,建立并进入一个空目录/ooxx
,将loop0
挂到/ooxx
这个虚拟目录上。所以访问目录的时候,其实就是在访问mydisk.img
虚拟硬盘ldd bash
分析bash
程序动态链接库有哪些。
返回上一级,创建一个/lib
目录,模仿我们根目录里面的目录结构里面程序的摆放位置。拷贝动态链接库中的链接文件。echo是bash自带的打印程序,打印“hello” 到abc.txt
切回
\ooxx
目录卸载/ooxx, 再去访问/ooxx,就是空了。不过/root/mashibing下mydisk.img还存在
lsof
命令: 查看一个进程打开了哪些文件
任何程序都有0、1、2这种输入输出流的文件描述符
1
2
3$ exec 8< xxoo.txt
$ cd /proc/$$/fd
$ ls -l利用8去读取xxoo.txt
到根目录的/proc/$$/fdread标准输入0来自于8,把8指向的文件内容赋给a
不过read只读到换行符就不读了。所以read通过8只读取到xxoo.txt里面的第一行$ read a 0<& 8
打开新的程序,进入/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 | $ ls ./ |
第一个命令直接在交互界面输出(显示当前目录),而第二个命令不会在交互界面输出,因为指定让ls的标准输出1不要再指向屏幕了,而是换了一下映射,输出到了ls.out中。
第三条命令从ooxx.txt中标准读入,再标准输出到cat.txt中。
其实我们就把原来程序中硬编码的东西改变掉了。
1 | $ read a |
执行上面第一个命令后,阻塞等待用户。实际上read的0是键盘输入,直到遇到回车换行结束。并把内容输出到a中。用第二个命令进行查看输入的内容。第三个命令不会阻塞等待用户,因为直接从cat.out读取。不过因为读到换行符就结束了,所以只有第一行。
1 | $ ls ./ /ooxx |
执行上面第一个命令后,会显示当前目录,同时报错找不到ooxx。
执行上面第二个命令后,意味着将当前目录输入到ls01.out,/ooxx的报错输入到ls02.out
执行第三个命令,默认覆盖输出。
1 | $ ls ./ /ooxx 1> ls04.out 2>& 1 |
执行上面命令后,可以将俩个输出都放到一个文件中(不被覆盖)
如果重定向描述符在右边,需要在前面加一个&
。
且重定向符的绑定是有顺序的。
管道
管道的目的是,前面的输出作为后面的输入
1 | $ head -n text.txt |
上面第一个命令,可以连续读出text.txt文件的前n行;上面第二个命令,可以连续读出text.txt文件的后n行,如果要读出确定的第n行,需要用到管道。
1 | $ head -8 text.txt | tail -1 |
先把前8行拿出来,然后交给tail -1
,即可显示第8行。
1 | $ x = 100 |
正常来说,父子进程是会变量隔离的,所以在一个bash里面启动子bash,再读x是读不出来的。
export x 让x具备环境变量导出能力,它的任何子进程再执行echo $x,就可以读取变量x。
所以配置有时候需要环境变量(加上export),否则这些变量只在当前进程中有用。
指令块
{
、}
相当于一个指令,括号需要与其他字符分开,括号内可以放很多东西。
1 | $ { echo "sdfsdf"; echo "123"; } |
易错问题
1 | $ a=1 |
bash是个解释执行的程序体系,不会先看到管道的。当看到管道的时候,会先在左边启动一个子进程,右边再启动一个子进程, 并让两个子进程输入输出通过管道pipipeline对接。
等到第三个命令结束后,又回到父进程,所以a=9
已经没了,还是a=1
1 | $ echo $$ |
之前说,$$
和$BASHPID
都可以取出id号。区别:
先看到$$
,扩展成父进程id 4398,然后才看到管道。然后左边起一个进程,右边起一个进程。左边的$$
已经是4398字符串了,直接输出。
而$BASHPID
,先看到管道。再看到前面的,所以扩展为了4496被输出。
1 | $ echo $$ |
另开一页去访问这台服务器,确定4512是管道左边的子进程,是4398的子进程。$ ps -fe | grep 4398
$ cd /proc/4512/fd
,$ cd /proc/4513/fd
,$ lsof -op 4512
可以看到4512的1是管道,4513的0是管道。
为什么使用buffer的IO要比不使用的快?