下图是一个32位Linux进程的内存空间分布图。
(1)保留区
从0开始到0x08048000这一段,属于保留区。实际上,这部分并不是个单一的内存区域,而是禁止用户进程访问的地址区域的总称,这段地址空间中受到操作系统保护。
如果这段内存区能操作的话,你的C代码中的空指针NULL也就合法了。所以,大多数操作系统中,极小的地址通常都是不允许访问的。C语言将无效指针赋值为0(NULL)也是出于这种考虑,因为0地址上正常情况下不会存放有效的可访问数据。
(2)代码段(text)
通常用于存放程序执行代码(即CPU执行的机器指令)。指令中包括操作码和操作对象(或对象地址引用),如果操作对象是立即数,直接会包含在本区代码段中;若是局部数据,将在栈区分配空间,然后引用该数据地址;若位于BSS段和数据段,指令会引用该数据地址。
(3)数据段(Data)
如果程序的数据是全局变量,并且定义的同时有初始化值。这样的变量,就存储在数据段。然后在进程在加载,这一段的内存就被复制到相应的内存。
当程序读取数据段的数据时,系统会触发缺页故障,从而分配相应的物理内存;当程序读取BSS段的数据时,内核会将其转到一个全零页面,不会发生缺页故障,也不会为其分配相应的物理内存。
(4)BSS段
没有初始化的全局变量会被加载到此段内存区。
虽然是没有初始化的全局变量,一般情况下,当加载器(loader)加载程序时,将为BSS段分配的内存初始化为0。这种初始化为0的动作一般是在程序运行到入口函数main()之前执行,不论是嵌入式软件、win进程或者是Linux平台,都差不多这样处理。BSS段不包含数据,仅维护开始和结束地址,以便内存能在运行时被有效地清零。
注意,编译器将所有未初始化的全局变量归拢到一起,放置在BSS段,进程运行的时候,采用把这块BSS连续内存区间置0的方式,非常高效。其实,BSS并不占用目标文件内的实际空间,只是在进程运行起来后占内存空间,即BSS段在应用程序的磁盘文件(二进制映象)中并不存在。
(5)堆(heap)
堆用于存放进程运行时动态分配的内存段,可动态扩张或缩减。
分配的堆内存是经过字节对齐的空间,以适合原子操作。堆管理器通过链表管理每个申请的内存,由于堆申请和释放是无序的,最终会产生内存碎片。堆内存一般由应用程序分配释放,回收的内存可供重新使用。若程序员不释放,程序结束时操作系统可能会自动回收。
堆中内容是匿名的,只能通过指针间接访问,不能按名字直接访问。当进程调用malloc(C)/new(C++)等函数分配内存时,新分配的内存动态添加到堆上(扩张);当调用free(C)/delete(C++)等函数释放内存时,被释放的内存从堆中剔除(缩减) 。
堆的末端由break指针标识,当堆管理器需要更多内存时,可通过系统调用brk()和sbrk()来移动break指针以扩张堆,一般由系统自动调用。
使用堆时经常出现两种问题:1) 释放或改写仍在使用的内存(“内存破坏”);2)未释放不再使用的内存(“内存泄漏”)。当释放次数少于申请次数时,可能已造成内存泄漏。泄漏的内存往往比忘记释放的数据结构更大,因为所分配的内存通常会圆整为下个大于申请数量的2的幂次(如申请111B,会圆整为128B)。
操作系统为堆维护一个记录空闲内存地址的链表。当系统收到程序的内存分配申请时,会遍历该链表寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点空间分配给程序。若无足够大小的空间(可能由于内存碎片太多),有可能调用系统功能去增加程序数据段的内存空间,以便有机会分到足够大小的内存,然后进行返回。
(6)内存映射段(mmap)
mmap其实和堆一样,也是动态分配内存的。
在Linux中,若通过malloc()请求一大块内存,大小超过128K就会使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0)。C运行库将创建一个匿名内存映射(该映射没有对应的文件, 可用于存放程序数据),而不使用堆内存。”大块” 意味着比阈值 MMAP_THRESHOLD还大,缺省为128KB,可通过mallopt()调整。
另外,mmap/munmap的IO映射也使用这个区域。内核将硬盘文件的内容直接映射到内存,内存映射是一种方便高效的文件I/O方式。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read()/write()等操作。 因而被用于装载动态共享库。用户也可创建匿名内存映射,该映射没有对应的文件, 可用于存放程序数据。
(7)栈
代码编译器和链接器后,进程栈的初始化大小就被计算而定了下来。但是,栈在运行过程中,随着函数的调用次序的变化而动态变动,入栈的函数越深,栈的所占用的内存空间就增大,反之则内存空间变小。所以,栈的实时大小并不是固定的。
当然,栈区也有界限,不可能无限增长,栈空间限制RLIMIT_STACK在32位模式下一般为 8M。在Linux中可以通过ulimit -s命令查看和设置栈的最大值,当程序使用的栈超过该值时, 发生栈溢出(Stack Overflow),程序收到一个段错误(Segmentation Fault)。注意,调高栈容量可能会增加内存开销和启动时间。
(8)内核空间
内核总是驻留在内存中,是操作系统的一部分。内核空间为内核保留,不允许应用程序读写该区域的内容或直接调用内核代码定义的函数,如果违反就会触发段异常错误(Segmentation Fault)。
2 mmap基本概念任何应用程序都可通过Linux的mmap()系统调用请求这种映射。
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
大家知道,在Linux上面一切皆是文件,在Linux平台上有多种设备,比如IO端口(点亮一个LED)、LCD控制器、磁盘控制器,所谓操作设备,其实就是往设备的物理地址读写数据。除了open/read/write之外,还有ioctl、ioremap和mmap。
由于应用程序不能直接操作设备硬件地址,所以操作系统提供了这样的一种机制——内存映射,把设备地址映射到进程虚拟地址,mmap就是实现内存映射的接口。
mmap的好处是,mmap把设备内存映射到虚拟内存,则用户操作虚拟内存相当于直接操作设备了,省去了用户空间到内核空间的复制过程,相对IO操作来说,增加了数据的吞吐量。
3 命令行参数信息:argc,argvshell启动进程时,会把用户输入的参数传递到进程中去。代码的main(int argc,char *argv[])中,argc是传入参数的个数,argv数组存入的是传入的参数,从0开始,第一个固定存放的是程序本身的文件名,后面依次是传入的参数。
这里再说一句,main是进程的主要入口。系统在main函数进入之前,已经干了很多活,比如初始化BSS数据,内核准备了各种环境,比如建立进程文件描述符表之类的,这些就绪之后,才轮到main发威。
4 进程环境列表每一个进程都有与其相关的称之为环境列表的字符串数组,也可以简称为环境,其中每个字符串都以名称=值形式定义,因此环境是“名称-值”的成对集合,可存储任何信息,所以也把列表中的名称称为环境变量。每个程序都会接收到一张环境表。与参数表一样,环境表也是一个字符指针数组,其中每个指针包含一个以null结束的C字符串的地址。全局变量environ则包含了该指针地址。
shell命令printenv就可以看到环境变量。
$ printenv
SHELL=/bin/bash
QT_ACCESSIBILITY=1
COLORTERM=truecolor
XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/etc/xdg
XDG_MENU_PREFIX=gnome-
GNOME_DESKTOP_SESSION_ID=this-is-deprecated
LANGUAGE=zh_CN:zh
MANDATORY_PATH=/usr/share/gconf/ubuntu.mandatory.path
GNOME_SHELL_SESSION_MODE=ubuntu
SSH_AUTH_SOCK=/run/user/1000/keyring/ssh
PATH=/home/songguoya/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
获取某个环境变量值
char* getenv(const char *name) //name 是环境变量名,该函数返回对应环境变量的值。
修改环境变量
int putenv(char*string)
修改环境变量有时很有用处。因为修改后,对该进程后续创建的所有子进程均可见。进程间、程序间都可以用这种形式通信。
int setenv(const char*name,const char*value,int overwrite);
移除name环境变量
int unsetenv(const char *name);
参数name为环境列表名,该函数的作用就是从环境中移除name环境变量。
清空整个环境变量
int clearenv(void)
5 执行非局部跳转臭名昭著的setjmp()、longjmp(),最好不要在工程中用它!



