exec系统调用 && 进程的加载过程
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系统调用 && 进程的加载过程的更多相关文章
- ELF文件的加载过程(load_elf_binary函数详解)--Linux进程的管理与调度(十三)
加载和动态链接 从编译/链接和运行的角度看,应用程序和库程序的连接有两种方式. 一种是固定的.静态的连接,就是把需要用到的库函数的目标代码(二进制)代码从程序库中抽取出来,链接进应用软件的目标映像中: ...
- 动态符号链接的细节 与 linux程序的加载过程
转: http://hi.baidu.com/clivestudio/item/4341015363058d3d32e0a952 值得玩味的一篇分析程序链接.装载.动态链接细节的好文档 导读: by ...
- insmod模块加载过程代码分析1【转】
转自:http://blog.chinaunix.net/uid-27717694-id-3966290.html 一.概述模块是作为ELF对象文件存放在文件系统中的,并通过执行insmod程序链接到 ...
- Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析
Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...
- linux内核启动以及文件系统的加载过程
Linux 内核启动及文件系统加载过程 当u-boot 开始执行 bootcmd 命令,就进入 Linux 内核启动阶段.普通 Linux 内核的启动过程也可以分为两个阶段.本文以项目中使用的 lin ...
- 你所不知道的SQL Server数据库启动过程(用户数据库加载过程的疑难杂症)
前言 本篇主要是上一篇文章的补充篇,上一篇我们介绍了SQL Server服务启动过程所遇到的一些问题和解决方法,可点击查看,我们此篇主要介绍的是SQL Server启动过程中关于用户数据库加载的流程, ...
- ThinkPHP3.2 加载过程(四)
前言: 由于比较懒散,但是又是有点强迫症,所以还是想继续把ThinkPHP3.2的加载过程这个烂尾楼补充完整. ========================================分割线= ...
- 在Qt Quick中一个简单Hello World加载过程
Qt5基本类图: QQmlEngine QQmlEngine类提供了一个QML引擎,用于管理由QML文档定义的对象层次架构,QML提供了一个默认的QML上下文(根上下文,获取函数QQmlEngi ...
- web.xml的加载过程配置详解
一:web.xml加载过程 简单说一下,web.xml的加载过程.当我们启动一个WEB项目容器时,容器包括(JBoss,Tomcat等).首先会去读取web.xml配置文件里的配置,当这一步骤没有 ...
随机推荐
- 让PHP能够调用C的函数-FFI扩展
在大型公司中,一般会有很我编程语言的配合.比如说让 Java 来做微服务层,用 C++ 来进行底层运算,用 PHP 来做中间层,最后使用 JS 展现效果.这些语言间的配合大部分都是通过 RPC 来完成 ...
- python实现rtsp取流并截图
import cv2 def get_img_from_camera_net(folder_path): cap = cv2.VideoCapture("rtsp://admin:admin ...
- CF39C-Moon Craters【dp】
正题 题目链接:https://www.luogu.com.cn/problem/CF39C 题目大意 坐标轴上有\(n\)个圆,给出每个圆的位置\(c_i\)和半径\(r_i\). 要求选出最多的圆 ...
- 使用jacob调用Windows的com对象,进行word、ppt等转换成ptf、html(二)
富文本转pdf : 注意:simsun.ttc 可以百度下载:http://www.pc6.com/softview/SoftView_100415.html package com.orangecd ...
- Python自动化测试发送邮件太麻烦?!一起聊一聊 Python 发送邮件的3种方式
1. 前言 发送邮件,我们在平时工作中经用到,做为测试人员,在自动化测试中用的也比较多,需要发送邮件给某领导 SMTP是Python默认的邮件模块,可以发送纯文本.富文本.HTML 等格式的邮件 今天 ...
- MFC修改窗口图标
Visual Studio写MFC应用程序,默认的程序左上角图标是自带的(如下图),想要自己个性化定制一个新的图标则需要以下几个步骤. 一.准备工作(icon图标) 首先准备一个自己个性化定制的图片, ...
- 腾讯混合云存储 TStor 系列再添新成员,并行存储一体机正式发布
最近国内某大型互联网公司依靠其数据优势成功上市,可见数据的重要性,而数据和存储密不可分,您真的知道自己需要更高性能存储吗? 在当今数据爆发式增长的时代,数据已经成为很多行业最重要的资源,没有之一. 数 ...
- 详解package-lock.json的作用
目录 详解package-lock.json package-lock.json的作用 版本号的定义规则与前缀对安装的影响 改动package.json后依旧能改变项目依赖的版本 当前项目的真实版本号 ...
- 在Vue中使用JSX,很easy的
摘要:JSX 是一种 Javascript 的语法扩展,JSX = Javascript + XML,即在 Javascript 里面写 XML,因为 JSX 的这个特性,所以他即具备了 Javasc ...
- 论文解读(MPNN)Neural Message Passing for Quantum Chemistry
论文标题:DEEP GRAPH INFOMAX 论文方向: 论文来源:ICML 2017 论文链接:https://arxiv.org/abs/1704.01212 论文代码: 1 介绍 本文的目标 ...