execve(const char *filename, char *const argv[ ], char *const envp[ ])
视频教程以及实际代码可以看这一个教程
其他的需要的知识
GDT表
GDT表虚拟内存
页表
任务切换
fork实现
elf文件加载
这一个是一个Linux下面的标准接口
这一个的实际作用的是执行一个可执行文件
把当前程序替换成要执行的程序, 而同时保留原程序运行的方法是,fork+exec
第二个参数是利用数组指针来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。
函数执行成功时没有返回值,执行失败时的返回值为-1.
#include<unistd.h>
main()
{
char *argv[ ]={"ls", "-al", "/etc/passwd", NULL};
char *envp[ ]={"PATH=/bin", NULL}
execve("/bin/ls", argv, envp);
}
实现的思路
CPU寄存器的初始化
在使用的时候需要一个新的页表, 记录这一个新的进程使用的地址信息
由于使用系调用的时候会记录进入的时候的CPU寄存器信息, 返回的时候弹出信息, 这一个新的进程再返回的时候, 首先使用是栈里面的记录的信息, 而不是tss表里面的信息
返回的时候这里面的信息也需要进行更改, 如果不改变的话会返回之前的页表对应的位置
该变信息的时候可以使用tss里面的记录的esp0的值进行计算, 使用结构体syscall_frame_t(一个记录压栈时候寄存器顺序的结构体, 定位到对应的位置)
栈的初始化(参数传递)
在使用新的栈的时候, 设置esp需要减去一下系统调用的参数的位置(系统调用返回的时候使用retf会把栈里面的参数弹出来, 新的任务进入的时候栈里面没有这几个参数, 需要预留空间), 以及初始化一下main函数的参数在栈里面
这里可以使用把所有的信息放在栈里面
实际的实现
//执行一个可执行文件
int sys_execve(char *name, char ** argv, char ** env){
task_t *task = task_current();//获取当前的任务
uint32_t new_page_dir = memory_create_uvm();//获取一个新的页表给任务使用
uint32_t old_page_dir = task->tss.cr3;//记录一下现在使用的页表
//使用这一个新的文件的名字初始化任务名字
kernel_strncpy(task->name, get_file_name(name), TASK_NAME_SIZE);
if(! new_page_dir)
{
goto exec_failed;
}
//获取这一个的入口, 以及加载这一个文件到新的页表里面
//这里实际是加载一个elf文件, 以及从文件头获取他的入口地址
//这里需要注意的是实际使用的虚拟地址是还未使用的页表里面的
//实际加载的时候需要对使用的内存申请, 映射, 复制
uint32_t entry = load_elf_file(task, name, new_page_dir);
if(entry == 0){
goto exec_failed;
}
//预留一段空间放参数(main函数的参数)
uint32_t stack_top = 栈的顶部虚拟地址 - 预留的参数保存地址;
//为这一个任务的新页表申请一下栈空间
int err = memory_alloc_for_page_dir(new_page_dir,
MEM_TASK_STACK_TOP - MEM_TASK_STACK_SIZE(实际的虚拟地址最小值), MEM_TASK_STACK_SIZE()大小, 权限(用户可使用, 可写));
if(err < 0){
goto exec_failed;
}
int argc = strings_count(argv);//获取参数的个数
//把这一个参数按照之前图里面的格式复制到栈里面预留的空间
//之后main函数可以直接使用
err = copy_args((char *)stack_top, new_page_dir, argc, argv);
if(err < 0)
{
goto exec_failed;
}
//获取记录了栈里信息的地址
syscall_frame_t * frame = (syscall_frame_t *)(系统调用的时候记录的特权级esp - sizeof(syscall_frame_t)(实际压入的信息的大小));
//改变特权级0的栈里面的信息用于返回
frame->eip = entry;
frame->eax = frame->ebx = frame->ecx = frame->edx = 0;
frame->esi = frame->edi = frame->ebp = 0;
frame->eflags = EFLAGS_DEFAULT | EFLAGS_IF;
//预留一下栈里面参数的位置
frame->esp = stack_top - sizeof(uint32_t) * SYSCALL_PARAM_COUNT;
//栈里面需要有初始的参数的值
task->tss.cr3 = new_page_dir;
mmu_set_page_dir(new_page_dir);
//销毁之前的页表
memory_destroy_uvm(old_page_dir);
return 0;
exec_failed:
if(new_page_dir){
//错误处理
}
return -1;
}