2019-01-17 01:28:10 qq_39545674 阅读数 85

Unix系统的内存管理

1、什么是内存?

内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁。计算机中所有程序的运行都是在内存中进行的,因此内存的性能对计算机的影响非常大。内存(Memory)也被称为内存储器,其作用是用于暂时存放CPU中的运算数据,以及与硬盘外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来,内存的运行也决定了计算机的稳定运行。 内存是由内存芯片、电路板、金手指等部分组成的。

 

2、什么是内存管理?

内存管理是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。

 

3、一个进程的内存空间划分

3.1 进程的定义

有些人会将进程、线程、程序这几个概念混淆。

程序是代码编译链接的产物,是存储在硬盘上的文件,程序是静止的,没有运行。

运行起来的程序就是进程,是在内存中运行的程序,也就是把程序加载到了内存中,是运行的。即程序是在硬盘上的,进程是在内存里的。

一个程序可以运行出多个进程,一个进程也可以用多个程序。内存中的每个进程,都在/proc目录下建立一个目录,目录名就是进程ID(PID),进程结束,目录消失。这个目录就是进程的文件方式。cat /proc/进程ID/maps 查看进程的内存分配情况。getpid()函数获取当前进程的ID。

线程这里只做简单的介绍,形象一点,进程是老板,线程是老板的员工。

3.2 进程的内存空间划分

一个进程的内存空间可划分为以下部分:

1、代码区 :用于存放代码(函数)的区域,是只读区。函数指针就是指向代码区的地址。

2、全局区 :用于存放全局变量的区域和静态(static)变量。

3 、BSS段 :用于存放未初始化的全局变量的区域。BSS段在主函数执行之前会清零。

4、栈区(堆栈stack) :用于局部变量的区域,包括函数的参数和非static的局部变量。系统自动管理栈区。

5、堆区(heap):也叫自由区,程序员唯一可以控制的区域,通常内存分配、回收都是在堆区。malloc,free都是在堆区。堆区的内存系统完全不管,程序员全权管理。(进程结束时,所有内存会自动释放)。

6、只读常量区:存放字符串字面值“asd”和const修饰的全局变量。这个区域也是只读区,很多书把只读常量区并入代码区。

地址从小到大排列顺序:

1、代码区

2、只读常量区

3、全局区

4、BSS段

5、堆区

6、栈区

4、虚拟内存地址空间的机制

Unix/Linux使用虚拟内存地址的方式管理内存,每个进程先天都有0-(4G-1)的虚拟内存地址空间,本质就是整数(编号)。虚拟内存地址本身不能储存任何数据,必须映射到物理内存或者硬盘上的文件后 才能存储数据,否则引发段数据,目前程序员接触的都是虚拟内存地址。

虚拟内存的地址分为用户层和内核层,用户层0-3G,3G-4G是内核层(可以设置),用户层不能直接访问内核层,通过系统函数可以。

内存的基本单位是字节,内存地址是逐字节的,但是内存映射的基本单位不是一个字节,是4096字节(4k),叫一个内存页。函数getpagesize()可以查看内存页的大小。

如果地址弄错了,则会引发段错误,常见的段错误原因有两点:

1、使用没有映射的虚拟内存地址存储数据/获取数据。

2、对没有权限的区域进行操作,比如修改只读区。

5、内存管理的相关函数

1、malloc()函数和free()函数

void* malloc(size_t size)

size是要分配的字节数,返回分配内存的首地址。

内存分配的函数要做两件事情:

1、分配虚拟内存地址

2、映射物理内存/硬盘文件--第一次映射,后面用完了再映射。malloc申请小块内存时,一次映射33个内存页,用完后再继续映射,申请大块内存时,(超过31个内存页),会映射比申请稍微多一点的内存页。

malloc()函数除了分配正常的内存空间,还需要额外开辟一些空间,用于

存储一些附加信息,比如分配内存的大小。

free()一定会释放虚拟内存地址,以便虚拟内存地址可以循环利用,不一定

解除映射,超过33个内存页的部分会释放,最后33个内存页不释放,直到

进程结束时释放。

 

2、sbrk()函数和brk()函数

sbrk()和brk()系统的底层会维护一个位置,通过位置的移动完成内存的分配与回收。映射内存时,以一个内存页作为基本单位。

