zoukankan      html  css  js  c++  java
  • 《APUE》读书笔记—第八章进程控制

    本章介绍Unix的进程控制,包括进程创建,执行程序和进程终止,进程的属性,exec函数系列,system函数,进程会计机制。

    1、进程标识符

      每一个进程都有一个非负整数标识的唯一进程ID。ID为0表示调度进程,即交换进程,是内核的一部分,也称为系统进程,不执行任何磁盘操作。ID为1的进程为init进程,init进程不会终止,他是一个普通的用户进程,需要超级用户特权运行。获取标识符函数如下:

    #include <sys/types.h>
    #include <unistd.h>
    pid_t getpid(void);    //调用进程的进程ID
    pid_t getppid(void);     //调用进程的父进程ID
    gid_t getgid(void);       //调用进程的实际组ID
    gid_t getegid(void);    //调用进程的有效组ID
    uid_t getuid(void);     //调用进程的实际用户ID
    uid_t geteuid(void);   //调用进程的有效用户ID

    2、fork函数

      一个现有的进程可以调用fork函数创建一个新进程。函数原型为:pid_t fork(void)。有fork创建的新进程称为子进程,fork函数调用一次返回两次。子进程的返回值为0,父进程的返回值为新子进程的ID。子进程和父进程举行执行fork调用后的指令,子进程是父进程的副本。子进程获得父进程的数据空间、栈和队的副本,父子进程不共享这些存储空间部分,共享正文段。子进程相当于父进程克隆了一份自己。 创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

      fork出错可能有两种原因:
        1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
        2)系统内存不足,这时errno的值被设置为ENOMEM。

    注意的是:父进程设置的文件锁不会被子进程继承。

    写个程序,创建一个子进程,在子进程中改变变量的值,然后父子进程同时输出变量的值,看看有什么变化,同时输出子进程的标识符信息。程序如下:

    View Code
     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <sys/types.h>
     5 #include <unistd.h>
     6 #include <errno.h>
     7 //全局变量
     8 int global = 100;
     9 char buf[]="a write to stdout.\n";
    10 
    11 int main()
    12 {
    13     int     var;
    14     pid_t   pid;
    15 
    16     var = 90;
    17     write(STDOUT_FILENO,buf,sizeof(buf)-1);
    18     //创建一个子进程
    19     pid = fork();
    20     if(pid == -1)      //出错
    21     {
    22         perror("fork() error");
    23         exit(-1);
    24     }
    25     if(pid == 0)     //子进程
    26     {
    27         printf("This is child process.\n");
    28         printf("Child process id is:%d\n",getpid());
    29         printf("Father process id is:%d\n",getppid());
    30         //子进程修改数据
    31         global++;
    32         var++;
    33     }
    34     if(pid > 0)   //父进程
    35       sleep(3);  //等待子进程先执行
    36     printf("pid = %d,ppid =%d,global=%d,var=%d\n",getpid(),getppid(),global,var);
    37     return 0;
    38 }
     程序执行结果如下:

    从结果可以看出子进程拥有自己的数据空间,不与父进程共享数据空间。

      vfork函数的调用序列和返回值与fork相同,但是vfork并不将父进程的地址空间完全复制到子进程中,在调用exec和exit之前在父进程的空间中运行。vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。

      进程终止最后都会执行内核中的同一段代码,为相应的进程关闭所有打开描述符,释放它所使用的存储器等。子进程可以通过exit函数通知父进程是如何终止的,父进程调用wait或waitpid函数可以获取终止状态。子进程是在父进程调用fork后产生的,如果父进程在子进程之前终止,则将子进程的父进程改变为init进程,保证每一个进程都有一个父进程。一个已经终止,但其父进程尚没有对其进行善后处理的进程称为僵死进程(zombie)。由init进程领养的子进程不会变成僵死进程,因为init进程在子进程终止的时候会调用一个wait函数取得子进程的终止状态。

      当一个进程正常或者异常终止的时,内核就像其父进程发送SIGCHLD信号。调用wait和waitpid函数可能发生的情况:(1)如果所有子进程都还在运行,则阻塞;(2)如果一个子进程已经终止,正等待父进程获取进程终止状态,则取得孩子的终止状态立刻返回;(3)若果没有任何子进程,则立即出错返回。如果在任意时刻调用wait,则进程可能会阻塞。

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

    pid_t wait(int *status);   //在一个子进程终止前,wait使调用者阻塞

    pid_t waitpid(pid_t pid, int *status, int options); //可以使调用者不阻塞

    #include <sys/types.h>
    #include <sys/resource.h>
    #include <sys/wait.h>
    pid_t wait3(int *
    status, int options,struct rusage *rusage);
    pid_t wait4(pid_t
    pid, int *status, int options, struct rusage *rusage);
    写个程序,子进程给出退出状态,父进程通过wait和waitpid获取退出状态。程序如下:

    View Code
     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <sys/wait.h>
     5 #include <sys/types.h>
     6 #include <signal.h>
     7 
     8 int main()
     9 {
    10     pid_t pid;
    11     int status;
    12     if((pid = fork()) == -1)
    13     {
    14         perror("fork() error");
    15         exit(-1);
    16     }
    17     if(pid == 0)
    18       exit(0);
    19     if(wait(&status) == pid)
    20             printf("child normal exit,exit status=%d\n",WEXITSTATUS(status));
    21     if((pid = fork()) == -1)
    22     {
    23         perror("fork() error");
    24         exit(-1);
    25     }
    26     if(pid == 0)
    27         abort();
    28     if(waitpid(pid,&status,0) == pid)
    29             printf("child abnormal termination,signal number=%d\n",WTERMSIG(status));
    30 }

    程序执行结果如下:

    waitpid函数中的pid参数取值情况:
    pid=-1   等待任一子进程,此时相当于wait
    pid>0     等待期进程ID与pid相等的子进程
    pid==0    等待期组ID等于调用进程组ID的任一个子进程
    pid<-1    等待其组ID等于pid绝对值的任一子进程

    另外提供了一种避免僵死进程的方法:调用fork两次。

      exec函数,用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另外一个程序。当调用exec函数时,该进程执行的程序完全替换为新进程,exec函数不新建进程,只是用一个全新的程序替换了当前的正文、数据、堆和栈段。执行完之后,进程ID不会改变。在进程间通信的时候,经常需要调用exec函数启动另外一个例程。函数原型如下:

    #include <unistd.h>
    extern char **environ;
    int execl(const char *path, const char *arg, ...);
    int execlp(const char *file, const char *arg, ...);
    int execle(const char *path, const char *arg,..., char * const envp[]);
    int execv(const char *path, char *const argv[]);
    int execvp(const char *file, char *const argv[]);
    int execve(const char *file, char *const argv[],char *const envp[]);

    其中l表示列表(list),v标识矢量(vector)。execl、execlp、execle中每个命令行参数都是一个单独参数,这种参数以空指针结尾。execv、execvp、execve命令行参数是一个指针数组。e标识环境变量,传递参数。写个程序进行测试,程序分为两部分,exec调用的程序,exec执行程序。程序如下:

    exec调用程序如下,可执行文件名称为exectest,存放在/home/anker/Programs目录下。

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 
     4 int main(int argc,char *argv[])
     5 {
     6     int         i;
     7     char        **ptr;
     8     extern char **environ;
     9 
    10     for(i=0;i<argc;++i)
    11         printf("argv[%d]=%s\n",i,argv[i]);
    12     for(ptr=environ;*ptr!=0;ptr++)
    13         printf("%s\n",*ptr);
    14     exit(0);
    15 }

     exec执行程序如下:存放在/home/anker/Programs目录下。

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <errno.h>
    
    char *env_init[] = {"LENGTH=100","PAHT=/tmp",NULL};
    
    int main()
    {
        pid_t pid;
        char *path = "/home/anker/Programs/exectest";
        char *filename = "exectest";
        char *argv[3]= {0};
        argv[0] = "exec";
        argv[1] = "test";
        if((pid=fork()) == -1)
        {
            perror("fork() error");
            exit(-1);
        }
        else if(pid == 0)
        {
            printf("Call execle:\n");
            execle(path,argv[0],argv[1],(char*)0,env_init);
        }
        waitpid(pid,NULL,0);
    
        if((pid=fork()) == -1)
        {
            perror("fork() error");
            exit(-1);
        }
        else if(pid == 0)
        {
            printf("Call execve:\n");
            execve(filename,argv,env_init);
        }
        waitpid(pid,NULL,0);
        exit(0);
    
    }

    执行结果如下:

    system函数,在程序中执行一个命令字符串。例如system("date > file")。函数原型如下:

    #include <stdlib.h>
    int system(const char *command);

    system函数实现中调用了fork、exec和waitpid函数,因此有三种返回值。system函数实现一下,没有信号处理。程序如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <errno.h>
    
    int mysystem(const char * cmdstring);
    
    int main()
    {
        int status;
        status = mysystem("date");
        if(status < 0)
        {
           printf("mysystem() error.\n");
        }
        status = mysystem("who");
        if(status < 0)
        {
           printf("mysystem() error.\n");
        }
        exit(0);
    }
    
    int mysystem(const char * cmdstring)
    {
        pid_t  pid;
        int status;
    
        if(cmdstring == NULL)
            return 1;
        if((pid = fork()) == -1)
        {
            status = -1;
        }
        else if(pid == 0)   //在子进程中调用shell脚本
        {
            execl("/bin/sh","sh","-c",cmdstring,(char *)0);
            _exit(127);
        }
        else
        {
            while(waitpid(pid,&status,0) < 0)
            {
                if(errno != EINTR)
                {
                    status = -1;
                    break;
                }
            }
        }
        return status;
    }

    程序执行结果如下:

    system函数可以设置用户的ID,这是一个安全漏洞。

    进程会计:启用后,每当进程结束时候内核就写一个会计记录,包括命令名、所使用的CPU时间总量、用户ID和组ID、启动时间等。accton命令启动会计处理,会计记录写到指定的文件中,Linux中位于/var/account/ pacct。会计记录结构定义在<sys/acct.h>头文件中。

    #define ACCT_COMM 16

    typedef u_int16_t comp_t;

    struct acct {
        char ac_flag;           /* Accounting flags */
        u_int16_t ac_uid;       /* Accounting user ID */
        u_int16_t ac_gid;       /* Accounting group ID */
        u_int16_t ac_tty;       /* Controlling terminal */
        u_int32_t ac_btime;     /* Process creation time(seconds since the Epoch) */
        comp_t    ac_utime;     /* User CPU time */
        comp_t    ac_stime;     /* System CPU time */
        comp_t    ac_etime;     /* Elapsed time */
        comp_t    ac_mem;       /* Average memory usage (kB) */
        comp_t    ac_io;        /* Characters transferred (unused) */
        comp_t    ac_rw;        /* Blocks read or written (unused) */
        comp_t    ac_minflt;    /* Minor page faults */
        comp_t    ac_majflt;    /* Major page faults */
        comp_t    ac_swaps;     /* Number of swaps (unused) */
        u_int32_t ac_exitcode;  /* Process termination status(see wait(2)) */
        char      ac_comm[ACCT_COMM+1];/* Command name (basename of last executed command; null-terminated) */
        char      ac_pad[X];    /* padding bytes */
    };
    用户标识,用getlogin函数获取用户的登录名。函数原型如下:char *getlogin(void)。

    进程时间:墙上时钟时间、用户CPU时间和系统CPU时间。任一个进程都可以调用times函数获取它自己及已终止子进程时间。进程时间操作函数及结构如下:

    #include <sys/times.h>

    clock_t times(struct tms *buf);

    struct tms {
        clock_t tms_utime;  /* user time */
        clock_t tms_stime;  /* system time */
        clock_t tms_cutime; /* user time of children */
        clock_t tms_cstime; /* system time of children */
    };

    总结:通过本章的学习。完全的了解Unix的进程控制,掌握了fork、exec簇、wait和waitpid进程控制函数。另外学习了system函数和进程会计。了解了解释器文件及其工作方式,用户标识和进程时间。

  • 相关阅读:
    出售几个闲置的玉米
    竞价账户时好时坏怎样分析找到原因?
    两行代码轻松搞定网页自动刷新,简单,实用。
    BCompare文件对比神器 工程师 开发必备工具之一
    关于 Apache 屏蔽 IP 地址
    一段 JavaScript 实现禁止用户打开控制台与鼠标右键查看源码
    Apache使用.htaccess防盗链禁止用户下载
    HTML5下video右键禁用-禁止右键下载视频
    python装饰器
    部署nginx支持lua
  • 原文地址:https://www.cnblogs.com/Anker/p/2820346.html
Copyright © 2011-2022 走看看