一.进程地址空间:
1.进程
操作系统内核为每个被创建的进程都建立一个PCB(进程控制块或进程描述符)来保存与其相关的信息,PCB存在于进程的高 1 G空间,也就是内核空间中。在具体linux内核实现中,使用一个名为
task_struct的结构体来描述的,在内核/include/linux/sched.h头文件中有具体定义。
操作系统组织PCB的方式有索引式(数组)、链式(链表)。Linux系统是采用链式方式来组织PCB的,对于不同的状态建立起一个进程队列。在系统中可能有很多个进程处于相同的状态,这些进程构成一个进程队列。
2. Linux虚拟存储组织:
通过下面的图可以一看一下Linux内核数据结构组织一个进程虚拟地址空间的方式。内核为系统中每个进程维护了一个单独的任务结构
task_struct, 结构中包含了内核运行该进程的所有信息。
3.
程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。
初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据。
未初始化过的数据(BSS):在程序运行初未对变量进行初始化的数据。
栈 (Stack):存储局部、临时变量,函数调用时,存储函数的返回指针,用于控制函数的调用和返回。在程序块开始时自动分配内存,结束时自动释放内存,其操作方式类似于数据结构中的栈。
堆 (Heap):存储动态内存分配,需要程序员手工分配,手工释放.注意它与数据结构中的堆是两回事,分配方式类似于链表。
每个进程都有自己的地址空间。对32位进程来说,由于32位指针可以表示从0x00000000到0xFFFFFFFF之间的任一值,地址空间的大小为4GB。对64位进程来说,由于64位指针可以表示从0x00000000'00000000到0xFFFFFFFF'FFFFFFFF之间的任一值, 地址空间大小为16GB。其实这个地址空间是不存在的,也就是我们所说的进程虚拟内存空间。
linux操作系统每个进程的地址空间都是独立的,其实这里的独立说得是物理空间上得独立。那相同的虚拟地址,不同的物理地址,他们之间是怎样联系起来的呢?当一个程序被执行时,该程序的内容必须被放到进程的虚拟地址空间,对于可执行程序的共享库也是如此。可执行程序并非真正读到物理内存中,而只是链接到进程的虚拟内存中。
在进程创建的过程中,程序内容被映射到进程的虚拟内存空间,为了让一个很大的程序在有限的物理内存空间运行,我们可以把这个程序的开始部分先加载到物理内存空间运行,因为操作系统处理的是进程的虚拟地址,如果在进行虚拟到物理地址的转换工程中,发现物理地址不存在时,这个时候就会发生缺页异常(nopage),接着操作系统就会把磁盘上还没有加载到内存中的数据加载到物理内存中,对应的进程页表进行更新。也许你会问,如果此时物理内存满了,操作系统将如何处理?
当物理内存溢出时,linux使用"最近最少使用(LeastRecently Used ,LRU)"(很多缓存技术都是这个原则)页面调度技巧来公平地选择哪个页可以从系统中删除。
4.ptmalloc
在没有用malloc 分配内存之前,堆区没有内存。
Linux 进程的默认地址空间, 对 heap 的操作, 操作系统提供了brk()系统调用,设置了Heap的上边界, 对 mmap 映射区域的操作,操作系 统 供了 mmap()和 munmap()函数。
因为系统调用的代价很高,不可能每次申请内存都从内核分配空间,尤其是对于小内存分配。 而且因为mmap的区域容易被munmap释放,所以一般大内存采用mmap(),小内存使用brk()。
在开辟<=128K时,brk指针会向后走,第一次申请的字节若不超过128k,系统会为其分配128k+4K的空间,如果多次malloc申请空间,每申请一次就需要修改一次mm_struct中brk的位置,就要执行一次系统调用,这样系统消耗会特别大,而多出来的4K的空间存放了分配信息,当free()时会用到。开辟的内存在用户空间的库上,当free时,这块空间归还给库,当进程结束时,归还系统。
在开辟〉128K时,通过mmap映射在堆和栈的中间映射一段内存,free时直接归还给系统。
不管内存是在哪里被分配的,用什么方法分配,用户请求分配的空间在ptmalloc中都使用一个chunk来表示。 用户调用free()函数释放掉的内存也并不是立即就归还给操作系统,相反,它们也会被表示为- -个chunk, ptmalloc使用特定的数据结构来管理这些空闲的chunk。
5.chunk