Linux的内存管理

一、内存管理的调用关系

    用户层

        STL     自动申请/释放内存     调用C++

        C++     new/delete          调用C

        C       malloc/free         调用POSIX或者 Linux

        POSIX   brk/sbrk            调用内核

        Linux   mmap/munmap         调用内核

    系统层

        kernal kmalloc/vmalloc      调用驱动

        driver get_free_page  

二、进程映像

    程序是存储在磁盘上的可执行文件(二进制文件、脚本文件)

    当执行程序时,系统会自动将该文件加载到内存中,在内存中的分布情况称为进程映像

    从低地址到高地址的分区:

        text    代码段

        data    数据段

        bss     静态数据段

        heap    堆

        stack   栈

        environ 环境变量表

        argv    命令行参数

        命令:ps -aux 查看当前所有进程信息

            可以查看进程号

        /proc/进程号/maps

       

    总结:

    1、栈内存的增长方向受操作系统影响,大部分是从高地址向低地址增长,但有些系统例如Ubuntu就是从低地址向高地址增长

    2、如果是栈内存存储数组数据,数组中元素的增长方向一定是从低地址向高地址增长

虚拟内存:

    1、操作系统会为每个进程分配4G的虚拟内存

    2、用户只能使用虚拟内存,不能直接使用物理内存

    3、虚拟内存要与物理内存进行映射后才能被用户使用,如果使用了没有映射的虚拟内存就会产生段错误

    4、虚拟内存地址与物理内存的映射由操作系统(MMU)动态维护

    5、虚拟内存能让系统使用更安全,不会暴露真实的物理内存地址,另一方面操作系统可以让进程使用比实际物理内存更大的地址空间

    6、4G的虚拟内存地址分为两个部分

        [0G,3G) 用户空间

        [3G,4G) 内核空间

    7、当进程\线程运行在用户空间时称为进程处于用户态,当进程\线程运行在内核空间时称为进程处于内核态

    8、当进程处于内核态时,进程运行存储使用在内核空间,此时cpu可以发出并执行任何指令,并且运行的代码不受任何限制,可以自由地访问任意有效的地址,也可以直接访问接口

    9、当进程处于用户态时,进程运行存储使用在用户空间,此时被执行的代码要受到CPU很多的检查,例如:用户进程只能访问自己映射过的内存

    10、所有进程的内核空间的代码、数据都是映射在同一块物理内存中,有内核来负责维护

    11、用户空间的代码不能直接访问内核空间的代码和数据,可以通过系统调用(API 调用系统接口函数)切换到内核态,间接的访问内核、与内核交换数据

