僵尸進程(轉)

前面的文章中,我們已經了解了父進程和子進 程的概念,并已經掌握了系統調用exit的用法,但可能很少有人意識到,在一個進程調用了exit之后,該進程并非馬上就消失掉,而是留下一個稱為僵尸進 程(zombie)的數據結構。在linux進程的5種狀態中,僵尸進程是非常特殊的一種,它已經放棄了幾乎所有內存空間,沒有任何可執行代碼,也不能被 調度,僅僅在進程列表中保留一個位置,記載該進程的退出狀態等信息供其他進程收集,除此之外,僵尸進程不再占有任何內存空間。從這點來看,僵尸進程雖然有 一個很酷的名字,但它的影響力遠遠抵不上那些真正的僵尸兄弟,真正的僵尸總能令人感到恐怖,而僵尸進程卻除了留下一些供人憑吊的信息,對系統毫無作用。
  
  也許讀者們還對這個新概念比較好奇,那就讓我們來看一眼linux里的僵尸進程究竟長什么樣子。
  
  當一個進程已退出,但其父進程還沒有調用系統調用wait(稍后介紹)對其進行收集之前的這段時間里,它會一直保持僵尸狀態,利用這個特點,我們來寫一個簡單的小程序:
  
  /* zombie.c */
  #include
  #include
  main()
  {
   pid_t pid;
  
   pid=fork();
  
   if(pid<0) /* 如果出錯 */
   printf("error occurred!n");
   else if(pid==0) /* 如果是子進程 */
   exit(0);
   else /* 如果是父進程 */
   sleep(60); /* 休眠60秒,這段時間里,父進程什么也干不了 */
   wait(null); /* 收集僵尸進程 */
  }
  
  
  
  sleep的作用是讓進程休眠指定的秒數,在這60秒內,子進程已經退出,而父進程正忙著睡覺,不可能對它進行收集,這樣,我們就能保持子進程60秒的僵尸狀態。
  
  編譯這個程序:
  
  $ cc zombie.c -o zombie
  
  
  
  后臺運行程序,以使我們能夠執行下一條命令:
  
  $ ./zombie &
  [1] 1577
  
  
  
  列一下系統內的進程:
  
  $ ps -ax
  ... ...
   1177 pts/0 s 0:00 -bash
   1577 pts/0 s 0:00 ./zombie
   1578 pts/0 z 0:00 [zombie ]
   1579 pts/0 r 0:00 ps -ax
  
  
  
  看到中間的“z”了嗎?那就是僵尸進程的標志,它表示1578號進程現在就是一個僵尸進程。
  
   我們已經學習了系統調用exit,它的作用是使進程退出,但也僅僅限于將一個正常的進程變成一個僵尸進程,并不能將其完全銷毀。僵尸進程雖然對其他進程 幾乎沒有什么影響,不占用cpu時間,消耗的內存也幾乎可以忽略不計,但有它在那里呆著,還是讓人覺得心里很不舒服。而且linux系統中進程數目是有限 制的,在一些特殊的情況下,如果存在太多的僵尸進程,也會影響到新進程的產生。那么,我們該如何來消滅這些僵尸進程呢?
  
  先來了解 一下僵尸進程的來由,我們知道,linux和unix總有著剪不斷理還亂的親緣關系,僵尸進程的概念也是從unix上繼承來的,而unix的先驅們設計這 個東西并非是因為閑來無聊想煩煩其他的程序員。僵尸進程中保存著很多對程序員和系統管理員非常重要的信息,首先,這個進程是怎么死亡的?是正常退出呢,還 是出現了錯誤,還是被其它進程強迫退出的?其次,這個進程占用的總系統cpu時間和總用戶cpu時間分別是多少?發生頁錯誤的數目和收到信號的數目。這些 信息都被存儲在僵尸進程中,試想如果沒有僵尸進程,進程一退出,所有與之相關的信息都立刻歸于無形,而此時程序員或系統管理員需要用到,就只好干瞪眼了。
  
  那么,我們如何收集這些信息,并終結這些僵尸進程呢?就要靠我們下面要講到的waitpid調用和wait調用。這兩者的作用都是收集僵尸進程留下的信息,同時使這個進程徹底消失。下面就對這兩個調用分別作詳細介紹。
  
  
  wait
  
  
  簡介
  
  wait的函數原型是:
  
  #include /* 提供類型pid_t的定義 */
  #include
   pid_t wait(int *status)
  
  
  
   進程一旦調用了wait,就立即阻塞自己,由wait自動分析是否當前進程的某個子進程已經退出,如果讓它找到了這樣一個已經變成僵尸的子進 程,wait就會收集這個子進程的信息,并把它徹底銷毀后返回;如果沒有找到這樣一個子進程,wait就會一直阻塞在這里,直到有一個出現為止。
  
  參數status用來保存被收集進程退出時的一些狀態,它是一個指向int類型的指針。但如果我們對這個子進程是如何死掉的毫不在意,只想把這個僵尸進程消滅掉,(事實上絕大多數情況下,我們都會這樣想),我們就可以設定這個參數為null,就象下面這樣:
  
  pid = wait(null);
  
  
  
  如果成功,wait會返回被收集的子進程的進程id,如果調用進程沒有子進程,調用就會失敗,此時wait返回-1,同時errno被置為echild。
  
  實戰
  
  下面就讓我們用一個例子來實戰應用一下wait調用,程序中用到了系統調用fork,如果你對此不大熟悉或已經忘記了,請參考上一篇文章進程管理相關的系統調用(1)。
  
  /* wait1.c */
  #include
  #include
  #include
  #include
  main()
  {
   pid_t pc,pr;
  
   pc=fork();
  
   if(pc<0) /* 如果出錯 */
   printf("error ocurred!n");
   else if(pc==0){ /* 如果是子進程 */
   printf("this is child process with pid of %dn",getpid());
   sleep(10); /* 睡眠10秒鐘 */
   }
   else{ /* 如果是父進程 */
   pr=wait(null); /* 在這里等待 */
   printf("i catched a child process with pid of %dn"),pr);
   }
   exit(0);
  }
  
  
  
  編譯并運行:
  
  $ cc wait1.c -o wait1
  $ ./wait1
  this is child process with pid of 1508
  i catched a child process with pid of 1508
  
  
  
   可以明顯注意到,在第2行結果打印出來前有10秒鐘的等待時間,這就是我們設定的讓子進程睡眠的時間,只有子進程從睡眠中蘇醒過來,它才能正常退出,也 就才能被父進程捕捉到。其實這里我們不管設定子進程睡眠的時間有多長,父進程都會一直等待下去,讀者如果有興趣的話,可以試著自己修改一下這個數值,看看 會出現怎樣的結果。
  
  參數status
  
  如果參數status的值不是null,wait就會把子進程退出 時的狀態取出并存入其中,這是一個整數值(int),指出了子進程是正常退出還是被非正常結束的(一個進程也可以被其他進程用信號結束,我們將在以后的文 章中介紹),以及正常結束時的返回值,或被哪一個信號結束的等信息。由于這些信息被存放在一個整數的不同二進制位中,所以用常規的方法讀取會非常麻煩,人 們就設計了一套專門的宏(macro)來完成這項工作,下面我們來學習一下其中最常用的兩個:
  
  ● wifexited(status)
  
  這個宏用來指出子進程是否為正常退出的,如果是,它會返回一個非零值。
  
  (請注意,雖然名字一樣,這里的參數status并不同于wait唯一的參數--指向整數的指針status,而是那個指針所指向的整數,切記不要搞混了。)
  
  ● wexitstatus(status)
  
   當wifexited返回非零值時,我們可以用這個宏來提取子進程的返回值,如果子進程調用exit(5)退 出,wexitstatus(status)就會返回5;如果子進程調用exit(7),wexitstatus(status)就會返回7。請注意,如 果進程不是正常退出的,也就是說,wifexited返回0,這個值就毫無意義。
  
  下面通過例子來實戰一下我們剛剛學到的內容:
  
  /* wait2.c */
  #include
  #include
  #include
  main()
  {
   int status;
   pid_t pc,pr;
   pc=fork();
   if(pc<0) /* 如果出錯 */
   printf("error ocurred!n");
   else if(pc==0){ /* 子進程 */
   printf("this is child process with pid of %d.n",getpid());
   exit(3); /* 子進程返回3 */
   }
   else{ /* 父進程 */
   pr=wait(&status);
  
   if(wifexited(status)){ /* 如果wifexited返回非零值 */
   printf("the child process %d exit normally.n",pr);
   printf("the return code is %d.n",wexitstatus(status));
   }else /* 如果wifexited返回零 */
   printf("the child process %d exit abnormally.n",pr);
   }
  
  }
  
  
  
  編譯并運行:
  
  $ cc wait2.c -o wait2
  $ ./wait2
  this is child process with pid of 1538.
  the child process 1538 exit normally.
  the return code is 3.
  
  
  
  父進程準確捕捉到了子進程的返回值3,并把它打印了出來。
  
  當然,處理進程退出狀態的宏并不止這兩個,但它們當中的絕大部分在平時的編程中很少用到,就也不在這里浪費篇幅介紹了,有興趣的讀者可以自己參閱linux man pages去了解它們的用法。
  
  進程同步
  
   有時候,父進程要求子進程的運算結果進行下一步的運算,或者子進程的功能是為父進程提供了下一步執行的先決條件(如:子進程建立文件,而父進程寫入數 據),此時父進程就必須在某一個位置停下來,等待子進程運行結束,而如果父進程不等待而直接執行下去的話,可以想見,會出現極大的混亂。這種情況稱為進程 之間的同步,更準確地說,這是進程同步的一種特例。進程同步就是要協調好2個以上的進程,使之以安排好地次序依次執行。解決進程同步問題有更通用的方法, 我們將在以后介紹,但對于我們假設的這種情況,則完全可以用wait系統調用簡單的予以解決。請看下面這段程序:
  
  #include
  #include
  main()
  {
   pid_t pc, pr;
   int status;
   pc=fork();
   if(pc<0)
   printf("error occured on forking.n");
   else if(pc==0){
   /* 子進程的工作 */
   exit(0);
   }else{
   /* 父進程的工作 */
   pr=wait(&status);
   /* 利用子進程的結果 */
   }
  }
  
  
  
   這段程序只是個例子,不能真正拿來執行,但它卻說明了一些問題,首先,當fork調用成功后,父子進程各做各的事情,但當父進程的工作告一段落,需要用 到子進程的結果時,它就停下來調用wait,一直等到子進程運行結束,然后利用子進程的結果繼續執行,這樣就圓滿地解決了我們提出的進程同步問題。
  
  
  waitpid
  
  
  簡介
  
  waitpid系統調用在linux函數庫中的原型是:
  
  #include /* 提供類型pid_t的定義 */
  #include
  pid_t waitpid(pid_t pid,int *status,int options)
  
  
  
  從本質上講,系統調用waitpid和wait的作用是完全相同的,但waitpid多出了兩個可由用戶控制的參數pid和options,從而為我們編程提供了另一種更靈活的方式。下面我們就來詳細介紹一下這兩個參數:
  
  ● pid
  
  從參數的名字pid和類型pid_t中就可以看出,這里需要的是一個進程id。但當pid取不同的值時,在這里有不同的意義。
  
  pid>0時,只等待進程id等于pid的子進程,不管其它已經有多少子進程運行結束退出了,只要指定的子進程還沒有結束,waitpid就會一直等下去。
  
  pid=-1時,等待任何一個子進程退出,沒有任何限制,此時waitpid和wait的作用一模一樣。
  
  pid=0時,等待同一個進程組中的任何子進程,如果子進程已經加入了別的進程組,waitpid不會對它做任何理睬。
  
  pid<-1時,等待一個指定進程組中的任何子進程,這個進程組的id等于pid的絕對值。
  
  ● options
  
  options提供了一些額外的選項來控制waitpid,目前在linux中只支持wnohang和wuntraced兩個選項,這是兩個常數,可以用"|"運算符把它們連接起來使用,比如:
  
  ret=waitpid(-1,null,wnohang | wuntraced);
  
  
  
  如果我們不想使用它們,也可以把options設為0,如:
  
  ret=waitpid(-1,null,0);
  
  
  
  如果使用了wnohang參數調用waitpid,即使沒有子進程退出,它也會立即返回,不會像wait那樣永遠等下去。
  
  而wuntraced參數,由于涉及到一些跟蹤調試方面的知識,加之極少用到,這里就不多費筆墨了,有興趣的讀者可以自行查閱相關材料。
  
  看到這里,聰明的讀者可能已經看出端倪了--wait不就是經過包裝的waitpid嗎?沒錯,察看<內核源碼目錄>/include/unistd.h文件349-352行就會發現以下程序段:
  
  static inline pid_t wait(int * wait_stat)
  {
   return waitpid(-1,wait_stat,0);
  }
  
  
  
  返回值和錯誤
  
  waitpid的返回值比wait稍微復雜一些,一共有3種情況:
  
  ● 當正常返回的時候,waitpid返回收集到的子進程的進程id;
  
  ● 如果設置了選項wnohang,而調用中waitpid發現沒有已退出的子進程可收集,則返回0;
  
  ● 如果調用中出錯,則返回-1,這時errno會被設置成相應的值以指示錯誤所在;
  
  當pid所指示的子進程不存在,或此進程存在,但不是調用進程的子進程,waitpid就會出錯返回,這時errno被設置為echild;
  
  /* waitpid.c */
  #include
  #include
  #include
  main()
  {
   pid_t pc, pr;
   pc=fork();
   if(pc<0) /* 如果fork出錯 */
   printf("error occured on forking.n");
   else if(pc==0){ /* 如果是子進程 */
   sleep(10); /* 睡眠10秒 */
   exit(0);
   }
  
   /* 如果是父進程 */
   do{
  pr=waitpid(pc, null, wnohang); /* 使用了wnohang參數,waitpid不會在這里等待 */
  if(pr==0){ /* 如果沒有收集到子進程 */
   printf("no child exitedn");
   sleep(1);
   }
  }while(pr==0); /* 沒有收集到子進程,就回去繼續嘗試 */
   if(pr==pc)
   printf("successfully get child %dn", pr);
   else
   printf("some error occuredn");
  }
  
  
  
  編譯并運行:
  
  $ cc waitpid.c -o waitpid
  $ ./waitpid
  no child exited
  no child exited
  no child exited
  no child exited
  no child exited
  no child exited
  no child exited
  no child exited
  no child exited
  no child exited
  successfully get child 1526
  
  
  
  父進程經過10次失敗的嘗試之后,終于收集到了退出的子進程。
  
  因為這只是一個例子程序,不便寫得太復雜,所以我們就讓父進程和子進程分別睡眠了10秒鐘和1秒鐘,代表它們分別作了10秒鐘和1秒鐘的工作。父子進程都有工作要做,父進程利用工作的簡短間歇察看子進程的是否退出,如退出就收集它。
  
   也許有不少讀者從本系列文章一推出就開始讀,一直到這里還有一個很大的疑惑:既然所有新進程都是由fork產生的,而且由fork產生的子進程和父進程 幾乎完全一樣,那豈不是意味著系統中所有的進程都應該一模一樣了嗎?而且,就我們的常識來說,當我們執行一個程序的時候,新產生的進程的內容應就是程序的 內容才對。是我們理解錯了嗎?顯然不是,要解決這些疑惑,就必須提到我們下面要介紹的exec系統調用。
  
  簡介
  
  說是exec系統調用,實際上在linux中,并不存在一個exec()的函數形式,exec指的是一組函數,一共有6個,分別是:
  
  #include
  int execl(const char *path, const char *arg, ...);
  int execlp(const char *file, const char *arg, ...);
  int execle(const char *path, const char *arg, ..., char *const envp[]);
  int execv(const char *path, char *const argv[]);
  int execvp(const char *file, char *const argv[]);
  int execve(const char *path, char *const argv[], char *const envp[]);
  
  
  
  其中只有execve是真正意義上的系統調用,其它都是在此基礎上經過包裝的庫函數。
  
  exec函數族的作用是根據指定的文件名找到可執行文件,并用它來取代調用進程的內容,換句話說,就是在調用進程內部執行一個可執行文件。這里的可執行文件既可以是二進制文件,也可以是任何linux下可執行的腳本文件。
  
   與一般情況不同,exec函數族的函數執行成功后不會返回,因為調用進程的實體,包括代碼段,數據段和堆棧等都已經被新的內容取代,只留下進程id等一 些表面上的信息仍保持原樣,頗有些神似"三十六計"中的"金蟬脫殼"。看上去還是舊的軀殼,卻已經注入了新的靈魂。只有調用失敗了,它們才會返回一個 -1,從原程序的調用點接著往下執行。
  
  現在我們應該明白了,linux下是如何執行新程序的,每當有進程認為自己不能為系統和擁 護做出任何貢獻了,他就可以發揮最后一點余熱,調用任何一個exec,讓自己以新的面貌重生;或者,更普遍的情況是,如果一個進程想執行另一個程序,它就 可以fork出一個新進程,然后調用任何一個exec,這樣看起來就好像通過執行應用程序而產生了一個新進程一樣。
  
  事實上第二種 情況被應用得如此普遍,以至于linux專門為其作了優化,我們已經知道,fork會將調用進程的所有內容原封不動的拷貝到新產生的子進程中去,這些拷貝 的動作很消耗時間,而如果fork完之后我們馬上就調用exec,這些辛辛苦苦拷貝來的東西又會被立刻抹掉,這看起來非常不劃算,于是人們設計了一種"寫 時拷貝(copy-on-write)"技術,使得fork結束后并不立刻復制父進程的內容,而是到了真正實用的時候才復制,這樣如果下一條語句是 exec,它就不會白白作無用功了,也就提高了效率。
  
  稍稍深入
  
  上面6條函數看起來似乎很復雜,但實際上無論是作用還是用法都非常相似,只有很微小的差別。在學習它們之前,先來了解一下我們習以為常的main函數。
  
  下面這個main函數的形式可能有些出乎我們的意料:
  
  int main(int argc, char *argv[], char *envp[])
  
  
  
  它可能與絕大多數教科書上描述的都不一樣,但實際上,這才是main函數真正完整的形式。
  
   參數argc指出了運行該程序時命令行參數的個數,數組argv存放了所有的命令行參數,數組envp存放了所有的環境變量。環境變量指的是一組值,從 用戶登錄后就一直存在,很多應用程序需要依靠它來確定系統的一些細節,我們最常見的環境變量是path,它指出了應到哪里去搜索應用程序,如 /bin;home也是比較常見的環境變量,它指出了我們在系統中的個人目錄。環境變量一般以字符串"xxx=xxx"的形式存在,xxx表示變量 名,xxx表示變量的值。
  
  值得一提的是,argv數組和envp數組存放的都是指向字符串的指針,這兩個數組都以一個null元素表示數組的結尾。
  
  我們可以通過以下這個程序來觀看傳到argc、argv和envp里的都是什么東西:
  
  /* main.c */
  int main(int argc, char *argv[], char *envp[])
  {
   printf("n### argc ###n%dn", argc);
   printf("n### argv ###n");
   while(*argv)
   printf("%sn", *(argv++));
   printf("n### envp ###n");
   while(*envp)
   printf("%sn", *(envp++));
   return 0;
  }
  
  
  
  編譯它:
  
  $ cc main.c -o main
  
  
  
  運行時,我們故意加幾個沒有任何作用的命令行參數:
  
  $ ./main -xx 000
  ### argc ###
  3
  ### argv ###
  ./main
  -xx
  000
  ### envp ###
  pwd=/home/lei
  remotehost=dt.laser.com
  hostname=localhost.localdomain
  qtdir=/usr/lib/qt-2.3.1
  lessopen=|/usr/bin/lesspipe.sh %s
  kdedir=/usr
  user=lei
  ls_colors=
  machtype=i386-redhat-linux-gnu
  mail=/var/spool/mail/lei
  inputrc=/etc/inputrc
  lang=en_us
  logname=lei
  shlvl=1
  shell=/bin/bash
  hosttype=i386
  ostype=linux-gnu
  histsize=1000
  term=ansi
  home=/home/lei
  path=/usr/local/bin:/bin:/usr/bin:/usr/x11r6/bin:/home/lei/bin
  _=./main
  
  
  
  我們看到,程序將“./main”作為第1個命令行參數,所以我們一共有3個命令行參數。這可能與大家平時習慣的說法有些不同,小心不要搞錯了。
  
  現在回過頭來看一下exec函數族,先把注意力集中在execve上:
  
  int execve(const char *path, char *const argv[], char *const envp[]);
  
  
  
   對比一下main函數的完整形式,看出問題了嗎?是的,這兩個函數里的argv和envp是完全一一對應的關系。execve第1個參數path是被執 行應用程序的完整路徑,第2個參數argv就是傳給被執行應用程序的命令行參數,第3個參數envp是傳給被執行應用程序的環境變量。
  
   留心看一下這6個函數還可以發現,前3個函數都是以execl開頭的,后3個都是以execv開頭的,它們的區別在于,execv開頭的函數是以 “char *argv[]”這樣的形式傳遞命令行參數,而execl開頭的函數采用了我們更容易習慣的方式,把參數一個一個列出來,然后以一個null表示結束。這 里的null的作用和argv數組里的null作用是一樣的。
  
  在全部6個函數中,只有execle和execve使用了 char *envp[]傳遞環境變量,其它的4個函數都沒有這個參數,這并不意味著它們不傳遞環境變量,這4個函數將把默認的環境變量不做任何修改地傳給被執行的 應用程序。而execle和execve會用指定的環境變量去替代默認的那些。
  
  還有2個以p結尾的函數execlp和 execvp,咋看起來,它們和execl與execv的差別很小,事實也確是如此,除execlp和execvp之外的4個函數都要求,它們的第1個參 數path必須是一個完整的路徑,如"/bin/ls";而execlp和execvp的第1個參數file可以簡單到僅僅是一個文件名,如"ls",這 兩個函數可以自動到環境變量path制定的目錄里去尋找。
  
  實戰
  
  知識介紹得差不多了,接下來我們看看實際的應用:
  
  /* exec.c */
  #include
  main()
  {
   char *envp[]={"path=/tmp",
   "user=lei",
   "status=testing",
   null};
   char *argv_execv[]={"echo", "excuted by execv", null};
   char *argv_execvp[]={"echo", "executed by execvp", null};
   char *argv_execve[]={"env", null};
   if(fork()==0)
   if(execl("/bin/echo", "echo", "executed by execl", null)<0)
   perror("err on execl");
   if(fork()==0)
   if(execlp("echo", "echo", "executed by execlp", null)<0)
   perror("err on execlp");
   if(fork()==0)
   if(execle("/usr/bin/env", "env", null, envp)<0)
   perror("err on execle");
   if(fork()==0)
   if(execv("/bin/echo", argv_execv)<0)
   perror("err on execv");
   if(fork()==0)
   if(execvp("echo", argv_execvp)<0)
   perror("err on execvp");
   if(fork()==0)
   if(execve("/usr/bin/env", argv_execve, envp)<0)
   perror("err on execve");
  }
  
  
  
  程序里調用了2個linux常用的系統命令,echo和env。echo會把后面跟的命令行參數原封不動的打印出來,env用來列出所有環境變量。
  
  由于各個子進程執行的順序無法控制,所以有可能出現一個比較混亂的輸出--各子進程打印的結果交雜在一起,而不是嚴格按照程序中列出的次序。
  
  編譯并運行:
  
  $ cc exec.c -o exec
  $ ./exec
  executed by execl
  path=/tmp
  user=lei
  status=testing
  executed by execlp
  excuted by execv
  executed by execvp
  path=/tmp
  user=lei
  status=testing
  
  
  
  果然不出所料,execle輸出的結果跑到了execlp前面。
  
  大家在平時的編程中,如果用到了exec函數族,一定記得要加錯誤判斷語句。因為與其他系統調用比起來,exec很容易受傷,被執行文件的位置,權限等很多因素都能導致該調用的失敗。最常見的錯誤是:
  
  找不到文件或路徑,此時errno被設置為enoent;
  
  數組argv和envp忘記用null結束,此時errno被設置為efault;
  
  沒有對要執行文件的運行權限,此時errno被設置為eacces。
  
  
  進程的一生
  
  
  下面就讓我用一些形象的比喻,來對進程短暫的一生作一個小小的總結:
  
  隨著一句fork,一個新進程呱呱落地,但它這時只是老進程的一個克隆。
  
  然后隨著exec,新進程脫胎換骨,離家獨立,開始了為人民服務的職業生涯。
  
   人有生老病死,進程也一樣,它可以是自然死亡,即運行到main函數的最后一個"}",從容地離我們而去;也可以是自殺,自殺有2種方式,一種是調用 exit函數,一種是在main函數內使用return,無論哪一種方式,它都可以留下遺書,放在返回值里保留下來;它還甚至能可被謀殺,被其它進程通過 另外一些方式結束他的生命。
  
  進程死掉以后,會留下一具僵尸,wait和waitpid充當了殮尸工,把僵尸推去火化,使其最終歸于無形。
  
  這就是進程完整的一生。
  
  
  小結
  
  
  本文重點介紹了系統調用wait、waitpid和exec函數族,對與進程管理相關的系統調用的介紹就在這里告一段落,在下一篇文章,也是與進程管理相關的系統調用的最后一篇文章中,我們會通過兩個很酷的實際例子,來重溫一下最近學過的知識。




