linux 的内存管理

一 内存的管理

内存的页式管理

  1. cpu 在使用内存时,先向内存管理子系统申请内存,然后得到的是虚拟地址,user 通过虚拟地址,通过页表,可以访问到实际的物理地址。
  2. 对于32为的系统,每一个进程都可以访问4G 的虚拟地址空间。
  3. 内核使用的虚拟地址空间是1G, user 使用的虚拟地址空间为3G .

内存描述图

内存的段式管理

  1. 内存中,将程序分为代码段 数据段 堆段 栈段等分开存储。
  2. cpu 通过 cs ds es ss 等寄存器 记录内存中各个段的起始地址。
  3. 编译器会将code 编译为a.out (a.out 中有不同的段),从而load 到内存中不同的段内。
    如下图 是cpu 直接访问物理地址情况下的示意图
    内存的段式管理
    gcc -S hello.c -o hello.S
    可以获得 hello.c 汇编后的程序
  1     .file   "hello.c"
  2     .section    .rodata     // 从这里开始是数据段,数据段是只读的                                                                                                                
  3 .LC0:
  4     .string "hello"   
  5     .text         // 从这个开始是 代码段 
  6     .globl  main
  7     .type   main, @function
  8 main:
  9 .LFB0:
 10     .cfi_startproc
 11     pushq   %rbp
 12     .cfi_def_cfa_offset 16
 13     .cfi_offset 6, -16
 14     movq    %rsp, %rbp
 15     .cfi_def_cfa_register 6
 16     subq    $16, %rsp
 17     movl    $3, -12(%rbp)
 18     movl    $4, -8(%rbp)
 19     movl    -12(%rbp), %eax
 20     imull   -8(%rbp), %eax
 21     movl    %eax, -4(%rbp)
 22     movl    $.LC0, %edi
 23     call    puts
 24     movl    $0, %eax
 25     leave
 26     .cfi_def_cfa 7, 8
 27     ret
 28     .cfi_endproc
 29 .LFE0:
 30     .size   main, .-main
 31     .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609"
 32     .section    .note.GNU-stack,"",@progbits //这里是堆段

进程

每个进程有自己的pid,相当于一个进程的身份证号。

  1. 可以用 getpid 来获取当前进程的pid.
  2. 如何查看进程的映像?
    cat /proc/进程的pid/maps

tip : 可以通过预编译处理来获取 pid_t 的类型
gcc -E pid_process.c -o pid_process.i

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

int main()
{
	printf("pid : %d \n", getpid());
	getchar();
	return 0;
}

编译以上层次,运行后,通过
cat /proc/进程的pid/maps 有如下显示

00400000-00401000 r-xp 00000000 08:01 786746                             /home/book/danei/unix/a.out   // 可读 可执行 表示代码段
00600000-00601000 r--p 00000000 08:01 786746   //  只读数据段                          /home/book/danei/unix/a.out
00601000-00602000 rw-p 00001000 08:01 786746  // 可读写数据段                           /home/book/danei/unix/a.out
02333000-02354000 rw-p 00000000 00:00 0                                  [heap]
7f16e2343000-7f16e2503000 r-xp 00000000 08:01 135798                     /lib/x86_64-linux-gnu/libc-2.23.so
7f16e2503000-7f16e2703000 ---p 001c0000 08:01 135798                     /lib/x86_64-linux-gnu/libc-2.23.so
7f16e2703000-7f16e2707000 r--p 001c0000 08:01 135798                     /lib/x86_64-linux-gnu/libc-2.23.so
7f16e2707000-7f16e2709000 rw-p 001c4000 08:01 135798                     /lib/x86_64-linux-gnu/libc-2.23.so
7f16e2709000-7f16e270d000 rw-p 00000000 00:00 0 
7f16e270d000-7f16e2733000 r-xp 00000000 08:01 135770                     /lib/x86_64-linux-gnu/ld-2.23.so
7f16e2916000-7f16e2919000 rw-p 00000000 00:00 0 
7f16e2932000-7f16e2933000 r--p 00025000 08:01 135770                     /lib/x86_64-linux-gnu/ld-2.23.so
7f16e2933000-7f16e2934000 rw-p 00026000 08:01 135770                     /lib/x86_64-linux-gnu/ld-2.23.so
7f16e2934000-7f16e2935000 rw-p 00000000 00:00 0 
7ffed8db8000-7ffed8dd9000 rw-p 00000000 00:00 0                          [stack]   //  栈段
7ffed8de5000-7ffed8de8000 r--p 00000000 00:00 0                          [vvar]
7ffed8de8000-7ffed8dea000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

