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;
}

附:

exec系统调用 && 进程的加载过程的更多相关文章

  1. ELF文件的加载过程(load_elf_binary函数详解)--Linux进程的管理与调度(十三)

    加载和动态链接 从编译/链接和运行的角度看,应用程序和库程序的连接有两种方式. 一种是固定的.静态的连接,就是把需要用到的库函数的目标代码(二进制)代码从程序库中抽取出来,链接进应用软件的目标映像中: ...

  2. 动态符号链接的细节 与 linux程序的加载过程

    转: http://hi.baidu.com/clivestudio/item/4341015363058d3d32e0a952 值得玩味的一篇分析程序链接.装载.动态链接细节的好文档 导读: by ...

  3. insmod模块加载过程代码分析1【转】

    转自:http://blog.chinaunix.net/uid-27717694-id-3966290.html 一.概述模块是作为ELF对象文件存放在文件系统中的,并通过执行insmod程序链接到 ...

  4. Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析

    Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...

  5. linux内核启动以及文件系统的加载过程

    Linux 内核启动及文件系统加载过程 当u-boot 开始执行 bootcmd 命令,就进入 Linux 内核启动阶段.普通 Linux 内核的启动过程也可以分为两个阶段.本文以项目中使用的 lin ...

  6. 你所不知道的SQL Server数据库启动过程(用户数据库加载过程的疑难杂症)

    前言 本篇主要是上一篇文章的补充篇,上一篇我们介绍了SQL Server服务启动过程所遇到的一些问题和解决方法,可点击查看,我们此篇主要介绍的是SQL Server启动过程中关于用户数据库加载的流程, ...

  7. ThinkPHP3.2 加载过程(四)

    前言: 由于比较懒散,但是又是有点强迫症,所以还是想继续把ThinkPHP3.2的加载过程这个烂尾楼补充完整. ========================================分割线= ...

  8. 在Qt Quick中一个简单Hello World加载过程

    Qt5基本类图: QQmlEngine    QQmlEngine类提供了一个QML引擎,用于管理由QML文档定义的对象层次架构,QML提供了一个默认的QML上下文(根上下文,获取函数QQmlEngi ...

  9. web.xml的加载过程配置详解

      一:web.xml加载过程 简单说一下,web.xml的加载过程.当我们启动一个WEB项目容器时,容器包括(JBoss,Tomcat等).首先会去读取web.xml配置文件里的配置,当这一步骤没有 ...

随机推荐

  1. mysql操作中卡死 解决方法

    1.使用指令查询当前进程 show full processlist; -- 查询全部当前进程; show processlist;-- 只列出前100条 2.找出卡死的进程id 3.删除卡死进程 k ...

  2. pycharm 操作excel

    一.安装openpyxl Python中,往excel写入数据通常使用openpyxl库.也可以使用pandas库.这里讲解如何通过openpyxl库操作excel.pip install openp ...

  3. Linux文件(夹)属性与权限

    文件属性与权限,文件权限设置 参考资料:鸟哥的Linux私房菜 用户与用户组 Linux系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这 ...

  4. CometOJ-[Contest #10]鱼跃龙门【exgcd】

    正题 题目链接:https://cometoj.com/problem/1479 题目大意 给出\(n\)求一个最小的\(x(x>0)\)满足 \[\left(\sum_{i=1}^xi\rig ...

  5. YbtOJ#791-子集最值【三维偏序】

    正题 题目链接:http://www.ybtoj.com.cn/contest/123/problem/1 题目大意 给出\(3\)个长度为\(n\)的排列\(A,B,C\).然后一个下标集合\(S\ ...

  6. Go变量与基础数据类型

    一.基础介绍 Go 是静态(编译型)语言,是区别于解释型语言的弱类型语言(静态:类型固定,强类型:不同类型不允许直接运算) 例如 python 就是动态强类型语言 1.Go 的特性: 跨平台的编译型语 ...

  7. Redis的一些常用命令

    查看所有键 keys * 首先先向数据库中插入一些键值对 演示keys *命令 keys *查询所有键的方式是遍历数据库中的键,其时间复杂度为O(n),如果数据库的数量一旦过大,其效率就大大降低,因此 ...

  8. Python&Selenium 数据驱动测试【unittest+ddt+xml】

    一.摘要 本博文将介绍Python和Selenium做自动化测试时,基于unittest框架,借助ddt模块,使用xml文件作为测试输入. 二.xml文件 保存路径:D:\\Programs\\Pyt ...

  9. 模拟赛T2 交换 解题报告

    模拟赛T2 交换 解题报告 题目大意: 给定一个序列和若干个区间,每次从区间中选择两个数修改使字典序最小. \(n,m\) 同阶 \(10^6\) 2.1 算法 1 按照题意模拟,枚举交换位置并比较. ...

  10. PAT (Basic Level) Practice (中文)1076 Wifi密码 (15分)

    1076 Wifi密码 (15分) 下面是微博上流传的一张照片:"各位亲爱的同学们,鉴于大家有时需要使用 wifi,又怕耽误亲们的学习,现将 wifi 密码设置为下列数学题答案:A-1:B- ...