本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/254288.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/254288.shtml
英文地址,請注明出處:http://en.pswp.cn/news/254288.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ComblockEngine 引擎實踐

comblockEngine 參考資料 官方網址 學習備注 熟悉內容 1.python 的字符串操作 2.flags 屬性的作用域 flags等 問題每天 1 1.基本數據類型 基本類型 2.數據庫表的操作 操作1 3.屬性的定義在.def中。self.出來之后&#xff0c;在繼承多個類的時候會不會重復。應該是子類的屬性覆蓋…

王陽明詩歌集

此心光明&#xff0c;夫復何求。&#xff08;王陽明死前對弟子的遺言&#xff09; 0. 非常少年、非常父子 山近月遠覺月小&#xff0c;便道此山大于月。若人有眼大如天&#xff0c;當見山高月更闊。—— 《蔽月山房》&#xff0c;作于12歲&#xff1b;1. 中秋 吾心自有光明月&a…

子承父業-C#繼承

文章目錄繼承的定義和使用實例繼承的特性繼承的可傳遞性繼承的單一性繼承中的訪問修飾符base和this關鍵字basethis實例博主寫作不容易&#xff0c;孩子需要您鼓勵 萬水千山總是情 , 先點個贊行不行 繼承是軟件復用的一種形式。使用繼承可以復用現在類的數據和行為&#xff…

