【CSAPP-02】从源程序到进程(二)
前言
前文已经介绍了源程序到进程的第一部分:编译,汇编,链接,本文介绍可执行文件的装载和执行,并探讨操作系统对进程的管理一、操作系统三个抽象
简单介绍操作系统进程、虚拟内存、文件三大抽象,便于后续内容的理解
进程
一个执行程序的实例,每个程序都运行在进程的上下文
抽象1:(处理器的逻辑控制流)时间分片,并发控制
抽象2:(内存系统)进程为每个程序提供了他自己的私有地址空间
(1)内核模式和用户模式
(2)进程控制
- 获取进程id:getpid()
- 进程的创建:
/*
fork:
1.子进程获得与父进程虚拟内存相同但独立的副本,且具有相同的文件描述符
2.调用一次,返回两次,父进程返回子进程pid,子进程返回0
3.并发执行
*/
#include <unistd.h>
#include <sys/types.h>
pid_t fork(void);
- 进程的执行:
/*
execve:在当前进程上下文加载运行新程序
filename:可执行目标文件
argv:参数列表
envp:环境列表变量
加载到main:
int main(int argc, char *argv[], char *envp[]);
*/
#include <unistd.h>
int execve(const char *filename, const char *argv[], const char *envp[]);
- 等待进程
//waitpid:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statusp, int options);
//wait
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *statusp);
- 进程休眠
#include <unistd.h>
unsigned int sleep(unsigned int secs);
int pause(void);
- 进程终止
正常终止
return or exit
异常终止
控制移交给操作系统,跳转到内核异常处理代码
僵死进程
父进程调用wait等待子进程结束
虚拟内存
组织为存放在磁盘上的N个连续字节单元的数组
特点1:物理内存是虚拟内存的缓存,进程不必全部加载到内存,节省空间
特点2:为进程提供独立私有空间,防止干扰

- 页表
页命中:从物理地址取出数据操作
页不命中:根据替换策略选择牺牲页,改写页表,先从虚拟内存加载到物理内存再访问 - 地址翻译
- 再看进程管理
fork:创建时共享虚拟内存,记为私有的写时复制
execve:加载器将代码、数据映射到私有区域,库函数映射到共享区域
二、加载执行
将可执行目标文件加载到内存,构造虚拟内存映射,并跳转到程序入口点
linux> ./prog
-
流程
① 输入命令:$./hello
② shell命令行解释器构造argv和envp;
③ 调用fork()函数创建子进程,其地址空间与shell父进程完全相同,包括只读代码段、读写数据段、堆及用户栈等
④ 调用execve()函数在当前进程(新创建的子进程)的上下文中加载并运行hello程序。将hello中的.text节、.data节、.bss节等内容加载到当前进程的虚拟地址空间
⑤ 调用hello程序的main()函数,hello程序开始在一个进程的上下文中运行。 -
execve加载细节
① 删除已存在的用户区域(自父进程独立)。
②映射私有区:为代码、数据、.bss和栈区域创建新的区域结构,所有这些区域都是私有的、写时才复制的。
③ 映射共享区:比如标准C库libc.so,映射到用户虚拟地址空间中的共享区域内。
④设置PC:设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。
⑤execve调用成功不会返回,只有当出现错误时,例如找不到需要执行的程序时,execve才会返回到调用程序。