在学完了「计算机组成原理」之后,开始学习操作系统,这一篇博文主要讲讲简单的进程原理理解。
当我们把可执行文件执行的时候,就是创建了一个「进程」。其中背后的原理十分繁琐复杂,来吧让我们开始。
什么是进程
首先得搞清楚程序和进程的关系
程序就是静态的代码和数据
而进程就是程序的一次运行过程
进程有自己的生命周期,随着任务的启动而创建,随着任务的完成(或终止)而消亡,它所占用的资源也随着进程的终止而释放
一个可执行目标文件(即程序)可被加载执行多次,也就是说,一个程序可能对应多个不同的进程
进程在 Linux 中的表示
Linux 中:
所有的进程用一个双向循环列表(task list)描述,列表中的每一个元素就是一个进程
单个进程用进程描述符(process descriptor)的结构来描述,其结构类型定义为 task_struct,包含了一个进程的所有信息
现在具体说明 task_struct 是如何指向进程的虚拟内存的
在 task list 中有有很多个 task_struct,每个 task_struct 是一个进程描述符。
每一个进程描述符里面有一个 mm(memory map 内存映射) 指向 mm_struct (内存描述符 memory descriptor)
内存描述符存储了入上图所示中程序的每个段的开始(stary)与结束(end),权限(prot),flags(对象的类型)
进程的加载
现在我们已经简单的理解了,进程在 Linux 操作系统中是如何表现的了。现在我们来继续学习简单的进程加载:
假设我们有一个 hello world!的可执行文件
在 shell 中输入 ./hello [回车]
在 shell 命令行解释器里面构造 argv 和 envp
调用 fork() 函数,创建一个子进程,与父进程 shell 完全相同(只读/共享)包括,数据段,代码段,可读写段,堆及用户栈
调用 execve 函数。在当前进程中(新创建的子进程)的上下文中,加载运行 hello 程序,将 hello 程序中的 .text 节、.data 节、.bss 节等内容加载到当前进程的虚拟地址空间(仅仅修改当前进程上下文中关于存储器映射的数据结构,不从磁盘拷贝程序)
调用 hello 程序的 main 函数,hello 在一个进程的上下文运行
(整体过程如下)
上面的过程比较复杂,以我现在的水平还不能完全说清,只能对一些疑惑进行解答
argv 和 envp
假设我们有这样的代码:
#include
#include
int main(int argc, char *argv[], char *envp[])
{
char *home, *host;
home = getenv("HOME");
host = getenv("HOSTNAME");
printf("the args count %d \n", argc);
for (int i = 0; i < argc; i++)
{
printf("the args we have: %s \n", argv[i]);
}
printf("Your home directory is %s on %s.\n", home, host);
return 0;
}
生成可执行文件并运行:
g++ main.c -o main && ./main 1 2 3 4
main 函数的原型就是
int main(int argc, char *argv[], char *envp[]) {
}
那么在 shell 解释器中会构造这三个变量argc argv envp,存在主存中。
(像这样)
存储器映射(memory map)
之前我们说加载 hello 程序的时候,并不是真正把硬盘里面的内容加载到主存中,仅仅修改当前进程上下文中关于存储器映射的数据结构,不从磁盘拷贝程序
那么什么是存储器映射呢?
存储器映射就是:将虚拟地址空间的一个区域与硬盘上的一个对象建立关联(生成页表项),并初始化一个 vm_area_struct 结构信息
如何生成页表项的内容,暂且不说。。。(其实我也不会),先来说说如何生成 vm_area_struct
可通过 mmap 函数实现 vm_area_struct,下面说说 mmap 的功能以及参数的含义
功能
将指定文件 fd 中偏移量 offset 开始的长度为 length 的一块信息映射到虚拟空间中起始地址为 start,结尾地址为 end 的区域。并初始化相应页表项,建立文件地址和区域之间的映射关系。
port 参数
指定该段的访问权限,对应 vm_area_struct 中的 vm_prot,一共有这么几个值
flags 参数
指所映射对象的类型,对应 vm_area_struct 中的 vm_flags,一共有这么几个值
在 Linux 中:
一旦虚拟页被装入内存中,以后都是在主存页框和硬盘中的交换文件(swap file)之间调进调出。交换文件被内核维护,被称作交换分区(swap area)或者交换空间(swap space)
虚拟空间划分
根据上面的知识,我们可以把虚拟空间划分为:
同一个可执行文件被执行多次时
同一个可执行文件被执行多次时,也就是一个程序产生了多个进程。由于同一个可执行文件对应不同进程时
,只读代码区一样,可读可写数据区开始也一样,但属于私有对象为节省主存,多采用写时拷贝技术
假设进程 1 和进程 2 是同一个可执行文件:
进程 1 在运行过程中,内核为其分配若干个页框,并标记为只读
进程 2 在运行过程中,内核只需把给进程 1 的页框号填入进程 2 的页表项中,也标记为只读
如果进程 2 只是只读或者执行,那么就公用一块内存
如果进程 2 发生了写操作,就会发生因访问违例(写只读区),这时,内核就会重新分配页框(并拷贝),再将新内容写入。并修改进程 2 的页表项。