首页 > 系统相关 >Linux环境编程-内存管理

Linux环境编程-内存管理

时间:2023-09-09 16:55:30浏览次数:35  
标签:sbrk 映射 int 编程 内存 Linux include 虚拟内存

一、内存管理

  • 用户层

STL     智能指针/容器 自动分配、释放       调用C++

C++     new/delete                       调用C

C       malloc/free               调用POSIX\Linux

POSIX   brk/sbrk                  调用内核

Linux   mmap/munmap               调用内核

  • 系统层

Kernal  kmalloc/vmalloc            调用驱动

dirver  get_free_page

二、进程映像

程序存储在磁盘上的可执行文件(脚本、二进制),当执行程序时,系统会把可执行文件加载到内存形成进程,一个程序可以同时加载出多个进程

进程在内存中的分布情况就是进程映像,从低地址到高地址依次是:

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

练习1:打印出各个内存段的数据的地址,与该进程的maps文件中记录的内存分布比较

maps 文件可以查看某个进程的代码段、栈区、堆区、动态库、内核区对应的虚拟地址

maps文件只能显示简单的分区,smap文件可以显示每个分区的更详细的内存占用数据

查看maps:/proc/进程ID/maps

查看进程ID:

命令:ps -aux

函数:getpid()

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int Num = 10;
int Num1;

int main(int argc,const char* argv[])
{
    printf("text: %p\n",main);
    printf("read: %p\n","hehe");
    printf("data: %p\n",&Num);
    printf("bss:  %p\n",&Num1);
    printf("heap: %p\n",malloc(4));
    int num;
    printf("stack:%p\n",&num);
    extern char** environ;
    printf("env:  %p\n",*environ);
    printf("argv: %p\n",argv[0]);
    printf("gedit /proc/%u/maps\n",getpid());
    for(;;);
}

三、虚拟内存

1、32位系统会给每个进程分配4G的虚拟内存空间

2、进程、用户只能使用访问虚拟内存,无法直接使用真实的物理内存

3、虚拟内存只有与物理内存进行映射后才能使用,否则会产生段错误

4、虚拟内存与物理内存的映射和对应使用都是由操作系统动态维护

5、虚拟内存技术一方面是为了让系统更加安全,可以不暴露真实的物理内存地址给用户,又可以让一个进程出错后不影响其他进程和系统的运行,另一方面可让进程使用比实际物理内存更大的空间

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

[0~3G)  用户空间

[3G~4G) 内核空间

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

8、在内核态下,进程运行在内核空间,此时CPU可以执行任何指令,此时运行的代码不受任何限制,可以自由访问任何有效的地址

9、在用户态下,进程运行在用户空间,此时进程不能直接访问内核空间的数据,可以通过系统调用(API 系统接口函数)的方式切换到内核态,间接地访问内核空间的数据

10、对虚拟内存越界访问(访问没有映射过的虚拟内存),导致段错误

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

sbrk/brk/mmap/munmap

malloc分析

malloc使用:

#include <stdio.h>
#include <stdlib.h>
int main(int argc,const char* argv[])
{
    int* p = malloc(4);
    free(p);
}

关于 malloc 获取虚拟内存实际调用POSIX还是Linux提供的函数受到操作系统、编译器、字节数的影响,大致逻辑:

1、如果m请小于128Kb调用sbrk\brk

2、如果申请大于128Kb调用mmap\munmap

malloc 通过 brk() 方式申请的内存,free 释放内存的时候,并不会把内存归还给操作系统,而是缓存在 malloc 的内存池中,待下次使用;

malloc 通过 mmap() 方式申请的内存,free 释放内存的时候,会把内存归还给操作系统,内存得到真正的释放。

注意:strace ./a.out  可以追踪程序的底层调用(用户层)

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

注意sbrk、brk底层维护一个映射位置指针,该指针指向虚拟内存中映射过的内存的最后一个字节的下一个位置,可以通过移动该指针来实现映射内存和取消映射的效果

sbrk

void *sbrk(intptr_t increment);
    功能:通过增量increment来调整映射位置指针的位置,从而进行映射或取消映射
    increment:增量(字节为单位)
            0   获取映射位置指针的位置
            >0  映射内存
            <0  取消映射
    返回值:映射指针原来的位置

实例:

#include <stdio.h>
#include <unistd.h>

int main(int argc,const char* argv[])
{
    int* ptr = sbrk(4);
    *(ptr+1023) = 1000;
    printf("%d\n",*(ptr+1023));

    sbrk(-4);
} 

brk

int brk(void *addr);
    功能:通过修改映射指针指向addr的地址,从而进行映射或取消映射
    addr:
            > 原来位置 映射
            < 原来位置 取消映射
    返回值:成功返回0 失败返回-1

实例:

#include <stdio.h>
#include <unistd.h>

int main(int argc,const char* argv[])
{
    int* base = sbrk(0);
    int ret = brk(base+1);
    *base = 1234;
    printf("%d %d\n",*base,ret);
}   

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

实例:

#include <stdio.h>
#include <unistd.h>

int main(int argc,const char* argv[])
{
    int* arr = sbrk(0);

    for(int i=0; i<10; i++)
    {
        sbrk(4);
        arr[i] = i;
        printf("%d\n",arr[i]);
    }

//    brk(arr);
    sbrk(-40);
}

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

#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>

bool is_prime(int num)
{
    for(int i=2; i<=num/2; i++)
    {
        if(0 == num%i) return false;    
    }
    return true;
}

