当前位置: 首页 > 科技观察

说说操作系统的内存管理

时间:2023-03-23 11:57:56 科技观察

内存管理是操作系统的主要功能。从操作系统启动到进程0(空闲进程)的创建,大部分运行的代码都与内存有关。操作系统的内存管理大致分为几个层次:1.物理内存管理物理内存是计算机上真正的内存大小,可以通过BIOS获取这个数据。分页后,物理内存的管理结构是一个数组,每一项代表1个物理内存页,每页4096字节。如下图所示:物理内存的管理结构在一个简单的内核demo中,物理内存页的管理结构只能有一项:atomic_trefs;即物理内存页的引用计数:计数为0表示空闲,>0表示正在使用,具体数字表示共享该页的进程数。简单的内核demo一般不支持SMP架构,所以自旋锁(spinlock)也省了。在对称多处理器(SMP)CPU上,需要自旋锁,因为全局数据结构将被多个CPU并发访问。那么,物理内存页的管理结构至少有两项:atomic_tspinlock;atomic_t参考;自旋锁的作用类似于应用程序中的锁(互斥量),只是在获取失败后会继续再次获取,直到获取成功。voidspin_lock(atomic_t*lock){while(spin_trylock(lock)==0);}这个是加锁自旋锁的函数,while循环直到成功,如果失败就在那里自旋,一直往回转,所以称为自锁。扭锁。它用于(对称多处理器)SMP环境以保护共享数据结构:当一个CPU持有自旋锁时,另一个CPU无法访问共享数据。如果是单CPU环境,不需要使用自旋锁,直接关闭中断即可。在单CPU的情况下,关闭中断可以防止内核并发,共享数据不会被践踏。但是多个CPU必须使用自旋锁,因为关闭中断只能关闭当前CPU,不能关闭其他CPU:这时候就需要自旋锁来保护共享数据。物理内存的管理数组是最重要的全局共享数据。当某个进程需要申请内存时,哪些内存页是空闲的,哪些已经被使用了,要看这个数组。在加自旋锁的时候,一定要先关闭中断,因为如果在加锁之后和关闭中断之前刚好有一个中断,在中断处理函数中再次请求同一个锁,就会递归死锁向上。Linux内核关闭和锁定的函数调用:spin_lock_irqsave()。Linux内核分配物理内存页的函数调用:get_free_pages(),可以分配1页或连续的多页内存。如果分配了多页内存,则起始地址必须按页数对齐。2.虚拟内存管理虚拟内存是通过进程的页表来管理的。为了节省物理内存,新创建的进程与父进程共享同一组物理内存页。只有当一个新进程要写一个内存页时,它才会复制一个新的物理内存页给它,然后取消与父进程共享该页,这就是copy-on-write。Thecopy-on-writeprocess写时复制过程:1)申请新的内存页,2)将旧内存页的内容复制到新内存页,3)填充新内存页的地址到子进程的页表中,4)将旧内存页的引用计数减1。因此,当一个新进程刚被创建时,它的用户空间并没有自己的物理内存页,只有当它是操作需要,通过copy-on-write逐位添加,使物理内存最大程度的空闲。另一种尽可能保持物理内存空闲的机制是按需加载:1)mmaping文件时,操作系统不会直接为文件分配内存并将其内容加载到内存中,2)但是当进程真正读取某个部分时文件的一部分,它申请一个物理内存页,并将这部分内容从磁盘读取到内存中。写入时复制,读取时加载。当大火迫在眉睫时,Linux系统不会把物理内存给进程。3.用户态的内存功能以上机制都在OS内核中,应用程序的代码不用管它。应用程序分配内存的最低级别函数是brk()系统调用。brk()函数brk()是一个系统调用,其作用是修改应用程序数据段的末尾,从而分配或回收应用程序的堆空间。brk系统调用的功能在C库中被封装成sbrk()和brk()两个函数,更符合人们的习惯:sbrk()用于申请内存:void*sbrk(intincrement);brk()用于回收内存:intbrk(void*addr);事实上,Linux系统只有一个brk()系统调用,它不仅设置了进程数据段的结束,还将这个值返回给应用程序。Linux内核头文件的sys_brk()函数在Linux内核的头文件中,brk()系统调用的处理函数sys_brk()是这样定义的,如上图所示。如果想直接使用系统调用,可以使用linux的syscall()函数,依次传入调用号和参数列表,就可以看到哪些是真正的系统调用,哪些是包C库。syscall()函数的声明是:longsyscall(longnumber,...);它的参数是可变的,系统调用的参数最多只有6个,因为寄存器的个数是有限的。在sbrk()和brk()的基础上重新包装就是人们经常使用的malloc()和free()。malloc()申请的内存是一块一块的,可以乱序释放,不影响使用。brk()和sbrk()申请的内存必须按顺序释放,因为它会修改进程的数据段末尾:如果使用了数据段末尾(brk)以外的堆空间,则为分段错误。所以linuxman手册上说应用程序不应该使用sbrk()和brk()来申请和释放内存。brk()的作用只是通知Linux内核堆内存的哪个范围是可用的。真正的物理内存页是在进程真正读写内存的时候申请的,是由内核根据copy-on-write/demandloading自动完成的,应用程序并不知道这一点。Linux还将不经常使用的物理内存页交换到磁盘(即交换分区)以释放更多内存。因此,当内存不足时,磁盘的读写频率也会增加。