内存分配原理:
当一个进程发生缺页中断的时候,进程会陷入内核态,执行以下操作:
1、检查要访问的虚拟地址是否合法
2、查找/分配一个物理页
3、填充物理页内容(读取磁盘,或者直接置0,或者啥也不干)
4、建立映射关系(虚拟地址到物理地址)
重新执行发生缺页中断的那条指令
如果第3步,需要读取磁盘,那么这次缺页中断就是majflt,否则就是minflt。
如何查看进程发生缺页中断的次数:
ps -o majflt,minflt -C program
majflt代表major fault,中文名叫大错误,minflt代表minor fault,中文名叫小错误。
这两个数值表示一个进程自启动以来所发生的缺页中断的次数。
malloc的工作原理
malloc函数分配内存主要是使用brk和mmap系统调用
brk(): 小于128k
将数据段(.data)的最高地址指针_edata往高地址推;
mmap(): 大于128k
是在堆和栈之间(文件映射区域)找分配一块空闲的虚拟内存,
都是虚拟内存,没有分配物理内存。
在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,
然后建立虚拟内存和物理内存之间的映射关系。
内核为用户进程分配内存的特点
1)内核中只要请求内存得以满足,都会返回页描述符的地址或线性地址
2)当用户态进程申请动态内存时,并没有获取请求的页框,而是获得一个线性区,(malloc())
当第一次访问分配的线性区的时候,(因为线性区没有那么大)发生缺页中断,
为线性区分配物理内存,最后建立虚拟地址和物理地址的映射
原因:进程对动态内存的请求认为是不紧迫的,内核总是推迟给用户态进程分配动态内存。
由于用户态进程是不可信任的,内核必须随时准能被捕获用户态进程引起的寻址错误
free的工作原理
对内存块进行了 free 调用之后,这块内存标记为未被使用,但是堆的内存还是没有释放
举例,假射系统调用brk先分配了内存A,然后在分配内存B,系统调用mmap分配了内存C
如果free由mmap分配的内存C,直接将C中的虚拟内存和物理内存释放回给操作系统
如果free有brk分配的A,A的虚拟内存和物理内存都没有释放,但是A的内存可以重用 //但内存没有被释放
如果再释放brk分配的B,如果A和B的内存和大于128k,
则将A和B的虚拟内存和物理内存都释放回给操作系统,否则也没有释放。 //连续的未使用的内存大于128k时一起释放
为什么等到第一次访问虚拟内存的时候才分配物理内存呢
因为申请的内存不一定马上使用,推迟分配可以系统拥有更多的空闲物理内存去出来其他事,从而提高系统的吞吐量。
为什么达到128k才释放呢: //堆的内存分配算法多种多样
因为小于128k的内存并不够mmap分配,不释放,下次申请小内存时,直接重用该内存即可。
大于128k,可以释放该内存,以便mmap函数可以分配该内存。
程序执行时的内存分配(虚拟内存)
白色区域:防止攻击者猜测到段的起始地址,发起远程攻击。(没有映射到物理内存上,实际不占内存空间)
由上到下:
kernel space:内核预留的的一段虚拟内存空间,
(内核的空间的页表有特殊flag,内核空间在所有程序中指向的物理地址是一样的,
内核代码总是可寻址的.随时接受中断或系统调用. 与此相反用户的地址空间则会随着进程的切换不断变化)
栈:
调用一个新的函数,会在栈上创建一个新的栈帧,每当函数返回值这个栈帧会被自动销毁.
严格遵循LIFO的顺序,不需要复杂的数据结构来跟踪栈地址,只需要一个栈顶指针可以搞定.
栈空间一直重复使用(push\pop)有利于栈内存活跃在cpu cache中加快访问速度.
栈空间用尽后继续push数据会触发栈空间的扩展. 这会触发一个 page fault 然后在内核中调用expand_stack()函数.
该函数调用acct_stack_growth()来判断是否可以增长占空间.
如果当前栈空间的大小小于RLIMIT_STACK(8M),可以继续增长栈空间. 该过程由内核完成进程不会感知到.
当用户的占空间已经达到允许的最大值时,内核会给进程发送一个Segmentation Fault信号终止该进程.
进程的栈空间只会增大不会缩小,有点像联邦运算,只增不减.
mmap区域:
在这些区域中内核将文件直接映射到地址空间中. 应用程序可以显示创建这些区域,通过调用mmap()/CreateFileMapping()/MapViewOfFile().
内存映射是一种高效和方便操作文件的一种方式, 所以内存映射通常被用来加载动态链接库.
malloc 一个非常大块的内存,标准c库会通过mmap来创建这块内存区间而不使用Heap内存, (>128k)
堆 //不同与数据结构中的堆,更类似于链表
堆提供了程序运行时的内存分配, 堆内存的生命周期在函数之外.
当前堆的内存足够程序使用,在当前堆中寻找可用内存就行,.
否则的话需要调用brk()系统调用在内核中增大堆内存.
.data 已初始化的
.bss 未初始化的
.text 常量,及汇编代码
保留区 用于捕捉异常(空指针,非法引用)。