void *sbrk(int increment)

参数increment是增量,增量为正数时,分配内存,增量为负数时,回收内存

执行成功返回之前的地址,失败返回-1.增量为0,取当前位返回移动之前的位置(可用内存的首地址),这个返回值对于增量为负数的情况没有意义。sbrk()函数与malloc()的实现原理完全不同。

sbrk()在分配内存时很方便,但是回收内存时很麻烦。一般用brk()回收内存。

int brk(void* addr),成功返回0,失败返回-1。

brk()的使用方式就是直接传递一个地址过来,做新位置。brk()必须和sbrk()结合使用,获得第一个位置。

 

3、mmap()和munmap() ---Unix的系统函数,更贴近底层

void* mmap(void* addr,size_t length,int prot,int flags,int fd,off_t offset)

参数addr可以指定映射的首地址,一般为0,交给内核指定。length是分配内存的大小,映射时以页为单位。prot是分配内存的权限,一般用PROT_READ|PORT_WRITE。flags时标识,通常包括以下三个:

MAP_SHARED MAP_PRIVATE :二选一,指明映射的内存是否共享,MAP_SHARED只对映射文件有效。

MAP_ANONYMOUS:映射物理内存,默认映射文件。

fd是文件描述符,在映射文件时有用

offset是文件的偏移量,指定映射文件从哪里开始。

映射物理内存时,fd和offset给0即可

返回成功返回首地址,失败返回MAP_FAILED==(void*)-1

 

int munmap(void* addr,size_t length);

取消映射。

需要说明的是,这三对函数都能完成内存的分配与回收,到底应该用哪一个需要根据实际情况来决定。

系统调用

用户层的程序不能直接访问内核层,系统的核心功能必须通过内核层控制。因此,系统提供了一系列的函数,允许用户层的程序通过函数进入内核层,从而完成功能。这些函数,统称为系统调用(System call)。

2009-05-15 20:30:00 adousen 阅读数 589

 

Unix内核态也称管态(Windows称内核模式)和用户态也称目态(Windows称用户模式)是系统指令调用权限上的一组概念。

而在内存上,
Windows分为系统工作区和用户工作区。Win32中2GB以下的内存区域属系统工作区,用来存储内核代码、设备驱动程序等。
而Unix稍显复杂,他把程序的内存分为正文段和数据段,然后用一个PCB块(进程控制块)来指明各部分内存存放地址。正文段式程序是可再入程序(也就是可以被进程共享的程序)。而数据段存放非共享程序和程序工作的数据,它包括三部分:用户栈、用户数据区、系统工作区。所以,执行系统调用时,需要把提供给用户程序的参数从用户态的存储区域(用户数据区)传送到核心态的存储区域(系统工作区)。

2010-03-20 08:53:00 nellson 阅读数 11856

    共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。因此,采用共享内存的通信方式效率是非常高的。

 

【应用场景】

 

1. 进程间通讯-生产者消费者模式

 

    生产者进程和消费者进程通讯常使用共享内存,比如一个网络服务器,接入进程收到数据包后,直接写到共享内存中,并唤醒处理进程,处理进程从共享内存中读数据包,进行处理。当然,这里要解决互斥的问题。

 

2. 父子进程间通讯

 

    由于fork产生的子进程和父进程不共享内存区,所以父子进程间的通讯也可以使用共享内存,以POSIX共享内存为例,父进程启动后使用MAP_SHARED建立内存映射,并返回指针ptr。fork结束后,子进程也会有指针ptr的拷贝,并指向同一个文件映射。这样父、子进程便共享了ptr指向的内存区。

 

3. 进程间共享-只读模式

 

    业务经常碰到一种场景,进程需要加载一份配置文件,可能这个文件有100K大,那如果这台机器上多个进程都要加载这份配置文件时,比如有200个进程,那内存开销合计为20M,但如果文件更多或者进程数更多时,这种对内存的消耗就是一种严重的浪费。比较好的解决办法是,由一个进程负责把配置文件加载到共享内存中,然后所有需要这份配置的进程只要使用这个共享内存即可。

 

【共享内存分类】

 

1. POSIX共享内存对象

 

const char shmfile[] = "/tmp";
const int size = 100;

 