MAVEN創建并打包web項目

maven項目是由一個maven project和多個maven module組成的&#xff0c;以下簡介一下maven webapp的創建和打包&#xff0c;前提是你已經安裝配置好maven了。打開eclipse。依照例如以下操作&#xff1a; 我們首先當然要先創建一個project。如上圖選擇。 如今maven project已經創…

GNU gcc的編譯工具用法(轉)

對大多數不從事Linux平臺C語言開發的人來說&#xff0c;GNU gcc的一套工具和Linux平臺的共享庫的使用還是十分陌生的&#xff0c;其實我也不太熟悉&#xff0c;姑且寫點基礎知識&#xff0c;權當做備忘吧。 一、GNU gcc的編譯工具用法 我們先來寫一個簡單的C程序&#xff1a;…

學界 | CVPR 2018頒布五大獎項,何愷明獲年輕學者獎

年度計算機視覺和模式識別盛會CVPR&#xff08;Conference on Computer Vision and Pattern Recognition&#xff09;在美國鹽湖城開幕啦&#xff01; 據統計&#xff0c;本屆大會有超過3309篇大會論文投稿&#xff0c;接收979篇論文。 面對如此多的論文評審任務&#xff0c;CV…

virtual、override-蝌蚪和青蛙-C#多態

文章目錄多態的概念多態的實現方法重載、重寫、隱藏重載重寫隱藏博主寫作不容易&#xff0c;孩子需要您鼓勵 萬水千山總是情 , 先點個贊行不行 多態的概念 什么是多態&#xff1f; 多態一次最早用于生物學&#xff0c;指同一種族的生物具有不同的特性。比如青蛙小時候是蝌…

