问题
execve(...) 的参数分别是什么?有什么意义?
第一个参数是程序路径,第二个参数是进程参数,第三个参数是环境变量
再论 execve(...)
main 函数 (默认进程入口)
int main(int argc, char* argv[])
- argc - 命令行参数个数
- argv - 命令行参数数组
进程空间概要
Linux 进程地址空间的布局大致如上如所示,从高地址到低地址的布局分别为 启动参数和环境变量 (通过 execve() 拷贝父进程得到的)、栈 (用于函数调用)、堆 (用于动态分配内存)、未初始化的数据 (用于存放未初始化的全局变量和静态局部变量)、初始化的数据 (用于存放初始化的全局变量和静态局部变量) 、代码 (用于存放我们定义的函数)
下面我们通过一个程序来查看进程内存地址空间的布局
mem.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
static int g_init = 255;
static float g_uninit;
static void text()
{
}
int main(int argc, char* argv[])
{
static double s_init = 0.255;
static double s_uninit;
int i = 0;
int* p = malloc(4);
printf("argv[0] = %p\n", argv[0]);
printf("&i = %p\n", &i);
printf("p = %p\n", p);
printf("&g_uninit = %p\n", &g_uninit);
printf("&s_uninit = %p\n", &s_uninit);
printf("&g_init = %p\n", &g_init);
printf("&s_init = %p\n", &s_init);
printf("text = %p\n", text);
free(p);
return 0;
}
我们按照上面的进程地址空间布局图,按高地址到低地址,打印进程地址空间不同区域的地址值
程序运行结果如下图所示:
通过打印可以看出,进程地址空间的不同区域确实是按照上面的进程地址空间布局图的顺序从高到低排列的
下面的程序输出什么?为什么?
child.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
int i = 0;
sleep(3);
for(i=0; i<argc; i++)
printf("exec = %d, %s\n",
getpid(), argv[i]);
return 0;
}
parent.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#define EXE "child.out"
int create_process(char* path, char* args[])
{
int ret = fork();
if( ret == 0 )
{
execve(path, args, NULL);
}
return ret;
}
void zero_str(char* s)
{
while( s && *s ) *s++ = 0;
}
int main()
{
char path[] = EXE;
char arg1[] = "hello";
char arg2[] = "world";
char* args[] = {path, arg1, arg2, NULL};
printf("%d begin\n", getpid());
printf("%d child = %d\n", getpid(), create_process(EXE, args));
zero_str(path);
zero_str(arg1);
zero_str(arg2);
printf("%d path = %s\n", getpid(), path);
printf("%d arg1 = %s\n", getpid(), arg1);
printf("%d arg2 = %s\n", getpid(), arg2);
printf("%d end\n", getpid());
return 0;
}
这个程序在子进程 execve() 执行后,将传递给子进程的进程参数都清空
程序运行结果如下图所示:
通过打印可以看出,在父进程执行完 fork() 和 execve() 后,传递给子进程的进程参数已经被清空了;但在子进程中,子进程中的进程参数并没有被清空,这是因为父进程在执行完 fork() 和 execve() 后,子进程会将父进程传递给它的进程参数再复制一份,所以尽管在父进程中修改了传递给子进程的进程参数,也影响不了子进程的进程参数
Linux 启动参数 (命令行规范)
由选项,选项值,操作数组成
选项由短横 (-) 开始,选项名必须是单个字母或数字字符
选项可以有选项值,选项与选项值之间可用空格分隔 (-o test <=> -otest)
如果多个选项均无选项值,可合而为一 (-a -b -c <=> -abc)
既不是选项,也不能作为选项值的参数是操作数
第一次出现的双横线 (--) 用于结束所有选项,后续参数为操作数
Linux 启动参数 (命令行参数)解析
规则:if:s
示例:
if:s 这个规则定义了:有 3 个选项,分别为 -i、-f、和 -s,其中 -i 和 -s 不带选项值,-f 必须带选项值
第 1 个示例:-f 是选项,abc 是它的选项值,def 是操作数
第 2 个示例:-s 和 -i 是选项,-v 是一个不存在的选项
第 3 个示例:abc 是操作数,-f 是选项,gg 是它的选项值,de 是操作数,-s 是选项
第 4 个示例:abc 是操作数,-- 表示后面的参数都是操作数,所以 -- 后面的 -f -s 都是操作数
第 5 个示例:-s 是选项,它不带选项值,所以 abc 是操作数;-i 是选项;-f 是选项,但它后面不带选项值,所以是不合法的
Linux 启动参数 (命令行参数) 编程
getopt(...) 从 argc 和 argv 中获取下一个选项
- 选项合法:选项值为选项字符,optarg 指向选项值字符串
- 选项不合法:返回字符 '?' ,optopt 保存当前选项字符 (错误)
- 选项合法但缺少选项值:返回字符 ':',optopt 保存当前选项字符 (错误)
默认情况下 getopt(...) 对 argv 进行重排,所有操作数位于最后位置
optstring 规则的拓展定义
起始字符可以是 :,+,- 或省略
- 省略:=> 出现选项错误时,程序中通过 : 或者 ? 进行处理并给出默认错误提示
- : => 错误提示开关,程序中通过 : 或者 ? 进行处理 (无默认错误提示)
- + => 提前停止开关,遇见操作数时返回 -1,认为选项处理完毕 (后续都是操作数)
- - => 不重排开关,遇见操作数时,返回 1,optarg 指向操作数字符串
- 组合 => +: or -:
进程参数编程
main.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
int i = 0;
int c = 0;
int iflg = 0;
int fflg = 0;
int sflg = 0;
char* fvalue = NULL;
while( (c = getopt(argc, argv, "f:is")) != -1 )
{
switch( c )
{
case 'f':
fflg = 1;
fvalue = optarg;
break;
case 'i':
iflg = 1;
break;
case 's':
sflg = 1;
break;
case '?':
printf("Unknow option: -%c\n", optopt);
break;
case ':':
printf("-%c missing option argument\n", optopt);
break;
case 1:
printf("inter: %s\n", optarg);
break;
default:
printf("ret = %d\n", c);
}
}
printf("fflg = %d, fvalue = %s, iflg = %d, sflg = %d\n", fflg, fvalue, iflg, sflg);
for(i=optind; i<argc; i++)
{
printf("parameter: %s\n", argv[i]);
}
return 0;
}
c 是 getopt 匹配到的选项字符,为 f 时,optarg 指向它对应的选项值;为 ? 时,表示当前选项不合法;为 : 时,表示当前选项没有选项值;为 1 时,表示为操作数,optarg 指向这个操作数
optind 是操作数在 argv 中的起始下标
由于 optstring 的起始字符不是 + 或 -,这种情况下,getopt(...) 会对 argv 进行重排,所有操作数位于最后位置
程序运行结果如下图所示:
下面使用 optstring 的拓展规则进行实验
使用 +:
mian.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
int i = 0;
int c = 0;
int iflg = 0;
int fflg = 0;
int sflg = 0;
char* fvalue = NULL;
while( (c = getopt(argc, argv, "+:f:is")) != -1 )
{
switch( c )
{
case 'f':
fflg = 1;
fvalue = optarg;
break;
case 'i':
iflg = 1;
break;
case 's':
sflg = 1;
break;
case '?':
printf("Unknow option: -%c\n", optopt);
break;
case ':':
printf("-%c missing option argument\n", optopt);
break;
case 1:
printf("inter: %s\n", optarg);
break;
default:
printf("ret = %d\n", c);
}
}
printf("fflg = %d, fvalue = %s, iflg = %d, sflg = %d\n", fflg, fvalue, iflg, sflg);
for(i=optind; i<argc; i++)
{
printf("parameter: %s\n", argv[i]);
}
return 0;
}
+ 会使得 getopt(...) 不会对 argv 进行重排,在碰到操作数以后,getopt(...) 返回 -1,不会再进行匹配了
: 会使得 getopt(...) 匹配失败后,不会有输出错误提示的打印
程序运行结果如下图所示:
使用 -:
mian.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
int i = 0;
int c = 0;
int iflg = 0;
int fflg = 0;
int sflg = 0;
char* fvalue = NULL;
while( (c = getopt(argc, argv, "-:f:is")) != -1 )
{
switch( c )
{
case 'f':
fflg = 1;
fvalue = optarg;
break;
case 'i':
iflg = 1;
break;
case 's':
sflg = 1;
break;
case '?':
printf("Unknow option: -%c\n", optopt);
break;
case ':':
printf("-%c missing option argument\n", optopt);
break;
case 1:
printf("inter: %s\n", optarg);
break;
default:
printf("ret = %d\n", c);
}
}
printf("fflg = %d, fvalue = %s, iflg = %d, sflg = %d\n", fflg, fvalue, iflg, sflg);
for(i=optind; i<argc; i++)
{
printf("parameter: %s\n", argv[i]);
}
return 0;
}
- 会使得 getopt(...) 不会对 argv 进行重排,optind 这个时候等于 argc,在碰到操作数以后,getopt(...) 返回 1,optarg 指向匹配到的操作数
: 会使得 getopt(...) 匹配失败后,不会有输出错误提示的打印
程序运行结果如下图所示: