【OSTEP】动态内存开辟 | 内存API常见错误 | UNIX: brk/sbrk 系统调用 | mmap创建匿名映射区域 | mmap创建以文件为基础的映射区域

简介: 【OSTEP】动态内存开辟 | 内存API常见错误 | UNIX: brk/sbrk 系统调用 | mmap创建匿名映射区域 | mmap创建以文件为基础的映射区域

💭 写在前面

本系列博客为复习操作系统导论的笔记,内容主要参考自:

  • Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau, Operating Systems: Three Easy PiecesA. Silberschatz, P. Galvin, and G. Gagne,
  • Operating System Concepts, 9th Edition, John Wiley & Sons, Inc., 2014, ISBN 978-1-118-09375-7.Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .



0x00 内存API概述(Overview of Memory APIs)

前置知识:芝士雪豹, 动态内存的开辟与释放

🔍 前置知识:【维生素C语言】第十三章 - 动态内存管理

💭 malloc 函数:

#include <stdlib.h>
void* malloc(size_t size)

Allocate a memory region on the heap.   用于在堆上开辟一块内存空间。

  • size_t size : 内存块的大小(字节数)
  • size_t 为无符号整数类型
  • 开辟成功:返回一个指向由 malloc 分配的内存块的void类型的指针。
  • 开辟失败 : 返回空指针 (null ptr)

When a process asks for dynamic memory (e.g., through malloc()), it doesn’t get additional physical memory; instead, it gets the right to use a new range of virtual address space

当一个进程申请内存时(比如通过 malloc),它并没有得到额外的物理内存,而是 得到了使用一段新的虚拟地址空间的权限

Allocate a logically contiguous memory but the mapped physical memory may not be contiguous.

开辟的内存地址只是逻辑上是连续的,但实际的物理内存地址不一定是连续的。

int *x = malloc(10 * sizeof(int));
printf(“%d\n”, sizeof(x));

🚩 运行结果:4

int x[10];
printf(“%d\n”, sizeof(x))

🚩 运行结果:40

* 老生常谈:数组名是首元素地址,sizeof 数组名是一个例外,是整个数组大小。

💭 free 函数:

#include <stdlib.h>
void free(void* ptr)

该函数用于释放程序员在堆上动态开辟的内存。

  • void *ptr:传要释放的空间的指针
  • 无返回值

0x01 常见错误:忘记开空间了

char *src = “hello”; //character string constant
char *dst; //unallocated
strcpy(dst, src); //segfault and die

char *src = “hello”; //character string constant
char *dst (char *)malloc(strlen(src) + 1 ); // allocated 
strcpy(dst, src); //work properly

这里 strlen(src) + 1,是给 \0 的。

0x02 常见错误:空间没开够

下面的代码有可能会寄,也可能不会寄的代码,纯靠人品:

char *src = “hello”; //character string constant
char *dst (char *)malloc(strlen(src)); // too small
strcpy(dst, src); //work properly

0x03 常见错误:忘记初始化分配的内存

你虽然正确的 malloc 了,但忘记在新分配的数据类型中填写一些值了,Dont do that!!!

如果你忘记了,你的程序会遇到未初始化读取(uninitialized read)。

他会读取一些未知的数据,这就不可控了。鬼知道那里可能会有什么?

如果走运,读到的程序仍然有效(0),如果不走运,就会使一些随机的、有害的东西,直接寄。

int *x = (int *)malloc(sizeof(int)); // allocated
printf(“*x = %d\n”, *x); // uninitialized memory access

0x04 常见错误:忘记释放内存

内存泄露问题,这个话题以及是在熟悉不过的陈芝麻烂谷子了,这里就不赘述了。

记住一定要 free 就完事了,free 了之后顺便再把指针置NULL。

常见错误:悬空指针

有时候程序会在用完之前释放内存,这种错误被称为悬空指针(dangling pointer)。

如果在用完前就提前释放了内存,就会导致程序崩溃或覆盖有效的内存。

比如你调用了 free,但随后再次调用 malloc 来分配其他内容,这重新利用了错误释放的内存。

0x05 常见错误:反复释放内存

程序有时还会不止一次的释放内存,这被称为重复释放(double free)。

这么做的结果是未定义的。内存分配库可能会改到困惑,并且会做各种奇奇怪怪的事情,其中崩溃是最常见的结果。

int *x = (int *)malloc(sizeof(int)); // allocated
free(x); // free memory
free(x); // free repeatedly

free 了没开的内存(瞎free):

int *x = (int *)malloc(sizeof(int)); // allocated
free(x+12); // free memory

0x06 brk / sbrk 系统调用

#include <unistd.h>
int brk(void *addr)
void *sbrk(intptr_t increment);
  • 可以要求操作系统扩大堆的空间。
  • malloc 用的就是 brk 系统调用。
  • break:堆的末端在地址空间中的位置。
  • brk 将新的 break 设置为 addr 所指定的值。
  • sbrk 与 brk 类似,但 sbrk 是通过增量字节(increment bytes)来增加或减少 break。
  • sbrk是系统调用,是 Unix/Linux 系统提供的接口(只能在Unix/Linux系统下才能用的)。C 程序员不应该直接调用 brk 或 sbrk,这不是我们该碰的。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
  void* curr_brk, * tmp_brk = NULL;
  printf("Welcome to sbrk example:%d\n", getpid());
  /* sbrk(0) gives current program break location */
  tmp_brk = curr_brk = sbrk(0);
  printf("Program Break Location1:%p\n", curr_brk);
  /* brk(addr) inc/dec program break location */
  brk(curr_brk + 4096);
  curr_brk = sbrk(0);
  printf("Program break Location2:%p\n", curr_brk);
  brk(tmp_brk);
  curr_brk = sbrk(0);
  printf("Program Break Location3:%p\n", curr_brk);
  return 0;
}

