第8章——《进程控制》

### 实验环境介绍
* gcc:4.8.5
* glibc:glibc-2.17-222.el7.x86_64
* os:Centos7.4
* kernel:3.10.0-693.21.1.el7.x86_64

函数fork

fork
* 子进程和父进程继续执行fork之后,子进程是父进程的副本。子进程获得父进程数据空间、堆和栈的副本。父进程和子进程不共享这些存储空间。父进程和子进程共享正文段
* 父进程和子进程之间使用写时复制技术。父进程数据段、堆、栈由父进程和子进程共享,如果父进程和子进程中的任一一个试图修改这些区域,则内核只为哪块内存制作一个副本,通常是虚拟存储系统中的一“页”
* 测试代码:

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

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
    exit(1);\
} while (0)

int globvar = 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 */

    if ((pid = fork()) < 0) {
        err_sys("fork error");
    } else if (pid == 0) { /* child */
        globvar++; /* modify variables */
        var++;
    } else {
        sleep(2); /* parent */
    }

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

[root@localhost part_8]# ./fork 
a write to stdout
before fork                # 这里由于printf是输出到标准输出,且标准输出指向终端设备(此时缓冲类型为行缓冲),所以父进程直接刷新了缓冲区,子进程得到的缓冲区为空
pid = 7176, glob = 7, var = 89
pid = 7175, glob = 6, var = 88
[root@localhost part_8]#

[root@localhost part_8]# ./fork > fork.out
[root@localhost part_8]# cat fork.out 
a write to stdout
before fork
pid = 7178, glob = 7, var = 89
before fork              # 这里由于printf是输出到标准输出,且标准输出指向文件(此时缓冲类型是全缓冲),所以父进程没有刷新了缓冲区,子进程得到的缓冲区和父进程一样,所以会有两次输出
pid = 7177, glob = 6, var = 88
进程文件共享
  • 在父进程打开文件后进行fork,进程间对文件的共享如图
    共享
  • 如果父子进程写同一描述符指向的文件,但又没有任何形式的同步,那么他们的输出会相互混合(描述符是fork之前打开)
fork之后,子进程继承父进程的属性
  • 继承的属性包括:
    • 实际用户ID、实际组ID、有效用户ID、有效组ID
    • 附属组ID
    • 进程组ID
    • 会话ID
    • 控制终端
    • 设置用户ID标志和设置组ID标志
    • 当前工作目录
    • 根目录
    • 文件模式创建屏蔽字(umask)
    • 信号屏蔽和注册
    • 对任一打开文件描述符的执行时关闭(文件描述符标志close-on-exec)
    • 环境
    • 连接的共享存储段
    • 存储映像
    • 资源限制
  • 父子进程之间的区别:
    • fork的返回值不同
    • 进程ID不同、父进程ID不同
    • 子进程的tms_utime、tms_stime、tms_cutime和tms_ustime的值设置为0(后面介绍)
    • 子进程不继承父进程设置的文件锁
    • 子进程的未处理的闹钟被清除
    • 子进程的未处理信号集为空集
      子进程从fork返回后立即调用exec,这个组合操作叫做spawn。UNIX系统一般把这两个操作分开,因为很多场合需要单独使用fork,不跟随exec,使得在fork和exec之间可以更改自己的属性。如I/O重定向、用户ID、信号注册(第15章再来讨论)

vfork函数

  • vfork与fork的不同
    • vfork保证子进程先运行
    • 子进程调用exec和exit之后,父进程才运行。在exec之前,子进程和父进程共享地址空间
  • 测试代码1:
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
    exit(1);\
} while (0)

int globvar = 6; /* external variable in initialized data */

int
main(void)
{
    int var; /* automatic variable on the stack */
    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 */
        globvar++; /* modify parent's variables */
        var++;
        _exit(0); /* child terminates */
    }

    /* parent continues here */
    printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,
      var);
    exit(0);
}

