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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM