概述
所谓程序,就是一系列计算机指令的集合。而进程,就是程序的具体实现,是活动实体。
当计算机开机的时候,内核建立一个init进程。Linux内核并不提供直接建立新进程的系统调用,故 剩下的所有进程都是init进程通过fork机制建立的。fork是一个系统调用。进程存活于内存中,每个 进程都在内存中分配有属于自己的一片虚拟地址空间。当进程fork的时候,Linux在内存中开辟出一片 新的地址空间给新的进程,并将老的进程空间中的内容复制到新的空间中,此后两个进程同时运行。
与人相似,进程也有其诞生到消亡的生命周期。程序从main()函数开始,相当于进程的出生,得到一个 大小为4G(32位机,2^32)的虚拟地址空间,进程描述符是其身份证。
进程地址空间
进程的虚拟地址空间被分成各种内存区,也叫段,大致可分为代码段、数据段、堆栈三类。
*text:代码段,存放程序的可执行命令。
*data:存放已初始化数据,包括全局变量。
*BSS:存放未初始化数据。
*堆:用于扩展进程的线性地址空间。
*栈:包含所有已分配内存的局部变量,用作函数调用。
进程描述符
内核在创建进程时,在创建task_struct的同时为进程创建相应堆栈,使得每个进程会有两个栈,一个 用户栈存在于用户空间;一个内核栈存在于内核空间。
在内核中,进程描述符是一个名为task_struct的结构体,存放在进程内核栈的尾端。该结构体定义于 /usr/src/linux/include/linux/sched.h中,用于存放进程的属性和信息,与进程相关的所有内核 信息都存储在这个结构体中。 内核采用循环双向链表task_list来存放所有进程描述符,并借助全局变量current来存放当前运行进 程的task_struct的引用。
关于进程描述符更详细的介绍,可以参考tanglinux-Linux进程管理之task_struct结构体。
进程描述符中名为mm_struct的结构体是进程内存空间的描述信息(mm指向进程所拥有的内存描述符, 而active_mm指向进程运行时所使用的内存描述符。对于普通进程而言,这两个指针变量的值相同。)
struct mm_struct *mm, *active_mm;
内存描述符是一个链表与红黑树结合的结构。
内存使用
代码示例:
int main(int argc, char * argv[])
{
char * pBuffer = NULL;
pBuffer = new unsigned char [64];
*pBuffer = '0';
delete []pBuffer;
return 0;
}
代码申请一段空间(如malloc、new),并没有直接申请到物理内存,而是在进程描述符(内存描述符)
的红黑树上新增一个节点,得到一个虚拟的逻辑地址。当这段内存的变量初始化或第一次使用时(*pBuffer = '0'
),CPU才
完成其从逻辑地址到线性地址再到物理地址的转换(该转换过程在其第一次使用时完成,再次使用时当然
无须如此繁琐了)。当这段内存空间被释放(free、delete)时,在进程描述符中删去该段地址,在红
黑树中删除该结点。
理论上,申请和释放内存都有失败的可能。前者要求我们申请之后须作判断,后者发生的概率太小,与 中国足球夺得世界杯冠军相当,可以不做检查。
进程与线程
进程是资源分配的基本单位,也是调度运行的基本单位;线程是调度的基本单位。
Windows至今也没有真正的多进程概念,它的调度实体就是线程,进程只是一堆数据结构。而Linux将 进程和线程作了同等对待,进程和线程在内核一级没有差别,只是通过特殊的内存映射方法使它们从用 户的角度看来有了进程和线程的区别。
每个进程都有自己独立的地址空间,同一程序的不同进程虽然代码段相同,地址空间实际是不同的;同 一进程的多个线程共享一个进程的代码段,数据段,文件描述符,共享库,信号,堆等资源,只是有各 自独立的栈。这些线程实际上就是地址空间相同的特殊的进程。
内核态和用户态
进程在生命周期内,或者执行自己的代码,或者执行内核代码。内核代码是在系统调用被执行时、异常 发生时或者中断到来时(中断执行程序中)执行的代码。
若进程执行的不是内核代码,则进程正运行在用户态;如果进程执行内核代码,则进程处于内核态。
处理器通常使用某个控制器中的一个模式位来提供这种功能,该寄存器描述了当前进程享有的特权。当 设置了模式位时,进程就运行在内核模式中。内核态的进程可以执行指令集中的任何指令,并且访问系 统中任何存储器的位置;用户态的进程不允许执行特权指令,比如:停止处理器、改变模式位、或者发 起一个IO操作,也不允许用户态进程直接引用地址空间中内核区的代码和数据,用户程序必须通过系统 调用接口间接的访问内核代码和数据。