• 对地址空间的简单理解
  • 地址空间存在的原因
  • “挂起”进程状态
  • fork()函数创建子进程与进程地址空间的关系
1.地址空间的理解

语言层面理解内存,常把内存分为堆区、栈区、数据段、代码段的区域,实际上,这里所指的内存,并不是真正的物理内存,而是虚拟内存,称之为“地址空间”。

程序加载到内存中被执行,由程序变为进程,从进程的角度来看,每一个进程都是独占整个内存的,但实质上,操作系统为每一个进程创建一个地址空间,通过页表映射到实际的物理内存空间,每个进程访问物理内存的一部分。

在Linux操作系统中,操作系统管理进程时,是先描述进程特征再组织管理,即PCB的机制,查看Linux源代码,在Linux中使用struct task_struct的结构体来描述进程的属性,并以此结构体来管理进程,在task_struct结构体中,存在指针指向mm_struct,表示进程对应的地址空间,可见操作系统为每一个进程都创建了一个进程空间,并用mm_struct结构体的数据结构来进行管理。

Linux操作系统-进程地址空间_fork

2.为什么会有地址空间?

为什么会存在地址空间,可以从三个方面分析。

首先,如果进程的代码直接在物理内存上执行,代码中常常涉及对特定地址的写入,如果我们所写的代码越界访问(这是常见的情况),直接修改了其他进程的数据,可能会导致其他进程的崩溃和不安全,所以为了使进程之间具有独立性和安全性,采用地址空间加页表映射对物理内存进行读写,如果进程代码中出现非法访问,可以直接在页表处对非法访问进行拦截,保护进程的独立性和安全。

其次,进程地址空间通过页表映射访问物理内存,理论上来说,只要可以建立映射关系,我们是可以访问合法的物理内存的任意位置的,即针对进程来说,它并不关心到底访问物理内存的哪一块区域,只要通过页表映射可以访问即可,所以使用进程地址空间,本质上也使得操作系统对进程地址空间的管理和对内存的管理得以分离,即实现了进程管理和内存管理的低耦合,只要可以通过页表映射即可。

最后,通过延迟分配物理内存,可以提高整机的效率,在进程中,可能会出现这样一种情况,申请一块空间,例如:申请一个变量,但并不对这块空间进行任何的操作,如果进程直接访问物理内存,在这种情况下就会造成内存的浪费,但是采用进程地址空间机制,可以避免这种情况,申请一块空间,操作系统只是在地址空间中分配给进程一块地址,但在页表中,甚至可以先不分配物理内存并建立映射关系,只有等进程真正对这块空间进行操作的时候,才给其分配物理内存,这段时间内,这块内存可以先被别的进程使用,即延迟分配的策略,大大提高了运行效率。

3.进程状态-挂起

在OS中,一个进程存在很多种状态,例如就绪、运行、僵尸状态、挂起等等。

前面提到,我们为保证效率,对进程的分配物理空间可以采用延迟分配的策略,同样的,我们创建运行一个进程时,尤其是该进程的规模比较大时,我们可以先不把其所有的代码和数据加载到内存,而是一部分一部分加载到内存执行,可以提高效率,把执行完后、一段时间内不会再被执行的代码换出物理内存的过程,称为进程的挂起。

4.fork()函数创建子进程

系统函数fork()可以实现在一个进程中创建一个子进程,如果创建成功,该函数向父进程返回子进程的pid,向子进程返回0,创建失败则返回负数。

在操作系统中,进程实质上不仅仅包括其代码和数据,还包括描述和用于管理其的数据结构-PCB,在父进程中,创建一个子进程,意味着要给自己进程也创建一个对应的PCB,用于对子进程的管理。

子进程PCB在创建的时候,大多信息均是直接拷贝父进程的信息,包括页表,地址空间等。只有当子进程/父进程对数据进行写入时,操作系统才会将子进程和父进程要写入的数据做分离,在此之前两者是共享进程代码和数据的。