shm_open创建一个名称为tmp,大小为100字节的共享内存区对象后,在/dev/shm/下可以看到对应的文件,cat可以看到内容。

root:/home/#ls -al /dev/shm/tmp
-rw------- 1 root root 100 10-15 13:37 /dev/shm/tmp

 

访问速度:非常快,因为 /dev/shm 是tmpfs的文件系统, 可以看成是直接对内存操作的,速度当然是非常快的。

 

持续性:随内核,即进程重启共享内存中数据不会丢失,内核自举或显示调用shm_unlink或rm掉文件删除后丢失。

 

2.  POSIX内存映射文件

const char shmfile[] = "./tmp.shm";
const int size = 100;

 

open在指定目录下创建指定名称后文件,cat可以看到内容。

root:/home/#ls -al ./tmp.shm

-rw-------  1 root    root    100 10-15 13:42 tmp.shm

 

访问速度:慢于内存区对象,因为内核为同步或异步更新到文件系统中,而内存区对象是直接操作内存的。

持续性:随文件,即进程重启或内核自举不后丢失,除失显示rm掉文件后丢失。

 

3. SYSTEM V共享内存

 

共享内存创建后,执行ipcs命令,会打印出相应的信息,比如下面所示,key为申请时分配的,可以执行ipcrm -M 0x12345678 删除,nattch字段为1表示有一个进程挂载了该内存。

 

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status     
0x12345678 32769      root      644        10         1

 

访问速度:非常快,可以理解为全内存操作。

持续性: 随内核,即进程重启共享内存中数据不会丢失,内核自举或显示调用shmdt或使用ipcrm删除后丢失。

 

与POSIX V共享内存区对象不同的是,SYSTEM V的共享内存区对象的大小是在调用shmget创建时固定下来的,而POSIX共享内存区大小可以在任何时刻通过ftruncate修改。

 

 

【代码示例】

 

下面给出三种共享内存使用方法的示例代码,都采用父子进程间通讯,并未考虑互斥,仅做示例供大家参考。

 

1.POSIX共享内存对象

  

 

 

编译执行

root:/home/ftpuser/ipc#g++ -o shm_posix -lrt shm_posix.cc     
root:/home/ftpuser/ipc#./shm_posix
Child 2280: start

There is 3 item in the shm
1: Nellson
2: Daisy
3: Robbie

Parent 2279 get child status:0

 

2.POSIX文件映射

 

 

编译执行

 

root:/home/ftpuser/ipc#g++ -o map_posix map_posix.cc
root:/home/ftpuser/ipc#./map_posix
Child 2300: start

There is 3 item in the shm
1: Nellson
2: Daisy
3: Robbie

Parent 2299 get child status:0

 

3.SYSTEM V 共享内存对象

 

 

编译执行

 

root:/home/ftpuser/ipc#g++ -o shm_v shm_v.cc 
root:/home/ftpuser/ipc#./shm_v
Child 2323: start

There is 3 item in the shm
1: Nellson
2: Daisy
3: Robbie

Parent 2322 get child status:0

 

【性能测试】

下面对三种方式进行性能测试,比较下差异。

测试机信息:

AMD Athlon(tm) Neo X2 Dual Core Processor 6850e

cpu:1.7G

os: Linux 2.6.18

 

测试方式:

打开大小为SIZE的共享内存,映射到一个int型的数组中,循环写数组、读数组。

重复10W次,计算时间开销。

 

内存大小

Shmopen+mmap(ms)

Open+mmap

Shmget

4k

1504

1470

1507

16k

6616

6201

5994

64k

25905

24391

24315

256k

87487

76981

69417

1M

253209

263431

241886

 

重复1K次,计算时间开销。

 

内存大小

Shmopen+mmap(ms)

Open+mmap(ms)

Shmget(ms)

1M

5458

5447

5404

4M

21492

21447

21307

16M

90880

93685

87594

32M

178000

214900

193000

 

分析:

Sytem V方式读写速度快于POSIX方式,而POSIX 共享内存和文件映射方式相差不大, 共享内存性能略优。

 

附上测试源码:

  

 

 

 

2010-09-28 16:02:00 liuhongzhu 阅读数 1893

     工作中需要查看一台hp unix的服务器内存,查找资料找到之后,记录下来和大家分享。

     cat  /var/adm/syslog/syslog.log |grep Physical

