day02

一、内存管理
    用户层
    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    代码段      二进制指令、常量(数值、"字符串字面值"、被const修饰过的原data段的数据)
                        只读,强制修改会段错误  
        data    数据段:    初始化过的全局变量、初始化过的静态局部变量
        bss     静态数据段  未初始化过的全局变量、未初始化过的静态局部变量  
                        程序运行前会自动清0
        heap    堆:     程序员手动管理的大量数据,管理麻烦、申请释放受控,与指针配合使用,使用不当可能会内存泄漏、内存碎片  
        stack   栈:    局部变量、块变量
            大小有限、自动申请、自动释放
        environ 环境变量表:    所有的环境变量
            每个进程都有一份,修改不会影响系统真正的环境变量的值
        argv    命令行参数:    程序执行时附带的参数

        练习:打印出各个内存段的数据地址,与该进程的maps文件记录的内存分布比较
            查看maps:/proc/进程ID/maps
            查看进程ID:
                命令:ps -aux    函数:getpid()

三、虚拟内存
    1、32位系统会给每个进程分配4G的虚拟内存空间
    2、进程、用户只能使用访问虚拟内存,无法直接使用真实的物理内存
    3、虚拟内存只有与物理内存进行映射后才能使用,否则会产生段错误
    4、虚拟内存与物理内存的映射和对应使用由操作系统动态维护
    5、虚拟内存计数一方面为了让系统更安全,可以不暴露真实的物理内存地址给用户,又可以让一个进程出错后不影响其他进程和系统的运行,另一方面可让进程使用比实际物理内存更大的空间
    6、4G的虚拟内存地址分成两个部分
        [0~3G)  用户空间
        [3G~4G] 内核空间
    7、当进程/线程运行在内核空间时,称该进程/线程处于内核态,运行在用户空间称为用户态
    8、在内核态下,进程运行在内核空间,此时CPU执行任何指令,此时运行的代码不受任何限制,可以自由访问任何有效的地址
    9、在用户态下,进程运行在用户空间,此时进程不能直接访问内核空间的数据,可以通过系统调用(API 系统接口函数)的方式切换到内核态,间接地访问内核空间的数据
    10、对虚拟内存越界访问(访问没有映射过的内存),导致段错误

四、映射虚拟内存与物理内存的函数
    sbrk/brk/mmap/munmap
    关于 malloc 获取虚拟内存实际调用POSIX、Linux提供的函数收到操作系统、编译器、字节数的影响,大致逻辑:
                            1、如果申请小于128kb调用sbrk/brk
                            2、如果申请大于128kb调用mmap/munmap
            注意:strace ./a/out可以追踪程序的底层调用(用户层)

    注意:系统内存映射是以页为单位(1页=4096字节)
   
    注意:sbrk、brk底层维护一个映射位置指针,该指针指向虚拟内存中映射过的内存的最后一个字节的下一个位置,可以通过移动该指针来实现映射内存和取消映射的效果
    void *sbrk(intptr_t increment);
    功能:通过增量increment来调整映射位置指针的位置,从而进行映射或取消映射
        increment  (字节)
            =0  获取映射位置指针的位置
            >0  映射内存
            <0  取消映射
    返回值:返回映射指针原来的位置
    int brk(void *addr);
    功能:通过修改映射指针指向addr的地址,从而进行映射或取消映射
        addr:
            > 原来位置  映射
            < 原来位置  取消映射
    返回值:成功返回0,失败返回-1

    总结:sbrk、brk都属于POSIX标准中的内存映射函数,都是可以单独进行映射,取消映射,但是配合使用最方便(sbrk映射、brk取消映射)

    练习2:计算出前1000之前的素数,存储在堆内存中,要求尽可能少的浪费内存

    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_FIXED   如果提供的addr无法映射则直接失败
            MAP_ANONYMOUS   指定映射虚拟内存,不映射文件fd、offset失效
            MAP_SHARED  对映射后的内存可以直接共享给其他进程
            MAP_PRIVATE 对映射后的内存 只能当前进程使用
                注意:flags中必须在MAP_SHARED和MAP_PRIVATE二选一
        fd: 文件描述符 可以让文件映射到物理内存,不需要映射文件直接写0
        offset: 文件的偏移位置,从该位置开始映射文件
    返回值:成功返回映射后的内存首地址,失败返回(0xFFFF FFFF)
                                            (void*)-1

    int munmap(void *addr,size_t length);
    功能:取消映射
        addr:   映射的内存首地址
        length: 要取消映射的字节数
    返回值:成功返回0,失败返回-1

五、Linux内存管理总结
    1、mmap/munmap底层不维护任何东西,直接在堆内存中选择合适的内存进行映射,返回映射成功后的首地址
    2、sbrk/brk底层维护一个映射位置指针,该指针记录了通过sbrk/brk映射内存的末尾位置,通过改变该指针的位置来映射和取消映射
    3、malloc/free底层调用了sbrk/brk或者mmap/munmap,虚拟内存必须与物理内存建立关系后才能使用
    4、每个进程都有4G(32位)的虚拟内存空间
    5、内存管理的重点是理解Linux对内存管理的机制,而不是sbrk/brk/mmap/munmap函数的使用

