《unix环境高级编程》--- 进程控制

进程ID为0的进程通常是调度进程,常被成为交换进程,是内核的一部分,不执行磁盘上的程序,被称为系统进程。
进程ID为1的进程通常是init进程,在自举结束时由内核调用。将系统引导到一个状态。是一个普通的用户进程,而非内核中的系统进程,但以超级用户特权运行。
进程ID为2的进程是守护进程,复制支持虚拟存储系统的分页操作。

fork函数示例

#include "apue.h"

int glob = 6;  /* external variable in initialized data */
char buf[] = "a write to stdout\n";

int main(void)
{
    int var;  /* automatic variable on the stack */
    pid_t pid;

    var = 88;
    if(write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
        err_sys("write error");
    printf("before fork\n");  /* we don't flush stdout */

    /*
       pid_t fork(void);
       创建一个新进程
       子进程的返回值是0,可通过getppid获得父进程ID
       父进程的返回值是子进程ID
    */
    if((pid = fork()) < 0)
    {
        err_sys("fork error");
    }
    else if(pid == 0)  /* child */
    {
        glob++;    /* modify variables */
        var++;
    }
    else
    {
        sleep(2);  /* parent 使子进程先执行*/
    }   

    printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
    exit(0);
}

这里写图片描述
子进程的变量值改变了,而父进程没有
strlen不包含终止null字节的字符串长度,而sizeof则计算包含终止null字节的缓冲区长度

这里写图片描述
write不带缓冲。因为在fork之前调用write,所以数据写到标准输出一次。
但标准I/O带缓冲,如果标准输出连接到终端设备,则是行缓冲,否则全缓冲。
当以交互方式运行,因标准输出缓冲区由换行符冲洗,printf只输出一次。
但当将标准输出重定向到一个文件时,得到printf输出两次,因为在fork之前调用了printf一次,
但当调用fork时,该行数据仍在缓冲区中,会被复制到子进程中。在exit最终会冲洗父、子进程中的缓冲区。

vfork函数示例
vfork创建一个新进程,但不将父进程的空间完全复制到子进程中。子进程在调用exec或exit之前,在父进程的空间运行。
保证子进程先运行,在它调用exec或exit之后,父进程才被调度。

#include "apue.h"

int glob = 6;

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

    var = 88;
    printf("before vfork\n");  /* we don't flush stdio */
    if((pid = vfork()) < 0)
    {
        err_sys("vfork error");
    }
    else if(pid == 0)  /* child */
    {
        glob++;
        var++;
        _exit(0);  /* 不执行标准I/O缓冲冲洗 */
    }   
    /*
       Parent continue here
    */
    printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
    exit(0);
}

这里写图片描述
因为子进程在父进程的空间中运行,所以子进程改变了父进程中的变量值。

打印exit状态的说明

#include "apue.h"
#include <sys/wait.h>

void pr_exit(int status)
{
    /* 若为正常终止子进程返回的状态,则为真 */
    if(WIFEXITED(status))
    {
        printf("normal termination, exit status = %d\n", 
                  /* 取子进程传递给exit、_exit或_Exit参数的低8位 */
                   WEXITSTATUS(status));  
    }
    /* 若为异常终止子进程返回的状态,则为真 */
    else if(WIFSIGNALED(status))
    {
        printf("abnormal termination, signal number = %d%s\n",
               /* 取使子进程终止的信号编号 */
               WTERMSIG(status),
            #ifdef WCOREDUMP
            /* 若以产生终止进程的core文件,则返回真 */
            WCOREDUMP(status)? " (core file generated)": "");
        #else
            "");
        #endif
    }
    /* 若为当前暂停子进程的返回的状态,则为真 */
    else if(WIFSTOPPED(status))
    {
        printf("child stopped, signal number = %d\n",
            /* 取使子进程暂停的信号编号 */
            WSTOPSIG(status));
    }
}

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

    if((pid = fork()) < 0)
        err_sys("fork error");
    else if(pid == 0)                  /* child */
        exit(7);

    /*
       pid_t wait(int *statloc);
       如果所有子进程都还在运行,则阻塞
       如果一个子进程已终止,取得该子进程终止状态后立即返回
       如果没有任何子进程,则立即出错返回
    */
    if(wait(&status) != pid)           /* wait for child */
        err_sys("wait error");
    pr_exit(status);                   /* and print its status */

    if((pid = fork()) < 0)
        err_sys("fork error");
    else if(pid == 0)                  /* child */
        abort();                   /* generates SIGABORT */

    if((wait(&status)) != pid)          /* wait for child */
        err_sys("wait error");
    pr_exit(status);

    if((pid = fork()) < 0)
        err_sys("fork error");
    else if(pid == 0)                  /* child *
        status /= 0;               /* divide by 0 generates SIGFPE */

    if(wait(&status) != pid)           /* wait for child */
        err_sys("wait error");
    pr_exit(status);             

    exit(0);
}

