前言:以前也知道exec族函数,但没有彻底掌握,昨天又从新学习了一遍,基本彻底掌握了,还有一些父子进程和循环建立子进程的问题,还要介绍一下环境变量,今天分享一下。算法
1、环境变量
先介绍下环境的概念和特性,再举例子吧。shell
环境变量,是指在操做系统中用来指定操做系统运行环境的一些参数。一般具有如下特征:编程
① 字符串(本质) ② 有统一的格式:名=值[:值] ③ 值用来描述进程环境信息。数组
存储形式:与命令行参数相似。char *[]数组,数组名environ,内部存储字符串,NULL做为哨兵结尾。bash
使用形式:与命令行参数相似。ide
引入环境变量表:须声明环境变量。extern char ** environ; 函数
环境变量跟不少东西有关系,例如接下来的exec族函数,这也是为何要先介绍下环境变量的缘由,对理解exec族函数颇有帮助;例如,Linux是什么样的系统?多用户多任务开源系统,每一个用户的登陆信息环境变量都会记录。举例一下经常使用的环境变量:学习
-
PATH
可执行文件的搜索路径。ls命令也是一个程序,执行它不须要提供完整的路径名/bin/ls,然而一般咱们执行当前目录下的程序a.out却须要提供完整的路径名./a.out,这是由于PATH环境变量的值里面包含了ls命令所在的目录/bin,却不包含a.out所在的目录。PATH环境变量的值能够包含多个目录,用:号隔开。在Shell中用echo命令能够查看这个环境变量的值:编码
$ echo $PATHurl
-
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); //删除
2、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\n", getpid(), getppid()); } else if (pid == 0) { //child printf("child pid = %d, parentID = %d\n", getpid(), getppid()); } return 0; }
程序很简单再也不解释,但要说明几个问题,结果以下:
看到结果知道了父进程也有父进程的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\n", getpid()); } else { //sleep(i); printf("I'm %dth child, pid = %d\n", i+1, getpid()); } return 0; }
演示结果:
会出现最开始的问题:输出跑到终端上。接下来解释为何会出现这个问题?
缘由:shell、父进程和子进程都抢夺CPU,shell当父进程执行return 0,认为父进程执行完了,返回到终端,当子进程还没执行完,就输出到终端了。
3、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("========================\n"); 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\n"); } return 0; }
演示结果就不展现了,能够本身在终端手动输入命令,进行对照。
2.execl函数
加载一个进程, 经过 路径+程序名 来加载。
跟execlp的主要区别在于不是经过环境变量获取了,相对路径也是能够的。
上面程序注释部分有这个程序。
3.execv函数
int execv(const char *path, char *const argv[]);
注意“v”使用命令行参数。
上面程序注释部分有这个程序。
就不一一举例了,有兴趣能够本身试一下。
总结:有问题,欢迎及时评论、交流与学习。