六、系统调用(系统API、系统函数)
    系统调用指的是由操作系统提供的一些功能给程序员调用,这些功能已经被封装成c语言函数的形式,但是他们不属于标准的一部分,也不是真正的函数
    一般的应用进程工作运行在用户态(0~3G),使用系统调用时进程会转入到内核态(3~4G)
    常用的c标准库函数的大部分时间都工作在用户态,当底层有进行系统调用时会偶尔转入内核态工作
    通过time命令查看进程在两种状态下的运行时间情况
        real        执行总时间
        user        用户态执行时间
        sys         内核态执行时间
    系统调用的代码是内核的一部分,其外部接口以C函数的形式存储在共享库中(linux-gate.so.1、 /lib/ld-linux.so.2),当调用这些外部借口时进程会以软中断的方式进入内核态执行真正的系统调用函数

七、一切接文件
    Linux/UNIX操作系统把所有的服务、设备、协议都抽象成文件的形式,提供了一套统一而简单的文件IO的系统调用,简称系统的文件IO
    也就是说在UNIX/Linux中任何对象都可以被当作是某种特殊的文件,都可以像访问文件一样,访问这些对象
    文件分类:
        普通文件                -   包括文本文件、二进制文件、各种压缩文件  
        目录文件                d   必须有执行权限才能进入目录
        块设备文件              b   保存大块数据的硬件设备,例如磁盘
        字符设备文件            c   操作字符相关的设备 例如键盘、鼠标
        socket文件(套接字文件)  s   通常用于网络通信
        管道文件                p   用于进程间通信
        链接文件                l   类似Windows的快捷方式

八、文件相关的系统调用
    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*,代表了一个打开的文件(>=0),负数表示失败
    int open(const char *pathname, int flags, mode_t mode);
    功能:创建文件
        flags:  O_CREAT
        mode:
            S_IRWXU  00700  user  (file owner) has read, write,
                       and execute permission

            S_IRUSR  00400 user has read permission

            S_IWUSR  00200 user has write permission

            S_IXUSR  00100 user has execute permission

            S_IRWXG  00070 group has read, write,  and  execute
                       permission

            S_IRGRP  00040 group has read permission

            S_IWGRP  00020 group has write permission

            S_IXGRP  00010 group has execute permission

            S_IRWXO  00007 others have read, write, and execute
                       permission

            S_IROTH  00004 others have read permission

            S_IWOTH  00002 others have write permission

            S_IXOTH  00001 others have execute permission
        注意:可给宏名,也可以给权限掩码(0xxx)
    返回值一样
 
    int creat(const char *pathname, mode_t mode);
    功能:创建文件
        mode:同上
    练习:测试fopen的各种打开方式与open的flags的对应清空
        "w":open("hehe.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666)
        "W+":open("hehe.txt", O_RDWR|O_CREAT|O_TRUNC, 0666)
        "r":open("hehe.txt", O_RDONLY)
        "r+":open("hehe.txt", O_RDWR)
        "a":open("hehe.txt", O_WRONLY|O_CREAT|O_APPEND, 0666)
        "a+":open("hehe.txt", O_RDWR|O_CREAT|O_APPEND, 0666)

    ssize_t write(int fd, const void *buf, size_t count);
    功能:把内存中的数据写入到文件中
        fd:文件描述符 open、creat的返回值
        buf:待写入的内存的首地址
        count:要写入的字节数
    返回值:成功写入的字节数

    ssize_t read(int fd, void *buf, size_t count);
    功能:从文件中读取数据到内存中
        buf:存储读取出来的数据的内存首地址
        count: 要读取的字节数
    返回值:成功读取的字节数

    作业1:分别使用标准IO和系统IO写入100W个整数到文件中,测试运行速度,分析为什么
        结论:在同等数据的写如下,使用标准IO比直接使用系统IO更快
        原因:标准IO有缓冲区机制(缓冲区满写入磁盘),在执行fwrite写文件时,数据不是直接调用系统IO写入磁盘,而是先存放在内存的缓冲区中,直到缓冲区满后才会调用一次系统IO全部写入磁盘,因此对系统IO的调用次数、用户态内核态的转换次数都大大降低
              而直接使用系统IO是没有缓冲区机制,因此耗时增加
        解决:如果给系统IO也加上缓冲区机制,那么它的速度要比标准IO更快
        系统IO:
            real    0m0.032s
            user    0m0.023s
            sys     0m0.008s
        标准IO:
            real    0m1.262s
            user    0m0.044s
            sys     0m1.212s
    作业2:使用系统IO实现一个带覆盖检测的cp命令
        cp src dest
posted @ 2023-08-15 20:31  歪爱慕外  阅读(22)  评论(0)    收藏  举报