1. 程式人生 > >程序的建立、等待以及退出

程序的建立、等待以及退出

           學習了程序的進步概念之後,接下來我們就來學習一下程序的建立、等待以及終止等。

1、程序的建立

       在Linux中,fork()函式是非常重要的函式,它從已存在的程序中再建立一個新程序。新程序為子程序,而原程序為父程序。

(1) pid_t fork(void);

        返回值:子程序返回0,父程序返回子程序id,出錯返回-1;

       程序呼叫fork(),當控制轉移到核心中的fork程式碼後,核心做:

  • 分配新的記憶體塊和核心資料結構給子程序;
  • 將父程序部分資料結構內容拷貝至子程序;
  • 新增子程序到系統程序列表當中;
  • fork返回,開始排程器排程;

      

          當一個程序呼叫fork()之後,就有兩個二進位制程式碼相同的程序,而且他們都執行到相同的地方。但每個程序都將開始他們自己的旅行。

          下面就來看一段程式碼:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
	pid_t pid;
	printf("Before:pid is %d\n",getpid());
	if((pid=fork())==-1)
	{
		perror("fork");
		exit(1);
	}
	printf("After:pid is %d,fork return %d\n ",getpid(),pid);
	sleep(1);
	return 0;
}

   執行結果如下:

   

          有圖可看出,一行Before,兩行After。程序31373先列印Before,然後再列印After。另一個After訊息由31374列印。且31373 return 31374,而31374 return 0。不知道你注意沒?31374沒有列印Before,這是為什麼呢?    

       由此可以看出,fork()之前父程序獨立執行,fork()之後,父子兩個執行流分別執行。注意:fork()之後,誰先執行完全由排程器決定。

        通常,父子程式碼共享,父子再不寫入時,資料也是共享的,當任意一方試圖寫入時,便以寫時拷貝的方式各自一份副本進行修改。父子程序共用同一塊虛擬地址空間,通過頁表對映到不同的實體地址。

       fork()的常規用法:

  • 一個父程序希望複製自己,使父子程序同時執行不同的程式碼段。例如:父程序等待客戶端請求,生成子程序來處理請求。
  • 一個程序要執行一個不同的程式,例如:子程序從fork()返回後呼叫exec()函式。

       fork()呼叫失敗的話有兩個因素:(1)系統中有太多的程序;(2)實際使用者的程序數超過了限制;

(2)Vfork()函式

        在Linux中,用來建立子程序的還有Vfork()函式,但是Vfork函式與fork()函式有些不同;

  •  Vfork()函式用於建立一個子程序,而子程序與父程序共享地址空間,fork的子程序具有獨立地址空間;
  •  Vfork()函式保證子程序先執行,在它呼叫exec或者(exit)之後父程序才肯被排程; 

         下面就來看看Vfork()函式的使用:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int glob=100;

int main()
{
	pid_t pid=vfork();
	if(pid==-1)
	{
		perror("vfork");
		exit(1);
	}
	else if (pid==0)
	{//child
		sleep(5);
		glob=200;
		printf("child glob is %d\n",glob);
		exit(0);
	}
	else
	{//parent
		printf("parent glob is %d\n",glob);
	}
	return 0;
}

    執行結果:


       由截圖結果可見子程序修改了父程序的變數值,由此更能說明子程序在父程序的地址空間中執行。

2、程序的退出

        程序的退出場景有三種:

     (1)程式碼執行完畢,執行結果正確;

     (2)程式碼執行完畢,執行結果錯誤;

     (3)程式碼未執行完畢,異常退出;

         程序的退出場景有三種,就說明程序的退出方式也不會只有一種,下面說說程序退出的方式:  

       (1)正常退出 (可以通過命令echo $ ?檢視程序退出碼):

                從main函式返回;呼叫exit;_exit;

       (2)異常退出:Ctrl + C,訊號終止;

       _exit()函式:

              #include<unistd.h>

              void _exit(int status);

              引數:status定義了程序的終止狀態,父程序通過wait來獲取該值;

              說明:雖然status是int,但是隻要低8位可以被父程序使用。所有_exit(-1)時,在終端執行$?可以發現返回值是255

  exit()函式:

          #include<unistd.h>

         void exit(int status);

        exit最後也會呼叫exit,但在呼叫exit之前,還做了幾件事:

  •    執行使用者通過atexit或on_exit定義的清理函式;
  • 關閉所有開啟流,所有的快取資料均被寫入;
  • 呼叫_exit

   return 退出

          return是一種更常見的退出程序方法,執行return n等同於執行exit(n),因為呼叫main函式的執行時函式會將main的返回值當中exit的引數。

3、程序等待

     (1) 程序等待必要性

  •  之前學過,子程序退出,父程序如果不管不顧,就可能造成‘殭屍程序’的問題,進而造成記憶體洩漏。
  • 此外,程序一旦變成殭屍狀態,那就變成了銅牆鐵壁,可以任意殺人的大魔王 kill -9也就無能為力了,因為誰也沒有辦法殺死一個已經死去的程序,就像你永遠無法叫醒一個裝睡的人一樣。
  • 還有,父程序派給子程序的任務完成的任務完成的如何,我們需要知道。比如: 子程序執行完成,結果正確還是錯誤,或者是否正常退出。
  • 父程序通過程序等待的方式,回收子程序的資源,獲取子程序的退出狀態。          

(2)程序等待的方法

    wait()方法:

        #include<sys/types.h>

        #include<sys/wait.h>

        pid_t wait(int *status);

        引數:輸出型引數,獲取子程序退出狀態,不關心則可以設定為NULL;

        返回值:成功返回被等待程序的pid,失敗返回-1;

    waitpid()方法:

         pid_t   waitpid(pid_t  pid ,int *ststus ,int options);

         返回值:當正常返回的時候waitpid返回收集到的子程序的程序ID;

                       如果設定了選項WNOHANG,而呼叫中waitpid發現沒有已退出的子程序可收集,則返回0;

                       如果呼叫中出錯了,則返回-1,這時error會被設定出相應的值以指示錯誤所在;

        引數:pid: pid=-1,等待任何一個子程序。與wait等效。

                            pid>0,等待其程序ID與pid等待的子程序。

                 status: WIFEXITED(status):若為正常終止子程序返回的狀態,則為真。(檢視程序是否是正常退出)

                            WEXITSTATUS(status):若WIFEXITED非零,提取子程序退出碼。(檢視程序的退出碼)

                options:WNOHANG:若pid指定的子程序沒有結束,則waitpid()函式返回0,不會等待。若正常結束,則返回該子程序的ID。

  •  如果子程序已經退出,呼叫wait/waitpid 時 ,wait/waitpid會立即返回,並且釋放資源,獲得子程序退出資訊。
  • 如果在任意時刻呼叫wait/waitpid,子程序存在且正常執行,則肯引起阻塞。
  • 如果不存在該子程序,則立即出錯返回。

       下面來看看函式具體呼叫過程:


   以上就是程序的建立、等待、退出的簡單回顧。