jvm--3.內存管理

5.JVM內存管理 JAVA虛擬機在執行java程序的過程中&#xff0c;會把它管理的內存分成若干個不同的數據區域。 ------------------------------------------------------------------------------------— | 運行時數據區 | | ----------- -------- ----------------- | | | 方法…

[BZOJ2458][BeiJing2011]最小三角形

題目描述 Description Xaviera現在遇到了一個有趣的問題。平面上有N個點&#xff0c;Xaviera想找出周長最小的三角形。由于點非常多&#xff0c;分布也非常亂&#xff0c;所以Xaviera想請你來解決這個問題。為了減小問題的難度&#xff0c;這里的三角形也包括共線的三點。 輸…

Makefile中的變量

Makefile中的變量 2007-11-03 12:03Makefile中變量有以下幾個特征&#xff1a; 1. Makefile中變量和函數的展開&#xff08;除規則命令行中的變量和函數以外&#xff09;&#xff0c;是在make讀取makefile文件時進行的&#xff0c;這里的變量包括了使用“”定義和使用指示符“d…

小技巧集錦

2019獨角獸企業重金招聘Python工程師標準>>> jackson JsonDeserialize 使用方法&#xff1a; 實現方法注解寫在set方法上。 public class CustomJsonDateDeserializer extends JsonDeserializer<Date> {private SimpleDateFormat datetimeFormat new SimpleD…

