• 进程基本-进程创建,僵尸进程,exec系列函数


    Linux系统中,进程的执行模式划分为用户模式内核模式,当进程运行于用户空间时属于用户模式,
    如果在用户程序运行过程中出现系统调用或者发生中断事件,就要运行操作系统(即核心)程序,进程的运行模式就变为内核模式
    在该模式下运行的进程可以执行机器特权指令,而且该进程的运行不受用户的干预

    在Linux操作系统中,通过fork()系统调用来创建子进程

    目标

    创建进程

    头文件

    #include <unistd.h>

    函数原型

    pid_t_result=fork(void)

    参数

    返回值

    -1  如果出错

    0  返回到子进程

    pid 将子进程的进程id返回给父进程

    将执行fork的操作进程称为父进程,被fork()创建的进程称为该进程的子进程,
    当父进程执行fork操作时,操作系统内核执行如下任务完成进程的创建工作:
    1.为子进程分配新的内存块和内核数据结构
    2.复制原来的进程信息到新的进程,即子进程和父进程具有相同的可执行代码
    3.向运行进程集添加新的进程
    4.fork执行结束后,将控制返回给2个进程,此时两个进程可独立执行,执行顺序取决于进程调度

    #include <stdio.h>
    main(){
        int ret_from_fork,mypid;
        mypid=getpid();
        printf("Before :my pid is %d
    ",mypid );
        ret_from_fork=fork();
        sleep(1);
        printf("After:my pid is %5d,ret_from_fork:%5d,parent pid :%5d
    ",getpid(),ret_from_fork,getppid());
    }
    [root@centos1 process]# ./fork
    Before :my pid is 22526
    After:my pid is 22526,ret_from_fork:22527,parent pid :21968
    After:my pid is 22527,ret_from_fork:    0,parent pid :22526

    父进程创建子进程后,子进程除了具有相同的代码段拷贝外,也具有相同的数据段,
    即父进程的全局变量名称和值子进程也会一起拷贝过去,但他们之间是独立的,可独立改变相互不受影响

    #include <sys/types.h>  //pid_t 类型的在此
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>   //exit 用
    
    int glob=6;  //全局变量
    
    int main(){
        int local;
        pid_t pid;
        local = 8;
        if( (pid=fork()) == 0){
            sleep(2);
        }else if(pid>0){
            glob =60;
            local =80;
            sleep(10);
        }
        printf("glob=%d,local=%d,mypid=%d
    ",glob,local,getpid());
        exit (0);
    }
    [root@centos1 process]# ./var
    glob=6,local=8,mypid=22800
    glob=60,local=80,mypid=22799
    
    第一行输出为子进程,第二行未父进程的输出,说明父子进程由相同的代码,但他们变量之间相互不影响

    一般情况下,父进程创建子进程是为了执行特定的任务,通过执行exec家族系列调用让子进程执行新的任务
    此时,由exec调用提供的命令的指令代码替换子进程的代码,相当于对子进程进行了换脑

    int execl(const char*path,const char* arg0,const char* arg1,.... NULL)
    int execlp(const char*path,const char* arg0,const char* arg1,.... NULL)
    int execv(const char*path,const char* argv[])
    int execvp(const char*path,const char* argv[])

    目标

    在指定路径中查找并执行一个文件

    头文件

    #include <unistd.h>

    函数原型

    result=execvp(const *file,const char *argv[])

    参数

    file 要执行的文件名

    argv 字符串数组

    返回值

    -1 如果出错

    若成功,execvp 没有返回值

    excel,excelp完全相同
    excev,execvp 完全相同
    excel,excev要求提供可执行文件的绝对路径或相对路径名
    带p的 使用$PATH环境变量查找程序
    主要区别是:
    1.可执行文件的查找方式:不是P结尾的都是完整的目录路径,p结尾的可只给出文件名,系统自动从环境变量中进行查找
    2.参数的传递方式:
    有2种方式,逐个列举;将所有的参数整体构造指针数组传递,以函数名第5位字母来区分
    l(list)的表示逐个列举方式 ,语法为char *arg;
    字母为v(vertor)的表示将所有参数整体构造指针数组传递,语法为: *const argv[]
    exec系列调用没找到和执行文件返回-1 ,否者进程用可执行文件替换它的代码、数据和堆栈
    过程:
    1.将制定的程序复制到它的进程
    2.用制定的字符串数组作为argvp[]传给这个程序
    3.运行这个程序

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h> 
    
    main(){
        char *arglist[1];
        pid_t pid;
        arglist[0]="-l";
        arglist[1]="-a";
        printf("parent:%d
    ",getpid());
        pid=fork();
        if( pid==0 ){
            printf("son1  pid is:%d
    ",getpid() );
            execvp("ls",arglist);
            printf("son2  pid is:%d
    ",getpid() );
        }else{
            printf("parnet pid:%d
    ",getpid() );
        }
        printf("in %d
    ",getpid());
        
    }
    /**
    execvp 后的当前进程的代码不再执行
    root@centos1 c]# ./exec
    parent:26307
    parnet pid:26307
    in 26307
    son1  pid is:26308
    [root@centos1 c]# .  ..  exec  exec.c  fork.c  var.c
    
    **/

     僵尸进程

    当父进程还没有结束而子进程结束运行,同时父进程没有使用wait系统调用获取子进程的结束状态时
    子进程就成为僵尸进程
    父进程先结束不会产生僵尸进程。
    僵尸进程没有任何代码、数据和堆栈,占用不了多少资源,但它存在于系统的任务列表中。在进程表里仍占了1个位置占用进程号
    一般要避免出现这种情况
    当使用ps查看进程时 如果进程名称旁边出现defunct,则表明该进程为僵尸进程

    #include <stdio.h>
    #include <unistd.h>
    
    void parent_code(int delay){
        sleep(delay);
    }
    main(){
        pid_t pid;
        int status;
    
        pid=fork();
        if(pid == 0){
    
        }
        if(pid>0){
            parent_code(20);
        }
    }
    /*
    [root@centos1 c]# gcc -o zombie zombie.c 
    [root@centos1 c]# ./zombie&
    [1] 28577
    [root@centos1 c]# ps
       PID TTY          TIME CMD
     25201 pts/0    00:00:00 bash
     28577 pts/0    00:00:00 zombie
     28578 pts/0    00:00:00 zombie <defunct>
     28580 pts/0    00:00:00 ps
     */

    为了防止子进程成为僵尸进程,一般在父进程调用wait()系统调用等待子进程的结束并获取子进程的返回状态
    wait调用做2件事:
    首先暂停调用它的进程直到子进程结束,然后取得子进程结束时传给exit的值

    目标

    等待子进程的结束

    头文件

    #include <sys/wait.h>

    #include <sys/types.h>

    函数原型

    pid_t pid=wait(int *status)

    参数

    status指向一个保存子进程返回状态的整型变量

    返回值

    如果不存在子进程,返回-1

    若有任何一个子进程结束,则返回该子进程的pid并保存其返回状态在status中,同时wait调用也结束

    #include <sys/wait.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    void child(int delay){
        sleep(delay);
        exit(0);
    }
    
    void parent(int *status){
        wait(status);
    }
    
    main(){
        pid_t pid;
        int status;
        printf("Before:%d
    ",getpid());
        pid=fork();
        if(pid == 0){
            child(10);
        }
        if(pid >0 ){
            printf("pid =%d
    ",getpid());
            parent(&status);
            printf("status =%d
    ",status);
        }
    }
    /*
    [root@centos1 c]# ./a.out
    Before:14901
    pid =14901
    status =0
    
    */

    上述代码可防止僵尸进程的产生
    waitpid()亦可实现wait()调用类似功能
    多数情况下会使用waitpid()
    他们的主要区别是:
    1.wait()只能得到任何一个子进程结束的状态,当一个父进程有多个子进程时,在某个子进程结束,则wait可得到其结束状态
    此时无法再得到其它子进程的结束状态,此时容易产生其它子进程的僵尸进程
    2.wait()调用属于阻塞调用,父进程执行该指令后,器等待子进程结束之后才能执行它后面的代码,
    而waitpid()可提供非阻塞调用的方式
    3.waitpid()调用可以等待指定的子进程具有比wait多的功能

    目标

    等待某个子进程的结束

    头文件

    #include <sys/wait.h>

    #include <sys/types.h>

    函数原型

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

    参数

    pid=-1 等待任一个子进程.wait等效

    pid>0 等待进程idpid的子进程

    pid=0 等待其组id等于调用进程组id的任一子进程

    pid<-1 等待其组id等于pid绝对值的任一进程

    options选项:

    WNOHANG 表示如果没有任何已经结束的子进程则马上返回,不等待

    WUNTRACED 如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会

    0 作用和wait一样,阻塞父进程,等待子进程结束

     

     

    返回值

    如果不存在子进程,返回-1

    若有指定子进程结束,则返回该子进程的pid并保存其返回状态在status,同时waitpid调用也结束

    子进程退出状态检测的宏

    说明

    WIFEXITED(status)

    如果子进程正常结束,则为非0,此时可调用WEXITSTATUS(status)

    取得子进程exit()返回的结束代码

    WIFSIGNALED(status)

    如果子进程是因为信号则此宏的值为真,

    此时可用WIERMSIG(status)取得子进程因信号而终止的信号代码

    WIFSTOPPED(status)

    如果子进程处于暂停执行情况则此宏为真.采用WSTOPPSIG(status)

    取得引发子进程暂停的信号代码

  • 相关阅读:
    实操ES6之Promise
    RabbitMQ入门指南
    【从零开始撸一个App】PKCE
    SpringCloud Alibaba Nacos Config 配置中心
    SpringCloud Alibaba Nacos 服务发现 Feign进行消费
    SpringCloud Alibaba Nacos 服务发现 RestTemplate进行消费
    SpringCloud Alibaba Nacos 服务注册
    SpringCloud Alibaba Nacos 服务治理中心
    开发者-管理者 设计陷阱
    java8中的Stream API实战
  • 原文地址:https://www.cnblogs.com/HKUI/p/9030047.html
Copyright © 2020-2023  润新知