exec源码阅读
loadseg
这是内核中用于加载程序段的函数,它负责将程序段加载到页表的虚拟地址va处。
// Load a program segment into pagetable at virtual address va.
// va must be page-aligned
// and the pages from va to va+sz must already be mapped.
// Returns 0 on success, -1 on failure.
static int
loadseg(pagetable_t pagetable, uint64 va, struct inode *ip, uint offset, uint sz)
{
uint i, n;
uint64 pa;
for(i = 0; i < sz; i += PGSIZE){
pa = walkaddr(pagetable, va + i);
if(pa == 0)
panic("loadseg: address should exist");
if(sz - i < PGSIZE)
n = sz - i;
else
n = PGSIZE;
if(readi(ip, 0, (uint64)pa, offset+i, n) != n)
return -1;
}
return 0;
}其中readi用于从文件读取内容到物理内存中。
flags2perm
这个函数负责将ELF程序头标志转换为页表权限位。
exec
begin_op和end_op是xv6系统日志相关的函数。
这部分通过路径查找文件inode,找不到即释放日志锁并返回错误码-1。ilock对inode加锁确保独占访问该文件。
然后是ELF头检查部分和页表初始化。readi从文件读取ELF头结构,并检查elf_magic。然后为进程创建新页表(替换原有页表)。
这部分加载ELF程序到内存,遍历程序头表,里面包含一些有效性检查,然后调用uvmalloc进行内存分配与映射,并用loadseg加载内容。最后释放inode锁,提交文件系统事务,ip=0标记inode已处理完成。
这部分保存旧内存大小,用于后面内存替换并安全释放旧内存。
这部分为进程分配用户栈,+1用于设置保护页,uvmclear清除保护页的PTE_U标志,sp是栈顶指针,stackbase是栈基地址。
这部分将命令行参数压入用户栈,并记录地址。copyout将参数字符串复制到用户栈,ustack[argc]记录参数地址,以0结束。
这部分将参数指针的地址数组复制到用户栈。
这里修改进程的trapframe,当进程恢复执行时,寄存器将携带正确参数。a1寄存器存储参数指针数组地址。
这部分从文件路径中提取程序名,并保存到进程结构体中,用于Debug。
那么至此,准备工作就全部完成了。
这部分提交新的用户镜像并清理旧资源。先保存旧页表地址,然后切换至新页表并进行一些参数更新,最后释放旧页表。
返回参数给用户空间。
最后是错误处理逻辑。