用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
其实有六种以exec开头的函数,统称exec函数:
#include <unistd.h>
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 execve(const char *path, char *const argv[], char *const envp[]);
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。
这些函数原型看起来很容易混,但只要掌握了规律就很好记。不带字母p(表示path)的exec函数第一个参数必须是程序的相对路径或绝对路径,例如"/bin/ls"或"./a.out",而不能是"ls"或"a.out"。对于带字母p的函数:
*
如果参数中包含/,则将其视为路径名。
*
否则视为不带路径的程序名,在PATH环境变量的目录列表中搜索这个程序。
带有字母l(表示list)的exec函数要求将新程序的每个命令行参数都当作一个参数传给它,命令行参数的个数是可变的,因此函数原型中有...,...中的最后一个可变参数应该是NULL,起sentinel的作用。对于带有字母v(表示vector)的函数,则应该先构造一个指向各参数的指针数组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是NULL,就像main函数的argv参数或者环境变量表一样。
对于以e(表示environment)结尾的exec函数,可以把一份新的环境变量表传给它,其他exec函数仍使用当前的环境变量表执行新程序。
exec调用举例如下:
char *const ps_argv[] ={"ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL};
char *const ps_envp[] ={"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execv("/bin/ps", ps_argv);
execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp);
execve("/bin/ps", ps_argv, ps_envp);
execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execvp("ps", ps_argv);
事实上,只有execve是真正的系统调用,其它五个函数最终都调用execve,所以execve在man手册第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。
图 30.5. exec函数族
exec函数族
一个完整的例子:
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
perror("exec ps");
exit(1);
}
执行此程序则得到:
$ ./a.out
PID PPID PGRP SESS TPGID COMMAND
6614 6608 6614 6614 7199 bash
7199 6614 7199 6614 7199 ps
由于exec函数只有错误返回值,只要返回了一定是出错了,所以不需要判断它的返回值,直接在后面调用perror即可。注意在调用execlp时传了两个"ps"参数,第一个"ps"是程序名,execlp函数要在PATH环境变量中找到这个程序并执行它,而第二个"ps"是第一个命令行参数,execlp函数并不关心它的值,只是简单地把它传给ps程序,ps程序可以通过main函数的argv[0]取到这个参数。
调用exec后,原来打开的文件描述符仍然是打开的[37]。利用这一点可以实现I/O重定向。先看一个简单的例子,把标准输入转成大写然后打印到标准输出:
例 30.4. upper
/* upper.c */
#include <ctype.h>
#include <stdio.h>
int main(void)
{
int ch;
while((ch = getchar()) != EOF) {
putchar(toupper(ch));
}
return 0;
}
运行结果如下:
$ ./upper
hello THERE
HELLO THERE
(按Ctrl-D表示EOF)
$
使用Shell重定向:
$ cat file.txt
this is the file, file.txt, it is all lower case.
$ ./upper < file.txt
THIS IS THE FILE, FILE.TXT, IT IS ALL LOWER CASE.
如果希望把待转换的文件名放在命令行参数中,而不是借助于输入重定向,我们可以利用upper程序的现有功能,再写一个包装程序wrapper。
例 30.5. wrapper
/* wrapper.c */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd;
if (argc != 2) {
fputs("usage: wrapper file\n", stderr);
exit(1);
}
fd = open(argv[1], O_RDONLY);
if(fd<0) {
perror("open");
exit(1);
}
dup2(fd, STDIN_FILENO);
close(fd);
execl("./upper", "upper", NULL);
perror("exec ./upper");
exit(1);
}
wrapper程序将命令行参数当作文件名打开,将标准输入重定向到这个文件,然后调用exec执行upper程序,这时原来打开的文件描述符仍然是打开的,upper程序只负责从标准输入读入字符转成大写,并不关心标准输入对应的是文件还是终端。运行结果如下:
$ ./wrapper file.txt
THIS IS THE FILE, FILE.TXT, IT IS ALL LOWER CASE.
分享到:
相关推荐
1.exec函数族 exec 为 execute(执行),exec 函数族用来替换调用进程所执行的程序,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行,替换前后进程的 PID 不会改变。 exec函数族中包括6个...
Linux系统编程——进程替换:exec 函数族,相关教程链接如下: http://blog.csdn.net/tennysonsky/article/details/46004367
本文结合Understanding Linux Kernel英文版第三版以及Linux2.6.11源代码详细分析了Linux exec函数族调用时内核对于进程切换所完成的一系列关于进程运行环境的初始化的过程。
作者:王姗姗,华清远见嵌入式学院... 下面来看下exec函数族: #include int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(cons
exec用被执行的程序完全替换调用它的程序... 下面来看下exec函数族: #include int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(cons
linux代码
一、描述进程——PCB ·进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合 ·我们称为PCB,Linux操作系统下的PCB是:task struct 2、task_struct——PCB的一种 ·在Linux中描述进程的结构体...
(1)用exec函数调用shell指令。 (2)用fork()函数创建子进程。 (3)复制一个进程,父进程通过管道发送给子进程,并呈现在屏幕上。应用函数msgget、msgrcv、msgsnd,读、写消息队列中的消息并打印输出。 (4)...
fork函数创建子进程和孙子进程 fork函数创建两个子进程 exec函数族应用
进程基本概念及相关函数进程定义fork函数exec函数族wait函数waitpid函数 进程定义 从不同角度,进程可以有不同定义: 1.进程是程序的一次执行过程 2.进程是一个程序及其数据在处理机上顺序执行时所发生的活动。 3....
fork创建子进程和孙子进程fork创建两个子进程exec函数族应用
包含进程创建、进程退出、exec族函数、system函数和popen函数的简要介绍
Linux下生成进程换出进程杀死进程例子 函数fork,exec族函数,exit函数例子
4.2.4 进程执行exec()函数系列 99 4.2.5 所有用户态进程的产生进程init 100 4.3 进程间通信和同步 101 4.3.1 半双工管道 101 4.3.2 命名管道 107 4.3.3 消息队列 108 4.3.4 消息队列的一个例子 114 ...
相信许多人对php手册中语焉不详的curl_multi一族的函数头疼不已,它们文档少,给的例子 更是简单的让你无从借鉴,我也曾经找了许多网页,都没见一个完整的应用例子。 curl_multi_add_handle curl_multi_close ...
8.9 exec函数 8.10 更改用户ID 和组ID 8.10.1 setreuid 和setregid函数 8.10.2 seteuid和 setegid函数 8.10.3 组ID 8.11 解释器文件 8.12 system函数 8.13 进程会计 8.14 用户标识 8.15 进程时间 8.16 小结 习题 ...
12.2 进程的叠加:exec...()函数族系 12.3 使用pipe()实现进程间的通信 12.4 信号 12.5 例子:哲学家用餐问题 12.6 矩阵的动态分配 12.6.1 为什么二维数组无法满足要求 12.6.2 用指针数组创建矩阵 12.6.3 调整...
这个是我在学习Linux的时候做的思维导图笔记,...3.进程:进程相关命令,相关函数、孤儿进程、僵尸进程、进程替换exec族 4.进程间通信:管道特点,有名管道和无名管道,以及管道读写特点,共享内存映射,信号等知识。
4.2.4 进程执行exec()函数系列 99 4.2.5 所有用户态进程的产生进程init 100 4.3 进程间通信和同步 101 4.3.1 半双工管道 101 4.3.2 命名管道 107 4.3.3 消息队列 108 4.3.4 消息队列的一个例子 114 ...