You don't have javascript enabled. Good luck! :(

This is a test

You’ll find this post in your _posts directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run jekyll serve, which launches a web server and auto-regenerates your site when a file is updated.

To add new posts, simply add a file in the _posts directory that follows the convention YYYY-MM-DD-name-of-post.ext and includes the necessary front matter. Take a look at the source for this post to get an idea about how it works.

Jekyll also offers powerful support for code snippets:

def print_hi(name)
  puts "Hi, #{name}"
end
print_hi('Tom')
#=> prints 'Hi, Tom' to STDOUT.

Check out the Jekyll docs for more info on how to get the most out of Jekyll. File all bugs/feature requests at Jekyll’s GitHub repo. If you have questions, you can ask them on Jekyll Talk.

  Jun 6, 2018     WenYuan     Linux  UPDATE: Jun 8, 2018

[Linux C] fork 觀念由淺入深

fork 是 Linux 系統中常用的多工函數, 而 fork 同時也是 Linux 的 System call (系統呼叫), 當你呼叫了 fork 函數後, 會創建一個和當前 process 一模一樣的子程序, 從而進行多工動作… 同時我也會說明 fork 使用時會遇到的各種問題並且一一解答


什麼是程序 (process)

在開始談 fork 之前, 必須要了解什麼是程序 (process):

  • 程式碼 (program) : 假設你今天寫了是一支程式叫 example.c , 而且你尚未執行它, 則此時這支程式就叫 program

  • 程序 (process) : 倘若你把 example.c 編譯並執行, 程式被載入記憶體, 而且進到作業系統排程執行時, 便稱為程序, 而且每個程序都會有自己專屬的編號, 叫做 ProcessID

所以 fork 就是把當前的 process (父程序) 又分支出另一個 process (子程序) , 而且父子程序長的一模一樣


fork 的函數雛型 (man page 定義)

#include <unistd.h>
...
pid_t fork(void);
  • fork() 可能會有以下三種回傳值:
    • -1 : 發生錯誤
    • 0 : 代表為子程序
    • 大於 0 : 代表為父程序, 其回傳值為子程序的 ProcessID

注意: 其回傳值是 pid_t , 不是 int 哦!


使用 fork 之後的現象

當你的程序呼叫 fork() 函數時, 原本的程序 (父程序) 就會分支出另一支程序 (子程序) , 如下圖所示:

  • 我們可以試著練習下面範例 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* example1.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(){
    // 從呼叫 fork 開始, 會分成兩支程序多工進行
    pid_t PID = fork();

    switch(PID){
        // PID == -1 代表 fork 出錯
        case -1:
            perror("fork()");
            exit(-1);
        
        // PID == 0 代表是子程序
        case 0:
            printf("I'm Child process\n");
            printf("Child's PID is %d\n", getpid());
            break;
        
        // PID > 0 代表是父程序
        default:
            printf("I'm Parent process\n");
            printf("Parent's PID is %d\n", getpid());
    }

    return 0;
}
  • 範例 1 執行結果:


僵屍程序 (Zombie Process)

講到 fork() 是不得不介紹僵屍程序的, 這是在做 Multi-process 開發時經常會遇到的問題

  • 何謂僵屍程序 (Zombie Process):
    • 僵屍程序是指一支存活在系統中, 但是他卻沒有做任何事, 只是佔著並耗用系統資源的程序
    • 當使用 fork() 來建立子程序多工運行時, 如果子程序還沒運行結束就將父程序關閉的話, 就會有僵屍程序產生
    • 我以下面的範例 2 為例:
  • 程式範例 2 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* example2.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(){
    // 從呼叫 fork 開始, 會分成兩支程序多工進行
    pid_t PID = fork();

    switch(PID){
        // PID == -1 代表 fork 出錯
        case -1:
            perror("fork()");
            exit(-1);
        
        // PID == 0 代表是子程序
        case 0:
            printf("I'm Child process\n");
            printf("Child's PID is %d\n", getpid());
            sleep(3);
            break;
        
        // PID > 0 代表是父程序
        default:
            printf("I'm Parent process\n");
            printf("Parent's PID is %d\n", getpid());
    }

    return 0;
}
  • 編譯執行並使用 ps 察看系統進程

