|
前言:
在冯诺依曼的体系结构中,一个进程必须有:代码段,堆栈段,数据段。 内存管理是操作系统的核心功能之一,这对于编程以及系统管理都至关重要。在接下来的叙述中我将着眼于实用方面但兼顾内部原理。这些概念都是通用的,例子大都来源于Linux以及Windows操作系统。首先来描述一下内存中进程的分布。
多任务操作系统中进程都运行在各自的地址空间中。在32位系统中进程的地址空间范围是0~2^32 (也即0---4G)。虚拟地址空间通过页表映射到物理内存,页表由操作系统内核维护,由处理器来对其发出请求。每一个进程都有自己的一套页表。这时问题就出现了,一旦虚拟地址投入使用,它就用于此计算机中所有运行的软件,也包括内核本身。所以,虚拟地址空间的一部分必须保留给内核:

这并不意味内核需要使用如此多的物理内存,而是因为内核可以使用这部分地址空间去映射到任何他想要的物理内存。内核空间在页表中被标记为只有特权代码(ring2或者更低。在此说明一下特权等级的概念:Intel x86架构CPU共有四个特权等级,0~3。0级最高,3级最低。硬件在执行每条指令时会检查其特权等级。对于Linux/Unix,只是使用了0级和3级,即Ring0 和 Ring3。在Linux中来看,0级也就表示内核态,3表示用户态)。当用户态程序想染指内核空间时会引发页面异常。在Linux系统中,内核空间总是映射到相同的物理地址空间。
【文章福利】小编推荐自己的Linux内核源码交流群:【869634926】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前50名可进群领取,并额外赠送一份价值600的内核资料包(含视频教程、电子书、实战项目及代码)!

点击下方链接即可免费领取内核相关学习资料哦
学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
内核代码和数据总是可寻址的,为的是随时准备响应中断或者系统调用。相比之下,进程地址空间的用户态部分随着进程切换而变化。

蓝色的部分代表映射到物理内存的虚拟地址空间。而白色的部分表示未映射的地址空间。进程地址空间中不同的段对应内存段(segment)中不同段,比如堆、栈等等。要注意这些所谓的分段只存在于内存地址空间中,他们表示不同的内存地址范围而已,它和Intel的段表机制没有任何关系。 下面是一个标准的Linux进程中各部分在地址空间中的分布:

