Linux操作系統-進程和信號(2)

Linux操作系統-進程和信號(2)

分類:Linux環境編程

進程的標識

  有一些函數可以返回進程的標識符

#include <sys/types.h>
#include <unistd.h>
pid_t    getpid(void);     //返回調用進程的進程ID
pid_t    getppid(void);    //返回調用進程的父進程
uid_t    getuid(void);     //返回調用進程的實際用戶ID
uid_t    geteuid(void);    //返回調用進程的有效用戶ID
gid_t    getgid(void);     //返回調用進程的實際組ID
git_t    getegid(void);    //返回調用進程的有效組ID

進程的狀態

  • 運行狀態:指正在CPU中運行或者就緒的狀態,包括:內核運行態,用戶運行態,就緒態。Linux內核並不對此三種狀態進行區分
  • 可中斷睡眠狀態:當進程處於可中斷等待狀態時,系統不會調度該進程執行。當系統產生一箇中斷或者釋放了進程正在等待的資源,或者進程收到一個信號,都可以喚醒進程轉換到就緒狀態
  • 不可中斷睡眠狀態:不可中斷,指的不是CPU不響應外部硬件中斷,而是指進程不響應異步信號。處於該狀態的進程不響應信號,只有使用wake_up()函數明確喚醒才能轉換到就緒狀態。該狀態被設計用於保護內核的某些流程不被打斷,或者在進行某些I/O操作時避免進程與設備交互的過程中被打斷,造成設備陷入不可控狀態。
  • 暫停狀態:當進程收到信號SIGSTOP,SIGTSTP,SIGTTIN或SIGTTOU時就會進入暫停狀態。可向其發送SIGCONT信號讓進程轉換到可運行狀態。
  • 僵死狀態(TASK_ZOMBIE):當進程已停止運行,但其父進程還沒有詢問其狀態時,則該進程處於僵死狀態

  常見的狀態字符有:

STAT字符 說明
S 睡眠。通常是在等待某個事件的發生
R 運行/可運行,即在運行隊列中,處於正在運行或即將運行狀態
D 不可中斷的睡眠(等待,不響應異步信號)。通常是在等待輸入或輸出完成
T 停止
Z 殭屍進程
N 低優先級任務
s 進程是回話期首進程
+ 進程屬於前臺進程組
l 進程是多線程的
< 高優先級任務

進程的控制

system函數

  在進程中執行另一個程序的一個簡單方法是調用標準庫函數system,原型如下:

#include <stdlib.h>
int system(const char *command);

  解釋:system函數運行command命令並等待該命令完成,
本質是(執行“/bin/sh -c command”)。system函數調用成功時返回相應命令的退出狀態碼,如果無法啓動shell則返回127,發生其它錯誤時返回-1
注意:使用system函數並非啓動其它進程的理想手段,因其必須啓動一個shell,再使用shell執行相應的命令。下面有一個小例子:

//command.c
#include <stdio.h>

int main(int argc, char **argv)
{
    printf("這條命令是由調用system函數執行的。\n");

    return 0;
}

  將該段代碼編譯成command可執行文件。

//system.c
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    printf("接下來將要調用system函數來執行一個名爲command的命令。\n");

    system("./command");

    printf("Done.\n");

    return 0;
}

  編譯運行

biantiao@lazybone1994-ThinkPad-E430:~/Linux$ gcc -o system system.c
biantiao@lazybone1994-ThinkPad-E430:~/Linux$ ./system
接下來將要調用system函數來執行一個名爲command的命令。
這條命令是由調用system函數執行的。
Done.
biantiao@lazybone1994-ThinkPad-E430:~/Linux$

exec函數

  調用exec系列函數可以執行另外一個程序。這些函數的原型如下:

#include <unistd.h>
extern char **evniron;
int execl(const char *path, const char *arg, ...);
int execlp(cosnt char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, const char *argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[]);
int execve(cosnt char *path, char *const argv[], char *const envp[]);

  解釋:當一個進程調用一種exec函數時,該進程將完全由新程序替換當前進程的正文,數據,堆和棧段,所以調用exec前後進程ID並未改變。
  exec系列函數各參數和返回值的含義如下
- path:待運行的程序全路徑名
- file:待運行的程序名,通過PATH環境變量搜索其路徑
- arg:命令參數
- …:可選的一到多個命令參數,要求最後一個必須是NULL
- argv:命令參數指針數組
- envp:傳遞給待運行程序的環境變量指針數組
- 返回值:成功時不返回,出錯時返回-1

  這幾個函數之間的區別
  函數名包含字母p(表示path)的execlp, execvp和execvpe函數取文件名file作爲第一個參數,其它函數則取路徑名path作爲第一個參數。當指定file作爲參數時:如果file中包含/,則就將其視爲路徑名,否則就按PATH環境變量的設定,在相關目錄中搜尋可執行文件。
  函數名字中包含字母l的execl,execlp和execle要求將新程序的每個命令行參數都作爲一個單獨的參數,然後在最後一個參數後附加一個空指針參數結尾。
  函數名中包含字母v的另外三個函數execv,execvp和execve則要求先構造一個指向各參數的指針數組,數組的最後一個元素也必須是空指針,然後以該數組地址作爲這三個函數的參數。
  函數以字母e結尾的三個函數execle,execvpe和execve可以傳遞一個指向環境字符串指針數組的指針,該指針數組也必須以空指針作爲最後一個元素

