从一个ELF程序的加载窥探操作系统内核-(5)

从一个ELF程序的加载窥探操作系统内核-(5)

操作系统加载一个ELF程序看似一个EASY的动作,其实下面隐藏了很多很多OS内核的关键实现,让我们一起来解密其中的流程

作者是一个micro kernel的开发者,在设计动态链接器的时候,在此留下一些笔记,重点参考了以下资料文献

  • 《程序员的自我修养》
  • 《深入理解计算机系统》
  • 《现代操作系统-原理与实现》
  • 《深入理解LINUX内核》
  • 《设计模式/JAVA》
LINUX下的ELF加载器究竟是如何完成的

ELF加载器其实和OS的实现是紧密捆绑在一起的,就像glibc捆绑了linux一样,可移植性是很差的

  • LINUX下的ELF加载流程如下
    在这里插入图片描述

当ELF程序需要解释器(动态链接器)的时候,LINUX内核只完成了初步的解析工作,剩下的工作就转给链接器去完成了,也就是大名鼎鼎的ld.so

Linux下一个最小程序,也必须包括ld.so和libc.so这两个动态库

  • ld.so看起来是个动态库,实际上他是静态的,这里说的静态指的是他的运行不依赖任何外部库,只是被编译成了PIC了,算作披着羊皮的狼吧。但是既然ld被编译成了动态库的形式,那么必然有重定位工作要做,也就是对自己的重定位,重定位后就可以正常使用ld.so内的函数和全局变量了
  • 有同学说既然是这样,为什么不直接编译静态库算了,链接器自举操作纯属脱了裤子放屁,归根结底还是为了节省内存罢了

glibc下的ld.so链接器流程如下
在这里插入图片描述
链接器最重要的工作就是映射依赖库以及重定位工作,映射工作主要依赖mmap这个系统调用,重定位是链接器最复杂的,里面的细节可以参考ELF加载器的原理与实现

  • 完成重定位工作后,最后一步将程序转移到crt,crt是一个C运行时环境,CRT有什么用呢?
  • 很多同学有疑问?既然链接器都完成了全部工作,不应该跳转到main去运行程序吗?实际上在执行main前我们还需要为main做一点准备工作,这个工作就是由CRT完成

CRT主要有两个工作

  1. 用户堆管理的初始化
    1. malloc用户进程的堆分配是放在libc里来完成的,但是这个内存管理器的初始化工作是在crt中完成的!
  2. 输入输出设备初始化
    1. 如果要使用printf,实际上最后调用的是write系统调用(fd=STDOUT=1),初始化stdin/stdout/stderr这三个全局变量和相关权限的工作是在crt中完成的

伪代码如下

extern int main(int argc, char *argv[]);
extern int crt_io_initialize(void);
extern int crt_heap_initialize(void);

void exit(int code)
{

}

int crt_main_entry(int argc, char **argv)
{
	int ret;

	/* 初始化IO资源管理器 */
	crt_io_initialize();

	/* 初始化进程堆管理器 */
	crt_heap_initialize();

	/* 跳转到main */
	ret = main(argc, argv);

	/* 进程退出 */
	//exit(ret);

	return ret;
}

简单看一下IO是如何初始化的

struct streamlist tg_streamlist;

#define STDIN_FILENO                       0       /* File number of stdin */
#define STDOUT_FILENO                    1       /* File number of stdout */
#define STDERR_FILENO                     2       /* File number of stderr */

#define stdin       (&tg_streamlist.sl_std[STDIN_FILENO])
#define stdout     (&tg_streamlist.sl_std[STDOUT_FILENO])
#define stderr      (&tg_streamlist.sl_std[STDERR_FILENO])

tg_streamlist.sl_std[0].fs_fd        = STDIN_FILENO;
tg_streamlist.sl_std[0].fs_oflags   = O_RDONLY;

tg_streamlist.sl_std[1].fs_fd        = STDOUT_FILENO;
tg_streamlist.sl_std[1].fs_oflags   = O_WROK | O_CREAT;

tg_streamlist.sl_std[2].fs_fd        = STDERR_FILENO;
tg_streamlist.sl_std[2].fs_oflags   = O_WROK | O_CREAT;

简单看一下用户堆是如何初始化的

先使用brk(0)确定heap的起始地址,然后默认分配132KB内存,再初始化具体的内存管理算法,glibc是ptmalloc,后面的malloc和free就由内存管理算法去分配与释放

int crt_heap_initialize(void)
{
	void *base = NULL;

	/* 参考linux分配超过128KB使用MMAP
	 * 默认大小为128KB(0x20000)
	 * 内存管理的数据结构本身还需占用空间,则扩容至132KB(0x21000)
	 */
	unsigned long heap_size = 132 * 1024;

	base = (void *)brk(0);
	if (!base) {
		return -1;
	}

	void *end = (void *)((unsigned long)base + heap_size);
	end = (void *)brk(end);
	if (!end) {
		return -1;
	}

	return mm_heap_initialize(base, heap_size);
}

看一下我们的ELF链接脚本该如何写

ENTRY(crt_main_entry);

SECTIONS
{
    . = 0x08040000;
    .text :
    {
        *(.text .text.*)
    }
    .rodata :
    {
        *(.rodata .rodata.*)
    }
	. = ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1));
    .data :
    {
        *(.data .data.*)
    }
	
    .bss :
    {
        *(.bss .bss.*)
    }
}

总结一下crt的流程

在这里插入图片描述

最后crt最后以crt.o的方式被链接到elf程序中

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值