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;
}

而后程序输出以下:

相关文章
相关标签/搜索