理解進程的退出

  • 2020 年 3 月 12 日
  • 筆記

當一個進程調用exit的時候,就意味着他退出了。我們看一下他退出的時候,都做了什麼操作。

int sys_exit(int error_code)  {       return do_exit((error_code&0xff)<<8);  }     int do_exit(long code)   {       int i;       // 釋放代碼段和數據段頁表,頁目錄,物理地址       free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));       free_page_tables(get_base(current->ldt[2]),get_limit(0x17));       for (i=0 ; i<NR_TASKS ; i++)           // 找出當前進程的子進程           if (task[i] && task[i]->father == current->pid) {               // 子進程的新父進程是進程id為1的進程               task[i]->father = 1;               /*               父進程沒有調wait,子進程退出了,然後父進程也退出了。沒人回收子進程的pcb,給init進程發              */               if (task[i]->state == TASK_ZOMBIE)                   /* assumption task[1] is always init */                   (void) send_sig(SIGCHLD, task[1], 1);           }       // 關閉文件       for (i=0 ; i<NR_OPEN ; i++)           if (current->filp[i])               sys_close(i);       // 回寫inode到硬盤       iput(current->pwd);       current->pwd=NULL;       iput(current->root);       current->root=NULL;       iput(current->executable);       current->executable=NULL;       // 是會話首進程並打開了終端       if (current->leader && current->tty >= 0)           tty_table[current->tty].pgrp = 0;       if (last_task_used_math == current)           last_task_used_math = NULL;       // 是會話首進程,則通知會話里的所有進程會話結束       if (current->leader)           kill_session();       // 更新狀態       current->state = TASK_ZOMBIE;       current->exit_code = code;       // 通知父進程       tell_father(current->father);       // 重新調度進程       schedule();       return (-1);    /* just to suppress warnings */   }

釋放地址

// from是線性地址。釋放from開始,連續的n個大小為4MB的頁面對應的物理地址。最後釋放頁表、頁目錄項      int free_page_tables(unsigned long from,unsigned long size)  {          unsigned long *pg_table;          unsigned long * dir, nr;          // 判斷是否按4MB對齊          if (from & 0x3fffff)              panic("free_page_tables called with wrong alignment");          if (!from)              panic("Trying to free up swapper memory space");          // 算出size包含多少個MB,比如size是0 - 1>>22,則計算機後是1          size = (size + 0x3fffff) >> 22;          /*              頁目錄在地址0開始的地方,首先右移得到頁目錄索引,              根據索引得到頁目錄項內容,因為頁目錄項的內容佔4個位元組,              其中高20位是頁表地址,低12位是標記位,,所以要乘以4得到              from對應的頁目錄項的地址。即dir = from >> 22 << 2 = from >> 20,              但是代碼里是直接右移20位,所以需要和0xffc與,把低兩位置0,最後得到from              對應的頁目錄項的地址          */          dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */          for ( ; size-->0 ; dir++) {              // 低位是1說明該頁目錄項有效              if (!(1 & *dir))                  continue;              // *dir為頁表首地址,與0xfffff000是因為高二十位是有效地址,低12位是標記位              pg_table = (unsigned long *) (0xfffff000 & *dir);              // 釋放每個頁表指向的物理地址              for (nr=0 ; nr<1024 ; nr++) {                  // 頁表是否有效,有效則釋放*pg_table指向物理地址,以4kb對齊                  if (1 & *pg_table)                      // 與0xfffff000是因為高二十位是有效地址,低12位是標記位                      free_page(0xfffff000 & *pg_table);                  // 置頁表無效                  *pg_table = 0;                  // 下一個頁表                  pg_table++;              }              // 釋放頁表佔據的物理地址              free_page(0xfffff000 & *dir);              // 置頁目錄項為無效              *dir = 0;          }          invalidate();          return 0;      }

結束會話

// 結束會話,給該會話的所有進程發SIGHUP信號,因為子進程會繼承父進程的sessionid,所以if可能會多次成立      static void kill_session(void)  {          struct task_struct **p = NR_TASKS + task;            while (--p > &FIRST_TASK) {              if (*p && (*p)->session == current->session)                  (*p)->signal |= 1<<(SIGHUP-1);          }      }

通知父進程

// 子進程退出,通知進程id是pid的父進程      static void tell_father(int pid)      {          int i;            if (pid)              for (i=0;i<NR_TASKS;i++) {                  if (!task[i])                      continue;                  if (task[i]->pid != pid)                      continue;                  // 根據pid找到父進程,設置子進程退出的信號                  task[i]->signal |= (1<<(SIGCHLD-1));                  return;              }      /* if we don't find any fathers, we just release ourselves */      /* This is not really OK. Must change it to make father 1 */          printk("BAD BAD - no father foundnr");          // 釋放pcb結構          release(current);      }

我們發生子進程釋放了一系列的資源,但是沒有釋放pcb。即task_struct結構體。這時候子進程是殭屍進程。需要等待父進程處理。

// 等待pid進程退出,並且把退出碼寫到stat_addr變量  int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)  {      int flag, code;      struct task_struct ** p;        verify_area(stat_addr,4);  repeat:      flag=0;      for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) {          // 過濾不符合條件的          if (!*p || *p == current)              continue;          // 不是當前進程的子進程則跳過          if ((*p)->father != current->pid)              continue;          // pid大於0說明等待某一個子進程          if (pid>0) {              // 不是等待的子進程則跳過              if ((*p)->pid != pid)                  continue;          } else if (!pid) {              // pid等於0則等待進程組中的進程,不是當前進程組的進程則跳過              if ((*p)->pgrp != current->pgrp)                  continue;          } else if (pid != -1) {              // 不等於-1說明是等待某一個組的,但不是當前進程的組,組id是-pid的組,不是該組則跳過              if ((*p)->pgrp != -pid)                  continue;          }          // else {          //  等待所有進程          // }          // 找到了一個符合條件的進程          switch ((*p)->state) {              // 子進程已經退出,這個版本沒有這個狀態              case TASK_STOPPED:                  if (!(options & WUNTRACED))                      continue;                  put_fs_long(0x7f,stat_addr);                  return (*p)->pid;              case TASK_ZOMBIE:                  // 子進程已經退出,則返回父進程                  current->cutime += (*p)->utime;                  current->cstime += (*p)->stime;                  flag = (*p)->pid;                  code = (*p)->exit_code;                  release(*p);                  put_fs_long(code,stat_addr);                  return flag;              default:                  // flag等於1說明子進程還沒有退出                  flag=1;                  continue;          }      }      // 還沒有退出的進程      if (flag) {          // 設置了非阻塞則返回          if (options & WNOHANG)              return 0;          // 否則父進程掛起          current->state=TASK_INTERRUPTIBLE;          // 重新調度          schedule();          /*              在schedule函數里,如果當前進程收到了信號,會變成running狀態,              如果current->signal &= ~(1<<(SIGCHLD-1)))為0,即...0000000100000... & ...111111110111111...              說明當前需要處理的信號是SIGCHLD,因為signal不可能為全0,否則進程不可能被喚醒,              即有子進程退出,跳到repeat找到該退出的進程,否則說明是其他信號導致了進程變成可執行狀態,              阻塞的進程被信號喚醒,返回EINTR          */          if (!(current->signal &= ~(1<<(SIGCHLD-1))))              goto repeat;          else              return -EINTR;      }      return -ECHILD;  }      //父進程在wait_pid調用時釋放pcb的一頁內存,重新調度進程      void release(struct task_struct * p)      {          int i;            if (!p)              return;          for (i=1 ; i<NR_TASKS ; i++)              if (task[i]==p) {                  task[i]=NULL;                  free_page((long)p);                  schedule();                  return;              }          panic("trying to release non-existent task");      }