zl程序教程

您现在的位置是:首页 >  硬件

当前栏目

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

2023-09-14 09:15:59 时间

 

💭 写在前面

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

  • 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)

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

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

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

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

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

0x06 brk / sbrk 系统调用

0x07 mmap 创建匿名映射区域

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


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/.