温故而知新,整理一下看过linux文件系统的资料,以建立一个清晰的系统架构图。
整个体系结构图:
其中Individual file system 可能是Ext2,JFS,romfs 等各种不同格式的文件系统。
为了提高读写速度,数据都是缓存在内存中(页面高速缓存)。
文件读操作的工作流
粗略地分,读操作依次需要经过:
1. 用户界面层——负责从用户函数经过系统调用进入内核;
2. 基本文件系统层——负责调用文件写方法,从高速缓存中搜索数据页,返回给用户。
3. I/O调度层——负责对请求排队,从而提高吞吐量。
4. I/O传输层——利用任务列队异步操作设备控制器完成数据传输。
文件系统结构
文件系统类型结构(file_system_type)
超级块操作表(super_operations)
索引节点操作表(inode_operations)
页高速缓存(address_space_operations)
文件操作表(file_operations)
文件系统类型结构
挂载的文件系统
超级块
超级块就代表一个文件系统。它包含管理文件系统所需的信息,包括文件系统名称(比如 ext2)、文件系统的大小和状态、块设备的引用和元数据信息(比如空闲列表等等)。超级块通常存储在存储媒体上,但是如果超级块不存在,也可以实时创建它。
inode
关于inode,我们可以解析动态库升级的问题。Ld.so是使用文件inode来定位动态库是否已经加载的。在替换so文件时,如果在不停程序的情况下,直接用 cp new.so old.so的方式替换程序使用的动态库文件会导致正在运行中的程序崩溃。解决的办法是采用“rm+cp”或“mv+cp”来替代直接“cp”的操作方法。linux系统的动态库有两种使用方法:运行时动态链接库,动态加载库并在程序控制之下使用。
1、为什么在不停程序的情况下,直接用 cp命令替换程序使用的 so文件,会使程序崩溃?
很多同学在工作中遇到过这样一个问题,在替换 so文件时,如果在不停程序的情况下,直接用cp new.so old.so的方式替换程序使用的动态库文件会导致正在运行中的程序崩溃,退出。这与 cp命令的实现有关,cp并不改变目标文件的 inode,cp的目标文件会继承被覆盖文件的属性而非源文件。实际上它是这样实现的:
strace cp libnew.solibold.so 2>&1 |grep open.*lib.*.so
open("libnew.so",O_RDONLY|O_LARGEFILE) = 3
open("libold.so",O_WRONLY|O_TRUNC|O_LARGEFILE) = 4
在 cp 使用“O_WRONLY|O_TRUNC”打开目标文件时,原 so文件的镜像被意外的破坏了。这样动态链接器 ld.so不能访问到 so文件中的函数入口。从而导致 Segmentation fault,程序崩溃。ld.so加载 so文件及“再定位”的机制比较复杂,详情可参见参考文献2。
2、怎样在不停止程序的情况下替换so文件,并且保证程序不会崩溃?
答案是采用“rm+cp”或“mv+cp”来替代直接“cp”的操作方法。
在用新的so文件 libnew.so替换旧的so文件libold.so时,如果采用如下方法:
rm libold.so
cp libnew.so libold.so
采用这种方法,目标文件 libold.so 的 inode 其实已经改变了,原来的 libold.so 文件虽然不能用 ”ls” 查看到,但其 inode 并没有被真正删除,直到内核释放对它的引用。同理, mv 只是改变了文件名,其 inode 不变,新文件使用了新的 inode 。这样动态链接器 ld.so 仍然使用原来文件的 inode 访问旧的 so 文件。因而程序依然能正常运行。到这里,我们回想在上线操作中在替换可执行程序时,为什么直接使用 “cp new old” 这样的命令时,系统会禁止这样的操作,并且给出这样的提示 “cp:cannot create regular file `old': Text file busy” 。这时,我们采用的办法仍然是用 “rm+cp” 或者 “mv+cp” 来替代直接 “cp” ,这跟以上提到的 so 文件的替换有同样的道理。但是,为什么系统会阻止 cp 覆盖可执行程序,而不阻止覆盖 so 文件呢?这是因为 Linux 有个 Demand Paging 机制,所谓 “Demand Paging” ,简单的说,就是系统为了节约物理内存开销,并不会程序运行时就将所有页( page )都加载到内存中,而只有在系统有访问需求时才将其加载。 “DemandPaging” 要求正在运行中的程序镜像(注意,并非文件本身)不被意外修改,因此内核在启动程序后会锁定这个程序镜像的 inode 。对于 so 文件,它是靠 ld.so 加载的,而 ld.so 毕竟也是用户态程序,没有权利去锁定 inode ,也不应与内核的文件系统底层实现耦合。