映射虚拟内存与物理内存的函数:

    sbrk\brk\mmap\munmap

    关于malloc获取虚拟内存空间的底层实现,跟libc.so版本有关,大概的逻辑:

        1、如果分配的内存小于128k时,调用sbrk、brk

        2、如果大于128时,调用mmap、munmap

    注意:系统映射内存是以页(1页=4096字节)为最小单位的

    注意:sbrk、brk底层共同维护一个映射位置指针,该指针指向映射过的内存的下一个位置或者说是未映射的内存的第一个地址

    void* sbrk(intptr_t increment);

    功能:通过increment参数调整映射位置指针的位置,既可以映射内存也可以取消映射

    increment: 字节为单位

        0       获取当前指针的位置

        >0      映射内存

        <0      取消映射

    返回值:该指针映射前原来的位置

    int brk(void* addr);

    功能:直接使用addr地址修改映射指针位置指针的位置

    addr:

        >原来位置指针的位置     映射内存

        <原来位置指针的位置     取消映射

    返回值:成功0,失败-1

    注意:sbrk、brk都可以单独进行映射、取消映射的操作,但是一般习惯配合使用:sbrk辅助记录映射前的位置+映射操作,brk负责取消整个映射

    #include <sys/mman.h>

    注意:mmap、munmap底层不维护任何东西

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

    功能:映射虚拟内存与物理内存

    addr:指定要映射的虚拟内存首地址,可以手动指定,如果是NULL,则是系统自动指定

    length:映射的长度,字节为单位

    prot:映射后的权限

        PROT_EXEC   执行权限

        PROT_READ   读权限

        PROT_WRITE  写权限

        PROT_NONE   没有权限

            PROT_READ | PROT_WRITE

    flags:映射方式标志

        MAP_SHARED      映射内存与文件内容后,如果修改内存中的数据,文件内容会随之改变

        MAP_PRIVATE     映射内存与文件内容后,如果修改内存中的数据,文件内容不会随之改变 MAP_SHARE互斥,必须选一个

        MAP_ANONYMOUS   映射到虚拟内存,而不是映射文件,会忽略fd、offset参数

        MAP_FIXED   如果手动提供的addr无法进行映射,默认情况下会映射一个正确的地址,但加了该标志,则不会调整,直接执行失败

    fd:文件描述符,如果不映射文件,则给0即可

    offset:文件的偏移位置,不用给0即可

    返回值:成功返回映射后的内存首地址,失败返回(void*) -1\0xFFFFFFFF

    int munmap(void* addr,size_t length);

    功能:取消映射

    addr:要取消映射的首地址

    length:取消的字节数

    返回值:成功0,失败-1

    注意:可以使用strace ./a.out(可执行文件名)可以大概跟踪代码底层的执行情况

        sbrk底层调用了brk

    总结:

        1、重点是理解Linux内存管理机制(虚拟内存),而不是brk\sbrk\mmap\munmap系统函数的使用

        2、brk\sbrk底层维护一个虚拟内存位置指针,通过移动该指针来建立映射关系和取消映射关系

        3、mmap\munmap底层不维护任何东西,只返回一个映射后的虚拟内存地址

        4、malloc\free底层调用的是sbrk\brk 或者 mmap\munmap

系统调用(系统API)

    系统调用就是操作系统提供的一些功能以函数的行驶个程序员使用,注意虽然格式很像标准C的函数,但是它们不是标准C的内容,也不是真正的函数

    一般程序大部分时间都工作在用户态(0~3G),当偶尔发生系统调用时就会工作在内核态(3~4G)

    系统调用的代码使内核的一部分,其外部接口以函数形式定义在共享库中(linux-gate.so.1 /lib/ld-linux.so.2)

    系统调用的执行是借助软中断的方式从用户态进入到内核态后执行真正的系统调用

        time ./a.out    测试进程的运行时间分布

        real 0m0.001s   进程总执行时间

        user 0m0.001s   用户态执行时间

        sys  0m0.000s   内核态执行时间

        ldd\time\strace\size   ./a.out

Linux文件IO:

一切皆文件

    NUIX/Linux系统把所有的服务、设备都抽象成文件看待,并提供了一套简单而同一的系统接口,这部分系统接口就是所谓的系统文件读写,简称系统IO

    如果使用的是标准C库中的文件读写函数,简称为标准IO

文件的分类:

    普通文件        -   包括纯文本文件、二进制文件、压缩文件等

    目录文件        d   必须有x权限才能进入目录

    块设备文件      b   用于存储大块数据的设备 例如硬盘

    字符设备文件    c   例如键盘、鼠标

    管道文件        p   有名管道文件

    链接文件        l   类似于Windows的快捷方式

    Sockst文件      s   通常用于网络设备之间数据的链接交互

