《C编译原理》ubuntu下helloworld程序加载运行分析

一、理论

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) //系统调用号为11
arch/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:/data
3.JAVA相关

echo $JAVA_HOME

/usr/lib/jvm/java-6-sun"

echo $CLASSPATH

/usr/lib/jvm/java-6-sun/lib:.


你可能感兴趣的:(《C编译原理》ubuntu下helloworld程序加载运行分析)