fork函數

  一個現存進程創建一個新進程的唯一方法是調用fork或vfork函數

#include <unistd.h>
pid_t fork(void);

  由fork創建的新進程被稱爲子進程。該函數被調用一次,但返回兩次。兩次返回的區別是子進程的返回值是0,而父進程的返回值是子進程的PID。

  在調用fork之後,子進程和父進程繼續執行fork之後的指令。

  一般來說,在fork之後是父進程先執行還是子進程先執行是不確定的,父進程和子進程是完全獨立運行的。如果父,子進程之間相互同步,則要求採用某種形式的進程之間的通信機制。

  子進程是父進程的複製品。例如:子進程獲得父進程數據空間,堆和棧的複製品。注意,這是子進程所擁有的拷貝,父子進程並不共享這些儲存空間。如果正文段是隻讀的,則父,子進程共享正文段。

  Linux下的fork函數並不對父進程的數據段,堆和棧進行完全拷貝,而是使用了寫時複製的技術,讓父,子進程共享這些區域,而且內核將它們的存取許可權變爲只讀的。當有進程試圖修改這些區域時,才由內核爲有關部分做一個拷貝。

  fork函數有以下兩種用法
- 一個父進程希望複製自己,使父,子進程執行不同的代碼段。這在網絡服務進程中是常見的——父進程等待委託者的服務請求,當請求達到時,父進程調用fork,使子進程處理此請求,父進程則繼續等待下一個服務請求。
- 一個進程要執行一個不同的程序,這對shell是常見的。在這種情況下,子進程在從fork返回後立即調用exec。當然了,子進程在fork和exec之間可以更改自己的屬性。

vfork函數

  vfork函數與fork函數最大的一個區別是,vfork函數創建子進程後會阻塞父進程,其原型如下:

#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);

  vfork和fork一樣都創建一個子進程,但是它並不會將父進程的地址空間完全複製到子進程中,因爲子進程會立即調用exec或exit,於是也就不會訪問該地址空間。但在子進程調用exec或exit之前,它在父進程的空間中運行。

  vfork和fork之間的另一個區別是:vfork保證子進程先運行,在子進程調用exec或exit之後父進程纔可能被調度運行

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    pid_t pid;      /* 用於保存PID */
    char *message;  /* 用於保存消息字符串 */
    int n = 2;      /* 技術變量 */

    printf("fork program starting\n");
    pid = vfork();  /* 創建子進程 */
    switch (pid){
        case -1:
            perror("fork failed.\n");
            exit(1);
        case 0:
            message = "This is the child.";
            n = 5;
            break;
        default:
            message = "This is the parent.";
            n++;
            break;
    }

    for (; n > 0; n--){
        puts(message);
        sleep(1);
    }

    exit(0);
}

進程的終止狀態

  對於正常終止的情況,傳向exit或 _exit的參數,或main函數的返回值,指明瞭它們的退出狀態,內核以該“退出狀態”作爲進程的“終止狀態”。在異常終止的情況下,內核(不是進程本身)會產生一個指示其異常終止原因的終止狀態(termination status)。終止進程的父進程可使用wait或waitpid函數取得其終止狀態。

  如果父進程在子進程之前終止,則它們的父進程都將改爲init進程,這種處理方法保證每一個進程都有父進程。

  如果子進程在父進程之前終止,那麼父進程如何得到子進程的終止狀態呢?答案是,當進程終止的時候,內核並未馬上釋放其進程控制塊(PCB),而是在其中保留了一定量的信息,所以當終止進程的父進程調用wait或waitpid時,可以得到這些信息。這些信息至少包括進程ID,該進程的終止狀態,以及該進程使用的CPU時間總量。

  一個已經終止,但是其父進程尚未對其進行善後處理(獲取終止子進程的有關信息,釋放它扔佔用的資源)的進程被稱爲殭屍進程(zombie)

wait和waitpid函數

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

  wait函數等待任一子進程的結束,waitpid函數則可等待指定的子進程的結束。

  在父進程中調用wait或者waitpid可能發生如下情況:
- 阻塞(如果其所有子進程都還在運行)
- 帶子進程的終止狀態立即返回
- 出錯立即返回(如果它沒有子進程)

  當一個進程終止時,內核會向其父進程發送SIGCHLD信號。父進程默認處理是忽略該信號,但也可以設置一個信號發生時即被調用的回調函數以捕獲該信號。如果父進程在捕獲SIGCHLD信號的回調函數中調用wait,則可期望wait會立即返回。但是在一個任意時刻調用wait,則進程可能會阻塞

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章