Colderleo's blog Colderleo's blog
首页
Linux
C++
Python
前端
工具软件
mysql
索引
关于
GitHub (opens new window)

Colder Leo

热爱代码的打工人
首页
Linux
C++
Python
前端
工具软件
mysql
索引
关于
GitHub (opens new window)
  • 常见程序性能开销cost、latency延迟耗时的经验值
  • 面试常见问题
  • 静态链接-动态链接-elf详解-elfloader
  • 动态库和静态库的依赖问题
  • glibc和ld的编译调试-为某程序单独设置ld
  • dl_iterate_phdr遍历linkmap头、获取so加载地址
  • shell、bash语法和脚本模板
  • so文件查找路径
  • 逻辑地址-线性地址or虚拟地址-物理地址
  • 通过ELF读取当前进程的符号表并获取符号的版本信息
  • 虚拟内存,cache,TLB,页表
  • 用户内存空间分布和mmap
    • numa网卡绑定
    • 隔核绑核、服务器优化
    • popen底层原理和仿照实现-execl
    • tmux用法
    • ASLR机制
    • 程序后台运行、恢复前台nohup
    • 大页内存huge_page
    • 用perf查看page-fault
    • Bash设置显示全部路径
    • 查看socket fd状态,设置nonblock
    • cout输出到屏幕的过程
    • 多进程写同一文件-write原子性-log日志
    • vim用法
    • epoll用法
    • signal信号、软中断、硬中断、alarm
    • 内核模块
    • 读写锁之pthread_rwlock和内核rwlock自旋读写锁
    • systemtap
    • xargs、awk用法
    • openssl libssl.so.10.so缺失问题
    • netstat用法
    • fork函数
    • tcp延迟确认ack
    • 90.centos7上一次std-string编译错误定位
    • docker用法
    • find用法
    • dmesg
    • gcc编译用法
    • avx-sse切换惩罚
    • Centos7防火墙
    • chmod用法
    • kernel-devel安装版本
    • Linux-Centos7系统安装、网络设置、常见报错
    • linux下g++编译c++文件
    • MegaCli 安装及使用
    • mysql
    • mysql忘记密码修改方法
    • set用法
    • crontab
    • ssh传文件scp
    • ssh连接
    • tcpdump、tshark、tcpreplay用法
    • ubantu root登录以及创建新用户
    • ubuntu安装g++和gdb
    • uClibc编译失败解决方法
    • win10安装WSL open-ssh
    • yum升级git
    • 比较so文件版本-md5sum
    • 查看磁盘信息
    • 合并两个硬盘,挂载到一个文件夹下
    • 软件安装目录usr-local-src
    • 下载centos历史版本
    • sh脚本转可执行文件、加密
    • Linux
    gaoliu
    2021-10-06
    目录

    用户内存空间分布和mmap

    # 整体布局

    参考:https://blog.csdn.net/faxiang1230/article/details/106242380 (opens new window)

    • 白色区域表示gap,为了防止无意间的访问越界,对该区域的任何读写操作都会触发MMU异常进而收到SIGSEGV信号。

    • 地址空间底部依次是text,data,bss段,程序编译链接的时候就已经确定他们的size,可能加载的时候进行了地址随机化,但是相对地址是固定的.

    • heap段也就是堆区域,平时我们经常把堆和栈混为一谈,其实他们是分开的,在bss段上方有一个随机的gap区域,之后就是heap的基地址,随着程序的运行他动态向上增长.

    • mapping区域在在堆和栈中间,它是用来加载依赖动态库的,最顶部的动态库地址和栈基地址有一个间隔,为栈动态增长预留的 用户空间最上方是栈区域,栈的基地址是内核空间之下的地址,可能会有随机的偏移,随着函数的调用嵌套是向下增长的

    Flexible Process Address Space Layout In Linux

    # 用户栈

    我们通过ulimit -a看到系统对当前进程的栈size进行了限制,大部分是8M的限制。用户可以通过ulimit -s来修改栈的大小,可以扩充到100M的大小

    # bss段

    参考:https://www.zhihu.com/question/23059602/answer/23486779 (opens new window)

    早期的计算机存储设备是很贵的,而很多时候,数据段里的全局变量都是0(或者没有初始值),那么存储这么多的0到目标文件里其实是没有必要的。所以为了节约空间,在生成目标文件的时候,就把没有初始值(实际就是0)的数据段里的变量都放到BSS段里,这样目标文件就不需要那么大的体积里(节约磁盘空间)。只有当目标文件被载入的时候,加载器负责把BSS段清零(一个循环就可以搞定)。 之后,这个规则慢慢的成为一个标准配置,大多数编译器也就都支持了BSS段。

    ​ bss段通常用来存放程序中未初始化的或初始化为0的全局变量和静态变量,负责将BSS段清零的工作一般是由加载器完成的,当一个可执行文件被加载的时候,加载器(可以简单的理解为操作系统)负责把BSS段的内存清零。有初始化值的全局变量和静态变量在data段。

    #include <stdio.h>
    
    int a = 0;
    int b;
    int c = 5;
    
    int main()
    {
        printf("data: c,f;  bss:a,b,d,e\n");
    
        static int d = 0;
        static int e;
        static int f = 5;
    
        printf("a-bss : %p\n", &a);//00404014
        printf("b-bss : %p\n", &b);//00404010
        printf("c-data: %p\n", &c);//00402000
        printf("d-bss : %p\n", &d);//00404018
        printf("e-bss : %p\n", &e);//00404028
        printf("f-data: %p\n", &f);//00402004
    
        return 0;
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    # mmap

    mmap()可以指定起始地址,如果该地址不可用,则系统会分配一个随机地址。返回值为为实际分配的地址。

    mmap()分配的最小长度为pagesize,在shell中可以用getconf PAGE_SIZE获取,一般默认是4096。

    代码中可以通过int pagesize = sysconf(_SC_PAGE_SIZE); 获取页大小。

    #include <sys/mman.h>
    
    void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
    int munmap(void *addr, size_t length);
    
    1
    2
    3
    4
    • 指定map的地址。为NULL则由kernel分配地址。不为NULL时,该地址仅供kernel参考(除非指定MAP_FIXED)。
    • prot为读写执行权限
    • flags为打开方式,MAP_SHARED表示共享方式映射内存。如果指定了MAP_FIXED,则会映射到指定的地址。如果该地址之前已经被映射,则会抛弃前面的映射。 anonymous匿名的,不关联文件。
    • fd, 映射文件的fd
    • offset, 映射到映射文件的offset,必须为内存页大小的整数倍

    RETURN VALUE On success, mmap() returns a pointer to the mapped area. On error, the value MAP_FAILED (that is, (void *) -1) is returned, and errno is set to indicate the cause of the error.

      On  success,  munmap()  returns 0.  On failure, it returns -1, and errno is set to indicate the cause of the error
      (probably to EINVAL).
    
    • open()和shm_open() 参考https://bbs.csdn.net/topics/390598198/ (opens new window)

    普通的open()会打开一个文件,而shm_open()会在/dev/shm目录上生成一个文件,而且会校验该目录下是不是挂载了tmpfs文件系统,如果不是也不能正常打开。因为这个文件存在tmpfs文件系统下,在不用的情况系统会自动删除掉。

    所以如果需要保存文件,则open()普通文件,然后用mmap映射。如果只是ipc通信,不需要保存文件内容,则用shm_open()更方便,打开的临时文件不用了会自动删除。

    // #define _GNU_SOURCE
    #include <sys/types.h>
    #include <sys/mman.h>
    #include <string>
    #include <iostream>
    #include <unistd.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <string.h>
    #include <fcntl.h>
    #include <error.h>
    
    using namespace std;
    
    
    //页大小一般为4096字节,读每页开头一个字节,从而建立tlb表项。注意要防止编译器优化。
    void touch_page_tlb(void *addr, size_t len) {
        for(int i=0; i<len; i+=4096) {
            uint8_t *a = (uint8_t *)((char *)addr+i);
            __asm__ __volatile__("" :  : "m"(*a) : );
        }
    }
    
    
    //典型用法, 不指定映射地址,  map长度为len字节,  文件偏移offset=0
    int main()
    {
        const char *filename = "/tmp/mmap_test";
        int len = 8192;
        int fd = open(filename, O_CREAT | O_RDWR, 0666);
        if(fd < 0) {
            std::cerr << "file open failed: " << strerror(errno) << std::endl;
            return 1;
        }
        if(ftruncate(fd, len)) { //更改文件大小
            std::cerr << "ftruncate failed: " << strerror(errno) << std::endl;
            close(fd);
            return 2;
        }
        uint8_t *addr1 = (uint8_t *)mmap(NULL, len, PROT_READ | PROT_WRITE , MAP_SHARED , fd, 0);
        // close(fd);
        if(addr1==MAP_FAILED){
            std::cerr << "mmap failed: " << strerror(errno) << std::endl;
            return -1;
        }
        printf("mmap succeed, addr1=%p\n", addr1);
    
    
        uint8_t *addr2 = (uint8_t *)mmap(NULL, len, PROT_READ | PROT_WRITE , MAP_SHARED , fd, 0);
        if(addr2==MAP_FAILED){
            std::cerr << "mmap 2 failed: " << strerror(errno) << std::endl;
            return -1;
        }
        else printf("mmap succeed, addr2=%p\n", addr2);
    
    
        // 内存预读取
        // madvise(addr1, len, MADV_WILLNEED | MADV_ACCESS_MANY); //建议内核该内存区域即将使用,使用方式为多进程随机读取
        // _mm_prefetch(addr1, len)//预读到cache,一般不需要
        touch_page_tlb(addr1, len);
    
        printf("main end\n");
        return 0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64

    下面是映射固定地址的用法

    int test_fixed_mmap()
    {
        //指定映射地址为0x400000, map长度为4096字节, MAP_ANONYMOUS不关联文件(通常初始化为0)  fd=-1  文件偏移offset=0
        uint8_t *mem1 = (uint8_t *)mmap((void *)0x400000, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, 
            MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
    
        uint8_t *mem2 = (uint8_t *)mmap((void *)0x401000, 64, PROT_READ | PROT_WRITE | PROT_EXEC, 
            MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
    
    
        //测试mem1和mem2之间的关系,它们都处于本进程的同一用户地址空间,二者距离为0x1000(4096),可通过偏移互相访问
        printf("mem1=0x%p\n", mem1);
        printf("mem2=0x%p\n", mem2);
    
        int *mem1_0 = (int *)mem1;
        int *mem1_0x1000 = (int *)(mem1 + 0x2000 - 4);
        int *mem2_0 = (int *)mem2;
        printf("mem1_0x1000=0x%p\n", mem1_0x1000);
        printf("mem2_0=0x%p\n", mem2_0);
        return 0;
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    编辑 (opens new window)
    上次更新: 2023/05/07, 17:27:54
    虚拟内存,cache,TLB,页表
    numa网卡绑定

    ← 虚拟内存,cache,TLB,页表 numa网卡绑定→

    最近更新
    01
    通过模板实现结构体成员拷贝-按成员名匹配
    05-07
    02
    c++17通过模板获取类成员的个数
    05-01
    03
    avx-sse切换惩罚
    04-30
    更多文章>
    Theme by Vdoing | Copyright © 2019-2023 Colder Leo | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式
    ×