APUE: Process Control

進程標識

每一個進程都由進程ID惟一標識, 而進程ID是獨一無二的.shell

#include <unistd.h>編程

pid_t getpid(void);    // returns: 調用的進程id服務器

pid_t getppid(void);    // returns: 調用進程的父id網絡

uid_t getuid(void);    // returns: 調用進程的實際用戶(real user)id異步

uid_t geteuid(void);    // returns: 調用進程的有效用戶(effective user)id函數

gid_t getgid(void);    // returns: 調用進程的實際分組(real group)id學習

gid_t getegid(void);    // returns: 調用進程的有效分組(effective group)idui

一個實際的例子:spa

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

int main( void )
{
    // 3824
    printf("%d\n", getpid());
    // 22432
    printf("%d\n", getppid());
    // 1000 
    printf("%d\n", getuid());
    // 1000 
    printf("%d\n", geteuid());
    // 1000 
    printf("%d\n", getgid());
    // 1000 
    printf("%d\n", getegid());

    return 0;
}

fork函數

調用fork函數可建立一個新進程.線程

#include <unistd.h>

pid_t fork(void);

        returns: 子進程中返回0, 父進程中返回子進程id, 錯誤返回-1

fork函數特殊在於: 返回兩個值, 子進程返回0而父進程返回子進程id. 子進程是父進程的一個副本, 它將得到父進程相同的數據和堆棧.

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

int globvar = 6;
char buf[] = "a write to stdout\n";

int main( void )
{
    int var;
    pid_t pid;
    var = 88;
    if (write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) - 1)
        printf("write error");
    printf("before fork\n");

    if ((pid = fork()) < 0) {
        printf("fork error");
    } else if (pid == 0) {
        globvar++;
        var++;
    } else {
        sleep(2);
    }

    printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);

    return 0;
}

終端輸出:

leicj@leicj:~/test$ ./a.out
a write to stdout
before fork
pid = 4062, glob = 7, var = 89
pid = 4061, glob = 6, var = 88
leicj@leicj:~/test$ ./a.out > temp.out
leicj@leicj:~/test$ cat temp.out 
a write to stdout
before fork
pid = 4066, glob = 7, var = 89
before fork
pid = 4065, glob = 6, var = 88

重定向到終端, 緩衝區會被刷新; 重定向到文件, 則不會.

文件共享

考慮如下一個例子:

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

int main( void )
{
    FILE *file = fopen("temp", "w+");
    pid_t pid;
    char buf1[] = "abcdef\n";
    char buf2[] = "ABCDEF\n";

    if (fputs(buf1, file) == EOF) {
        printf("write error\n");
    }
    if ((pid = fork()) < 0) {
        printf("fork error\n");
    } else if (pid == 0) {
        if (fputs("child:\n", file) == EOF) {
            printf("child write error\n");
        }
        if (fputs(buf2, file) == EOF) {
            printf("child write error\n");
        }
    } else {
        sleep(2);
        if (fputs("parent:\n", file) == EOF) {
            printf("parent write error\n");
        }
        if (fputs(buf1, file) == EOF) {
            printf("write error\n");
        }
    }

    return 0;
}

終端輸出:

leicj@leicj:~/test$ ./a.out
leicj@leicj:~/test$ cat temp
abcdef
child:
ABCDEF
abcdef
parent:
abcdef

這裏可能會有點疑惑是: 爲何"parent:"前面會有"abcdef"? 緣由也是由於寫入文件狀況下, 緩衝區並未被刷新, 因此第一個fputs會被執行一遍.

fork通常有兩個做用:

1. 父進程和子進程毫無相關, 執行fork後父進程就關閉描述符, 子進程也同樣. 例如在網絡編程中進程使用到, 一個服務器, 開多個客戶端進程.

2. 父進程等待子進程關閉(即一些處理須要子進程完成, 父進程才能繼續下去)

vfork函數

    與fork函數有如下兩點不一樣:

1. vfork與fork同樣都建立一個子進程, 可是它並不將父進程的地址空間徹底複製到子進程中, 由於子進程會當即調用exec或exit, 因而也就不會訪問該地址空間.

