进程地址空间由进程可寻址的虚拟内存组成,linux采取的虚拟内存技术使得所有进程以虚拟方式共享内存。对于某个进程,它好像可以访问所以物理内存,而且它的地址空间可以远远大于物理内存。进程的虚拟内存区域可以包括各种内存对象:
文本段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存中的镜像,包含一些字符串、常量和只读数据。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。
数据段:数据段用来存放可执行文件中已初始化全局变量,换句话说就是存放程序静态分配的变量和全局变量。
BSS段:BSS段包含了程序中未初始化的全局变量,在内存中 bss段全部置零。实际上为减少二进制程序的大小,linux只是将该段映射到零页上
堆(heap):堆是用于存放进程运行中被动态分配的内存区域,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
栈:栈是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
除了上述内存对象,还有共享库的映射、内存映射文件、共享内存等对象。当可执行文件被装入时,进程并不为所有对象立即分配实际的物理内存,而是尽量的推迟分配。注意:int *p = malloc(4); printf("%d\n",*p); 你会发现结果为0,linux会对没有关联到物理页的虚拟内存的读操作直接返回零页。
内核使用内存描述符struct mm_struct(进程描述符struct task_struct的mm域)表示进程的地址空间。fork()创建进程的时候,子进程复制父进程的地址空间。线程组中的线程共享同一地址空间。内核线程没有地址空间,也即没有相关的内存描述符,因此内核线程在用户空间没有相关的映射。一般内核线程也不会访问用户空间,通常只在系统调用内核代表用户进程执行,内核才访问用户空间。内核线程访问内核内存地址时,也是需要页表的,通常直接使用上一个进程的页表(内存描述符的pgd字段指向页全局目录),这样可以省事不少。内核态的进程修改了内核空间的页表项时,理应更新系统所有进程的相应页表项,但是操作会耗费不少时间 ,linux采用了一种延迟方式,上一节的非连续内存分配有所阐述。
特别说明一下:虚拟页有两种状态,valid和invalid,有效页关联一个实际的数据页,它可能位于RAM,也可能位于硬盘上的交换分区和文件(进程访问时会产生page fault),一个无效页表示没有被分配和使用。
vm_area_struct结构描述了指定地址空间内连续区间上的一个独立内存范围。内核将每个内存区域作为一个单独的内存对象管理,每个内存区域具有一致的属性,相应的操作也一致。/proc/<pid>/maps或者pmap pid 的输出会显示pid对应进程的虚拟内存区域。
上面给出了一个进程的地址空间,每一行都是用start-end perm offset major:minor inode image形式表示的,包含了很多的映射文件。经过验证,程序执行时产生了5次page faults. 内核总是尽量推迟给用户态进程分配动态内存,所以堆栈是不会在开始实际分配物理内存的,当我们使用malloc()是也是如此。
内核提供了各种函数对虚拟内存区域的操作,如合并、插入、查找、创建和删除。为了优化查找,内核维护了VMA的链表和红黑树结构。
1.创建VMA
do_map为当前进程创建并初始化一个新的VMA,当分配之后,可以将该VMA与相邻的具有相同的访问权限的VMA进行合并:
unsigned long do_mmap(struct file *file, unsigned long addr, unsigned long len, unsigned long prot,unsigned long flag, unsigned long offset)
该函数映射由file指定的文件,具体映射的是从文件中偏移offset处开始,长度为len范围内的数据,如果file为NULL且offset为0,那么代表这次映射没有和文件相关,称为匿名映射,否则称为文件映射。用户空间通过mmap调用获取do_mmap的功能。
2.文件映射
一个新建立的VMA就是不包含任何页的线性区,当进程引用其中的一个地址时,缺页异常发生,缺处理程序检查struct vm_operations_struct 中的fault函数是否被定义,如果其为NULL,说明VMA没有映射文件,为匿名映射,内核为其映射到该地址;如果不为NULL,则进行文件映射, paging in,返回page结构的指针。
3.建立页表
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);\
该函数为设置了PG_reserved的物理页和RAM之外的物理映射地址建立页表,它能够重新映射高端PCI缓冲区。
用户空间的内存管理
1.动态内存分配
void *malloc(size_t size); 分配固定字节大小的内存
vodi *calloc(size_t nr, size_t size); 为nr个大小为size字节的元素分配内存,内存每一位都清0
void realloc(void *ptr, size_t size); 改变已分配内存的大小,返回一个新空间的指针
2.匿名内存映射
int brk(void *end); 把堆的末端的地址设置为end指定的值
对于较大的内存分配,glibc并不使用堆,而是创建一个匿名内存映射(已用0初始化的大的内存块),由于不基于堆,因此不会造成碎片。每个内存映射都是页大小的整数倍,大小可调整。
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
例子:
void *p;
p = mmap(NULL, 512 * 1024, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1 ,0);
运维网声明
1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网 享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com