2016-06-13 09:32:25 tao546377318 阅读数 832

一:内存管理
       硬件层次
       内核层次
              内核映射
              堆扩展
       语言层次
            c:malloc
            c++:new
       数据结构
              STL
              智能指针
1.查看内存描述:
           首先要程序不能退出,一直执行。/proc/$(pid)/  存放进程运行时所有的信息包括内存结构。cat maps
          16进制1000代表4k,而4k代表一个内存叶。
          任何一个程序,内存空间分成4个基本部分:
        file test(可执行文件)//查看此文件的基本情况,ELF格式
          size test                   //此ELF二进制可执行文件结构情况
          依次表示为text(代码区),data(静态数据/全局初始化数据区),bss(未初始化数据区),dec(十进制总和),hex(十六进制总和),file(文件名)
          代码区:存放CPU执行的机器指令。通常,代码区是可共享的(即另外的执行程序可以调用它),使其可共享在目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的原因是防止程序意外地修改它的指令,因此数据在编译时在代码区中分配空间。
          全局初始化数据区/静态数据区:或简称数据段,该区包含了在程序中明确被初始化的全局变量,已经初始化的静态变量(包括全局静态变量和局部静态两变量)。但被const声明的变量以及字符串变量在代码段中申请空间。
           未初始化数据区:或称BSS区,存入的是未初始化全局变量和未初始化静态变量。BSS区的数据在程序开始执行之前被内核初始化为0或者空指针(NULL)
          1》代码区
          2》全局栈区
          3》 堆
          4》 局部栈
       进程查看
       ps aue a-所有, u-当前用户, e-有效进程
       08048000-----代码区的偏移地址是基本固定的,除非指定
       全局常量和字面值放在代码区,
        代码区---》全局区--》堆---》栈
 2.结论:
               1.内存分四个区
               2.各种变量对应存放区
               3.堆栈是一种管理内存的数据结构
               4.怎么去查看一个程序的内存地址
3.理解malloc的工作的原理。
           malloc使用一个数据结构(链表)维护分配的空间,链表的构成部分:分配的空间/上一个空间数据/下一个空间/空间大小等信息。对malloc分配的空间不要越界访问。因为容易破坏后台的维护结构。导致malloc/free/calloc/realloc正常工作。
          堆(malloc,new)的维护靠链表一共16个字节虽然malloc了4个自己,当前节点空间4字节,下一个成员空间4字节地址,前一个成员空间4字节地址,当前区域的大小4字节。
 4.C++的new与malloc的关系
             malloc       new              new[]
             realloc        new()定位分配
             calloc          new
             free             delete         delete[]
            结论:
                       new的实现使用的是malloc来实现的。
                      区别:new使用malloc后,还要初始化空间,基本类型,直接初始化成默认值。UDT(用户自定义)类型,调用构造器或指定构造器,new类型 或 new 类型(参数)。
                      delete调用free实现,delete负责调用析构器,然后调用free。
              new与new[]区别:new只用调用一个构造器。
                                             new[]循环对每个区域调用构造器。
              delete与delete[]区别:对析构函数的调用次数是不一样的,
              定位分配:char a[20]; int *p = new(a) int;//在a的位置分配内存,定位分配。
5.函数调用栈空间的分配与释放。
        5.1 总结:
                1.C语言中函数执行的时候有自己的临时栈空间。C++中有两个栈空间,函数本身,函数对象栈空间。
                2.函数的参数就在临时栈中,如果函数传递实参,则用实参来初始化临时的参数变量。
                3.通过寄存器返回值(使用返回值返回数据)。
                4.通过参数返回值(参数必须是指针)这个指针指向的区域必须事先分配。
                5.如果参数返回指针。参数就是双指针。
          5.2 _stdcall  _cdecl   _fastcall
                1.决定函数栈压栈的参数顺序,都是从右到左。
                2.决定函数栈的清空方式。每一个调用者自己清空栈;多有的调用函数只有一个清空函数清空;每个被调函数自己返回之前自己清空。
                3.决定了函数的名字转换方式。C++重载,是修改函数名,来实现重载。
6.far near huge指针(只存在Windows程序上,历史遗留问题,寻址范围)
      near  16为
      far     32位
      huge 综合