0x07 mmap 创建匿名映射区域

mmap:Creating Anonymous Mapping Region

#include <sys/mman.h>
void *mmap(void *ptr, size_t length, int prot, int flags,
int fd, off_t offset)

分配一个长度为 ptr 的内存区域(不在堆中)。

一些 malloc 实现会使用 mmap 来分配大块。

fd 参数应该是 -1,offset 应该是0。

0x08 mmap 创建以文件为基础的映射区域

Creating File-Backed Mapping Region

ptr = mmap(0, 40, flag, MAP_SHARED, fd, 0)) ;
if ( ptr == MAP_FAILED ) exit(EXIT_FAILURE);

使用长度字节映射文件描述符 fd 所指的文件内容,从文件的偏移量开始,偏移量必须是页面大小的倍数。

#include <stdio.h>
/* include more header files : stdlib.h, fcntl.h, unistd.h, sys/types.h, sys/mmap.h,
sys/stat.h errno.h */
int main(int argc, char* argv[])
{
  int fd, offset;
  char* data;
  struct stat sbuf;
  if ((fd = open("mmapdemo.c", O_RDONLY)) == -1) { perror("open"); exit(1); }
  // stat 获取文件大小
  if (stat("mmapdemo.c", &sbuf) == -1) { perror("stat"); exit(1); }
  offset = atoi(argv[1]); /* get offset */
    //  分配一个新的虚拟地址空间范围,并将其映射到进程中。
    data = mmap((caddr_t)0, sbuf.st_size, PROT_READ, MAP_SHARED, fd, 0));
      printf("byte at offset %d is '%c'\n", offset, data[offset]);
    return 0;
}

$ mmapdemo 30

byte at offset 30 is e

📌 [ 笔者 ]   王亦优
📃 [ 更新 ]   2022.
❌ [ 勘误 ]   /* 暂无 */
📜 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免,
              本人也很想知道这些错误,恳望读者批评指正!

📜 参考资料 

Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau, Operating Systems: Three Easy Pieces

A. Silberschatz, P. Galvin, and G. Gagne,

Operating System Concepts, 9th Edition, John Wiley & Sons, Inc., 2014, ISBN 978-1-118-09375-7.

Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

百度百科[EB/OL]. []. https://baike.baidu.com/.

相关文章
|
6月前
|
存储 算法 关系型数据库
实时计算 Flink版产品使用合集之在Flink Stream API中,可以在任务启动时初始化一些静态的参数并将其存储在内存中吗
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
127 4
|
1月前
|
存储 安全 Java
jdk21的外部函数和内存API(MemorySegment)(官方翻译)
本文介绍了JDK 21中引入的外部函数和内存API(MemorySegment),这些API使得Java程序能够更安全、高效地与JVM外部的代码和数据进行互操作,包括调用外部函数、访问外部内存,以及使用不同的Arena竞技场来分配和管理MemorySegment。
37 1
jdk21的外部函数和内存API(MemorySegment)(官方翻译)
|
5月前
|
C语言
【C语言】:总结动态内存的常见错误
【C语言】:总结动态内存的常见错误
31 0
|
1月前
|
Linux C++
Linux c/c++文件虚拟内存映射
这篇文章介绍了在Linux环境下,如何使用虚拟内存映射技术来提高文件读写的速度,并通过C/C++代码示例展示了文件映射的整个流程。
44 0
|
2月前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
62 11
|
1月前
|
程序员 Windows
程序员必备文件搜索工具 Everything 带安装包!!! 比windows自带的文件搜索快几百倍!!! 超级好用的文件搜索工具,仅几兆,不占内存,打开即用
文章推荐了程序员必备的文件搜索工具Everything,并提供了安装包下载链接,强调其比Windows自带搜索快且占用内存少。
41 0
|
2月前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
93 11
|
2月前
|
监控 Java 大数据
【Java内存管理新突破】JDK 22:细粒度内存管理API,精准控制每一块内存!
【9月更文挑战第9天】虽然目前JDK 22的确切内容尚未公布,但我们可以根据Java语言的发展趋势和社区的需求,预测细粒度内存管理API可能成为未来Java内存管理领域的新突破。这套API将为开发者提供前所未有的内存控制能力,助力Java应用在更多领域发挥更大作用。我们期待JDK 22的发布,期待Java语言在内存管理领域的持续创新和发展。
|
2月前
|
存储 安全 Linux
将文件映射到内存,像数组一样访问
将文件映射到内存,像数组一样访问
29 0
|
4月前
|
Java
jmap 查看jvm内存大小并进行dump文件内存分析
jmap 查看jvm内存大小并进行dump文件内存分析
87 3