linux中程序结构和进程结构 file 可执行文件如下:
[root@centos1 c]# file getopt_long
getopt_long: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
size 可执行程序
查看程序组成部分
[root@centos1 c]# size getopt_long
text data bss dec hex filename
2037 508 56 2601 a29 getopt_long
代码段:
text 主要存放 指令,操作以及只读的(常量)数据(比如字符串常量)
通常是指用来存放程序执行代码的一块内存区域。
这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。
在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等
数据段:
data 全局或者静态的已经初始化的变量 数据段属于静态内存分配
bss段:(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。
BSS 是英文Block Started by Symbol 的简称。BSS 段属于静态内存分配。
全局或者静态的未初始化的变量
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[]){
printf(&#34;hello c\n&#34;);
return 0;
}
[root@centos1 c]# size mem
text data bss dec hex filename
1151 492 16 1659 67b mem
int i=10;
int main(int argc,char *argv[]){
printf(&#34;hello c\n&#34;);
return 0;
}
[root@centos1 c]# size mem
text data bss dec hex filename
1151 496 16 1663 67f mem
data端增加了4
#include <stdio.h>
#include <stdlib.h>
int i=10;
static int j;
int main(int argc,char *argv[]){
printf(&#34;hello c\n&#34;);
return 0;
}
[root@centos1 c]# size mem
text data bss dec hex filename
1151 496 24 1671 687 mem
bss段增加到24
进程:linux操作系统最小的资源管理单元
一个进程时执行的程序段
程序在执行时,会动态的申请空间,执行子函数
Linux对一个进程管理采用以下方式
内核空间:
PCB(进程控制块) 结构体 taskstruct,负责管理进程的所有资源
成员 mm_struct 指向这个进程相关的内存资源
mm_struct指向一个结构体
栈
堆
BSS段
数据段
代码段
执行程序时,系统首先在内核空间中创建一个进程,为这个进程申请PCB(进程控制块 task_struct)
用于管理整个进程的所有资源,其中mm_struct成员用来管理与当先进程相关的所有内存资源
1.代码段,数据段,bss段,直接从磁盘拷贝到当前的内存空间,大小相等
2.动态空间
堆,栈空间,mmap段(映射其它库相关的信息)
#include <stdio.h>
#include <stdlib.h>
int i=10;
static int j;
int main(int argc,char *argv[]){
printf(&#34;hello c\n&#34;);
while(1);
return 0;
}
[root@centos1 ~]# ps -ef|grep mem
root 902 2 0 May20 ? 00:00:02 [vmmemctl]
root 28246 27937 98 22:20 pts/0 00:00:29 ./mem
root 28271 28249 0 22:21 pts/1 00:00:00 grep mem
一个进程的内存虚拟地址信息列表
[root@centos1 ~]# cat /proc/28246/maps
00400000-00401000 r-xp 00000000 00:12 33 /mnt/hgfs/www/web/thread/process_thread_study/c/mem
00600000-00601000 rw-p 00000000 00:12 33 /mnt/hgfs/www/web/thread/process_thread_study/c/mem
3339e00000-3339e20000 r-xp 00000000 fd:00 2490830 /lib64/ld-2.12.so
333a020000-333a021000 r--p 00020000 fd:00 2490830 /lib64/ld-2.12.so
333a021000-333a022000 rw-p 00021000 fd:00 2490830 /lib64/ld-2.12.so
333a022000-333a023000 rw-p 00000000 00:00 0
333a200000-333a38a000 r-xp 00000000 fd:00 2490831 /lib64/libc-2.12.so
333a38a000-333a58a000 ---p 0018a000 fd:00 2490831 /lib64/libc-2.12.so
333a58a000-333a58e000 r--p 0018a000 fd:00 2490831 /lib64/libc-2.12.so
333a58e000-333a590000 rw-p 0018e000 fd:00 2490831 /lib64/libc-2.12.so
333a590000-333a594000 rw-p 00000000 00:00 0
7f605b8ff000-7f605b902000 rw-p 00000000 00:00 0
7f605b910000-7f605b912000 rw-p 00000000 00:00 0
7ffce591e000-7ffce5933000 rw-p 00000000 00:00 0 [stack]
7ffce59ac000-7ffce59ad000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
[root@centos1 ~]# pmap 28246
28246: ./mem
0000000000400000 4K r-x-- /mnt/hgfs/www/web/thread/process_thread_study/c/mem
0000000000600000 4K rw--- /mnt/hgfs/www/web/thread/process_thread_study/c/mem
0000003339e00000 128K r-x-- /lib64/ld-2.12.so
000000333a020000 4K r---- /lib64/ld-2.12.so
000000333a021000 4K rw--- /lib64/ld-2.12.so
000000333a022000 4K rw--- [ anon ]
000000333a200000 1576K r-x-- /lib64/libc-2.12.so
000000333a38a000 2048K ----- /lib64/libc-2.12.so
000000333a58a000 16K r---- /lib64/libc-2.12.so
000000333a58e000 8K rw--- /lib64/libc-2.12.so
000000333a590000 16K rw--- [ anon ]
00007f605b8ff000 12K rw--- [ anon ]
00007f605b910000 8K rw--- [ anon ]
00007ffce591e000 84K rw--- [ stack ]
00007ffce59ac000 4K r-x-- [ anon ]
ffffffffff600000 4K r-x-- [ anon ]
total 3924K
内存映射写时申请
32G平台,一个进程拥有自己的4G虚拟地址空间与其它进程无关
进程和进程之间是相互独立的 进程地址空间的申请
- 代码段,数据段,BSS段,这三个部分直接从磁盘拷贝到内存,起始地址在当前32位平台linux下为0x08048000地址
- 堆段,动态变化 malloc系列。
- mmap映射文件(普通文件,也可以是其它类型的文件) 库,用户自己调用mmap函数
- 栈段
- 高地址1G空间供内核映射处理,用户空间不能直接处理
对和栈的其实地址默认是随机产生的,其目的是避免安全漏洞,但是可以指定堆中申请的起始地址
brk(系统调用)/sbrk (库函数)
#include <unistd.h>
int brk(void *addr); //指定下次申请堆空间的起始地址为addr
void *sbrk(intptr_t increment);//在当前的地址位置后移increment字节,如果为0返回当前值
<hr/>#include <stdio.h>
#include <stdlib.h>
int i=10;
int main(int argc,char *argv[]){
brk(0xc76000);
printf(&#34;sbrk return:%p\n&#34;,sbrk(0));
char *ptr=malloc(32);
printf(&#34;new malloc:%p\n&#34;,ptr);
return 0;
}
<hr/>[root@centos1 c]# ./brk
sbrk return:0xc76000
new malloc:0xc76010

原文参考:一文搞懂Linux进程结构内存分布是怎样的 - 进程管理 - 我爱内核网 - 构建全国最权威的内核技术交流分享论坛 往期精彩回顾:
超细节!十年码农讲述Linux网络新技术基石——eBPF and XDP
玩转腾讯首发Linux内核源码《嵌入式开发笔记》,也许能帮到你哦
什么?2022届秋招已经正式拉开了帷幕!不看就会后悔
如何正确理解Linux iNode
Linux内核进程状态源码分析
 |
|