这里写图片描述

调用fork两次以避免僵死进程
如果一个进程fork一个子进程,但不要它等待子进程终止,也不希望子进程处于僵死状态直到父进程终止,实现这一要求的技巧是调用fork两次。

#include "apue.h"
#include <sys/wait.h>

int main(void)
{
    pid_t pid;

    if((pid = fork()) < 0)
    {
        err_sys("fork error");
    }
    else if(pid == 0)  /* first child */
    {
        if((pid = fork()) < 0)
            err_sys("fork error");
        else if(pid > 0)
            exit(0);  /* parent from second fork == first child */

        /*
          We're the second child; our parent becomes "init" as soon
          as out real parent calls exit() in the statement above.
          Here's where we'd continue executing, knowing that when
          we're done, "init" will reap out staus.
        */
        sleep(2);
        printf("second child, parent pid = %d\n", getpid());
        exit(0);
    }

    /*
       pid_t waitpid(pid_t pid, int *staloc, int options);
       pid == -1  等待任一子进程
       pid > 0    等待进程ID为pid的子进程
       pid == 0   等待组ID等于调用进程组ID的任一子进程
       pid < -1   等待其组ID等于pid绝对值的任一子进程
       options:选项,如可使调用者不阻塞
    */
    if(waitpid(pid, NULL, 0) != pid)  /* wait for first child */
        err_sys("waitpid error");

    /*
       We're the parent (the origina process); we continue executing,
       knowing that we're not the parent of the second child.
    */
    exit(0);
}

这里写图片描述
没有按预期输出,父进程ID应该为1,即init。因为等待2秒后,真正的父进程终止,init成为父进程。

竞争条件
当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,则认为发生了竞争条件。
一个由子进程输出,另一个由父进程输出。因输出依赖于内核使这两个进程运行的顺序及每个进程运行的时间长度,所以包含了一个竞争条件。

#include "apue.h"

static void charatatime(char *);

int main(void)
{
    pid_t pid;

    if((pid = fork()) < 0)
    {
        err_sys("fork error");
    }
    else if(pid == 0)
    {
        charatatime("output from child\n");
    }
    else
    {
        charatatime("output from parent\n");
    }
    exit(0);
}

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

这里写图片描述

exec函数实例
这里写图片描述
这里写图片描述

echoall.c

#include "apue.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);

    exit(0);
}

exec1.c

#include "apue.h"
#include <sys/wait.h>

char *env_init[] = {"USER=unknown", "PATH=/tmp", NULL};

int main(void)
{
    pid_t pid;

    if((pid = fork()) < 0)
    {
        err_sys("fork error");
    }
    else if(pid == 0)   /* specify pathname, specify environment */
    {
        if(execle("/home/yjp/apue/8proc/echoall", "echoarg", "myarg1", 
               "MY ARG2", (char *)0, env_init) < 0)
        {
            err_sys("execle error");
        }
    }

    if(waitpid(pid, NULL, 0) < 0)
        err_sys("wait error");

    if((pid = fork()) < 0)
    {
        err_sys("fork error");
    }
    else if(pid == 0)  /* specify filename, inherit environment */
    {
        if(execlp("/home/yjp/apue/8proc/echoall", "echoall", 
                           "onley 1 arg", (char *)0) < 0)
            err_sys("execlp error");
    }

    if(waitpid(pid, NULL, 0) < 0)
        err_sys("wait error");

    exit(0);
}

这里写图片描述

更改用户ID和组ID

这里写图片描述

执行一个文件解释器的程序

#include "apue.h"
#include <sys/wait.h>

int main(void)
{
    pid_t pid;
    if((pid = fork()) < 0)
    {
        err_sys("fork error");
    }
    else if(pid == 0)  /* child */
    {
        if(execl("/home/yjp/apue/8proc/testinterp",
                 "testinterp", "myarg1", "MY ARG2", (char *)0) < 0)
            err_sys("execl error");
    }
    if(waitpid(pid, NULL, 0) < 0)   /* parent */
        err_sys("waitpid error");
    exit(0);
}

这里写图片描述
解释器文件必须具有可执行权限

system函数

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

int system(const char *cmdstring)
{
    pid_t pid;
    int status;

    if(cmdstring == NULL)
        return (1);    /* always a command processor with UNIX */

    if((pid = fork()) < 0)
    {
        status = -1;   /* probably out of process */
    }
    else if (pid == 0)     /* child */
    {
        /*
           shell的-c选项告诉shell程序取下一个命令行参数作为命令输入
               传递给shell的实际命令字符串可以包含任一有效的shell命令。
           例如,可以用<和>重定向输入和输出。
        */
        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        _exit(127);
    }
    else                   /* parent */
    {
        while(waitpid(pid, &status, 0) < 0)
        {
            if(errno != EINTR)
            {
                status = -1; /* error other than EINTR from waitpid() */
                break;
            }
        }
    }
    return (status);
}