我在範例 2 中的 20 行 (子程序區塊) 中加了 sleep(3) , 意思是子程序在做完事後還要停滯 3 秒才會結束, 結果子程序 3 秒還沒結束, 父程序就 return 0 並且關閉掉程式了

所以從上面的執行結果可以看到, 明明程式已經執行完且結束了, 但是子程序 (6984) 卻還活在系統中, 此時的子程序就是一個僵屍程序, 他不做任何事卻依然佔用系統資源

  • 該如何避免僵屍程序發生呢?
    • 可以使用 signal() 或者 wait() 來解決
    • 我在下面會一一介紹


使用 wait 來解決僵屍程序問題

最簡單的方法就是在父程序區塊中使用 wait() , 當父程序遇到 wait() 時, 必須停下來等待接收子程序的結束狀態值, 如下面的範例 3 所示

  • 程式範例 3 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* example3.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(){
    int exit_status;
    pid_t PID = fork();

    switch(PID){
        case -1:
            perror("fork()");
            exit(-1);
        case 0:
            printf("[Child] I'm Child process\n");
            printf("[Child] Child's PID is %d\n", getpid());
            printf("[Child] Enter my exit status: ");
            scanf("%d", &exit_status);
            sleep(3);
            break;
        default:
            printf("[Parent] I'm Parent process\n");
            printf("[Parent] Parent's PID is %d\n", getpid());
            wait(&exit_status);
            // WEXITSTATUS is an macro
            printf("[Parent] Child's exit status is [%d]\n", WEXITSTATUS(exit_status));
    }

    return 0;
}
  • 編譯執行結果

在範例 3 中的第 26 行, 我加入了 wait(exit_status) , 因此當父程序運行到這行時, 就會停下來等待子程序結束

子程序結束後會有個結束狀態值, 父程序將會接收到子程序的結束狀態值, 並放進 exit_status

此外, 範例 3 中的第 28 行 WEXITSTATUS 是一個巨集 (macro) , 他可以用來提取出指定的子程序結束狀態值

加入 wait() 後, 父程序就必須停下來等待子程序結束才可以繼續運行, 因此子程序就不會變成僵屍程序了 (你可以再使用 ps 指令驗證看看)


使用 signal 來解決僵屍程序問題

當子程序結束會發出一個 SIGCHLD 信號, 所以我們可以在父程序中使用 signal() 來接收 SIGCHLD 信號, 再給予應對的處理方式, 如下範例 4 :

  • 程式範例 4 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/* example4.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

static void sig_handler(int sig){
    int retval;
    
    if ( sig == SIGCHLD ){ 
        // 等待子程序的結束狀態
        wait(&retval);
        
        printf("CATCH SIGNAL PID=%d\n",getpid());
    }
}

int main(){
    int exit_status;

    // 呼叫 signal 來接收 SIGCHLD 信號
    signal(SIGCHLD,sig_handler);

    // 從呼叫 fork 開始, 會分成兩支程序多工進行
    pid_t PID = fork();

    switch(PID){
        // PID == -1 代表 fork 出錯
        case -1:
            perror("fork()");
            exit(-1);

        // PID == 0 代表是子程序
        case 0:
            printf("[Child] I'm Child process\n");
            printf("[Child] Child's PID is %d\n", getpid());
            sleep(5); // 暫停 5 秒
            break;

        // PID > 0 代表是父程序
        default:
            printf("[Parent] I'm Parent process\n");
            printf("[Parent] Parent's PID is %d\n", getpid());
            // wait(&exit_status);
    }

    return 0;
}
  • 編譯執行結果

你編譯執行可以發現, 父程序不再會阻塞下來等待子程序了, 因為我們已經將等待子程序的動作交給 SIGCHLD 處理函數來處理

當你執行完程式的 5 秒內馬上打 ps 指令觀看進程, 你會發現子程序 (132) 還在, 等到 5 秒後就會被 signal() 接收並結束, 所以也不會造成殭屍程序的產生