exec系统调用 && 进程的加载过程

exec系统调用会从指定的文件中读取并加载指令,并替代当前调用进程的指令。从某种程度上来说,这样相当于丢弃了调用进程的内存,并开始执行新加载的指令。

  • exec系统调用会保留当前的文件描述符表单。所以任何在exec系统调用之前的文件描述符,例如0,1,2等。它们在新的程序中表示相同的东西。

  • 通常来说exec系统调用不会返回,因为exec会完全替换当前进程的内存,相当于当前进程不复存在了,所以exec系统调用已经没有地方能返回了。

在运行shell时,我们不希望系统调用替代了Shell进程,实际上,Shell会执行fork,这是一个非常常见的Unix程序调用风格。对于那些想要运行程序,但是还希望能拿回控制权的场景,可以先执行fork系统调用,然后在子进程中调用exec。

以shell程序运行ls命令为例

int main(){
	int pid;
  ...
	if(fork() == 0){
    //子进程操作
    //加载新的程序后当前的内容将全部被舍弃,所以不会执行到下面打印函数
		exec("ls","-al");
	} else {
		//父进程操作
		do something...
	}
  printf("finish");
}

fork函数和exec函数共同组成了新进程的加载方式,这也是计算机创建新进程的一般方式(也许是唯一的方式)

下面代码展示了一个进程的内存映像究竟是如何一步一步建立的,还涉及了一些关于ELF可执行文件的知识(见附)。

希望能通过代码,让大家认识到进程实际上并没有那么神秘、复杂,对计算机的进程模型能有个更深的认识。

进程内存镜像
代码解析
int
exec(char *path, char **argv)
{
  char *s, *last;
  int i, off;
  uint64 argc, sz = 0, sp, ustack[MAXARG+1], stackbase;
  struct elfhdr elf;
  struct inode *ip;
  struct proghdr ph;
  pagetable_t pagetable = 0, oldpagetable;
  struct proc *p = myproc();

  begin_op();
	//获取path路径处的文件,即读取要加载的可执行文件
  if((ip = namei(path)) == 0){
    end_op();
    return -1;
  }
  ilock(ip);

  // Check ELF header
  // 先从文件中读取elf信息
  if(readi(ip, 0, (uint64)&elf, 0, sizeof(elf)) != sizeof(elf))
    goto bad;
  if(elf.magic != ELF_MAGIC)
    goto bad;
	
  //创建一个新的页表
  if((pagetable = proc_pagetable(p)) == 0)
    goto bad;

  // Load program into memory.
  // 借助elf中的phoff属性(program section header off 程序段头结点在elf文件中的偏移量)
  // 将程序所有的section写入其指定位置(在可执行程序编译时,其就指定好了哪个段在哪个逻辑地址)
  for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){
    //从文件中读取一个section header到ph中
    if(readi(ip, 0, (uint64)&ph, off, sizeof(ph)) != sizeof(ph))
      goto bad;
    if(ph.type != ELF_PROG_LOAD)
      continue;
    if(ph.memsz < ph.filesz)
      goto bad;
    if(ph.vaddr + ph.memsz < ph.vaddr)
      goto bad;
    uint64 sz1;
    //按照section header中的逻辑地址(ph.vaddr)和段长信息,在页表中开辟新的空间
    if((sz1 = uvmalloc(pagetable, sz, ph.vaddr + ph.memsz)) == 0)
      goto bad;
    sz = sz1;
    if(ph.vaddr % PGSIZE != 0)
      goto bad;
    // Load a program segment into pagetable at virtual address va.
    // 将segment写入到页表(即内存)中的对应位置
    if(loadseg(pagetable, ph.vaddr, ip, ph.off, ph.filesz) < 0)
      goto bad;
  }
  iunlockput(ip);
  end_op();
  ip = 0;
	
  //将可执行文件的内容全部写入内存后,开始创建堆栈
  p = myproc();
  uint64 oldsz = p->sz;

  // Allocate two pages at the next page boundary.
  // Use the second as the user stack.
  sz = PGROUNDUP(sz);
  uint64 sz1;
  //分配两个page,第二个用来充当用户栈
  if((sz1 = uvmalloc(pagetable, sz, sz + 2*PGSIZE)) == 0)
    goto bad;
  sz = sz1;
  uvmclear(pagetable, sz-2*PGSIZE);
  sp = sz;
  stackbase = sp - PGSIZE;

  // Push argument strings, prepare rest of stack in ustack.
  // 把执行参数写入到栈中
  for(argc = 0; argv[argc]; argc++) {
    if(argc >= MAXARG)
      goto bad;
    sp -= strlen(argv[argc]) + 1;
    //内存对齐
    sp -= sp % 16; // riscv sp must be 16-byte aligned
    if(sp < stackbase)
      goto bad;
    //拷贝到栈中
    if(copyout(pagetable, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
      goto bad;
    ustack[argc] = sp;
  }
  ustack[argc] = 0;

  // push the array of argv[] pointers.
  //把参数数组的指针拷入到栈中
  sp -= (argc+1) * sizeof(uint64);
  sp -= sp % 16;
  if(sp < stackbase)
    goto bad;
  if(copyout(pagetable, sp, (char *)ustack, (argc+1)*sizeof(uint64)) < 0)
    goto bad;

  // arguments to user main(argc, argv)
  // argc is returned via the system call return
  // value, which goes in a0.
  // 把数组指针(即参数列表)写入到a1寄存器(该寄存器存储了函数第二个参数)
  p->trapframe->a1 = sp;

  // Save program name for debugging.
  //把文件名设置成进程名
  for(last=s=path; *s; s++)
    if(*s == '/')
      last = s+1;
  safestrcpy(p->name, last, sizeof(p->name));
    
  // Commit to the user image.
  // 设置进程属性,并且将相应的寄存器置为初始状态
  oldpagetable = p->pagetable;
  p->pagetable = pagetable;
  p->sz = sz;
  p->trapframe->epc = elf.entry;  // initial program counter = main
  p->trapframe->sp = sp; // initial stack pointer
  proc_freepagetable(oldpagetable, oldsz);
	
  //A0用来存储返回值/函数参数,
  return argc; // this ends up in a0, the first argument to main(argc, argv)

 bad:
  if(pagetable)
    proc_freepagetable(pagetable, sz);
  if(ip){
    iunlockput(ip);
    end_op();
  }
  return -1;
}

附:

ELF
posted @ 2021-10-28 22:41  zhushoucheng  阅读(1025)  评论(0编辑  收藏  举报