实际例子

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main()
{
	printf("pid : %d \n", getpid());
	char *str1 = "china";
	char *str2 = "china";
	// 悬空指针
	char *str3 ;
//	*str3 = 'v';
	int *p;
//	*p = 100;	
	// error  
	char buf[32] = "china";
	// error
	//char buf[32];
	//buf = "china";// error
	strcpy(buf,"china");


	printf("str1: %p\n",str1);
	printf("str2: %p\n",str2);

	printf("&str1: %p\n",&str1);
	printf("&str2: %p\n",&str2);
	printf("buf: %p\n",buf);
	getchar();
	return 0;
}


编译后,运行:

pid : 2875 
str1: 0x4007bf
str2: 0x4007bf
&str1: 0x7fff2935a5c0
&str2: 0x7fff2935a5c8
buf: 0x7fff2935a5d0

使用 cat/proc/2875/maps

00400000-00401000 r-xp 00000000 08:01 786722                             /home/book/danei/unix/a.out
00600000-00601000 r--p 00000000 08:01 786722                             /home/book/danei/unix/a.out
00601000-00602000 rw-p 00001000 08:01 786722                             /home/book/danei/unix/a.out
023c2000-023e3000 rw-p 00000000 00:00 0                                  [heap]
7f216dd9c000-7f216df5c000 r-xp 00000000 08:01 135798                     /lib/x86_64-linux-gnu/libc-2.23.so
7f216df5c000-7f216e15c000 ---p 001c0000 08:01 135798                     /lib/x86_64-linux-gnu/libc-2.23.so
7f216e15c000-7f216e160000 r--p 001c0000 08:01 135798                     /lib/x86_64-linux-gnu/libc-2.23.so
7f216e160000-7f216e162000 rw-p 001c4000 08:01 135798                     /lib/x86_64-linux-gnu/libc-2.23.so
7f216e162000-7f216e166000 rw-p 00000000 00:00 0 
7f216e166000-7f216e18c000 r-xp 00000000 08:01 135770                     /lib/x86_64-linux-gnu/ld-2.23.so
7f216e36f000-7f216e372000 rw-p 00000000 00:00 0 
7f216e38b000-7f216e38c000 r--p 00025000 08:01 135770                     /lib/x86_64-linux-gnu/ld-2.23.so
7f216e38c000-7f216e38d000 rw-p 00026000 08:01 135770                     /lib/x86_64-linux-gnu/ld-2.23.so
7f216e38d000-7f216e38e000 rw-p 00000000 00:00 0 
7fff51ab7000-7fff51ad8000 rw-p 00000000 00:00 0                          [stack]
7fff51b45000-7fff51b48000 r--p 00000000 00:00 0                          [vvar]
7fff51b48000-7fff51b4a000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

上述示例中涉及的到知识点

  1. str1 和str2 中存放的是“china”字符串的首地址,该字符串首地址的地址在进程maps 中的位置是在只读数据段中。并且 str1 和 str2 中放的是同一份字符串(“china”)的地址。指针类型的变量所占用的是四个字节的地址(32位系统中)。
  2. str1 和str2是两个变量,变量所放的位置在stack 中。这两个变量的地址是不一样的。buf 所在的位置也是在stack 中。
  3. char buf[32] = “china”; 这一句话有两层意思。
    第一: 在 只读数据段中开辟一个区间放“china”。
    第二: 在stack 中,开辟一个有32个字节的区间buf, 然后将 “china”字符串copy 到栈中。并且 buf 是一个常量,之后是不能变化的。因此 buf = “china” 是错误的
  4. char *str3 ; 中, str3 声明在栈中,由于没有初始化,因此str3 中变量的值(也即是指针的地址)无法确定,有可能真相的是无法使用的地址。因此 在 *str3 = ‘v’ 的时候,会出现段错误。 st3 是悬空指针,也成为野指针。
  5. 指针类型的变量在使用之前必须初始化为有效的地址空间。

