Linux内存总结

内存相关
  开发环境
    编译器
      gcc编译器
        什么是编译器:把人类能看的懂的语言翻译成机器能够看的懂的二进制语言的程序。
        编译器
          预处理器:把程序员编写的代码翻译成标准的C语言。
          翻译器:把标准的C语言编程成二进制语言(没有入口)。
          链接器:把若干个目标文件合并在一起生成可执行的二进制文件。
          装载器:把可执行的二进制文件按照操作系统要求装载到内存并开始执行(程序员的自我修养)。
        gcc编译器是GNU社区为了编译Linux内核开发的一套编译框架,不光能编译C语言,包括C++、java、Objective-C。
        常用的参数
          -E 只预处理,处理后的结果直接显示在屏幕上,如果需要保存需要加参数-ofile.i
          -c 只翻译不链接,只生成目标文件file.o
          -o 为编译出的结果重新命名。-o file
          -Wall 尽可能多的产生警告,编译检查更严格。
          -Werror 把警告当错误处理,如果有警告则不能再生成可执行程序。
          -g 生成调试信息,要与gdb配合使用。
          -S 生成汇编文件file.s
          -D 在编译时定义宏
        源码变成可执行程序的过程
          1、编写源码,vim code.c
          2、预处理生成以.i结尾的预处理文件,gcc -E code.c -o code.i
          3、生成.s结尾的汇编文件,gcc -S code.i -> code.s
          4、生成以.o结尾的目标文件,gcc -c code.s -> code.o
          5、链接(ld)若干个.o文件生成可执行程序,gcc a.o b.o c.o -> a.out(elf)
      GNU 编译框架
        支持众多编译语言
          C、C++、JAVA、Objective-C、Ada
        支持各种编译平台
          U L W
        构建过程(build)
          源代码变成可执行程序的过程a.c->a.out
            编写.c文件
            预处理 gcc -E a.c ->a.i
            汇编 gcc -S a.i 生成 a.s
            编译 gcc -C a.s 生成 a.o
            链接 gcc a.o libstd.o 生成 a.out
        gcc -v 查看编译器版本信息
          Ubuntu 12.04 LTS
          编译的版本号 gcc 版本4.6.3
          编译的位数i686-linux-gnu
          所支持的语言C、C++
        头文件路径:/usr/include
      文件类型
        .c
          源代码文件
        .h
          头文件
        .i
          预处理文件
        .s
          汇编文件
        .o
          目标文件
        .a
          静态库文件
        .so
          共享库文件
        .gch
          编译后的头文件
          file.h file.gch (优先使用)
      编译单个文件
        -C
          只编译不链接
        -E
          预处理
        -S
          汇编
        -Wall
          尽可能多的生成警告信息
        -Werror
          把警告信息当做错误处理
        -g
          生成调制信息
        -x
          指定要编译的语言
        -pedantic
          以ANSI标准检查语法,一旦出扩展的语法就会出现警告
      编译多个文件
        头文件的作用
          声明外部变量和外部函数
          定义宏常量、宏函数
          类型设计、类型重定义
          包含其他头文件
          借助“头文件卫士”防止重复包含
        防止头文件相互包含
          定义c.h,把a.h和b.h中的共用部分移动到c.h
        包含头文件和不包含头文件的区别
          如果没有头文件,编译器会猜测函数的格式(返回值int,参数按实参的类型猜测)
          #include""先在当前目录下查找,如果找不到再去系统指定的位置找
          #include<>系统指定的位置去找头文件
          编译器也可以指定头文件的查找路径 -I path
        编译单个.c文件,然后再合并成可执行文件
          gcc -c code.c->code.o
          gcc code.o->a.out
        编写Makefile脚本
      预处理指令
        #include
          包含头文件
        #define
          定义宏常量或宏函数
        #undef
          取消宏定义
        #if
          判断
        #else
          当#if为假时再次判断
        #elif
          与#if配合使用
        #endif
          结束判断
        #ifndef
          判断没有定义宏
        #ifdef
          判断定义宏
        ##
          连接两个标识符形成一个新的标识符
        #
          把参数转换成字符串字面值
        #error
          生成错误信息
        #waring
          生成警告信息
        #pragma pack (2)
          结构体超过2字节的,按照2字节对齐
        #pragma GCC poison key
          把key设置为病毒,禁止使用
        #line
          设置代码的行号
        如何在命令行定义宏:gcc -D 宏名=数值
      预定义的宏
        __BASE_FILE__
          当前正在编译的文件名
        __FILE__
          代码所在的文件名
        __LINE__
          获取行号
        __FUNCTION__
          获取函数名
        __func__
        __DATA__
          获取日期
        __TIME__
          获取时间
        __cplusplus__
          用户判断编译器的语法是否是C++
      与编译器相关的环境变量
        vi~/.bashrc
        C_INCLUDE_PATH
          设置C语言头文件路径
        CPATH
          设置C语言头文件路径
          export CPATH=/home/zhizhen
        LIBRARY_PATH
          设置库文件路径(编译时)
        LD_LIBRARY_PATH
          动态加载库文件路径(运行时)
        添加环境变量:source ~/.bashrc
        删除环境变量要关闭终端重新打开才有效
    库
      库(库文件):把若干个目标文件合并在一起形成的集合,就是代码的集合
        分久必合(方便使用),合久必分(文件维护)
        库文件的分类
          静态库
            调用者把所需的代码从静态库中直接拷贝到可执行文件中 .a
          共享库(动态库)
            调用者吧所需的代码先在共享库中确认,当执行时会把共享库和可执行文件一起加载,当需要执行共享库中的代码时直接从可执行文件中跳转过去 .so ,dll
          静态库:编译出的静态库相对比较大,不易修改(静态库的代码会变,可执行文件要重新编译),但执行效率高
          共享库:编译出的共享库相对比较小,容易修改(共享库发生改变,程序不用重新编译),但执行效率不高
      代码库
        长年代码的积累
    静态库
      创建静态库
        gcc - c code.c ->code.o
        ar -r libname.a code.o ...
      调用静态库
        静态库是需要与调用者一起编译(拷贝)
        直接调用(调用者与库在同一目录下)
          gcc hello.c libname.a
        设置LIBRARY_PATH,配合-lname
          vi ~/.bashrc
          export LIBRARY_PATH=$LIBRARY_PATH:/home/zhizhen
          gcc hello.c -lname
        -Lpath配合-lname
          gcc hello.c -Lpath -lname
      ar命令
        -r 生成静态库
        -q 往静态库中追加目标文件
        -d 从静态库中删除目标文件
        -t 显示静态库中所有的目标文件
        -x 把静态库展开成目标文件
    共享库
      创建共享库
        -fpic 位置无关,代码段的地址都使用的是相对位置
          gcc -fpic -c code.c->code.o
          gcc -shared -o libname.so code.o ...
      调用共享库
        与调用静态库的库方法一致
      共享库的运行
        调用共享库的可执行文件,运行时需要共享库一起加载并执行
        运行时查找共享库需要从LD_LIBRARY_PATH指定
        export LD_LIBRARY_PATH=$LD_LIBRARY:~/math/
      共享库相关命令
        -fpic
          小模式:生成的位置无关目标文件相对较小,速度较快,但只有个别平台支持
        -fPIC
          大模式:生成的位置无关目标文件相对较大,速度较慢,基本所有平台都支持
        ldd
          查找可执行文件所依赖的共享库文件
        ldconfig
          LD_LIBRARY_PATH环境变量所配置的路径会记录到ld.so.conf,每次开机时会共享库加载到ld.so.cache文件中,以此来提高共享库的运行速度,ldconfig可以重新生成ld.so.cache文件,而不用等到开机
          sudo ldconfig
      动态加载共享库
        在编译时不再查找共享库,在运行时才去查找并加载共享库
          要依靠环境变量
        #include<dlfcn.h>
        打开共享库
          void *dlopen(const char *filename,int flag);
            filename:库名或路径,如果只有库名则去LD_LIBRARY_PATH环境变量指定的位置查找
            flag:
              RTLD_LAZY 延时加载,使用时才加载
              RTLD_NOW 立即加载,程序执行时就被加载
            返回值:成功返回共享库的句柄,失败返回NULL
        dlsym:查找函数
          void *dlsym(void *handle,const char *symbol);
            handle:共享库的句柄,dlopen的返回值
            symbol:函数名
            返回值:成功返回函数指针,失败返回NULL
        dlclose:关闭共享库
          int dlclose(void *handle);
            handle:共享库的句柄,dlopen的返回值
            返回值:0关闭成功,非零失败
        dlerror:查看错误
          char *dlerror(void)
            当dlopen、dlsym、dlclose执行出错,再调用此函数获取错误信息
        gcc test.c -ldl
          l表示调用库
          dl表示动态库
    辅助工具
      nm
        查看目标文件、静态库、共享库、可执行文件中的符号列表
      strip
        删除目标文件、静态库、共享库、可执行文件中的符号列表
      objdump
        显示二进制模块的汇编信息
  内存管理
    环境变量
      什么是环境变量
        是程序了解操作系统配置的一个重要方法
          操作系统通过环境变量来告诉程序的资源放在什么位置
      每个程序执行后操作系统就给它一张环境变量表,每个程序一张
      全局变量 extern char** environ
      也可以通过main函数参数来获取
        int main(int argc,char** argv,char** env)
      操作环境变量的函数:
        stdlib.h
        name=value
        char *getenv(const char *name);
          通过环境变量名获取环境变量值
        int putenv(char *string);
          以name=value来设置环境变量,如果环境变量已经存在则覆盖,不存在则添加
        int setenv(const char *name,const char *value,int overwrite);(返回0成功,非0失败)
          以name,value方式来设置环境变量
          name:环境变量名
          value:环境变量值
          overwrite:如果环境变量已经存在,为0则不改变,不为0则改变
        int unsetenv(const char *name)
          删除环境变量
        int clearenv(void);
          清空环境变量表

    错误处理
      通过函数返回值表示错误
        合法或不合法
          数组的查找
          计算文件的大小
            int file_size(const char* path);
            调用ftell函数获取文件位置指针
            设置文件位置指针到文件末尾
        NULL或其他地址
          malloc、fopen、dlopen
          实现mem_cpy功能
            void *mem_cpy(void *dest, const void *src, size_t n);
            dest与src可能会有交集
        成功返回0,失败返回-1 bool
          bool top_stack(Stack* stack,TYPE* top);
          dlclose
          实现求余函数
            int mod(int num1,int num2,int* retp);
        永远成功
          printf
        通过全局变量来反映错误
          errno.h
          erron
          strerror(erron)
          perror(fopen)
    重定义
      #define TYPE int*
      typedef int* TYPE;重命名
      TYPE p1,p2,p3
    内存管理
      用户层
        STL智能指针/自动分配/分支释放
        C++ new/delete
        C malloc/calloc/realloc/free
        posix sbrk/brk 操作系统提供的内存管理方式
        Linux mmap/munmap
      内核层
        kernel
          kmalloc/vmalloc
        Driver
          get_free_page
        DDR4
    进程的映像
      存储在磁盘中的可执行文件叫程序
      把程序加载到内存中并执行,叫进程,进程可以看作是可执行程序的一个实例
      一个程序可以有很多个实例,每个进程都有一个唯一的编号
        getpid();
      进程在内存中的分布情况叫进程映像,从低到高依次排列情况
        代码段
          可执行文件会被加载到此处
        只读段
          字面值、常量
        全局段
          初始化过的全局变量、静态变量
        bss段
          未初始化的全局变量、静态变量
        堆
          new/delete/malloc/sbrk
        栈
          局部变量、块变量、函数的返回值
        命令行/环境变量 (最高)
          命令行执行程序时附加的参数,环境变量表
        /proc/pid/maps 可以查看内存的分布情况
        size a.out 可以查看代码段、全局段、bss段的大小
    虚拟内存
      每个进程都有独立的4G(320S)的虚拟地址空间
        0x00000000
        0xffffffff
      应用程序中用到的都是虚拟内存,永远无法访问到实际的物理内存
      虚拟内存不能直接使用,需要与物理内存建立映射关系才能使用,使用没有建立映射关系的虚拟内存将发生段错误
      虚拟内存与物理内存的映射是由操作系统动态维护,由操作系统统一内存的管理能提高程序和系统的安全性,还可以使用更多的内存,甚至比物理内存更大的内存。
      物理内存不能直接访问,是通过系统调用进入到内核层,然后再进行间接交换
      虚拟地址的0~3G用户使用,3~4G内核使用
        8bit=1byte
      在使用没有权限的内存时会发生段错误,使用没有映射过的内存会发生段错误
      每个进程对应一个虚拟地址空间,两个进程之间进行地址交换是没有意义的
      malloc背后有一个双向链表在维护它的内存管理,首次向malloc申请内存时,malloc会向操作系统申请进行内存映射(首次映射33页,1页默认4096byte)。之后的内存分配就从这33页中进行,当33页用完后,操作系统会再次映射33页
        当使用malloc进行内存管理时,不要破坏维护信息
        可能会影响下次内存的分配和之前内存的释放
      内存的映射是以页(4096byte)为单位,一页内存的字节数可以通过getpagesize()获取
    Linux内存映射函数
      mmap
        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
          flags
            MAP_SHARED 映射文件
            MAP_PRIVATE 数据只写入缓冲区,不更新到文件
            MAP_ANON 只映射内存
          fd
            映射文件时,文件描述符,如果不映射文件,写0
          offset
            映射文件时的偏移值,如果不映射文件,写0
          返回值
            映射后的内存的地址
      munmap
        int munmap(void *addr,size_t length);
          addr
            要取消映射的地址
          length
            字节数
          返回值
            成功返回0,失败返回非零
    POSIX的内存管理函数
      sbrk和brk维护一个内存末尾指针
      sbrk
        void *sbrk(intptr_t increment);
          increment 把内存的末尾指针移动increment个字节
          返回值:上次调用sbrk/brk的内存末尾指针
      brk
        int brk(void *addr);
          把内存末尾指针设置为addr
          返回值0表示成功,非零表示失败

原文地址:https://www.cnblogs.com/yanxutao/p/9419023.html