/*
* sys_execve() executes a new program.
*/
long sys_execve(const char __user *name, //需要执行的文件的绝对路径(存于用户空间)
const char __user *const __user *argv, //传入系统调用的参数(存于用户空间)
const char __user *const __user *envp, struct pt_regs *regs) //regs是系统调用时系统堆栈的情况(详细解释请参看情景分析之系统调用)
{
long error;
char *filename;
filename = getname(name); //copy *filename frome user space to system space.
error = PTR_ERR(filename);
if (IS_ERR(filename))
return error;
error = do_execve(filename, argv, envp, regs);
#ifdef CONFIG_X86_32
if (error == 0) {
/* Make sure we don't return using sysenter.. */
set_thread_flag(TIF_IRET);
}
#endif
putname(filename);
return error;
}
我们首先关注标签__user,这个标签表示其后边的变量是指向用户空间的地址的(详细的解释,请参看深入Linux内核框架P27)。
关于sys_execve参数的说明:Not only the register set with the arguments and the name of the executable file (filename) but also pointers to the arguments and the environment of the program are passed as in system programming. The notation is slightly clumsy because argv and envp are arrays of pointers, and both the pointer tothe array itself as well as all pointers in the array are located in the userspace portion of the virtual address space. Recall from the Introduction that some precautions are required when userspace memoryis accessed from the kernel, and that the __user annotations allow automated tools to check if everything is handled properly.
接下来的getname将要执行的文件名从用户空间拷贝到系统空间会调用如下函数:
static char *getname_flags(const char __user * filename, int flags)
{
char *tmp, *result;
result = ERR_PTR(-ENOMEM);
tmp = __getname(); //allocate a physical page in system space as cache. Because the file's name could be very long. (hu xi ming, Page 306)
if (tmp) {
int retval = do_getname(filename, tmp);
result = tmp;
if (retval < 0) {
if (retval != -ENOENT || !(flags & LOOKUP_EMPTY)) {
__putname(tmp);
result = ERR_PTR(retval);
}
}
}
audit_getname(result);
return result;
}
注意函数中的__getname();为文件名分配一个物理页面作为缓冲区,因为一个绝对路径可能很长,因此如果用临时变量的话,这个路径就被存储在系统堆栈段中,这显然是不合适的,因为系统堆栈段只有约7KB的空间。
之后调用do_getname()将filename从用户空间拷贝到分配到的系统物理页面上:
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;
}
对划红线部分代码的理解:在创建新进程的时候,有个copy_mm操作,将