标签:
在有进程地址空间虚拟化概念之前,所有的程序都得实打实的知道自己在物理内存中的分配(程序员手写分配啊!!!)。如果程序小、少,还能凑合着进行管理,但是,面对实际的多程序,大体量程序,不得不将内存的管理与程序的编写进行分离,尽管这样做“有一点1”降低效率。
Using Physical Address:
Using Virtual Address:
一、地址虚拟化与进程私有地址空间
进程的地址空间是私有的,名曰:私有地址空间。何为私有,这就与虚拟化有关了。操作系统 将内存地址的概念(虚拟地址)从内存地址的实现(物理地址)中抽象出来了,使得编程工作者可以不用考虑自己编写的代码、变量等数据在终端运行的时候具体位于哪块物理内存地址中。在编写程序时,我们无需关心对物理内存的分配,也不用担心不同的进程对”相同地址值2“操作时是否产生冲突,带来数据的不一致性。
? 图一:页表与地址空间转换
? 图二:不同进程的地址空间映射
1.1 虚拟内存
1.1.1 Why
How Does Everything Fit?
Memory Management:
how to protect:
how to share:
1.1.2 solution
1.1.3 Benefit
simplifying linking and loading:
memory protection:
. Extend PTEs with permission bits
. Page fault handler checks these before remapping
? if violated, send process SIGSEGV(segnmentation fault)
二、Unix操作进程的系统调用
2.1 获取进程ID
getpid函数和getppid函数:getpid函数返回调用这个函数的进程的PID3,getppid函数返回它的父进程的PID(创建调用这个函数的进程的进程)。
#include<sys/types.h>
#include<unistd.h>
pid_t getpid(void);
pid_t getppid(void);
pid_t定义在types.h中,类型为int.
2.2 创建和终止进程
1)终止。进程会因为三种原因终止:
- 收到一个信号,该信号的默认行为是终止进程。
- 从主程序返回。
- 调用exit函数。
#include<stdlib.h>
void exit(int status);
exit函数以status退出状态来终止进程。
2)fork函数。父进程通过调用系统调用fork函数创建一个新的运行子进程。
#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);
新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份拷贝4,包括文本、数据和bss 段、堆以及用户栈。子进程还获得与父进程任何打开文件描述符相同的拷贝,这就意味着当父进程调用fork 时,子进程可以读写父进程中打开的任何文件。父进程和新创建的子进程之间最大的区别在于它们有不同的PID。
如果能够在fork 函数在父进程和子进程中返回后立即暂停这两个进程,我们会看到每个进程的地址空间都是相同的。每个进程有相同的用户栈、相同的本地变量值、相同的堆、相同的全局变量值,以及相同的代码。
fork函数被父进程调用一次,返回两次:一次返回到父进程中,返回值是子进程的PID,一次返回到新创建的子进程中,返回值是0。返回值提供一个明确的方法来分辨程序是在父进程还是在子进程中执行。父进程和子进程在返回之后并发运行。
2.3 加载并运行程序
execve函数在当前进程的上下文中加载并运行一个新程序。
#include<unistd.h>
int execve(const char *filename, const char *argv[], const char *envp[]);
//如果成功,则不返回,如果错误,则返回-1。
参数列表
环境列表
execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量列表envp。作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
与一般情况不同,函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样,颇有些神似”三十六计”中的”金蝉脱壳”。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行5。
在execve 加载了filename 之后,它调用启动代码。启动代码设置栈,并将控制传递给新程序的主函数,该主函数有如下形式的原型
int main(int argc , char **argv, char **envp)
或者等价地,
int main(int argc , char *argv[] , char *envp[])j
当main 开始在一个32 位Linux 进程中执行时,用户栈有如下图所示的组织结构。
让我们从栈底(高地址)往栈顶(低地址)依次看一看。首先是参数和环境字符串,它们都是连续地存放在栈中的,一个接一个,没有分隔。栈往上紧随其后的是以null 结尾的指针数组,其中每个指针都指向栈中的一个环境变量串。全局变量environ 指向这些指针中的第一个envp [0] 。紧随环境变量数组之后的是以null 结尾的argv[] 数组,其中每个元素都指向栈中一个参数串。
在栈的顶部是main 函数的3 个参数:
1) envp ,它指向envp[] 数组
2) argv ,它指向argv[]数组
3) argc ,它给出argv[]中非空指针的数量在Linux中专门有库函数6对系统调用execve函数进行包装。
2.4 其他函数
参考【深入理解计算机系统】第8章\第8.4节 进程控制
标签:
原文地址:http://blog.csdn.net/unclerunning/article/details/51176715