2. vfork保證子進程先運行.

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

int globvar = 6;

int main( void )
{
    int var;
    pid_t pid;
    var = 88;
    printf("before vfork\n");
    if ((pid = vfork()) < 0) {
        printf("vfork error\n");
    } else if (pid == 0) {
        globvar++;
        var++;
        _exit(0);
    }
    printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);

    return 0;
}

終端輸出:

leicj@leicj:~/test$ ./a.out
before vfork
pid = 4411, glob = 7, var = 89

exit函數

五種正常的進程終止方式:

1. 在main函數中, 執行return函數.

2. 調用exit函數. 它會關閉全部的標準I/O流而且調用由atexit註冊的全部exit handlers.

3. 調用_exit或者_Exit. ISO C定義_Exit, 提供了不須要調用exit handler和信號的終止進程的方法. 它們均不會刷新緩衝區.

4. 進程中最後一個線程return回來.

5. 在最後一個線程中執行pthread_exit.

三種異常退出:

1. 調用abort, 它會生成SIGABRT信號.

2. 進程收到特殊的信號而終止.

3. 最後一個線程取消請求.

不管如何終止進程, 內核都會關閉全部打開的文件描述符, 釋放內存.

問題在於子進程的終止, 如何通知父進程. 對於三個exit函數(exit, _exit, _Exit), 它們都會將退出狀態返回給父進程. 在異常終止狀況下, 由內核生成一個終端狀態代表終止的緣由.

在調用fork函數時候, 會產生子進程, 但若是父進程先於子進程終止, 則默認init進程成爲子進程的父進程.

父進程會調用wait/waitpid來查看子進程是否已經終止. 若是子進程已經終止, 但父進程並未wait/waitpid它, 則子進程成爲僵死進程(子進程終止時依舊會保留一些信息, 例如進程號).

wait和waitpid函數

一個子進程終止(正常或者異常), 內核都會向父進程發送SIGGHLD信號. 不管是進程終止, 仍是信號的發送, 都是異步的. 因此父進程能夠忽略, 也能夠提供函數處理所接收到的信號(默認行爲是忽略).

而wait/waitpid會發生以下狀況:

1. 若是還有子進程在運行, 則阻塞.

2. 子進程終止時候當即返回.

3. 沒有任何子進程, 則返回錯誤.

#include <sys/wait.h>

pid_t wait(int *statloc);

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

                returns: 成功返回進程id, 失敗返回-1, 返回0後面描述

而兩個函數的不一樣之處在於:

1. wait函數會一直阻塞直到子進程終止, 而waitpid提供參數用於阻止阻塞.

2. waitpid可控制等待的是哪一個子進程.

 

 

 

 

 

 

4. wait和waitpid函數

    內核爲每一個終止子進程保存了必定量的信息,因此當終止進程的父進程調用wait或waitpid時,能夠獲得這些信息。這些信息至少包括進程ID,該進程的終止狀態,以及該進程使用的CPU時間總量。

    當調用wait或waitpid的進程可能會發生以下狀況:

1) 若是其全部子進程都還在運行,則阻塞

2) 若是一個子進程已終止,正等待父進程獲取其終止狀態,則取得該子進程的終止狀態當即返回

3) 若是它沒有任何子進程,則當即出錯返回

#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>

int main( void )
{
        pid_t   pid;
        int     status;

        if ((pid = fork()) < 0)
                printf("fork error\n");
        else if ( pid == 0 ){
                printf("wait函數會阻塞,直到子進程終止,並捕捉子進程的終止信息!這樣也能夠保證子進程比父進程先執行。\n");
                sleep( 2 );
        }

        if ( wait(&status) != pid )
                printf("wait error\n");

        if ( pid > 0 ){
                printf("main done\n");
        }
        return 0;
}


程序輸出:

 

waitpid的優點:

1) waitpid可等待一個特定的進程,而wait則返回任一終止子進程的狀態。

2) waitpid提供了一個wait的非阻塞版本。

3) waitpid支持做業控制

#include <sys/wait.h>
#include <unistd.h>