[root@localhost part_8]# ./vfork 
before vfork
pid = 7305, glob = 7, var = 89                    # 父子进程共享地址空间
[root@localhost part_8]# ./vfork > vfork.out      
[root@localhost part_8]# cat vfork.out             
before vfork
pid = 7307, glob = 7, var = 89                 # 父子进程共享地址空间
  • 测试代码2
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
    exit(1);\
} while (0)

int globvar = 6; /* external variable in initialized data */

int
main(void)
{
    int var; /* automatic variable on the stack */
    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 */
        globvar++; /* modify parent's variables */
        var++;
        exit(0); /* child terminates */             # 换个函数试试
    }

    /* parent continues here */
    printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,
      var);
    exit(0);                        、、
}

[root@localhost part_8]# ./vfork 
before vfork
pid = 7316, glob = 7, var = 89                            # 父子进程共享地址空间
[root@localhost part_8]# ./vfork > vfork.out      
[root@localhost part_8]# cat vfork.out 
before vfork
pid = 7318, glob = 7, var = 89                            # 父子进程共享地址空间

函数exit

  • 五种正常终止方式
    • main函数中return
    • 调用exit含糊是
    • 调用_exit或者_Exit函数
    • 进程的最后一个线程return,但是该线程的返回值不作为进程的返回值。而是以0作为返回值
    • 进程的最后一个线程调用pthread_exit函数,和前面一样
  • 3种异常终止方式
    • 调用abort,产生SIGABRT信号
    • 接收到某些信号
    • 最后一个线程对取消请求做出响应。
  • 如果父进程在子进程终止前提前终止,子进程的父ID为1
  • 父进程可以调用wait或者waitpid得到已经终止的子进程的信息
    • 进程id
    • 进程终止状态
    • 进程使用的cpu总量
  • 一个已经终止、但是父进程没有对其进行善后处理(使用wait函数之类的,获取终止子进程的有关信息、释放它所占用的资源)的进程成为僵尸进程zombie,ps命令中进程状态显示为Z的进程

wait和waitpid

  • 如何进行子进程的回收测试代码:
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
    exit(1);\
} while (0)

void pr_exit(int status);

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

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

    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 SIGABRT */

    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 */
        status /= 0; /* divide by 0 generates SIGFPE */

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

    exit(0);
}

void
pr_exit(int status)
{
    if (WIFEXITED(status))
        printf("normal termination, exit status = %d\n",
                WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
        printf("abnormal termination, signal number = %d%s\n",
                WTERMSIG(status),
#ifdef WCOREDUMP
                WCOREDUMP(status) ? " (core file generated)" : "");
#else
                "");
#endif
    else if (WIFSTOPPED(status))
        printf("child stopped, signal number = %d\n",
                WSTOPSIG(status));
}

[root@localhost part_8]# ./zombie 
normal termination, exit status = 7
abnormal termination, signal number = 6
abnormal termination, signal number = 8
  • 如果父进程不行等子进程,让子进程单独运行,可以以下实现
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
    exit(1);\
} while (0)


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 our real parent calls exit() in the statement above.
         * Here's where we'd continue executing, knowing that when
         * we're done, init will reap our status.
         */
        sleep(2);
        printf("second child, parent pid = %ld\n", (long)getppid());
        exit(0);
    }

    if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
        err_sys("waitpid error");

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

result:
[root@localhost part_8]# ./fork2 
[root@localhost part_8]# second child, parent pid = 1

函数exec

  • exec函数并不创建新的进程,只是用新程序替换了进程的正文段、数据段、堆栈
  • 基本源于:exec、exit、wait、exec
  • 新程序继承调用进程的下列属性:
    • 进程ID和父进程ID
    • 实际用户ID和实际组ID
    • 附属组ID
    • 进程组ID
    • 会话ID
    • 控制终端
    • 闹钟上尚余留的时间
    • 当前工作目录
    • 根目录
    • 文件模式创建屏蔽字(umask)
    • 文件锁
    • 进程信号屏蔽
    • 未处理信号
    • 资源限制
    • nice
    • tms_utime、tms_stime、tms_cutime以及tms_cstime值
  • exec族函数之间的关系
    exec

更改用户ID和更改组ID

