Linux进程和execX函数簇
来源:互联网 发布:java什么水平月薪一万 编辑:程序博客网 时间:2024/05/16 14:07
1. 进程
进程可以理解为正在内存中运行的程序的实例。每个进程都有一个进程描述符PID,且每个进程都是由其父进程创建的(init进程除外),所以都有父进程描述符PPID。
pid_t getpid(void); //获取进程IDpid_t getppid(void); //获取父进程ID
对于程序员来说,进程的作用不外乎是用来运行其他程序,即在某个进程中开启另一个进程去运行其他程序。这个”另一个进程”也称为子进程。子进程跟父进程一样,要得以正常运行都需要运行环境,即程序参数、环境变量、运行权限和工作路径。
1.1 程序参数
运行程序命令行的第一个参数,如”./a.out hello.file”中的”./a.out”
1.2 环境变量
环境变量通过getenv()获取、setenv()、putev()设置、unsetenv()删除
1.2.1 获取环境变量
int main(int argc, char* argv[]){ printf("main: pid = %d, ppid = %d\n", getpid(), getppid()); printf("PAHT = %s\n", getenv("PATH")); //获取本进程的PATH环境变量 return 0;}
运行结果:
7_fork_test$ ./a.out main: pid = 3330, ppid = 2872PAHT = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin7_fork_test$
上面程序是运行在Linux终端的,所以该进程为bash所创建的子进程,故其ppid等于bash的pid。
子进程的环境变量是由父进程赋予的,也就是a.out进程的环境变量是由bash进程给予的。命令env的执行结果是获取当前进程的所有环境变量,getenv(“PATH”)实现的操作则是从这些环境变量中抽取出PATH环境变量部分。
想要获取该进程(的父进程赋予)的所有环境变量可以将main函数原型声明定义的:
int main(int argc, char* argv[], char *environ[])
第3个参数environ等于父进程传来的所有的环境变量,打印操作为:
while (*environ != NULL){ printf("%s\n", *environ++);}
但是这样的main函数的定义并不通用。虽然历史上大多数unix系统对mian函数虽然都提供了如上的三个参数,但是后来ansi c规定main函数只有前面两个参数,因为环境变量environ是一个全局变量,作为main的参数三传进main函数并没有什么益处。
getenv()和setenv()等函数操作的是特定的环境变量而非environ变量。注意,argv和environ结束条件是都是NULL(这一点很重要,等下会用到)。所以打印运行参数列表还可以:
while(*argv != NULL){ printf("%s\n", *argv);}
因为带3个参数的main原型不通用,所以操作环境变量的代码为:
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>extern char **environ; //外部全局变量,用于存放环境变量,声明在stdlib.h中int main(int argc, char* argv[]){ while (*environ != NULL) { printf("%s\n", *environ++); } return 0;}
1.2.2 设置环境变量
setenv("PATH", "ABCDEF", 1);putenv("PATH = hello");unsetenv("PATH");
1.3 运行权限
运行权限通过setuid()、seteuid()和setgid()、setegid()设置。注意这些函数是修改权限操作,所以需要root用户执行。
1.4 工作路径
工作路径可以通过getcwd()获得,chdir()改变
2. execX函数簇
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[]);
当进程调用如上execX函数簇的任意一个时来启动新程序时,该进程用户空间(代码段、数据段、bss段、堆栈)完全被新程序覆盖。execX函数簇不会创建新的进程。这些函数的区别在于:
a. 标志新程序所在位置是使用路径还是文件名;
b. 参数的传递是使用参数列表还是argv[]数组的数组形式;
注意,如果是使用文件名来标识新程序的,也就是你没有指定可执行程序的路径,所以它会去$PATH环境变量所记录的路径去搜索该程序。
a. 函数名中含有’p’表示该函数不需要指定可执行程序的路径,否则需要;
b. 函数名中含有’l’表示该函数是以参数列表的形式接收形参,否则argvp[]数组形式;
2.1 execl()函数
execl函数是对新程序的标识需要指定路径,且形参是列表形式的:
int main(int argc, char* argv[]){ execl("/bin/ls", "ls", "-l", "/home/tom", NULL); return 0;}
参数1: 指定可执行程序
参数2开始到最后一个参数之前: 可执行程序的参数列表
最后一个参数: NULL是参数列表的结束标志
2.2 execlp()函数
execlp函数对新程序不需要指定路径,它是从$PATH环境变量所指定的目录中查找文件名为第一个参数指示的字符串,找到后执行它。第二个及以后的参数表示执行该文件时传递的参数列表,同理,最后一个参数必须为NULL。
int main(int argc, char* argv[]){ execlp("ls", "ls", "-l", "/home/tom", (char* )0); return 0;}
2.3 execle()函数
execle()函数跟execl()函数的差异在于execl()可传入环境变量envp[],也就是说进程启动新程序的时候可以为新程序指定环境变量。(当然,对于其他不能为新程序指定环境变量的函数,默认是把当前进程的环境变量赋给新程序)
//主调进程extern char **environ;int main(int argc, char* argv[]){ execle("./new_pro", "", NULL, environ); printf("main is return\n"); //这一句不会被执行 return 0;}
// 新程序extern char **environ;int main(void){ int i = 0; while (environ[i] != NULL) printf("%s\n", environ[i++]); //打印出所有环境变量 return 0;}
若在进程中修改环境变量然后传给新程序,新程序将得到被进程修改后的环境变量;相反,若在新程序中修改环境变量则不会影响父进程的环境变量。
// 主调进程int main(int argc, char* argv[]){ setenv("PATH", "ABCDEF", 1); execle("./new_pro", "", NULL, environ); printf("main is return\n"); //这一句不会被执行 return 0;}
// 新进程extern char **environ;int main(void){ printf("PATH = %s\n", getenv("PATH")); return 0;}
运行结果:
7_fork_test$ ./a.out PATH = ABCDEF7_fork_test$
execX函数簇用于启动新程序没错,它的特点是不会新创建一个进程,而是会把新程序的进程空间赋给主启动进程进程空间,这么一来,在execX函数调用处将后将不会执行主启动进程的代码了。要想启动一个新的进程来执行新程序,父子进程有独立的进程空间,这就需要创建进程fork()函数了。
2.4 execv()函数
execv()函数对新程序需要指定路径,形参是以”argv[]”数组形式传进来的
// 主调进程int main(void){ char* argv[] = {"hello", "word", NULL}; //注意argv[]与形参列表的区别,列表还需要传入再可执行程序名称 execv("./new_pro", argv); return 0;}
// 新进程int main(int argc, char** argv){ while (*argv != NULL) { printf("%s\n", *(argv++)); } return 0;}
运行结果:
7_fork_test$ ./a.out helloword7_fork_test$
2.5 execvp()函数
execvp()函数对新程序不需要指定路径,它会从$PATH指定的路径查找,形参是以argv[]的形式。不赘述。
3. fcntl()函数设置FD_CLOEXEC标志位的作用
前面了解到execX函数簇启动新程序后,新程序的进程空间会覆盖主调进程的进程空间。那么若在主调进程中打开一个文件且将文件描述符传给新程序,会怎样?
// 主调进程int main(void){ int fd; char buf[10] = {}; fd = open("./test.file", O_RDWR | O_CREAT, 0666); if (fd < 0) { perror("open"); return -1; } //fcntl(fd, F_SETFD, FD_CLOEXEC); sprintf(buf, "%d", fd); //将文件描述符传给新进程 execl("./new_pro", "./new_pro", buf, NULL); return 0;}
// 新程序int main(int argc, char* argv[]){ int ret; char buf[1024] = {}; int fd = atoi(argv[1]); printf("fd = %d\n", fd); ret = read(fd, buf, sizeof(buf)); if (ret < 0) { perror("read"); return -1; } printf("ret = %d\n", ret); return 0;}
测试文件test.file的内容为
hello world
运行结果:
7_fork_test$ ./a.out fd = 3ret = 127_fork_test$
显然,新程序能使用主调函数传来的文件描述符。但是在主调函数中去除”fcntl(fd, F_SETFD, FD_CLOEXEC);”的注释,那么运行结果:
7_fork_test$ ./a.out fd = 3read: Bad file descriptor7_fork_test$
可见,新程序已经不能使用主调函数传来的文件描述符了。结论是:
当文件描述符设置了FD_CLOEXEC,使用execX函数簇时,此描述符会被关闭,导致被该函数簇的调用的新程序不可使用该文件描述符。但是若用fork()创建的子进程,此文件描述符不关闭,仍可以使用,这个可以自行测试。
- Linux进程和execX函数簇
- Linux进程函数fork(),vfork(),execX()的深入理解
- 【Linux编程】进程终止和exit函数
- Linux编程 函数和进程操作
- fork()函数和linux进程号
- Linux下进程的“终结者”和Linux C函数
- Linux进程函数集合
- Linux进程函数大全
- linux进程函数
- Linux进程控制函数
- linux 进程控制函数
- Linux进程函数详解
- Linux进程函数大全
- linux进程函数大全
- linux进程---sigaction 函数
- Linux 进程控制函数
- linux进程相关函数
- Linux进程理解与实践(三)进程终止函数和exec函数族的使用
- 将二维数组的某一行传入到函数中
- word2Vec--(1) nltk实现简单的切词,情感分析,文本相似度(TF-IDF)
- jvisualvm 远程监控centos上的elasticsearch碰到的问题
- SQL SERVER【非域环境】同步复制(事务)之准备篇(1)
- 我希望进入大学时就能知道的一些事儿
- Linux进程和execX函数簇
- Unity项目UI图片压缩格式(UGUI)
- 了解一下 requireJS
- 区块链技术开发合法吗
- 输出全排列
- JS获取手机型号和系统
- idea 常用快捷键
- 设计模式学习之Builder模式
- Hibernate学习-13:操作持久化对象的方法