exec函数蔟:
Linux系统提供了六种不同的exec函数可供使用,它们常常被统称为exec函数蔟。exec函数的功能是执行另一个程序,这里的另一个程序是由exec函数的参数来指定的。
当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其main函数开始执行。需要注意的是,掉用exec并不创建新进程,所以,pid(进程id)并没有改变。
exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。
exec函数通常和fork一同使用。先由父进程fork一个子进程,再在子进程中调用exec函数,从而替换掉子进程的执行代码,但不替换子进程的pid。
函数声明:
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execve(const char *path, char *const argv[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
返回值:
六个函数返回∶如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。
参数:
这些函数之间的第一个区别是前四个取路径名作为参数,后两个则取文件名作为参数。
1. execl():用来执行参数path字符串所代表的文件路径,接下来的参数代表执行该文件时传递过去的argv(0)、argv[1]……,最后一个参数必须用空指针(NULL)作结束。2. execle():执行参数path字符串所代表的文件路径,接下来的参数代表执行该文件时传递过去的argv(0)、argv[1]……,最后一个参数必须用空指针(NULL)作结束。与execl不同的是,带e表示该函数取envp[]数组,而不使用当前环境。
3. execlp():会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,最后一个参数必须用空指针(NULL)作结束。
4. execv():用来执行参数path字符串所代表的文件路径,与execl()不同的地方在于execve()只需两个参数,第二个参数利用数组指针来传递给执行文件。
5. execvp():会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。
6.execve():用来执行参数filename字符串所代表的文件路径,第二个参数系利用数组指针来传递给执行文件,最后一个参数则为传递给执行文件的新环境变量数组。
错误代码:
EACCES
1. 欲执行的文件不具有用户可执行的权限。
2. 欲执行的文件所属的文件系统是以noexec 方式挂上。
3.欲执行的文件或script翻译器非一般文件。
EPERM
1.进程处于被追踪模式,执行者并不具有root权限,欲执行的文件具有SUID 或SGID 位。
2.欲执行的文件所属的文件系统是以nosuid方式挂上,欲执行的文件具有SUID 或SGID 位元,但执行者并不具有root权限。
E2BIG 参数数组过大
ENOEXEC 无法判断欲执行文件的执行文件格式,有可能是格式错误或无法在此平台执行。
EFAULT 参数filename所指的字符串地址超出可存取空间范围。
ENAMETOOLONG 参数filename所指的字符串太长。
ENOENT 参数filename字符串所指定的文件不存在。
ENOMEM 核心内存不足
ENOTDIR 参数filename字符串所包含的目录路径并非有效目录
EACCES 参数filename字符串所包含的目录路径无法存取,权限不足
ELOOP 过多的符号连接
ETXTBUSY 欲执行的文件已被其他进程打开而且正把数据写入该文件中
EIO I/O 存取错误
ENFILE 已达到系统所允许的打开文件总数。
EMFILE 已达到系统所允许单一进程所能打开的文件总数。
EINVAL 欲执行文件的ELF执行格式不只一个PT_INTERP节区
EISDIR ELF翻译器为一目录
ELIBBAD ELF翻译器有问题。
exec函数蔟之间的调用关系:
事实上,Linux系统只提供了一个exec系统调用,即execve。其它都是库函数(例如libc库),都是通过调用execve函数来实现的。它们的调用关系如下:
可见,有如下调用链:
execlp -> execv -> execvp -> execve
execl -> execvp -> execve
execle -> execve
程序举例:
1. execl:
//execl_test.c
#include<unistd.h>
int main()
{
execl("/bin/ls","ls","-al","/usr",(char * )0);
return 0;
}
2. execle:
//execle_test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execle(const char *path, const char *arg,..., char * const envp[]);
char *env_init[] = {"Hello=hello","Hi=hi",NULL};
int main(void)
{
printf("before execle****\n");
if(execle("./myenv","myenv",NULL,env_init) == -1)
{
printf("execle failed!\n");
}
printf("after execle*****\n");
return 0;
}
//myenv.c
#include <stdio.h>
#include <unistd.h>
//外部全局环境变量
extern char** environ;
int main(int argc , char *argv[])
{
int i;
char **ptr;
for(ptr = environ;*ptr != 0; ptr++)
printf("%s\n",*ptr);
return 0;
}%
上面代码中,编译如下:
% gcc myenv.c -o myenv
% gcc execle_test.c -o execle
运行结果:
% ./execle
before execle****
Hello=hello
Hi=hi
可见,从env_init将值传递给了编译好的myenv。
3.execv:
//execv_test.c
#include<unistd.h>
int main()
{
char * argv[ ]={"ls","-al","/usr",(char*)0};
execv("/bin/ls",argv);
return 0;
}
4. execve:
//execve_test.c
#include<unistd.h>
int main()
{
char *argv[]={"ls","-al","/usr",0};
char *envp[]={"PATH=/bin",0};
execve("/bin/ls",argv,envp);
return 0;
}
5. execlp:
//execlp_test.c
#include<unistd.h>
int main()
{
execlp("ls","ls","-al","/usr",(char *)0);
return 0;
}
6. execvp:
//execvp_test.c
#include<unistd.h>
int main()
{
char * argv[ ] ={ "ls","-al","/usr",0};
execvp("ls",argv);
return 0;
}
运行结果:
除了execle外,其余的运行结果均如下:
total 0
drwxr-xr-x@ 11 root wheel 352 1 1 2011 .
drwxr-xr-x 20 root wheel 640 1 1 2011 ..
drwxr-xr-x 1104 root wheel 35328 1 1 2011 bin
drwxr-xr-x 37 root wheel 1184 1 1 2011 lib
drwxr-xr-x 288 root wheel 9216 1 1 2011 libexec
drwxr-xr-x 14 root wheel 448 7 5 2011 local
drwxr-xr-x 232 root wheel 7424 1 1 2011 sbin
drwxr-xr-x 47 root wheel 1504 1 1 2011 share
drwxr-xr-x 6 root wheel 192 1 1 2011 standalone
......
exec函数载Android系统中的运用:
Android系统底层是基于Linux系统的,所以页支持exec函数蔟。Android系统源码中,也大量使用了exec函数蔟。例如:
1). 在abd.c中,利用exec启动server
int launch_server(int server_port)
{
......
pid_t pid = fork();
if(pid < 0) return -1;
if (pid == 0) {
// child side of the fork
// child process
int result = execl(path, "adb", "fork-server", "server", NULL);
// this should not return
fprintf(stderr, "OOPS! execl returned %d, errno: %d\n", result, errno);
}
......
}
先fork一个子进程,再调用execl启动server,这个server的进程就运行在这个新创建的子进程。
exec函数通常和fork一同使用。先由父进程fork一个子进程,再在子进程中调用exec函数,从而替换掉子进程的执行代码,但不替换子进程的pid。
2). su.c:
su.c就是我们通常用的su命令的源代码文件,在su.c中,也打量使用了exec函数:
/* User specified command for exec. */
if (argc == 3 ) {
if (execlp(argv[2], argv[2], NULL) < 0) {
fprintf(stderr, "su: exec failed for %s Error:%s\n", argv[2],
strerror(errno));
return -errno;
}
} else if (argc > 3) {
/* Copy the rest of the args from main. */
char *exec_args[argc - 1];
memset(exec_args, 0, sizeof(exec_args));
memcpy(exec_args, &argv[2], sizeof(exec_args));
if (execvp(argv[2], exec_args) < 0) {
fprintf(stderr, "su: exec failed for %s Error:%s\n", argv[2],
strerror(errno));
return -errno;
}
}
/* Default exec shell. */
execlp("/system/bin/sh", "sh", NULL);
3).在dalvik/vm/native/dalvik_system_Zygote.cpp中,执行shell:
/* native private static void nativeExecShell(String command);
*/
static void Dalvik_dalvik_system_Zygote_execShell(
const u4* args, JValue* pResult)
{
StringObject* command = (StringObject*)args[0];
const char *argp[] = {_PATH_BSHELL, "-c", NULL, NULL};
argp[2] = dvmCreateCstrFromString(command);
ALOGI("Exec: %s %s %s", argp[0], argp[1], argp[2]);
execv(_PATH_BSHELL, (char**)argp);
exit(127);
}
exec函数,都掌握了么?