• 第七章 进程控制开发[fork() exec exit _exit wait waitpid 守护进程]


    前言:

    1、fork 创建一个子进程,有两个返回值。返回0为子进程,返回大于0为父进程。

    2、exec 运行新的可执行文件,取代原调用进程的数据段、代码段和堆栈段。一般是运行fork后,在子进程中执行exec。

    3、exit(0)和_exit(0):exit(0)会先清理I/O缓冲后再调用系统exit,而_exit(0)是直接调用系统exit

    4、wait函数是用于使父进程(也就是调用wait的进程)阻塞,直到一个子进程结束或者该进程接到了一个指定的信号为止。如果该进程没有子进程或者他的子进程已经结束,则wait就会立即返回。

    5、守护进程使用在android的system下面,如netd,vold等。

    ====================================================================

    7.1.1 Linux 进程相关基本概念

    进程是一个程序的一次执行的过程。程序是静态的,进程是动态的,包括动态创建、调试和消亡的整个过程。

    2、进程控制块

    进程是Linux系统的基本调度单位,系统通过进程控制块描述并表示它的变化。

    进程控制块包含了进程的描述信息、控制信息以及资源信息,它是进程的一个静态描述。

    在Linux中,进程控制块中的每一项都是一个task_struct结构,它是在include/linux/sched.h中定义的。

    3.进程的标识

    进程号(PID, Process Idenity Number)   和  父进程号(PPID, parent process ID)

    在Linux中获得当前进程的PID和PPID的系统调用函数为getpid和getppid,

    用户和用户组标识、进程时间、资源利用情况等。

    4.进程运行的状态 进程是程序的执行过程,根据它的生命期可以划分成3种状态

    执行态/就绪态/等待态

    7.1.2 Linux下的进程结构

    Linux中的进程包含3个段,分别为“数据段”、“代码段”和“堆栈段”。

    数据段存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。

    代码段存放的是程序代码数据。

    堆栈段存放的是子程序的返回地址、子程序的参数以及程序的局部变量。

    在Linux系统中,进程的执行模式划分为用户模式和内核模式。

    如果用户程序执行过程中出现系统调用或者发生中断事件,就要运行操作系统程序,变成内核模式。

    7.1.4 Linux下的进程管理

    进程管理分为启动进程和调度进程。

    1.启动进程 

    主要有两种途径:手工启动和调度启动。手工启动是由用户输入命令直接启动进程,而调度启动是指系统根据用户的设置自行启动进程。

    (1)手工启动进程又可分为前台启动和后台启动。

    前台启动是手工启动一个进程的最常用方式。一般地,当用户键入一个命令如"ls -l"时,就已经启动了一个进程,并且是一个前台进程。

    后台启动往往是在该进程非常耗时,且用户也不急着需要结果的时候启动的。比如格式化文本文件的进程。

    (2)调度启动

    费时且占用资源的维护工作,并且在深夜无人职守的时候进行,用户可以事先进行调度安排,指定任务运行的时间或者场合。

    使用调度启动进程有几个常用的命令,如at命令在指定时刻执行相关进程,cron命令可以自动周期性的执行相关进程。

    2.调度进程

    调度进程包括对进程的中断操作、改变优先级、查看进程状态等。

    ps | top | nice | renice | kill | crontab | bg

    ======================================================================

    7.2 Linux进程控制编程

    进程创建

    1. fork()

    pid_t fork(void);

    在Linux中创建一个新进程的惟一方法是使用fork函数。它执行一次却返回两个值。

    (1) fork函数说明

    fork函数用于从已存在进程中创建一个新进程。新进程称为子进程,而原进程称为父进程。

    这两个分别带回它们各自的返回值,其中父进程的返回值是子进程的进程号,而子进程则返回0。因此,可以通过返回值来判断该进程是父进程还是子进程。

    #include<sys/types.h>
    #include<unistd.h>
    #include<stdio.h>
    #include<stdlib.h>
    
    int main(void){
        pid_t result;
    
        result = fork();
        if(result == -1){
          perror("fork");
          exit(1);
        } else if(result == 0){
          printf("The return value is 0,In child process!! My PID is %d
    ",getpid());
        } else {
          printf("The return value is %d, In father process !! My PID is %d
    ", result, getpid());
        }
    }
    fork

    (4)函数使用注意点

    2. exec函数族

    (1)exec函数族说明

    fork函数用于创建一个子进程,该子进程几乎copy了父进程的全部内容,但是这个新创建的进程如何执行呢?

    这个exec函数族就提供了一个在进程中启动另一个程序执行的方法,它可以根据指定的文件名或目录名找到可执行文件。

    在Linux中使用exec函数族主要有两种情况:

    *当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec函数族让自己重生

    *如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用任何一个exec,这样看起来就好像通过执行应用程序而产生一个新进程。(这种情况非常普遍)

    (2)exec函数族语法 函数族里有6个以exec开头的函数

    int execl(const char *path, const char *arg, ...)

    int execv(const char *path, char * const argv[])

    int execle(const char *path, const char *arg, ..., char * const envp[])

    int execve(const char *path, char * const argv[], char * const envp[])

    int execlp(const char *file, const char *arg, ...)从环境变量$PATH所指出的路径中进行查找

    int execvp(const char *file, char * const argv[])从环境变量$PATH所指出的路径中进行查找

    参数传递方式有两种:一种是逐个列举的方式,另一种是将所有参数整体构造指针数组传递。

    字母为“l"(list)的表示逐个列举,其语法为char *arg;字母为"v"(vector)的表示将所有参数整体构造指针数组传递,其语法为*const argv[]。

    (3)使用实例

    execlp
    #include<unistd.h>
    #include<stdio.h>
    #include<stdlib.h>
    int main(){
        if(fork() == 0){ 
            /*call execlp, just like call "ps -ef"*/
            if(execlp("ps","ps","-ef",NULL) < 0)
                perror("execlp error!");
        }   
    }
    execl
    #include<unistd.h>
    #include<stdio.h>
    #include<stdlib.h>
    
    int main(){
        if(fork() == 0){ 
            if(execl("/bin/ps", "ps", "-ef", NULL) < 0)
                perror("execl error!");
        }   
    }
    execle
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
        /*命令参数列表,必须以 NULL 结尾*/
        char *envp[]={"PATH=/tmp","USER=sunq",NULL};
        if(fork()==0){
            /*调用 execle 函数,注意这里也要指出 env 的完整路径*/
            if(execle("/usr/bin/env","env",NULL,envp)<0)
                perror("execle error!");
        }   
    }
    execve
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
        /*命令参数列表,必须以 NULL 结尾*/
        char *arg[]={"env",NULL};
        char *envp[]={"PATH=/tmp","USER=sunq",NULL};
        if(fork()==0){
            if(execve("/usr/bin/env",arg,envp)<0)
                perror("execve error!");
        }   
    }

    3.exit和 _exit

    (1)函数说明,exit和_exit函数都是用来终止进程的,进程会无条件的停止剩下的所有操作。

    但这两个函数还是有区别的

    _exit() 直接调用 exit系统调用

    exit()->调用退出处理函数->清理I/O缓冲->调用exit系统调用

    若想保证数据的完整性,就一定要使用exit()函数,因为程序处理数据有缓冲区。

    由于printf函数使用的缓冲I/O方式,该函数在遇到" "换行符时自动从缓冲区中将记录读出。

    如果没有" ",exit(0)能从缓冲区读出,而_exit(0)则不能。

    4. wait 和 waitpid

    (1)wait和waitpid函数说明

    wait函数是用于使父进程(也就是调用wait的进程)阻塞,直到一个子进程结束或者该进程接到了一个指定的信号为止。

    如果该进程没有子进程或者他的子进程已经结束,则wait就会立即返回。

    waitpid的作用和wait一样,但它并不一定要等待第一个终止的子进程,它还有若干选项。wait只是waitpid的一个特例

    (2)wait和waitpid函数格式说明

    pid_t wait(int *status)

    pid_t waitpid(pid_t pid, int *status, int options) 

    (3) waitpid使用实例

    本例中首先使用fork()新建一子进程,然后让其子进程暂停5s,接下来对原有的父进程使用waitpid函数,并使用参数WNOHANG使该父进程不会阻塞。若有子进程退出,则waitpid返回子进程号;若没有子进程限出,则waitpid返回0,并且父进程每隔一秒循环判断一次。

    waitpid
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
        pid_t pc,pr;
        pc=fork();  
        if(pc<0)
            printf("Error fork.
    ");
        /*子进程*/
        else if(pc==0){
            /*子进程暂停 5s*/
            sleep(5);
            /*子进程正常退出*/
            exit(0);
        }   
        /*父进程*/
        else{
            /*循环测试子进程是否退出*/
            do{ 
                /*调用 waitpid,且父进程不阻塞*/
                pr=waitpid(pc,NULL,WNOHANG);
                /*若子进程还未退出,则父进程暂停 1s*/
                if(pr==0){
                    printf("The child process has not exited
    ");
                    sleep(1);
                }    
            }while(pr==0);
            /*若发现子进程退出,打印出相应情况*/
            if(pr==pc)
                printf("Get child %d
    ",pr);
            else
                printf("some error occured.
    ");
        }   
    }

    7.3 Linux守护进程

    1.创建子进程,父进程退出

    pid = fork();

    if(pid>0)

        exit(0);父进程退出了。

    子进程变成了孤儿进程,会自动由1号进程(也就是init进程)收养它。这样,原先的子进程就会变成init进程的子进程了。

    2.在子进程中创建新会话

    pid_t setsid(void)

    进程组:进程组是一个或多个进程的集合。进程组由进程组ID来惟一标识。除了进程号PID之后,进程组ID也是一个进程的必备属性。每个进程组都有一个组长进程,其组长进程的进程号等于进程组ID,且该进程ID不会因组长进程的退出而受到影响。

    会话期:会话组是一个或多个进程组的集合。通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期,它们之间的关系如下图所示。

    setsid函数作用:

    setsid函数用于创建一个新的会话,并担任该会话组的组长。

    让进程摆脱原会话的控制。让进程摆脱原进程组的控制。让进程摆脱原控制终端的控制。

    由于调用fork函数时,子进程全盘拷贝了父进程的会话期、进程组控制终端等,虽然父进程退出了,但原先的会话期、进程组、控制终端等并没有改变。

    3.改变当前目录为根目录

    让"/"作为守护进程的当前工作目录。常见函数为chdir

    4.重设文件权限掩码 

    umask(0)

    5.关闭文件描述符

    for(i = 0; i<MAXFILE; i++)

      close(i);

    dameon
    /*dameon.c 创建守护进程实例*/
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<fcntl.h>
    #include<sys/types.h>
    #include<unistd.h>
    #include<sys/wait.h>
    #define MAXFILE 65535
    int main()
    {
        pid_t pc; 
        int i,fd,len;
        char *buf="This is a Dameon
    ";
        len =strlen(buf);
        pc=fork(); //第一步
        if(pc<0){
            printf("error fork
    ");
            exit(1);
        }else if(pc>0)
            exit(0);
        /*第二步*/
        setsid();
        /*第三步*/
        chdir("/");
        /*第四步*/
        umask(0);
        for(i=0;i<MAXFILE;i++)
            /*第五步*/
            close(i);
        /*这时创建完守护进程,以下开始正式进入守护进程工作*/
        while(1){
            if((fd=open("/tmp/dameon.log",O_CREAT|O_WRONLY|O_APPEND,0600))<0){
                perror("open");
                exit(1);
            }   
            write(fd, buf, len+1);
            close(fd);
            sleep(10);
        }   
    }

    7.3.3 守护进程的出错处理

    gdb是通过输出错误信息到控制终端来通知程序员

    守护进程使用syslog服务,将程序中的出错信息输入到“/var/log/messages"系统日志文件中。

    syslog是Linux中的系统日志管理服务,通过守护进程syslogd来维护。该守护进程在启动时会读一个配置文件"/etc/syslog.conf"。该文件决定了不同种类的消息会发送向何处。该机制提供了3个syslog函数,分别为openlog、syslog和closelog。

    void openlog(char *ident, int option, int facility)

    void syslog(int priority, char *format, ...)

    void closelog(void)

    (3)使用实例

    7.4实验内容

  • 相关阅读:
    java第十三周作业
    java第十三周随堂
    安卓作业
    5.29 第十三周作业
    5.28第十三周上机练习
    5.22第十二周作业
    5.21第十二章上机练习
    5.15 第十一周作业
    5.14 第十一周 上机练习
    5.7第十周上机练习
  • 原文地址:https://www.cnblogs.com/jimwind/p/2834147.html
Copyright © 2020-2023  润新知