一、exec替换进程映象
在进程的创建上Unix采用了一个独特的方法,它将进程创建与加载一个新进程映象分离。这样的好处是有更多的余地对两种操作进行管理。当我们创建
了一个进程之后,通常将子进程替换成新的进程映象,这可以用exec系列的函数来进行。当然,exec系列的函数也可以将当前进程替换掉。
二、exec关联函数组
-
包含头文件
<unistd.h>
-
功能
用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[]);
-
参数
path
参数表示你要启动程序的名称包括路径名
arg
参数表示启动程序所带的参数 -
返回值
成功,没有返回值----因为进程替换,他已经去执行别的进程,如果非要说,不妨说是替换进程的返回值,一个正常退出的程序的返回值为 0
失败,返回-1
-
可分为两组
execl
,execlp
,execle
(都带l
,可以将l
理解为list
)的参数个数是可变的,参数以一个空指针结束。execv
、execvp
和execvpe
的第二个参数是一个字符串数组(可以理解为vector
)。新程序在启动时会把在
argv
数组中给定的参数传递到main
(不管是不是进程替换,main
函数都是一个程序的入口函数) -
关于函数的一些解释
名字含字母“p”的函数会搜索PATH环境变量去查找新程序的可执行文件。如果可执行文件不在PATH定义的路径上,就必须把包括子目录在内的绝对文件名做为一个参数传递给这些函数。
名字最后一个字母为"e"的函数可以自设环境变量。
-
关于这一些列函数的实现
这些函数通常都是用execve实现的,这是一种约定俗成的做法,并不是非这样不可。
int execve(const char *filename, char *const argv[], char *const envp[]);
注意,前面6个函数都是
C
库函数,而execve
是一个系统调用。
六个exec
函数之间的关系如下图:
三、执行exec函数,下面属性是不发生变化的
- 进程
ID
和父进程ID
(pid
,ppid
) - 实际用户
ID
和实际组ID
(ruid
,rgid
) - 附加组
ID
(sgid
) - 会话
ID
- 控制终端
- 闹钟余留时间
- 当前工作目录
- 根目录
umask
- 文件锁
- 进程信号屏蔽
- 未处理信号
- 资源限制
- 进程时间
四、执行exec函数,将会发生变化的属性
-
关于 文件描述符的
close-on-exec
属性文件描述符如果存在
close-on-exec
标记的话,那么打开的文件描述符会被关闭。 -
如果可执行程序文件存在
SUID
和SGID
位的话,那么有效用户ID
和组ID
(euid
,gid
)会发生变化 -
关于信号处理方式
程序启动的时候,所有的信号处理方式都是默认的。
对
fork
来说,因为子进程和父进程的地址空间是一样的,所以信号处理方式保留了下来。接下来进行
exec
,会将所有设置成为捕捉的信号都修改成为默认处理,而原来已经设置成为忽略的信号就不发生改变。 -
示例
我们写一个简单的小程序,演示一下
exec
函数的用法,我们拿execle
举例吧/** * filename instanceOfExec.cpp * author zbq * date 2019.05.15 * */ #include <iostream> #include <cstdio> #include <cstdlib> #include <cassert> #include <unistd.h> #include <sys/wait.h> static const char * env_init[] = {"USER=root", "PATH=/root/study/apue", NULL}; int main(void) { pid_t pid = 0; pid = fork(); if(pid < 0) { std::cerr << "fork() faild " << std::endl; exit(1); } else if(pid == 0) { int ret_exec = execle("/usr/bin/echo", "echo", "hahaha, I'm from exec!!!", (char*)0); if(ret_exec < 0) { std::cerr << "execle() faild -_-|" << std::endl; exit(-1); } exit(0); } else { pid_t ret_wait = waitpid(pid, nullptr, 0); if(ret_wait < 0 || ret_wait != pid) { std::cerr << "waitpid() faild" << std::endl; exit(2); } } return 0; }
我们编译之:
g++ instanceOfExec.cpp -std=c++11
运行之:
./a.out
我们成功打印出了hahaha, I'm from exec!!!
,这和在bash
上输入echo "hahaha, I'm from exec!!!"
是一样的。 -
我们用
strace
命令来看一下这个过程是怎么样的execve("./a.out", ["./a.out"], [/* 24 vars */]) = 0 brk(NULL) = 0x12f2000 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f83d45ce000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=48797, ...}) = 0 mmap(NULL, 48797, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f83d45c2000 close(3) = 0 open("/lib64/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 \262\5\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=991616, ...}) = 0 mmap(NULL, 3171168, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f83d40a7000 mprotect(0x7f83d4190000, 2093056, PROT_NONE) = 0 mmap(0x7f83d438f000, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xe8000) = 0x7f83d438f000 mmap(0x7f83d4399000, 82784, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f83d4399000 close(3) = 0 open("/lib64/libm.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20S\0\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=1137024, ...}) = 0 mmap(NULL, 3150120, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f83d3da5000 mprotect(0x7f83d3ea6000, 2093056, PROT_NONE) = 0 mmap(0x7f83d40a5000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x100000) = 0x7f83d40a5000 close(3) = 0 open("/lib64/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220*\0\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=88776, ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f83d45c1000 mmap(NULL, 2184192, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f83d3b8f000 mprotect(0x7f83d3ba4000, 2093056, PROT_NONE) = 0 mmap(0x7f83d3da3000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x14000) = 0x7f83d3da3000 close(3) = 0 open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340$\2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=2151672, ...}) = 0 mmap(NULL, 3981792, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f83d37c2000 mprotect(0x7f83d3984000, 2097152, PROT_NONE) = 0 mmap(0x7f83d3b84000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c2000) = 0x7f83d3b84000 mmap(0x7f83d3b8a000, 16864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f83d3b8a000 close(3) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f83d45c0000 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f83d45be000 arch_prctl(ARCH_SET_FS, 0x7f83d45be740) = 0 mprotect(0x7f83d3b84000, 16384, PROT_READ) = 0 mprotect(0x7f83d3da3000, 4096, PROT_READ) = 0 mprotect(0x7f83d40a5000, 4096, PROT_READ) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f83d45bd000 mprotect(0x7f83d438f000, 32768, PROT_READ) = 0 mprotect(0x600000, 4096, PROT_READ) = 0 mprotect(0x7f83d45cf000, 4096, PROT_READ) = 0 munmap(0x7f83d45c2000, 48797) = 0 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f83d45bea10) = 4573 wait4(4573, hahaha, I'm from exec!!! NULL, 0, NULL) = 4573 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=4573, si_uid=0, si_status=0, si_utime=0, si_stime=0} --- exit_group(0) = ? +++ exited with 0 +++
在
51
,52
行我们看到fork
函数是调用clone
函数创建4573
号子进程的,然后子进程替换,父进程等待4573
号进程退出,当子进程退出的时候,操作系统会给父进程发送SIGCHILD
信号,父进程检测到该信号,就知道有子进程退出了。
五、system函数
-
功能
system
函数调用/bin/sh -c command
执行特定的命令,阻塞当前进程直到command
命令执行完毕 -
原型
int system(const char *command);
-
返回值
如果无法启动
shell
运行命令,system
将返回127
;出现不能执行
system
调用的其他错误时,返回-1
;如果
system
能够顺利执行,返回那个命令的退出码。 -
system
函数执行时,会调用fork
、execve
、waitpid
等函数。 -
示例
/** * filename testSystem.cpp * author zbq * date 2019.05.15 * */ #include <iostream> #include <cstdio> #include <cstdlib> #include <unistd.h> int main(void) { system("/usr/bin/echo \"hahaha, I'm from system function!\""); return 0; }
我们编译之:
g++ testSystem.cpp
运行之:
./a.out
得到结果:
我们利用
strace
命令来看看system
函数如何运作起来的execve("./a.out", ["./a.out"], [/* 24 vars */]) = 0 brk(NULL) = 0x1c20000 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fdc4a002000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=48797, ...}) = 0 mmap(NULL, 48797, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fdc49ff6000 close(3) = 0 open("/lib64/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 \262\5\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=991616, ...}) = 0 mmap(NULL, 3171168, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fdc49adb000 mprotect(0x7fdc49bc4000, 2093056, PROT_NONE) = 0 mmap(0x7fdc49dc3000, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xe8000) = 0x7fdc49dc3000 mmap(0x7fdc49dcd000, 82784, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fdc49dcd000 close(3) = 0 open("/lib64/libm.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20S\0\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=1137024, ...}) = 0 mmap(NULL, 3150120, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fdc497d9000 mprotect(0x7fdc498da000, 2093056, PROT_NONE) = 0 mmap(0x7fdc49ad9000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x100000) = 0x7fdc49ad9000 close(3) = 0 open("/lib64/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220*\0\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=88776, ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fdc49ff5000 mmap(NULL, 2184192, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fdc495c3000 mprotect(0x7fdc495d8000, 2093056, PROT_NONE) = 0 mmap(0x7fdc497d7000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x14000) = 0x7fdc497d7000 close(3) = 0 open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340$\2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=2151672, ...}) = 0 mmap(NULL, 3981792, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fdc491f6000 mprotect(0x7fdc493b8000, 2097152, PROT_NONE) = 0 mmap(0x7fdc495b8000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c2000) = 0x7fdc495b8000 mmap(0x7fdc495be000, 16864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fdc495be000 close(3) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fdc49ff4000 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fdc49ff2000 arch_prctl(ARCH_SET_FS, 0x7fdc49ff2740) = 0 mprotect(0x7fdc495b8000, 16384, PROT_READ) = 0 mprotect(0x7fdc497d7000, 4096, PROT_READ) = 0 mprotect(0x7fdc49ad9000, 4096, PROT_READ) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fdc49ff1000 mprotect(0x7fdc49dc3000, 32768, PROT_READ) = 0 mprotect(0x600000, 4096, PROT_READ) = 0 mprotect(0x7fdc4a003000, 4096, PROT_READ) = 0 munmap(0x7fdc49ff6000, 48797) = 0 rt_sigaction(SIGINT, {SIG_IGN, [], SA_RESTORER, 0x7fdc4922c280}, {SIG_DFL, [], 0}, 8) = 0 rt_sigaction(SIGQUIT, {SIG_IGN, [], SA_RESTORER, 0x7fdc4922c280}, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 clone(child_stack=0, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0x7fff17e54140) = 4553 wait4(4553, hahaha, I'm from system function! [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 4553 rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7fdc4922c280}, NULL, 8) = 0 rt_sigaction(SIGQUIT, {SIG_DFL, [], SA_RESTORER, 0x7fdc4922c280}, NULL, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=4553, si_uid=0, si_status=0, si_utime=0, si_stime=0} --- exit_group(0) = ? +++ exited with 0 +++
我们注意看一下
54
和55
行,先调用clone
函数,之后调用wait4
函数等待4553
号进程退出,其实fork
函数底层也是用clone
函数实现的。