创建的新的子进程在调用execv执行新的程序段(开始独立于父进程执行)的时候,子进程用户空间栈的安排应该是怎样的呢?参数是如何传递的呢?
编写如下两个程序:
程序一:test1.c
#include <stdio.h> int main(int args, char* argv[]) { char s[5] = "ABCD"; while(1) { printf("%s\n", s); printf("%s\n", argv[1]); } return; }
gcc -g -o test1 test1.c
程序二:test.c
#include "../include/apue.h" void main() { pid_t pid; char *args[] = {"/bin/echo", "Hello", "World!", NULL}; if((pid = fork()) < 0) err_sys("fail to fork!"); else if(pid == 0) { execve("./test1", args, NULL); } }gcc -g -o test test.c
我们来调试一下parent,涉及到多进程程序的调试,可以参考文章http://www.ibm.com/developerworks/cn/linux/l-cn-gdbmp/
$gdb parent
(gdb)set follow-fork-mode child //跟踪子进程
(gdb)b main //在父进程main处设置断点
(gdb)r //开始运行程序
(gdb)step //单步执行
...//直到执行到子进程的main
(gdb)
process 4163 is executing new program: /home/sun/桌面/UNIX环境高级编程/test/child
Breakpoint 1, main (args=3, argv=0xbfffff04) at child.c:2
2 int main(int args, char* argv[]) {
我们看到,从父进程传递过来的参数,参数的个数是三个,参数的指针所在地址是0xbffff04
(gdb)x/4x 0xbfffff04 //看一下这个地址存储的内容是什么
0xbfffff04: 0xbfffffdd //说明这个指针指向0xbfffffdd的内容
(gdb) x/36bc 0xbfffffdd //看一下0xbfffffdd开始的36个字节的内容
0xbfffffdd: 47 '/' 98 'b' 105 'i' 110 'n' 47 '/' 101 'e' 99 'c' 104 'h'
0xbfffffe5: 111 'o' 0 '\000' 72 'H' 101 'e' 108 'l' 108 'l' 111 'o' 0 '\000'
0xbfffffed: 87 'W' 111 'o' 114 'r' 108 'l' 100 'd' 33 '!' 0 '\000' 46 '.'
0xbffffff5: 47 '/' 116 't' 101 'e' 115 's' 116 't' 49 '1' 0 '\000' 0 '\000'
0xbffffffd: 0 '\000' 0 '\000' 0 '\000' Cannot access memory at address 0xc0000000
从0xbfffffdd开始至0xbfffffff存储的内容如下(以字节为单位):
/bin/echo0Hello0World!0./test100000
(0xc0000000处的内容是没有办法访问的,因为那是指向内核空间的。)
可以看到,这些字符正是我们传递过来的参数。
其中./test1是待执行的文件的名字,它是存储在栈的最顶端的,在系统调用execv时,内核会将待执行的文件名拷贝到内存空间,会有这样一个操作:
static int do_getname(const char __user *filename, char *page)
{
int retval;
unsigned long len = PATH_MAX;
if (!segment_eq(get_fs(), KERNEL_DS)) { //如果进程地址限制和KERNEL_DS不和相等,即当前进程没有运行在内核态
if ((unsigned long) filename >= TASK_SIZE) //如果filname>=TASK_SIZE,则非法访问了
return -EFAULT;
if (TASK_SIZE - (unsigned long) filename < PATH_MAX)
len = TASK_SIZE - (unsigned long) filename; //这个是为什么????
}
retval = strncpy_from_user(page, filename, len); //将filename从用户空间中拷贝到内核页面中。
if (retval > 0) {
if (retval < len)
return 0;
return -ENAMETOOLONG;
} else if (!retval)
retval = -ENOENT;
return retval;
}
这个函数具体的解释可以参考博文http://blog.csdn.net/sunnybeike/article/details/6899959。标注为红色的语句用来获取文件名的长度。经过上面的解释可以知道为什么这么做了吧。当然最高端的四个字节被置为NULL,这四个字节是不会被用到的,所以得到的len会比实际值大四个,但是没有关系,反正是NULL,在字符处理的时候NULL总是被忽略的。
《深入理解Linux内核(英文版)》P506的图也给出了清晰的解释,可以参考。但是图中显示的环境字符串是没有体现在我写的这两个简单的程序中的,而且我在怀疑,环境字符串是否会被用到,因为正如在do_getname代码中看到的,程序并没有处理有关环境变量的代码,这个,有待考究。。。