interface-C#接口-統一的標準

文章目錄接口的定義接口的實現實例1實例2接口的繼承博主寫作不容易&#xff0c;孩子需要您鼓勵 萬水千山總是情 , 先點個贊行不行 接口是面向對象編程的一個重要技術&#xff0c;在C#中負責實現多重繼承。一個接口定義一個協定&#xff0c;實現接口類或結構體必須遵守其協定…

JMeter入門(1):JMeter總體介紹及組件介紹

一、JMeter概述 JMeter就是一個測試工具&#xff0c;相比于LoadRunner等測試工具&#xff0c;此工具免費&#xff0c;且比較好用&#xff0c;但是前提當然是安裝Java環境&#xff1b;JMeter可以做(1)壓力測試及性能測試&#xff1b;(2)數據庫測試&#xff1b;(3)Java程序的測試…

二層交換機、三層交換機和路由器的基本工作原理和三者之間的主要區別

二層交換機:二層交換技術是發展比較成熟&#xff0c;二層交換機屬數據鏈路層設備&#xff0c;可以識別數據包中的MAC地址信息&#xff0c;根據MAC地址進行轉發&#xff0c;并將這些MAC地址與對應的端口記錄在自己內部的一個地址表中。 具體如下&#xff1a; &#xff08;1&…