用户id和组id
* 访问一个文件是基于用户ID和组ID的,当程序需要增加特权,或需要访问当前并不允许访问的资源时,我们需要更改自己的用户ID和组ID
* 更改用户ID的规则
* 如果进程具有超级用户权,则setuid函数将实际用户ID、有效用户ID以及保存的设置用户ID设置为uid
* 如果没有超级用户权,但uid等于实际用户id或保存的设置用户,则setuid只有将有效用户用户id设置为uid。不更改实际用户id和保存的设置用户id
* 测试代码如下:


[manjingliu@localhost part_8]$ ./uid 
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdarg.h> /* ISO C variable aruments */
#include <string.h>

#define MAXLINE 4096

static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
    char buf[MAXLINE];

    vsnprintf(buf, MAXLINE-1, fmt, ap);
    if (errnoflag)
        snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
          strerror(error));
    strcat(buf, "\n");
    fflush(stdout); /* in case stdout and stderr are the same */
    fputs(buf, stderr);
    fflush(NULL); /* flushes all stdio output streams */
}


void
err_sys(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(1, errno, fmt, ap);
    va_end(ap);
    // exit(1);
}

void pr_exit(int status);

int
main(void)
{
    uid_t uid = 0;
    int ret = setuid(uid);
    if (!ret)
        printf("set id = %d\n", uid);
    else
        err_sys("set root");

    uid = 1000;
    ret = setuid(uid);
    if (!ret)
        printf("set id = %d\n", uid);
    else
        err_sys("set manjingliu");

    uid = 1002;
    ret = setuid(uid);
    if (!ret)
        printf("set id = %d\n", uid);
    else
        err_sys("set 1002");
    exit(0);
}

result:
[manjingliu@localhost part_8]$ sudo ./uid 
set id = 0
set id = 1000
set 1002: Operation not permitted
[manjingliu@localhost part_8]$ ./uid 
set root: Operation not permitted
set id = 1000
set 1002: Operation not permitted
[manjingliu@localhost part_8]$ su
Password: 
[root@localhost part_8]# ./uid        
set id = 0
set id = 1000
set 1002: Operation not permitted
  • 更改三个id的方法
    更改id
函数setreuid和setregid
  • 交换实际用户id和有效用户id的值
  • 规则
    • 一个非特权用户总能交换实际用户ID和有效用户ID。这就允许一个设置用户ID程序可以交换成用户的普通权限,以后又可以再次交换为设置用户ID权限
      设置不同用户ID的各函数
解释器文件
  • 忽略
函数system
  • system函数的一种实现
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>

int
system(const char *cmdstring) /* version without signal handling */
{
    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 processes */
    } else if (pid == 0) { /* child */
        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        _exit(127); /* execl error */
    } else { /* parent */
        while (waitpid(pid, &status, 0) < 0) {
            if (errno != EINTR) {
                status = -1; /* error other than EINTR from waitpid() */
                break;
            }
        }
    }

    return(status);
}
进程会计
  • 忽略
用户标识

用户标识
* 获取登录名,如果调用此函数的进程没有连接到用户登录时所用的终端,则失败。一般这些进程都是守护进程

进程调度
  • 测试代码如下
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdarg.h> /* ISO C variable aruments */
#include <string.h>

#include <errno.h>
#include <sys/time.h>

#if defined(MACOS)
#include <sys/syslimits.h>
#elif defined(SOLARIS)
#include <limits.h>
#elif defined(BSD)
#include <sys/param.h>
#endif

#define MAXLINE 4096

static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
    char buf[MAXLINE];

    vsnprintf(buf, MAXLINE-1, fmt, ap);
    if (errnoflag)
        snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
          strerror(error));
    strcat(buf, "\n");
    fflush(stdout); /* in case stdout and stderr are the same */
    fputs(buf, stderr);
    fflush(NULL); /* flushes all stdio output streams */
}


void
err_sys(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(1, errno, fmt, ap);
    va_end(ap);
    // exit(1);
}

void
err_quit(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(0, 0, fmt, ap);
    va_end(ap);
    exit(1);
}


unsigned long long count;
struct timeval end;

