对于每次只加载 一个程序而言,是容易实现的
首先需要一个加载程序,假设为loader,需要被加载的程序为a,b,c,d,e……
以下所说的情况都没有考虑在程序切换前保存堆栈,状态寄存器等信息,这实际上是多任务切换所必须的,在实际操作的时候注意保存和恢复这些信息
(如果跳回loader是跳到loader起始地址也就是说重新从头运行loader则不需要这样做)
以下的实现与函数参数压栈规则、函数返回规则等相关,所以在不同的cpu上可能会有些差异,主要在于函数用跳转指令跳转之后如何返回的问题,这需要参考编译器实现和语言标准来确定
分为两种情况讨论
1.被加载的程序完全为机器码,也就是说不像exe,elf等格式一样除了机器码还有一些程序段的定位信息及其他一些信息。
这种情况下所有的被加载程序在编译时指定一个基准地址,其他地址分配从这个基准地址开始,用loader找到需要被加载的程序a,loader将a直接复制到基准地址开始的内存空间中(基准地址之前的空间一定要能完全容纳loader),保存此时的程序指针,然后将程序指针指向基准地址实现向a的跳转,所有的被加载程序在结束语句中添加跳转到loader的起始地址的语句,在a的退出函数中清理堆栈并且利用保存的返回指针回到loader继续执行。
优点:简单,容易实现
缺点:运行a之后运行b,之后再运行a,这时需要重新加载a
2.在被加载程序中增加某些信息
这个没仔细研究过,大概说一些解决办法
(1)程序头存放一张表,其中指出了每条函数调用在程序中的位置,程序被编译成位置无关的(对函数的调用实际上是相关的,数据地址是相对地址)。
loader在加载a到某一个地址,根据表中的信息和a被加载的地址,重新修改程序中函数调用的地址,然后再重复1中的工作。loader自己记录一张表存放各个程序对内存的占用情况以便确定加载下一个程序的地址。
(2)程序中的函数调用(以及取函数的地址等)全改为对函数指针的操作,程序头同样存放一张表,表中表明了每个函数指针在程序中的偏移,程序中的数据同样是位置无关的(也就是通过pc相对寻址取得,无论程序被加载到哪个位置,对数据的存取都不会出问题)。loader加载a到某一地址后,根据这个地址和表,修改每个函数指针的值,以便指向函数的实际地址,其他同(1)。这种方法实现起来更容易一些,缺点是不能直接使用函数调用了。
缺点:复杂,容易出错
优点:能同时加载多个程序,利用中断甚至可以实现动态的切换程序(而不是每次都要退出后重新进入程序)。
至于api的实现,实际上也是比较容易的,关键就在于函数地址的确定,比较简单的方式是准备一张表保存所有api函数的地址(类似于中断跳转表那样),如果不熟悉编译器的话。比方我们实现一个api函数memcpy,写一个memcpy.c实现这个函数,为了简便,我们再准备一个main.c其中有个main函数(只是为了编译通过而设,实际上是可以不需要main函数的,只是需要额外的处理),将二者编译之。将生成的二进制文件放到某个地址addr,写一个c源文件(汇编也可),在其中建立同名函数memcpy,函数体中只有一条使用内联汇编写的跳转语句,这条跳转语句跳到【addr+memcpy在程序中的偏移量】(此处要注意如何返回)。将最后写的这个c源文件编译成目标文件即是我们想要的库了,以后其他程序直接与这个目标文件链接即可。这种方法在确定memcpy咋程序中的偏移量时需要查map文件,稍显麻烦,其实还有一些其他自动化的方法……