Unity3D:視物有點眩暈的原因

設置Main Camera 的 Field of View 為100&#xff0c;看物體總覺得很不舒服。 設置為 60 就正常了。 根本原因&#xff0c;有待于分析 轉載于:https://www.cnblogs.com/makebetter/p/7063694.html

使用jQuery清空file文件域的解決方案

使用jQuery清空file文件域的解決方案 var file $("#file") file.after(file.clone().val("")); file.remove();

更改mysql最大連接數

方法一&#xff1a; 打開cmd&#xff0c;用"mysql -u root -p;"命令進入mysql, 輸入命令&#xff1a;show variables like "max_connections" 顯示最大連接數 更改最大連接數 : set global max_connections 5000 方法二&#xff1a; 在my.ini加上 max_co…

根據HTML5 獲取當前位置的經緯度【百度地圖】【高德地圖】

是想讓地圖的定位用戶位置更準確一些。 查看了介紹&#xff1a; http://www.w3school.com.cn/html5/html_5_geolocation.asp 看介紹中拿數據挺簡單。 <!DOCTYPE html> <html> <body> <p id"demo">點擊這個按鈕&#xff0c;獲得您的坐標&…

C#抽象類與密封類-abstract-sealed

文章目錄抽象類和抽象方法實現抽象方法接口、類和抽象類密封類博主寫作不容易&#xff0c;孩子需要您鼓勵 萬水千山總是情 , 先點個贊行不行 如果說繼承是面向對象設計理論的基石&#xff0c;那么抽象理論和方法就是繼承理論的頂梁柱。 抽象類和抽象方法 簡單的說&#x…

vs2010快捷鍵

Ctrl M O: 折疊所有方法 Ctrl M M: 折疊或者展開當前方法 Ctrl M L: 展開所有方法 1、強迫智能感知&#xff1a;CtrlJ&#xff1b;2、強迫智能感知顯示參數信息&#xff1a;Ctrl-Shift-空格&#xff1b;3、格式化整個塊&#xff1a;CtrlKF4、檢查括號匹配(在左右括號間切…