二,虚拟内存
       问题:
               一个程序不能访问另外一个程序的地址指向的空间。
        理解:
               1.每个程序开始地址都是0x80084000。
               2.程序中使用的地址不是物理地址,而是逻辑地址(虚拟内存)。
                 逻辑地址仅仅是编号,编号使用int 4字节表示。4294967296(42亿)--每个程序事件提供了4G的访问能力。
        问题:
                 逻辑地址与物理地址关联才有意义:过程本身称为内存映射(strace man---跟踪程序的执行过程)。
        背景:
                 虚拟内存的提出:禁止用户直接访问物理存储设备。有助于系统的稳定。
        结论:
                 虚拟地址与物理地址映射的时候,有一个基本单位4k--->1000(16进制)内存页
                 段错误:无效访问。
                 合法访问:比如malloc分配的空间之外的空间是不合法的,因为会占用别的内存。

三,虚拟内存的分配
              栈:编译器自动生产代码维护。
              堆:地址是否映射,映射的空间是否被管理。
             1.brk/sbrk 内存映射函数
             补充:帮助手册
                        man 节 关键字
                        节:1-8  1:Linux系统(shell)指令 
                                       2 : 系统函数
                                       3:标准C函数的帮助文档
                                       7 :  系统编程帮助
               分配释放内存:
               int brk(void *end);//分配空间,释放空间
               void *sbrk(int size);//返回空间地址
               应用:
                        1.使用sbrk分配空间
                        2.使用sbrk得到没有映射的虚拟地址。
                        3.使用brk分配空间
                        4.使用brk释放空间
               理解:
                        sbrk(int)
                        sbrk与brk后台系统维护一个指针,指针默认为null
                         调用sbrk,判定指针是否为0,是:得到大块空闲的首地址初始化指针
                                                                                同时把指针+size
                                                                         否:返回指针,并且 吧指针位置+size
               总结:
                       智能指针
                       stl           --- 维护一个容器管理内存
                       new        ---维护了一个malloc和一个结构管理内存
                       malloc    ---里面维护了一个链表
                       brk/sbrk --- 维护一个整数
                异常处理
                       int brk(void*)
                       void *sbrk(int)
                       如果成功,brk返回0,sbrk返回一个指针。
                              失败 ,brk返回-1,sbrk返回(void*)-1。
                        Unix函数错误,修改内部变量:
                               errno       
                         perror("hello:");
                         printf(%m\n);
                         printf("::%s",strerror(errno) ) ;再加一个外部的实体变量:extern int errno;
                 字符串函数string.h cstring
                 内存管理函数malloc memset mamcmp memcpy..... bzero
                 错误处理函数
                 标准IO函数
                  时间函数
                  类型转换函数    
六, 映射虚拟内存
        没有任何额外维护数据的内存分配。#include<sys/mman.h>
        mmap(分配)/unmmap(释放)4k(leng/4K\k+1)
        1.函数说明
           man mmap
          void *mmap(void *start, //指定映射的虚拟地址 0由系统指定开始的位置
                               size_t length, 映射空间大小 pagesize倍数
                                int prot,//映射权限PROT_NONE PROT|_READ PROT_WRITE PROT_EXE
                                 int flags, //映射方式
                                  int fd,//文件描述符号
                                  offset_toff);//文件中的映射开始位置(必须是内存叶的位置)
          映射方式:
                         内存映射:匿名映射(内存条)
                         文件映射:映射到某个文件
                                            只有文件映射最后两个参数有效。
                        sturct{ int a:2;}//段宽,以位为单位。
                          MAP_ANONYMOUS//用内存映射方式,不指定为文件映射
                           MAP_SHARED MAP_PRIVATE(二选一)
                         数组就是一个常指针。
        2.案例
        3.总结
                  选择什么样的内存管理方法?
                  STL
                   new
                   malloc(主要是小而多的数据)
                   brk/sbrk(同类型的大块数据,动态移动指针)
                   map/munmap(控制内存的访问/使用文件映射/可以控制内存共享)


Linux/Unix用valgrind检测内存泄漏

博文 来自: WangPegasus

unix内存管理

阅读数 143

Unix下的内存管理

阅读数 278

没有更多推荐了,返回首页