zoukankan      html  css  js  c++  java
  • exec族函数详解及循环创建子进程

      前言:之前也知道exec族函数,但没有完全掌握,昨天又重新学习了一遍,基本完全掌握了,还有一些父子进程和循环创建子进程的问题,还要介绍一下环境变量,今天分享一下。

      一、环境变量

      先介绍下环境的概念和特性,再举例子吧。

      环境变量,是指在操作系统中用来指定操作系统运行环境的一些参数。通常具备以下特征:

      ① 字符串(本质) ② 有统一的格式:名=值[:值] ③ 值用来描述进程环境信息。

      存储形式:与命令行参数类似。char *[]数组,数组名environ,内部存储字符串,NULL作为哨兵结尾。

      使用形式:与命令行参数类似。

      引入环境变量表:须声明环境变量。extern char ** environ;   

      环境变量跟很多东西有关系,例如接下来的exec族函数,这也是为什么要先介绍下环境变量的原因,对理解exec族函数很有帮助;例如,Linux是什么样的系统?多用户多任务开源系统,每个用户的登录信息环境变量都会记录。举例一下常用的环境变量:

    • PATH

      可执行文件的搜索路径。ls命令也是一个程序,执行它不需要提供完整的路径名/bin/ls,然而通常我们执行当前目录下的程序a.out却需要提供完整的路径名./a.out,这是因为PATH环境变量的值里面包含了ls命令所在的目录/bin,却不包含a.out所在的目录。PATH环境变量的值可以包含多个目录,用:号隔开。在Shell中用echo命令可以查看这个环境变量的值:

      $ echo $PATH

    • SHELL

      当前Shell,它的值通常是/bin/bash。

    • TERM

      当前终端类型,在图形界面终端下它的值通常是xterm,终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行。

    • LANG

      语言和locale,决定了字符编码以及时间、货币等信息的显示格式。

    • HOME

      当前用户主目录的路径,很多程序需要在主目录下保存配置文件,使得每个用户在运行该程序时都有自己的一套配置

      介绍跟环境变量相关的函数:

      char *getenv(const char *name);  //获取环境变量

      int setenv(const char *name, const char *value, int overwrite);  //添加或改变环境变量

      int unsetenv(const char *name);  //删除

      二、fork函数及循环创建子进程

      先说一个问题,学会fork并写程序时,可能都会遇到一个问题如下:

      

      ./a.out的输出跑到终端上了,想过为什么?接下来我会解释这个问题。

      1.fork函数

      原型如下:

      pid_t fork(void);

      很好理解创建一个子进程,但需要真正理解这个函数需要理解:执行一次返回两次,就是有两个返回值,如下:

      (1)返回子进程的pid

      (2)返回0

      2.getpid、getppid函数

      两个函数原型,如下:

      pid_t getpid(void);  //获取当前进程ID
      pid_t getppid(void);  //获取父进程ID

      3.fork创建子进程

      主要创建一个子进程,并打印当前进程和父进程ID,并且打下了当前父进程的父进程ID,想一下父进程的父进程ID会是多少呢?程序如下:

      

    #include<stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int main(void)
    {
        pid_t pid;
    
        pid = fork();
        if (pid == -1 ) {
            perror("fork");
            exit(1);
        } else if (pid > 0) {    //parent
            //sleep(1); //保证子进程先执行
            printf("I'm parent pid = %d, parentID = %d
    ", getpid(), getppid());
        } else if (pid == 0) {    //child
            printf("child  pid = %d, parentID = %d
    ", getpid(), getppid());
        }
    
        return 0;
    }
    View Code

      程序很简单不再解释,但要说明几个问题,结果如下:

      

      看到结果知道了父进程也有父进程的ID,并查找一下它,是bash其实就是shell,shell通过某种方式创子进程(就是我们程序中的父进程),然后子进程再创建子进程。

    对了,还有一个问题,有一个sleep函数,注释是:确保子进程先执行,父子进程的执行顺序是由CPU的调度算法决定,但为啥我注释了sleep,还是父进程先执行。说点题外话吧,APUE(unix环境高级编程)的作者做过实验,98%的概率的都是父进程先执行。

      4.循环创建子进程

      接下来怎么创建多个子进程,直接给正确的程序吧,先演示一下有些问题的代码,如下:

      

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[])
    {
        int n = 5, i;                //默认创建5个子进程
    
        if (argc == 2) {    
            n = atoi(argv[1]);
        }
    
        for (i = 0; i < n; i++)    //出口1,父进程专用出口
            if (fork() == 0)
                break;            //出口2,子进程出口,i不自增
    
        if (n == i) {
            //sleep(n);
            printf("I am parent, pid = %d
    ", getpid());
        } else {
            //sleep(i);
            printf("I'm %dth child, pid = %d
    ", 
                    i+1, getpid());
        }
    
        return 0;
    }
    View Code

      演示结果:

      

      会出现最开始的问题:输出跑到终端上。接下来解释为什么会出现这个问题?

      原因:shell、父进程和子进程都抢夺CPU,shell当父进程执行return 0,认为父进程执行完了,返回到终端,当子进程还没执行完,就输出到终端了。

      三、exec族函数 

      其实有七种以exec开头的函数,统称exec函数:

      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 execvpe(const char *file, char *const argv[],char *const envp[]);

      int execve(const char *path, char *const argv[], char *const envp[]);   //真的系统调用

      主要函数红色部分的说明:

      l (list)                           命令行参数列表

      p (path)                       搜素file时使用path变量

      v (vector)                    使用命令行参数数组

      e (environment)       使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量

      1.execlp函数

      加载一个进程,借助PATH环境变量  

      参数说明:

      file:文件名,通过PATH环境变量查找

      arg:命令行参数,要强掉一下,第一个arg相当于arg[0],并要以NULL结尾

      ...:可变参数

      通过调用ls来举例:

      execlp("ls","ls","-l",NULL);

      其实,可以试一下第二个标红的参数,随便写也不会有错误的,说明内核并不使用第二个参数。

      程序示例如下:

      

    #include <stdlib.h>
    #include <unistd.h>
    #include <stdio.h>
    
    int main(int argc, char *argv[])
    {
        printf("========================
    ");
    
        char *argvv[] = {"ls", "-l", "-F", "R", "-a", NULL};
        
    
        pid_t pid = fork();
        if (pid == 0) {
            //execl("/bin/ls", "ls", "-l","-F", "-a", NULL);
            //execv("/bin/ls", argvv);
            execlp("ls","ls","-l","-F","-a",NULL);
            perror("execlp");
            exit(1);
    
        } else if (pid > 0) {
            sleep(1);
            printf("parent
    ");
        }
    
    
        return 0;
    }
    View Code

      演示结果就不展示了,可以自己在终端手动输入命令,进行对照。

      2.execl函数

      加载一个进程, 通过 路径+程序名 来加载。

      跟execlp的主要区别在于不是通过环境变量获取了,相对路径也是可以的。

      上面程序注释部分有这个程序。

      3.execv函数

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

      注意“v”使用命令行参数。

      上面程序注释部分有这个程序。

      就不一一举例了,有兴趣可以自己试一下。

      总结:有问题,欢迎及时评论、交流与学习。

      

      

      

      

      

      

  • 相关阅读:
    【Redis】简介与安装
    【JAVA】 Java 连接池的工作原理
    【异常】ORA-01536: space quota exceeded for tablespace
    【Oracle】Oracle 序列步长问题
    【ORACLE】常用脚本
    【ORACLE】特殊的NULL
    【JAVA】Runtime
    【JAVA】Calendar
    出现脚本错误或者未正确调用 Page()
    小程序出现 “2 not found” 解决方案
  • 原文地址:https://www.cnblogs.com/liudw-0215/p/9667686.html
Copyright © 2011-2022 走看看