SylixOS动态加载器系列文章(5) 应用程序加载原理

SylixOS动态加载器系列文章(5) 应用程序加载

       前面介绍了SylixOS内核模块的加载,现在我们再来介绍下SylixOS应用程序和共享库的加载。应用程序和工程库是前面所说的位置无关ELF。是拥有段试图的ELF文件,相比内核模块文件,位置无关ELF文件更加便于加载,加载器无需再去拼接节,生成bss和common节以及计算,内存空间计算算法也要简单很多。但是应用程序加载要求加载器有更强大的处理能力,需要处理更多模块与模块之间的关系,进程与进程之间的关系。实时上SylixOS进程系统是从应用程序模块加载机制上逐步扩充出来的,目前,加载器仍然在进程管理管理中扮演着重要角色。

1.1     读取位置无关ELF

       应用程序和共享库都是位置无关的ELF文件,它们的区别只是应用程序文件提供一个入口地址,本文后续介绍中如无特殊情况我们全部使用共享库代替。共享库加载的第一步也是将文件读取到内存,加载器更加文件中的段表,读取应该加载的段,只需要加载类型为PT_LOAD的段。段表中记录了各个段应该加载到的内存位置,文件偏移和长度。对于位置无关ELF,链接器会假设ELF文件会被加载到0地址,加载器根据所有段的最大地址和最小地址计算内存长度,分派内存空间。ELF文件中所有内存地址信息需要根据分配的内存地址重新计算。SylixOS还会根据段标记位,将内存映射成可读、可写和可执行属性。SylixOS也支持写时复制技术,如果使能,内存会被已映射为共享页面。

       初始化内存只是读取ELF的第一步,在读取完成后,加载器需要解析ELF的dynamic段,dynmic段中汇集了ELF文件中几乎所有组织结果信息,这也是位置无关ELF比内核模块ELF更容易加载的原因之一,所有的组织结果信息都已经被整理好了。dynamic段同样是一个可加载段,段中的信息已表的形式存在,每个表项的类型字段表示该表项的意义。这些表项包括:符号表位置、长度,重定位表位置、长度,PLT表位置、依赖的库文件等信息。加载器将这些信息根据需要记录到一个临时结构体中。

1.2     导出符号

ELF文件读取到内存后,自身所有的符号位置都已经确定下来,这是可以将自身符号导出给系统。和内核模块一样,SylixOS为每个库文件建立独立的符号表保存在模块结构中,以便卸载是统一删除。导出符号的方式和内核模块文件基本一致,只是共享库从dynamic段获取符号表位置和大小。

1.3     处理依赖关系

加载共享库比加载内核模块复杂的地方主要在于需要自动处理库之间的依赖关系。如:A库依赖于B、C库,C库依赖于D、E库,这样形成一个树状结构,而如果E库又反过来依赖于A库,这样就形成一个环形依赖。每个库将自己依赖的库文件记录在dynamic段中,本例中A库中记录了B、C,C库中记录了D、E。SylixOS中以双向循环链表保存一个进程的模块链表,且在每个模块用一个指针表记录它所有依赖的下级模块。加载器读取一个模块完成后,根据依赖关系分别对其依赖的模块狗仔模块结构体添加到进程模块链表末尾,但不进行加载操作,只是标记为未加载状态,等本模块读取完成后再扫描环形链表,如果有未加载的模块再加载该模块,这样做主要是为了避免递归。递归会导致正序逻辑变复杂,堆栈深度不可控。当扫描模块链表发现没有未加载状态的模块结构时,所有模块便已经读取完成了。注意:这里只是读取完成,重定位操作需要等到所有模块读取完成后才能进行。系统模块图如下:

       SylixOS将kernel也看成一个独立的进程,内核模块被抽象为该进程依赖的库。上图总process1即为本文例子中的进程模块关系。


1.4     符号重定位

模块链表加载完成后,本次加载的模块尚未重定位,加载器遍历模块链表,逐一对模块进行重定位。重定位过程和内核模块类似,不同的是重定位表从dynamic段获取,外部符号的搜索顺序是:

l  搜索本进程符号表,即从本模块开始按循序遍历模块链表。

l  搜索内核符号表。

l  遍历内核模块链表,搜索所有全局模块。

可重定位ELF的外部符号引用全部使用GOT表实现,模块内部对外部符号直接地址引用全部转化为对GOT表中的间接地址引用。加载器将外部符号的绝对地址填入GOT表对因的表项中。不过SylixOS加载器不需要关系这些,对GOT的操作在ELF中被抽象为特定的重定位类型,加载器只需要按标准的方式处理它既可。

这里有必要提一下linux的延迟重定位技术,linux使用PLT表延迟了符号的重定位。linux加载才初始化阶段将GOT的地址填写为PLT表的对应表项的地址。PLT表的每个表项由一段代码组成,这段代码的功能是:将本项对应的符号信息、GOT信息作为参数调用加载器的函数,函数调用返回后再次跳转到GOT项地址。所有当程序第一次执行到GOT地址时其实是被转接到加载器函数,该函数完成符号的查找并填写到参数中GOT表项。如此,当程序再次执行该GOT项地址时就直接跳转到外部符号。

SylixOS不支持延迟加载,因为其应用场合不需要这种机制。

1.5     初始化和销毁函数调用

初始化和销毁函数不需要像内核模块那样由节名称指定,而是从dynamic段获取函数表指针和大小。库文件初始化函数在所有模块重定位完成后执行,加载器从本模块开始顺序遍历模块链表,执行尚未调用初始化函数的模块中的初始化函数表。销毁函数的执行则刚好相反,也是从本模块开始逆序遍历模块链表,执行模块引用计数为0的模块中的销毁函数列表。本文中所述本模块是指本次加载的主模块,如果是执行应用程序,则为进程主模块(我们将模块链表的第一个模块称为进程主模块),而如果使用dlopen API加载动态库,则为API中指定的模块。

1.6     模块状态和模块引用计数

本文中多次提到遍历模块链表,遍历过程中我们如何区分哪些模块需要处理,哪些模块不需要,模块状态和模块引用计数用于实现这个功能。模块状态标识模块当前处于加载过程中的哪个步骤,如模块结构体初始化完成,我们将模块标识为未加载状态,加载器在加载的遍历流程中加载处于未加载状态的模块,同样的原理可以推广到重定位和初始化函数调用的遍历流程。而模块计数用于记录本模块还有几个模块在使用,当模块引用计数为0是,在调用销毁函数和释放内存的遍历流程便可以处理它。

1.7     模块入口

模块入口有可执行ELF指定,一般为main函数,加载器将其记录到进程主模块中,在进程加载完成后,由系统调用。

1.8     模块卸载

当进程退出时,或者调用dlclose时,系统卸载模块。模块卸载流程如下:

l  首先根据名称查找模块结构,将其引用计数-1。

l  反复遍历进程模块链表,将引用计数为0的模块所依赖的模块引用计数-1,直到一次遍历完成,所有模块的引用计数都不在改变为止。

l  遍历模块链表,执行所有引用计数为0的模块中的销毁函数列表。

l  遍历模块链表,删除和释放所有引用计数为0的模块。


SylixOS官网:www.sylixos.com

SylixOS源码下载:git.sylixos.com

SylixOS百科:wiki.sylixos.com


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值