int main(void)
{
    int status;

    if((status = system("date")) < 0)
        err_sys("system() error");
    pr_exit(status);

    if((status = system("nosuchcommand")) < 0)
        err_sys("system() error");
    pr_exit(status);

    if((status = system("who; exit 44")) < 0)
        err_sys("system() error");
    pr_exit(status);
    exit(0);
}

这里写图片描述

设置用户ID程序
tsys.c

#include "apue.h"

int main(int argc, char *argv[])
{
    int status;

    if(argc < 2)
        err_quit("command-line argument required");

    if((status = system(argv[1])) < 0)
        err_sys("system() error");
    pr_exit(status);

    exit(0);
}

printuids.c

#include "apue.h"

int main(void)
{
    printf("real uid = %d, effective uid = %d\n", getuid(), geteuid());
    exit(0);
}

结果:

yjp@yjp-VirtualBox:~/apue/8proc$ ./tsys ./printuids    正常执行,无特权
real uid = 1000, effective uid = 1000
normal termination, exit status = 0
yjp@yjp-VirtualBox:~/apue/8proc$ su    成为超级用户
Password:     输入口令
root@yjp-VirtualBox:/home/yjp/apue/8proc# chown root tsys   更改所有者
root@yjp-VirtualBox:/home/yjp/apue/8proc# chmod u+s tsys    增加设置用户ID
root@yjp-VirtualBox:/home/yjp/apue/8proc# ls -l tsys        检验文件权限和文件所有者
-rwsrwxr-x 1 root yjp 13696 521 19:33 tsys                 
root@yjp-VirtualBox:/home/yjp/apue/8proc# ./tsys ./printuids  在特权下执行
real uid = 0, effective uid = 0
normal termination, exit status = 0
root@yjp-VirtualBox:/home/yjp/apue/8proc# exit              退出超级用户shell
exit
yjp@yjp-VirtualBox:~/apue/8proc$ ./tsys ./printuids         正常执行,无特权
real uid = 1000, effective uid = 1000
normal termination, exit status = 0

产生会计的程序

yjp@yjp-VirtualBox:~/apue/8proc$ su
Password: 
root@yjp-VirtualBox:/home/yjp/apue/8proc# touch /home/yjp/acct.txt
root@yjp-VirtualBox:/home/yjp/apue/8proc# accton /home/yjp/acct.txt
Turning on process accounting, file set to '/home/yjp/acct.txt'.
root@yjp-VirtualBox:/home/yjp/apue/8proc# exit
exit
yjp@yjp-VirtualBox:~/apue/8proc$ ./acdata
0+1 records in
0+1 records out
7 bytes copied, 0.000423651 s, 16.5 kB/s
yjp@yjp-VirtualBox:~/apue/8proc$ su
Password: 
root@yjp-VirtualBox:/home/yjp/apue/8proc# accton off
Turning off process accounting.
root@yjp-VirtualBox:/home/yjp/apue/8proc# exit
exit

时间以及所有执行命令行参数

#include "apue.h"
#include <sys/times.h>

static void pr_times(clock_t, struct tms *, struct tms *);
static void do_cmd(char *);

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

    setbuf(stdout, NULL);
    for(i=1; i<argc; i++)
        do_cmd(argv[i]);
    exit(0);
}

static void do_cmd(char *cmd)
{
    /*
       struct tms
       {
        clock_t tms_utime;   user CPU time
        clock_t tms_stime;   system CPU time
        clock_t tms_cutime;  user CPU time, terminated children
        clock_t tms_cstime;  system CPU time, terminated children
       };
    */
    struct tms tmsstart, tmsend;
    clock_t start, end;
    int status;

    printf("\ncommand: %s\n", cmd);

    if((start = times(&tmsstart)) == -1)  /* starting values */
        err_sys("times error");

    if((status = system(cmd)) < 0)   /* execute command */
        err_sys("system() error");

    if((end = times(&tmsend)) == -1)  /* ending values */
        err_sys("times error");

    pr_times(end-start, &tmsstart, &tmsend);
    pr_exit(status);
}

static void pr_times(clock_t real, struct tms *tmsstart, struct tms *tmsend)
{
    static long clktck = 0;

    if(clktck == 0)  /* fetch clock ticks per seconc first time */
        if((clktck = sysconf(_SC_CLK_TCK)) < 0)
            err_sys("sysconf error");

    printf(" real: %7.2f\n", real / (double) clktck);
    printf(" user: %7.2f\n", (tmsend->tms_stime-tmsstart->tms_stime)/(double)clktck);
    printf(" child user: %7.2f\n", (tmsend->tms_cutime-tmsstart->tms_cutime)/(double)clktck);
    printf(" child sys: %7.2f\n", (tmsend->tms_cstime-tmsstart->tms_cstime) / (double)clktck);
}

这里写图片描述

猜你喜欢

转载自blog.csdn.net/u012319493/article/details/80385759