进程的创建与可执行程序的加载

一、fork和exec系统调用在内核中的执行过程
  1)fork()函数用以创建普通进程:
  fork()函数是glibc中的API,通过软中断陷入内核,利用系统调用服务例程创建进程。fork()函数中封装了软中断指令int $0x80,执行这条汇编指令会跳转到预设的内核空间地址,该处为系统调用总处理程序system_call(),该函数作为系统调用服务例程执行前的引导。因为内核实现了很多不同的系统调用,所以进程必须指明需要哪个系统调用,这需要传递系统调用号作为函数的参数。所以,在执行int 0x80时,会将系统调用号传入eax寄存器来完成系统调用传参。
  接着,system_call()读取eax传递的系统调用号作为索引,通过存放在sys_call_table数组的系统调用分派表(dispatch table),找到该系统调用对应的服务例程并依次执行。相应的,该系统调用的服务例程为sys_fork(),而sys_fork()会调用do_fork()函数执行创建进程的操作(见附录一),具体的: 
  • 新的进程通过复制旧进程(即父进程)而建立。为了创建新进程,在系统物理内存中为新进程创建一个task_struct结构(使用 kmalloc 函数,以便得到一个连续的区域),将旧进程的 task_struct  结构内容复制到其中,再修改部分数据。
  • 为新进程分配新的核心堆栈页,分配新的进程标识符 pid。
  • 将这个新 task_struct 结构的地址填到task数组中,并调整进程链关系插入运行队列。于是这个新进程便可以在下次度时被选择执行。此时,由于父进程的上下文TSS结构复制到了子进程的 TSS结构中,通过改变其中的部分数据,便可以使子进程执行效果与父一致,都是从系统调用退出。
  • 系统调用返回:子进程将得到与父不同的返回值(返回父进程的是子进程的pid,而返回子进程的是 0)。

  另外,do_fork()中使用SIGCHLD标志作为参数,该克隆标志在处理后形成exit_signal,将在子进程退出时作为信号发送给父进程

  2)exec()函数将当前进程映像替换成新的程序文件:
  程序以可执行文件的形式存放在磁盘上,可执行文件是一个普通的文件,它描述了如何初始化一个新的进程上下文。与fork()相同,execl()也是glibc库中的API,用一个指定的可执行文件所描述的上下文代替进程的上下文。类似的,库函数
execlp,execle,execv,execve和execvp也执行相同的功能。
 
  对应于execl()的系统调用服务例程为sys_execve()函数,而sys_execve()会调用do_execve()创建进程上下文的操作(见附录二),具体的:
 

  • 删除已存在用户区域  
  • 映射私有区域 
  • 映射共享区域
  • 设置当前进程上下文的程序计数器,使之指向程序入口点
二、编程实现fork(创建一个进程实体) -> exec(将ELF可执行文件内容加载到进程实体) -> running program

见附录三

 三、可执行程序的加载

  在object文件中有三种主要的类型:

 1) 可重定位(relocatable)文件:保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。
 2) 一个可执行(executable)文件保存着一个用来执行的程序;该文件指出了exec()如何来创建程序进程映象。
 3)共享object文件:保存着代码和合适的数据,用来被链接器链接。被连接编辑器链接时,可以和其他的可重定位和共享object文件来创建其他的object;被动态链接器链接时,联合一个可执行文件和其他的共享object文件来创建一个进程映象。

  典型的ELF文件有如下图所示的格式:

  当加载器将可执行程序加载到内存中运行时,会根据ELF的段头部表,将可执行文件的代码和数据拷贝到进程的线性空间中,然后跳转到代码的第一条指令处执行。其中,创建可执行文件时,链接器会拷贝一些重定位和符号信息。这样,在执行程序时,加载器会根据.interp节的动态链接器的路径名,加载和运行相应的动态链接器,完成共享库的重定位。之后,将控制传递给应用程序,就可以执行共享库的代码了。通常,linux32的系统中,进程具有如下图所示的地址空间:

代码段中存放:全局常量(const)、字符串常量、函数以及编译时可决定的某些东西

数据段(初始化)中存放:初始化的全局变量、初始化的静态变量(全局的和局部的)

数据段(未初始化)(BSS)中存放:未初始化的全局变量、未初始化的静态变量(全局的和局部的)

堆中存放:动态分配的区域

栈中存放:局部变量(初始化以及未初始化的,但不包含静态变量)、局部常量(const)

附录:

一、linux 3.3x内核中fork()封装的系统调用:

arch/x86/syscalls/systcall_32.tbl

#
# 32-bit system call numbers and entry vectors
#
# The format is:
# <number> <abi> <name> <entry point> <compat entry point>
#
# The abi is always "i386" for this file.
#
0       i386    restart_syscall         sys_restart_syscall
1       i386    exit                    sys_exit
2       i386    fork                    sys_fork                 stub32_fork

 include/linux/syscalls.h

193#define SYSCALL_DEFINE0(name)      asmlinkage long sys_##name(void)

855asmlinkage long sys_fork(void);

kernel/fork.c

1641
1642#ifdef __ARCH_WANT_SYS_FORK
1643SYSCALL_DEFINE0(fork)
1644{
1645#ifdef CONFIG_MMU
1646        return do_fork(SIGCHLD, 0, 0, NULL, NULL);
1647#else
1648        /* can not support in nommu mode */
1649        return(-EINVAL);
1650#endif
1651}
1652#endif

 二、linux 3.3x内核中exec()封装的系统调用

arch/x86/syscalls/syscall_32.tbl

  2011      i386    execve                  sys_execve                      stub32_execve

include/linux/syscalls.h

 865asmlinkage long sys_execve(const char __user *filename,
 866                const char __user *const __user *argv,
 867                const char __user *const __user *envp);

arch/x86/kernel/process.c

/*
 * sys_execve() executes a new program.
 */
long sys_execve(const char __user *name,
        const char __user *const __user *argv,
        const char __user *const __user *envp, struct pt_regs *regs)
{
    long error;
    char *filename;

    filename = getname(name);
    error = PTR_ERR(filename);
    if (IS_ERR(filename))
        return error;
    error = do_execve(filename, argv, envp, regs);

#ifdef CONFIG_X86_32
    if (error == 0) {
        /* Make sure we don't return using sysenter.. */
                set_thread_flag(TIF_IRET);
        }
#endif

    putname(filename);
    return error;
}

 三、编程实现shell

1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <unistd.h>
  4 #include <stdlib.h>
  5 #include <sys/wait.h>
  6 #include <string.h>
  7 int main(){
  8         pid_t pid;
  9         char path[20];
 10         while(1){               
 11                 printf("print path(q to quit):\n");
 12                 scanf("%s",path);
 13                 if(!strcmp(path,"q"))
 14                         break;
 15                 pid=fork();
 16                 if(pid==0){
 17                         execl(path,NULL);
 18                         printf("child error end\n");
 19                         exit(0);
 20                 }       
 21                 else if(pid>0)
 22                         wait(NULL);
 23                 else{   
 24                         printf("ERROR");
 25                         exit(-1);
 26                 }
 27         } 
 28 } 
原文地址:https://www.cnblogs.com/wenxuanguan/p/3105705.html