void
checktime(char *str)
{
    struct timeval tv;

    gettimeofday(&tv, NULL);
    if (tv.tv_sec >= end.tv_sec && tv.tv_usec >= end.tv_usec) {
        printf("%s count = %lld\n", str, count);
        exit(0);
    }
}

int
main(int argc, char *argv[])
{
    pid_t pid;
    char *s;
    int nzero, ret;
    int adj = 0;

    setbuf(stdout, NULL);
#if defined(NZERO)
    nzero = NZERO;
#elif defined(_SC_NZERO)
    nzero = sysconf(_SC_NZERO);
#else
#error NZERO undefined
#endif
    printf("NZERO = %d\n", nzero);
    if (argc == 2)
        adj = strtol(argv[1], NULL, 10);
    gettimeofday(&end, NULL);
    end.tv_sec += 10; /* run for 10 seconds */

    if ((pid = fork()) < 0) {
        err_sys("fork failed");
    } else if (pid == 0) { /* child */
        s = "child";
        printf("current nice value in child is %d, adjusting by %d\n",
          nice(0)+nzero, adj);
        errno = 0;
        if ((ret = nice(adj)) == -1 && errno != 0)
            err_sys("child set scheduling priority");
        printf("now child nice value is %d\n", ret+nzero);
    } else { /* parent */
        s = "parent";
        printf("current nice value in parent is %d\n", nice(0)+nzero);
    }
    for(;;) {
        if (++count == 0)
            err_quit("%s counter wrap", s);
        checktime(s);
    }
}

result:
[manjingliu@localhost part_8]$ taskset -c 1 ./nice 20
NZERO = 20
current nice value in parent is 20
current nice value in child is 20, adjusting by 20
now child nice value is 39
parent count = 353444491
child count = 5220216
[manjingliu@localhost part_8]$ taskset -c 1 ./nice 
NZERO = 20
current nice value in parent is 20
current nice value in child is 20, adjusting by 0
now child nice value is 20
child count = 180986051
parent count = 180957702
进程时间
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdarg.h> /* ISO C variable aruments */
#include <string.h>
#include <errno.h>

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

#define MAXLINE 4096

static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
    char buf[MAXLINE];

    vsnprintf(buf, MAXLINE-1, fmt, ap);
    if (errnoflag)
        snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
          strerror(error));
    strcat(buf, "\n");
    fflush(stdout); /* in case stdout and stderr are the same */
    fputs(buf, stderr);
    fflush(NULL); /* flushes all stdio output streams */
}

void
err_sys(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(1, errno, fmt, ap);
    va_end(ap);
    // exit(1);
}

void
err_quit(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(0, 0, fmt, ap);
    va_end(ap);
    exit(1);
}

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

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]); /* once for each command-line arg */
    exit(0);
}

static void
do_cmd(char *cmd) /* execute and time the "cmd" */
{
    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 second 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_utime - tmsstart->tms_utime) / (double) clktck);
    printf(" sys: %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);
}

void
pr_exit(int status)
{
    if (WIFEXITED(status))
        printf("normal termination, exit status = %d\n",
                WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
        printf("abnormal termination, signal number = %d%s\n",
                WTERMSIG(status),
#ifdef WCOREDUMP
                WCOREDUMP(status) ? " (core file generated)" : "");
#else
                "");
#endif
    else if (WIFSTOPPED(status))
        printf("child stopped, signal number = %d\n",
                WSTOPSIG(status));
}

result:
normal termination, exit status = 0
[manjingliu@localhost part_8]$ ./pr_time "sleep 5" "date"

command: sleep 5
  real: 5.01
  user: 0.00
  sys: 0.00
  child user: 0.00
  child sys: 0.00
normal termination, exit status = 0

command: date
Mon Aug 13 11:43:51 CST 2018
  real: 0.00
  user: 0.00
  sys: 0.00
  child user: 0.00
  child sys: 0.01
normal termination, exit status = 0
[manjingliu@localhost part_8]$ 

猜你喜欢

转载自blog.csdn.net/u012570105/article/details/81627743