我要升级一个程序,在程序运行的时候用新的程序文件替换旧的程序文件,然后杀死进程,重新启动程序。在程序运行的时候替换程序文件,会导致进程出现异常吗?
调用系统调用execve()装载ELF文件的时候,函数load_elf_binary()为主程序的代码段和数据段创建私有的文件映射,为动态链接器的代码段和数据段创建私有的文件映射。动态链接器加载主程序依赖的共享库的时候,调用函数mmap()为共享库的代码段和数据段创建私有的文件映射。
内核为每个文件创建一个页缓存。进程读代码段或者数据段中的某一页的时候,直接把文件的页缓存中的物理页映射到进程的虚拟地址空间,当进程修改这一页的时候,就会生成页错误异常,页错误异常处理程序为文件的页缓存中的物理页生成一个副本,然后把虚拟页映射到这个副本,和文件脱离关系。进程没有修改的虚拟页,直接映射到文件的页缓存中的物理页,如果修改文件的这一页,那么进程可以看到,会影响进程。
第1种替换方法:打开旧的程序文件,使用函数ftruncate()把文件截断到长度为0,然后把新的程序文件复制过来。
直接修改程序文件对进程有影响,假设进程正在函数func1()里面调用函数func2()的时候替换程序文件,函数func2()的位置变化,那么会跳转到一个未知的地方,导致进程出现异常。
第2种替换方法:使用函数unlink()删除旧的程序文件,重新创建文件,然后把新的程序文件复制过来。
假设程序文件是“/sbin/test.elf”,属于EXT4文件系统。glibc库的函数unlink()调用系统调用unlink(),系统调用unlink()的处理过程如下。
(1)修改存储设备上的EXT4文件系统:从父目录“sbin”的数据中删除文件“test.elf”对应的目录项;把文件“test.elf”的索引节点中的硬链接计数减1,如果文件“test.elf”只有一个硬链接,也就是只有一个名称,那么硬链接计数变为0,把索引节点插入到孤儿链表。
(2)文件“test.elf”在内存中的dentry结构体,把它从散列表删除,把它的引用计数减1,因为引用计数大于0,所以没有释放dentry结构体,没有真正删除文件。
删除一个文件的时候,如果某个进程已经打开这个文件,那么删除的结果是:从父目录删除这个文件对应的目录项,把文件的索引节点中的硬链接计数减到0,但是没有删除文件自身。如果进程一直打开这个文件,直到设备断电,就会造成存储设备上文件的索引节点和数据块泄漏。为了解决这个问题,EXT4文件系统使用孤儿链表,超级块的字段s_last_orphan保存孤儿链表的第一个索引节点的编号,ext4_inode结构体的字段i_dtime(删除时间)被重用为保存下一个孤儿索引节点的编号。设备重启以后,挂载EXT4文件系统的时候,如果孤儿链表不是空的,那么释放孤儿链表中的每个索引节点。
当杀死进程的时候,关闭文件“test.elf”,把内存中的dentry结构体的引用计数减1,引用计数变为0,于是释放dentry结构体,释放dentry结构体的过程中把inode结构体的引用计数减1,引用计数变为0,处理如下。
(1)因为索引节点中的硬链接计数是0,所以修改存储设备上的EXT4文件系统:把文件截断到长度为0,释放数据块;把文件从孤儿链表删除;释放索引节点。
(2)释放内存中的inode结构体。
在EXT4文件系统中,旧的程序文件和新的程序文件使用不同的索引节点编号,是2个不同的文件。这种替换方法对进程没有影响。