int main( void )
{
        pid_t   pid1;
        pid_t   pid2;

        if ( ( pid1 = fork()) < 0 ){
                printf("fork error\n");
        }
        else if ( pid1 == 0 ){
                printf("fork 1....\n");
                exit( 0 );
        }

        if ( ( pid2 = fork() ) < 0 ){
                printf("fork error\n");
        }
        else if ( pid2 == 0 ){
                printf("fork 2....\n");
                exit( 0 );
        }

        if ( waitpid( pid2, NULL, 0 ) != pid2 ){
                printf("fork 2 exit error\n");
        }
        else{
                printf("fork 2 exit ok\n");
        }

        if ( waitpid( pid1, NULL, 0 ) != pid1 ){
                printf("fork 1 exit error\n");
        }
        else{
                printf("fork 1 exit ok\n");
        }

        return 0;
}


程序輸出:

 

5. 競爭條件

    父進程/子進程之間的相互競爭:

#include <stdio.h>
#include <sys/wait.h>

static void charatatime( char * );
int main( void )
{
        pid_t   pid;
        if ( ( pid = fork() ) < 0 ){
                printf("fork error\n");
        }
        else if ( pid == 0 )
                charatatime("output from child\n");
        else{
                charatatime("output from parent\n");
        }

        return 0;
}

static void charatatime( char *str )
{
        char    *ptr;
        int     c;
        setbuf( stdout, NULL );
        for ( ptr = str; ( c = *ptr++ ) != 0; )
                putc( c, stdout );
}


程序輸出有點亂:

 

6. exec函數

    exec函數一般用於執行可執行文件,因此在fork後直接運行較好。咱們先編寫一個用於exec執行的程序echoall.c:

#include <stdio.h>

int main( int argc, char *argv[] )
{
        int     i;
        char    **ptr;
        extern char **environ;

        for ( i = 0; i < argc; i++ )
                printf("argv[%d]:%s\n", i, argv[ i ] );
        for ( ptr = environ; *ptr != 0; ptr++ )
                printf("%s\n", *ptr );

        return 0;
}

而後編譯以下:

cc -o echoall echoall.c


    則echoall爲可執行文件,而後咱們就能夠編寫exec程序文件:

#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
char    *env_init[] = { "USER=unknown", "PATH=/tmp", NULL };

int main( void )
{
        pid_t   pid;
        if ( ( pid = fork() ) < 0 )
                printf("fork error\n");
        else if ( pid == 0 ){
                if ( execle( "/home/leichaojian/echoall","echoall","myarg1","MY ARG2", ( char * )0, env_init ) < 0 )
                        printf("execle error\n");
        }
        if ( waitpid( pid, NULL, 0 ) < 0 )
                printf("wait error\n");
        if ( ( pid = fork() ) < 0 )
                printf("fork error\n");
        else if ( pid == 0 ){
                if ( execlp( "/home/leichaojian/echoall","echoall","only 1 arg",( char * )0 ) < 0 ){
                        printf("execlp error\n");
                }
        }
        return 0;
}

    其中/home/leichaojian能夠經過指令pwd來獲得。程序運行以下:

 

7. 解釋器文件

    用一個程序來解釋腳本語言(PS:找段時間學習shell編程)

echoall.c:

#include <stdio.h>

int main( int argc, char *argv[] )
{
        int     i;
        char    **ptr;
        extern char **environ;

        for ( i = 0; i < argc; i++ )
                printf("argv[%d]:%s\n", i, argv[ i ] );
//      for ( ptr = environ; *ptr != 0; ptr++ )
//              printf("%s\n", *ptr );

        return 0;
}


而後咱們執行:

cc -o echoall echoall.c

 

shell.c:

 

#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>

int main( void )
{
        pid_t   pid;
        if ( ( pid = fork() ) < 0 )
                printf("fork error\n");
        else if ( pid == 0 ){
                if ( execl("/home/leichaojian/testinterp", "testinterp",
                        "myarg1","MY ARG2",(char *)0 ) < 0 )
                        printf("execl error\n");
        }
        if ( waitpid( pid, NULL, 0 ) < 0 )
                printf("waitpid error\n");
        return 0;
}

而後程序輸出以下:

相關文章
相關標籤/搜索