函数的栈段和堆段:

  1. 每个进程都有自己的栈帧,函数执行完后,函数对应的栈帧也就结束了。
  2. 每个进程都有自己的栈段, 栈段中可以包含多个栈帧
  3. 函数的生命周期是函数调用到函数执行完毕
  4. 进程的生命周期,是程序执行开始到执行结束
  5. 如果变量的空间分配在栈帧里,变量的声明周期就是函数的生命周期。
  6. 栈段 代码段和数据段的生命周期都是进程
  7. 栈帧的生命周期是对应的函数的生命周期。
  8. 自动局部变量和函数的形参 这些变量的空间分布在栈帧里面
  9. 全局变量 局部静态变量 分配在数据段中。
    经典示例:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int g_val = 10;
int count ()
{
	static int i = 1;
	i++;
	printf(" i = %d\n", i);
	printf("&i = %p\n", &i);
	return 0 ;
}
int main()
{
	printf("pid : %d \n", getpid());
	for (int n=0; n<5; n++)
		count();
	printf("&g_val = %p\n", &g_val);
	getchar();
	return 0;
}

运行结果如下:

  pid : 3204 
 i = 2
&i = 0x601048
 i = 3
&i = 0x601048
 i = 4
&i = 0x601048
 i = 5
&i = 0x601048
 i = 6
&i = 0x601048
&g_val = 0x601048
  1. 通过 cat /proc/3204/maps 可以看到 static int i 分配的空间在可读写的数据段中。
  2. nm a.out 可以看到

000000000060104c d i.3213

可以看到 static int i 是在编译阶段放在了内存中。因此在函数运行结束时,函数栈帧会释放,但是 i 不会释放。 再次运行的时候,不会去反复的执行static int i = 1; 这一句话。
3. 小写的d 表示内部的变量 d 放在数据段
4. 如果是 int i = 1; 由于函数每一次执行完成后,栈帧都会释放。再次执行式,i 的地址会重新分配,重新初始化,因此每一个输出的值都是2.
5. nm a.out | grep “g_val”

0000000000601048 D g_val

可以看到g_val 在分配在可读写的数据段中,D 表示全局变量。
6. 00000000004005b6 T count count 函数是全局的,因此是大写的T 如果函数前面加入static ,则变成了局部的函数, 只能在本程序内调用,成为局部函数。
7. 进程映像图
8. bss 段是未初始化的数据段。 如 static int i = 0; 整形的默认值是0,指针的默认值是空。

malloc (3) 分配的空间在堆上。
静态存储区间和动态存储区间

  1. 静态存储区间在编译的时候确定了
  2. 动态存储区间在执行的时候确定
    示例如下::
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
	printf("pid : %d \n", getpid());
	char *p = (char *)malloc(1024);
	printf("p content %p\n",p);
	strcpy(p,"beijing");
	printf("p content %s\n",p);
//	getchar();
	free(p);
	printf("p content %s\n",p);
	return 0;
}

运行结果如下:

pid : 6825 
p content 0x15e3420
p content beijing
p content beijing
  1. 在free§ 调用后,依然可以打出来p 中放的字符串是 beijing
    这是因为 free§,仅仅是表明p 的这一块地址可以重新使用的,
    但是现在可能还没有被重新分配和使用。
  2. 015e3000-01604000 rw-p 00000000 00:00 0 [heap] 可以看到在 cat proc/pid_number/maps 中,有堆相应的空间,并且p 放在了 这个空间中。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值