• 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表示成功,非零表示失败

  • 相关阅读:
    系统调用与库函数
    在树莓派上 搭建sqlite数据库
    (转)inux Read系统调用
    查看当前日期是这一年的第几天
    求解某个范围内的全部完数
    求s=a+aa+aaa+aaaa+aa...a的值,其中a是一个数字。
    输入三个整数x,y,z,请把这三个数据由大到小输出。
    模仿ArrayList底层实现
    可视化日历
    Oracle之约束条件1:主键约束
  • 原文地址:https://www.cnblogs.com/yanxutao/p/9419023.html
Copyright © 2020-2023  润新知