一 内存的管理
内存的页式管理
- cpu 在使用内存时,先向内存管理子系统申请内存,然后得到的是虚拟地址,user 通过虚拟地址,通过页表,可以访问到实际的物理地址。
- 对于32为的系统,每一个进程都可以访问4G 的虚拟地址空间。
- 内核使用的虚拟地址空间是1G, user 使用的虚拟地址空间为3G .
内存的段式管理
- 内存中,将程序分为代码段 数据段 堆段 栈段等分开存储。
- cpu 通过 cs ds es ss 等寄存器 记录内存中各个段的起始地址。
- 编译器会将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,相当于一个进程的身份证号。
- 可以用 getpid 来获取当前进程的pid.
- 如何查看进程的映像?
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]
上述示例中涉及的到知识点
- str1 和str2 中存放的是“china”字符串的首地址,该字符串首地址的地址在进程maps 中的位置是在只读数据段中。并且 str1 和 str2 中放的是同一份字符串(“china”)的地址。指针类型的变量所占用的是四个字节的地址(32位系统中)。
- str1 和str2是两个变量,变量所放的位置在stack 中。这两个变量的地址是不一样的。buf 所在的位置也是在stack 中。
- char buf[32] = “china”; 这一句话有两层意思。
第一: 在 只读数据段中开辟一个区间放“china”。
第二: 在stack 中,开辟一个有32个字节的区间buf, 然后将 “china”字符串copy 到栈中。并且 buf 是一个常量,之后是不能变化的。因此 buf = “china” 是错误的 - char *str3 ; 中, str3 声明在栈中,由于没有初始化,因此str3 中变量的值(也即是指针的地址)无法确定,有可能真相的是无法使用的地址。因此 在 *str3 = ‘v’ 的时候,会出现段错误。 st3 是悬空指针,也成为野指针。
- 指针类型的变量在使用之前必须初始化为有效的地址空间。
函数的栈段和堆段:
- 每个进程都有自己的栈帧,函数执行完后,函数对应的栈帧也就结束了。
- 每个进程都有自己的栈段, 栈段中可以包含多个栈帧
- 函数的生命周期是函数调用到函数执行完毕
- 进程的生命周期,是程序执行开始到执行结束
- 如果变量的空间分配在栈帧里,变量的声明周期就是函数的生命周期。
- 栈段 代码段和数据段的生命周期都是进程
- 栈帧的生命周期是对应的函数的生命周期。
- 自动局部变量和函数的形参 这些变量的空间分布在栈帧里面
- 全局变量 局部静态变量 分配在数据段中。
经典示例:
#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
- 通过 cat /proc/3204/maps 可以看到 static int i 分配的空间在可读写的数据段中。
- 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) 分配的空间在堆上。
静态存储区间和动态存储区间
- 静态存储区间在编译的时候确定了
- 动态存储区间在执行的时候确定
示例如下::
#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
- 在free§ 调用后,依然可以打出来p 中放的字符串是 beijing
这是因为 free§,仅仅是表明p 的这一块地址可以重新使用的,
但是现在可能还没有被重新分配和使用。 - 015e3000-01604000 rw-p 00000000 00:00 0 [heap] 可以看到在 cat proc/pid_number/maps 中,有堆相应的空间,并且p 放在了 这个空间中。