文件操作相关的系统调用:

    #include <sys/types.h>

    #include <sys/stat.h>

    #include <fcntl.h>

    int open(const char* pathname,int flags);

    功能:打开文件

    pathname:文件的路径

    flags:文件的打开方式

        O_RDONLY    只读

        O_WRONLY    只写

        O_RDWR      读写

        O_APPEND    追加

        O_CREAT     文件不存在则创建

        O_EXCL      文件存在,如果配合O_CREAT则失败

        O_TRUNC     文件存在则清空打开

    返回值:文件描述符,成功返回一个非负整数,类似FILE*,代表了打开后的文件

    int open(const char* pathname,int flags,mode_t mode);

    功能:创建文件

    flags:必须为O_CREAT mode参数就需要给

    mode:

        S_IRWXU  00700  用户   读写执行权限

        S_IRUSR  00400         读权限

        S_IWUSR  00200         写权限

        S_IXUSR  00100         执行权限

        S_IRWXG  00070  组     读写执行权限  

        S_IRGRP  00040         读权限

        S_IWGRP  00020         写权限

        S_IXGRP  00010         执行

        S_IRWXO  00007  其他用户

        S_IROTH  00004         读权限

        S_IWOTH  00002         写

        S_IXOTH  00001         执行

        注意:可以直接使用八进制数表示三组权限,例如

            0644 0664 0775

    返回值:文件描述符,成功返回一个非负整数,类似FILE*,代表了打开后的文件

    int creat(const char* pathname,mode_t mode);

    功能:创建文件

    mode:同上

    试验:fopen各种打开方式的open底层调用的的参数分别是什么

    strace ./a.out

    ssize_t write(int fd,const void* buf,size_t count);

    功能:把内存中的数据写入文件中

    fd:文件描述符,open的返回值

    buf:待写入的内存首地址

    count:要写入的字节数

    返回值:成功写入的字节数

    ssize_t read(int fd,const void* buf,size_t count);

    功能:从文件中读取数据到内存中

    fd:文件描述符,open的返回值

    buf:存储数据的内存首地址

    count:想要读取的字节数

    返回值:实际读取到的字节数

    int close(int fd);

    功能:关闭文件

    注意:write、read是fwrite和fread的底层调用

        结论:正常情况下,标准IO比系统IO的速度更快

        原因:

            1、标准IO中缓冲区机制,在写数据时不时每次都调用系统IO,而是把写入的数据存储在临时的缓冲区中,当缓冲区满时,才会调用一次系统IO写入数据到文件中

            2、直接使用系统IO时会频繁地切换内核态和用户态,非常耗时

        如果给系统IO增加缓冲区,它的速度一定会比标准IO快

随机读写:

    系统IO下,每个打开的文件都有一个写的位置指针,记录了从哪个字节开始进行读写文件,并且对文件进行读写操作时,它会自动往后移动

    当想要在任意位置进行读写文件时,可以通过改变位置指针的位置进行随机读写

    //标准IO

    fseek

    返回值:成功0,失败-1 借助ftell

    //系统IO

    off_t lseek(int fd, off_t offset,int whence);

    offset:偏移值 字节为单位

    whence:基础位置

        SEEK_SET    文件开头

        SEEK_CUR    当前位置

        SEEK_END    文件末尾

    返回值:调整后位置指针所在文件的第几个字节

    注意:当文件位置指针越过末尾后在写入数据时,越过的地方会形成"黑洞",该段"黑洞"会计算入文件大小中,但是不占用磁盘空间

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 内存管理主要包括内存节点、分区、页框和虚拟内存等概念。 1. 内存节点 Linux 根据 CPU 访问代价的不同将内存划分为不同的分区,即内存节点。内核以 struct zone 来描述内存分区。通常一个节点分为 DMA、Normal 和 High Memory 内存区。其中,DMA 内存区为直接内存访问分区,通常为物理内存的起始16M,供外设使用,外设和内存直接访问数据而无需 CPU 参与;Normal 内存区为从 16M 到 896M 的内存区;HighMemory 内存区为 896M 以后的内存区。 2. 分区 内存节点中的分区是内存管理的基本单位,每个分区都有自己的页框列表和空闲页框列表。页框是内存管理的最小单位,通常为 4KB。内核通过页框来管理内存,将内存分为多个页框,每个页框都有自己的状态,包括已分配、未分配、已使用等。 3. 页框 页框是内存管理的最小单位,通常为 4KB。内核通过页框来管理内存,将内存分为多个页框,每个页框都有自己的状态,包括已分配、未分配、已使用等。内核通过页表来映射虚拟地址和物理地址,将虚拟地址转换为物理地址。 4. 虚拟内存 虚拟内存是一种将硬盘中划出一段 swap 分区当作虚拟的内存,用来存放内存中暂时用不到的内存页,等到需要的时候再从 swap 分区中将对应的内存页调入到内存中的技术。硬盘此时相当于一个虚拟的内存。Linux 通过虚拟内存技术来扩展内存,使得进程可以使用比物理内存更大的内存空间。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值