int main(int argc,const char* argv[])
{
    int* arr = sbrk(0);
    int len = 0;

    for(int i=2; i<=1000; i++)
    {
        if(is_prime(i))
        {
            sbrk(4);
            arr[len++] = i;
        }
    }
    for(int i=0; i<len; i++)
    {
        printf("%d ",arr[i]);    
    }
    fflush(stdout);
    brk(arr);
}

mmap

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
    功能:映射虚拟内存与物理内存
    addr:映射内存的起始地址,可以自己指定,如果赋NULL则由操作系统指定
    length:要映射的字节数
    prot:映射后的权限
        PROT_EXEC   执行权限    PROT_EXEX | PROT_READ
        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:文件的偏移位置,从该位置开始映射文件
    返回值:成功返回映射后的内存首地址,失败返回(void*)-1

munmap

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

实例:

#include <stdio.h>
#include <sys/mman.h>

int main(int argc,const char* argv[])
{
	int* ptr = mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,0,0);	
	if((void*)(-1) == (void*)ptr)
	{
		perror("mmap");
		return -1;
	}

	for(int i=0; i<1024; i++)
	{
		ptr[i] = i+1;
		printf("%d ",ptr[i]);
	}

	if(munmap(ptr,4096))
	{
		perror("munmap");	
	}
	printf("success\n");
}

五、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函数的使用

标签:sbrk,映射,int,编程,内存,Linux,include,虚拟内存
From: https://www.cnblogs.com/ljf-0804/p/17689737.html

相关文章

  • Linux环境编程-文件管理
    一、一切皆文件Linux/UNIX操作系统把所有的服务、设备、协议都抽象成文件的形式,提供了一套统一而简单的文件IO的系统调用,简称系统的文件IO也就是说在UNIX\Linux中任何对象都可以被当做是某种特殊的文件,都可以像访问文件一样,访问这些对象通过ls-l命令可以查看文件属性信息,其中......
  • Go 中几种常见的编程模式
    模式可以理解成最佳实践,或者是约定俗成的规范或套路,熟悉一些常见模式可以方便理解项目代码。本文是参考左耳朵耗子的专栏文章做的笔记,另外也缅怀一下耗子叔。slice切片的数据共享与扩容迁移切片的数据共享切片就像是在数组上开的窗口,透过切片窗口可以看到和修改底层数组。这......
  • JVM内存模型,JVM、JRE、JDK之间的关系,Java程序是如何执行的
    一、什么是JVMJVM是JavaVirtualMachine(Java虚拟机)的简称,是一个虚构出来的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM屏蔽了与具体操作系统平台相关的信息,Java程序只需生成在Java虚拟机上运行的字节码,就可以在多种平台上不加修改的运行。JVM在执行字节码时,实......
  • linux DNS服务器配置
    1、yuminstall-ybind   安装域名服务vim/etc/named.conf这个服务使用 /etc/named.conf 作为配置文件。BIND在那个文件中使用像下面这样的一些语句:options:用于全局BIND配置。logging:配置哪些需要记录,哪些需要忽略。我推荐你看看 Linuxsyslogserver。zone:定......
  • linux gcc rpath
    linux下程序运行时如果想要到指定路径下查找依赖库,除了使用LD_LIBRARY_PATH,还可以使用编译选项rpath:g++-Wl,-rpath='$ORIGIN/libs'-omainmain.cpp-L.-lmylib那么只要把libmylib.so放到libs目录下,main即可正常执行。如果是在QT中,则改为:QMAKE_LFLAGS+="-Wl,-rpath='\$......
  • Linux 开发常用网站
    根据命令查安装包https://command-not-found.com/linuxkernel源码在线阅读https://elixir.bootlin.com/linux/v5.19.11/sourcelinuxkenel源码下载https://www.kernel.org/https://github.com/torvalds/linuxlinux内核文档在线阅读https://www.kernel.org/doc/html......
  • Windows平台 CLion 远程调试 Linux 的 C++ 程序
    Windows平台CLion远程调试Linux的C++程序1.CLion的安装Pass2.Linux环境的配置2.1.安装gdbserver这里举例Ubuntu环境下的安装:sudoapt-getinstallgdbserver2.2配置CLion2.2.1.配置Toolchains首先在CLion的File->Settings->Tools->SSHConfigu......
  • Linux部署项目常用命令(持续更新)
    防火墙配置#启动防火墙服务systemctlstartfirewalld#关闭防火墙服务systemctlstopfirewalld#查看防火墙服务状态systemctlstatusfirewalld#开机禁用防火墙服务systemctldisablefirewalld#开机自启防火墙服务systemctlenablefirewalld端口配置......
  • 网络编程
    title:网络编程index_img:https://tuchuangs.com/imgs/2023/08/12/aa149ca851821467.pngtags:-JavaSEcategories:-JavaSEexcerpt:网络编程网络编程:计算机之间通过网络传输数据。软件架构网络编程三要素IP上网设备在网络中的地址,是唯一的。127.0.0.1永......
  • 【笔记】二维数组在内存地址中的存储
    最近在学习STM32的ADC和DMA多通道采集过程中有使用到二维数组,姑且记录一下以作备忘。参考:http://c.biancheng.net/view/2022.html举个例子就能很简单的说明了创建一个M行N列的int数组,数组定义如下(例:M=3N=5)#defineM3#defineN5intarr[M][N];给数组按顺序赋值int(*......