exec系统调用会从指定的文件中读取并加载指令,并替代当前调用进程的指令。从某种程度上来说,这样相当于丢弃了调用进程的内存,并开始执行新加载的指令。

  • exec系统调用会保留当前的文件描述符表单。所以任何在exec系统调用之前的文件描述符,例如0,1,2等。它们在新的程序中表示相同的东西。

  • 通常来说exec系统调用不会返回,因为exec会完全替换当前进程的内存,相当于当前进程不复存在了,所以exec系统调用已经没有地方能返回了。

在运行shell时,我们不希望系统调用替代了Shell进程,实际上,Shell会执行fork,这是一个非常常见的Unix程序调用风格。对于那些想要运行程序,但是还希望能拿回控制权的场景,可以先执行fork系统调用,然后在子进程中调用exec。

以shell程序运行ls命令为例

int main(){
int pid;
...
if(fork() == 0){
//子进程操作
//加载新的程序后当前的内容将全部被舍弃,所以不会执行到下面打印函数
exec("ls","-al");
} else {
//父进程操作
do something...
}
printf("finish");
}

fork函数和exec函数共同组成了新进程的加载方式,这也是计算机创建新进程的一般方式(也许是唯一的方式)

下面代码展示了一个进程的内存映像究竟是如何一步一步建立的,还涉及了一些关于ELF可执行文件的知识(见附)。

希望能通过代码,让大家认识到进程实际上并没有那么神秘、复杂,对计算机的进程模型能有个更深的认识。

代码解析
int
exec(char *path, char **argv)
{
char *s, *last;
int i, off;
uint64 argc, sz = 0, sp, ustack[MAXARG+1], stackbase;
struct elfhdr elf;
struct inode *ip;
struct proghdr ph;
pagetable_t pagetable = 0, oldpagetable;
struct proc *p = myproc(); begin_op();
//获取path路径处的文件,即读取要加载的可执行文件
if((ip = namei(path)) == 0){
end_op();
return -1;
}
ilock(ip); // Check ELF header
// 先从文件中读取elf信息
if(readi(ip, 0, (uint64)&elf, 0, sizeof(elf)) != sizeof(elf))
goto bad;
if(elf.magic != ELF_MAGIC)
goto bad; //创建一个新的页表
if((pagetable = proc_pagetable(p)) == 0)
goto bad; // Load program into memory.
// 借助elf中的phoff属性(program section header off 程序段头结点在elf文件中的偏移量)
// 将程序所有的section写入其指定位置(在可执行程序编译时,其就指定好了哪个段在哪个逻辑地址)
for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){
//从文件中读取一个section header到ph中
if(readi(ip, 0, (uint64)&ph, off, sizeof(ph)) != sizeof(ph))
goto bad;
if(ph.type != ELF_PROG_LOAD)
continue;
if(ph.memsz < ph.filesz)
goto bad;
if(ph.vaddr + ph.memsz < ph.vaddr)
goto bad;
uint64 sz1;
//按照section header中的逻辑地址(ph.vaddr)和段长信息,在页表中开辟新的空间
if((sz1 = uvmalloc(pagetable, sz, ph.vaddr + ph.memsz)) == 0)
goto bad;
sz = sz1;
if(ph.vaddr % PGSIZE != 0)
goto bad;
// Load a program segment into pagetable at virtual address va.
// 将segment写入到页表(即内存)中的对应位置
if(loadseg(pagetable, ph.vaddr, ip, ph.off, ph.filesz) < 0)
goto bad;
}
iunlockput(ip);
end_op();
ip = 0; //将可执行文件的内容全部写入内存后,开始创建堆栈
p = myproc();
uint64 oldsz = p->sz; // Allocate two pages at the next page boundary.
// Use the second as the user stack.
sz = PGROUNDUP(sz);
uint64 sz1;
//分配两个page,第二个用来充当用户栈
if((sz1 = uvmalloc(pagetable, sz, sz + 2*PGSIZE)) == 0)
goto bad;
sz = sz1;
uvmclear(pagetable, sz-2*PGSIZE);
sp = sz;
stackbase = sp - PGSIZE; // Push argument strings, prepare rest of stack in ustack.
// 把执行参数写入到栈中
for(argc = 0; argv[argc]; argc++) {
if(argc >= MAXARG)
goto bad;
sp -= strlen(argv[argc]) + 1;
//内存对齐
sp -= sp % 16; // riscv sp must be 16-byte aligned
if(sp < stackbase)
goto bad;
//拷贝到栈中
if(copyout(pagetable, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
goto bad;
ustack[argc] = sp;
}
ustack[argc] = 0; // push the array of argv[] pointers.
//把参数数组的指针拷入到栈中
sp -= (argc+1) * sizeof(uint64);
sp -= sp % 16;
if(sp < stackbase)
goto bad;
if(copyout(pagetable, sp, (char *)ustack, (argc+1)*sizeof(uint64)) < 0)
goto bad; // arguments to user main(argc, argv)
// argc is returned via the system call return
// value, which goes in a0.
// 把数组指针(即参数列表)写入到a1寄存器(该寄存器存储了函数第二个参数)
p->trapframe->a1 = sp; // Save program name for debugging.
//把文件名设置成进程名
for(last=s=path; *s; s++)
if(*s == '/')
last = s+1;
safestrcpy(p->name, last, sizeof(p->name)); // Commit to the user image.
// 设置进程属性,并且将相应的寄存器置为初始状态
oldpagetable = p->pagetable;
p->pagetable = pagetable;
p->sz = sz;
p->trapframe->epc = elf.entry; // initial program counter = main
p->trapframe->sp = sp; // initial stack pointer
proc_freepagetable(oldpagetable, oldsz); //A0用来存储返回值/函数参数,
return argc; // this ends up in a0, the first argument to main(argc, argv) bad:
if(pagetable)
proc_freepagetable(pagetable, sz);
if(ip){
iunlockput(ip);
end_op();
}
return -1;
}

附:

最新文章

  1. Python 环境搭建,开发工具,基本语法
  2. randow()方法
  3. 9. javacript高级程序设计-客户端检测
  4. August 28th 2016 Week 36th Sunday
  5. Perl 中 Pod 的基本用法。
  6. VS2013和VS2008项目的互通
  7. 一些有用的javascript实例分析(二)
  8. Spring配置文件中如何使用外部配置文件配置数据库连接
  9. nginx+uwsgi部署django项目
  10. idea看源码
  11. angularjs+webapi2 跨域Basic 认证授权(二)
  12. 关于Android 8.0java.lang.SecurityException: Permission Denial错误的解决方法
  13. CSS-水平和垂直居中
  14. 用git,clone依赖的库
  15. 小强学渲染之OpenGL状态机理解
  16. Javascript 随机数函数 学习之一:产生服从均匀分布随机数
  17. 20155318 《网络攻防》 Exp8 Web基础
  18. jquery.nestable.min.js可拖动标签
  19. USACO 6.1 A Rectangular Barn
  20. .NET、NET Framewor以及.NET Core的关系(二)

热门文章

  1. (转载)Select for update/lock in share mode 对事务并发性影响
  2. Java基础系列(38)- 数组的使用
  3. Centos8.X 搭建Grafana+Jmeter+Influxdb 性能实时监控平台
  4. Leetcode 矩阵置零
  5. 用Python基本库实现进度条
  6. english note [6.3to6.9]
  7. 【Vue】淘气三千问之 data为什么是函数而不是对象?这河狸吗
  8. 常见JS
  9. WPF进阶技巧和实战07--自定义元素02
  10. 解决连接云服务器的redis失败