一、理论
1.程序的动态加载:即将可执行文件中当前要用到的部分从磁盘加载到内存,暂时不用的不加载;这样,可以让远大于物理内存的程序在机器上运行;
2.页管理与程序的动态加载:
物理内存:16KB;16KB/4KB = 4页,记F0~F3;
程序大小:32KB;32KB/4KB = 8页,记P0~P7。
完后分时加载,这部分有具体算法;不做讨论。
3.程序加载过程
首先,创建虚拟地址空间do_fork、完成物理地址到虚拟地址映射(非对应关系);
读取可执行文件头,建立虚拟空间和可执行文件的映射关系do_execv、完成虚拟地址到可执行文件中地址映射(对应关系);
将CPU的PC设置为可执行文件入口,启动运行。
说明:因为可执行文件在装载时是被映射的虚拟空间,所以、可执行文件又叫镜像文件。
二、结合execve()系统调用分析程序的装载/运行
注意:当系统调用返回的时候,控制权仍然在内核态中,它最终会负责切换到用户空间并让用户进程继续执行下去;将控制权切换给ret_from_sys,它会去检查那些在切换回用户空间之前需要完成的任务,如果没有需要做的事情,那么就恢复用户进程的状态,并将控制权交还给用户程序。
1.bionic库
bionic/libc/unistd/exec.c
#include <unistd.h> int execv(const char *name, char * const *argv){ (void)execve(name, argv, environ); return (-1); }
execve定义与实现:
bionic/libc/include/unistd.h
//C调用汇编 //声明该函数在外部实现;编译阶段不处理,在链接阶段和汇编中的符号做链接 extern int execve(const char *, char * const *, char * const *);
bionic/libc/arch-arm/syscalls/execve.S
#include <sys/linux-syscalls.h> execve: .save {r4, r7} stmfd sp!, {r4, r7} ;保护现场,将CPSR拷贝到SPSR ldr r7, =__NR_execve swi #0 ldmfd sp!, {r4, r7} ;恢复现场,将SPSR拷贝到CPSR movs r0, r0 bxpl lr b __set_syscall_errno .fnend
看看系统调用号:
bionic/libc/include/sys/linux-syscalls.h
#define __NR_execve (__NR_SYSCALL_BASE + 11)
2.SWI软中断到内核态
swi中断向量:
arch/arm/kernel/entry-common.S
ENTRY(vector_swi) addne scno, r7, #__NR_SYSCALL_BASE ;r7寄存器保存了系统调用编号! adr tbl, sys_call_table adr lr, ret_fast_syscall ;将ret_fast_syscall加载到lr连接寄存器 ldrcc pc, [tbl, scno, lsl #2] ;该指令执行完毕后,会执行lr寄存器中的指令,也就是返回用户空间 ENDPROC(vector_swi) #include "calls.S"arch/arm/kernel/calls.S
CALL(sys_execve_wrapper) //系统调用号为11arch/arm/kernel/entry-common.S
sys_execve_wrapper: add r3, sp, #S_OFF b sys_execve ENDPROC(sys_execve_wrapper)arch/arm/kernel/sys_arm.c
//汇编调用的C asmlinkage int sys_execve(char __user *filenamei, char __user * __user *argv, char __user * __user *envp, struct pt_regs *regs){ int error; char * filename; filename = getname(filenamei); error = PTR_ERR(filename); if (IS_ERR(filename)) goto out; error = do_execve(filename, argv, envp, regs); putname(filename); out: return error; }3.核心分析
fs/exec.c
int do_execve(char * filename, char __user *__user *argv, char __user *__user *envp, struct pt_regs * regs){ retval = search_binary_handler(bprm,regs);//程序装载 ...... acct_update_integrals(current);//将加载的程序地址赋给current指针,这样,当返回用户空间后就开始执行该程序 } 程序装载: int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs){ int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary; /* fs/binfmt_aout.c .load_binary = load_aout_binary, fs/binfmt_elf.c .load_binary = load_elf_binary, */ }
三、相关环境变量
1.补充共享库的静态加载路径
echo $LD_LIBRARY_PATH
/vendor/lib:/system/lib
比如,我需要将静态加载的共享库放到/data目录:
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/data
echo $LD_LIBRARY_PATH
/vendor/lib:/system/lib:/data
2.C可执行程序的环境变量
echo $PATH
/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
PATH=$PATH:/data
echo $PATH
/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin:/data3.JAVA相关
echo $JAVA_HOME
/usr/lib/jvm/java-6-sun"
echo $CLASSPATH
/usr/lib/jvm/java-6-sun/lib:.