Q1
# 程序是怎么加载进内存的
[toc]
# 加载可执行目标文件过程
当在shell中运行一个可执行目标文件时, 会fork自身然后调用execve函数,
execve函数首先会读取文件开头的一小部分字节, 并通过文件头标识判断是什么文件, 如果运行一个可执行文件(elf文件), 会调用对应的loader,
可执行文件, linux下为elf文件, 这种文件格式是分段的,一般由文件头指明各种信息, 其后为分段储存的信息, 大致可以分为数据段和代码段,
loader是一个操作系统级别的代码,启动后使用mmap将elf文件的代码段和数据段映射到内存, 此时都是虚拟内存,只有运行到这个位置时才会映射对应的物理内存,
然后应该是设置execve的返回地址为链接器的start函数,于是execve从内核级别代码运行结束返回时, 寄存器弹出被设置为对应的值, 指向栈段数据段等,这里返回的操作也类似与进程的切换, 只不过是原进程进入内核, 然后内核态代码会创建新进程,并返回时切换到新进程,
链接器会把在elf文件中标识的动态链接库载入内存, 然后初始化got和plt表, 当然这里也是虚拟内存,而且got表和plt表只有在程序中使用到时才会进行地址的解析绑定,然后链接器进行其他一些初始化工作,并且将对应的参数argv和env压栈, 跳转到可执行文件的_start函数运行,
于是可执行文件被装载和动态链接, 正式开始运行,_start函数,会进入__libc_start_main函数(这是动态链接库中的函数), 并在其中调用main函数, 于是正式进入用户代码运行,
进程在运行用户态代码时,会经常出现系统调用的使用,在系统调用结束时,可能会由操作系统在内核态进行进程切换,这时就形成了多进程的同时运行,但是在单独某个进程看起来,和自己在独立的运行其实没有什么两样,
当进程的用户代码运行结束,即main函数会返回, 会回到__libc_start_main
函数,并进行一些收尾工作,退出,整个进程结束,
其中由于所有的进程都是通过shell的fork+execve得到的,所以加载器代码其应该就在execve的具体实现中, 另一点由于这样的机制所以操作系统中的进程形成了树状结构的进程树,