1. 编译链接的一些知识
首先,我们来编写一个简单的程序,示例代码如下
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
int i=3;
int j=4;
int main()
{
printf("value i is %d\n,address of i is 0x%lx\naddress of j is 0x%lx\n",i,(unsigned int)&i,(unsigned int )&j);
}
程序片段1
程序片段1定义了两个变量,a和b,并分别赋值为3,4。然后在主函数main中,打印了i和j的值及其对其对应的虚拟地址。程序的运行结果如下:
value i is 3
address of i is 0x80495e0,
address of j is 0x80495e4
片段1
程序片段2中所示的0x80495e0,0x80495e4分别是i和j的虚拟地址,那么,这个地址是怎么确定的?由谁来确定?
通过查看编译后的可执行程序的符号列表,i和j的地址实际上是在链接的过程中确定的,由编译器来确定。
objdump -t a.out
a.out: file format elf32-i386
SYMBOL TABLE:
......
00000000 w *UND* 00000000 _Jv_RegisterClasses
08048494 g O .rodata 00000004 _fp_hw
08048478 g F .fini 00000000 _fini
00000000 F *UND* 0000019f __libc_start_main@@GLIBC_2.0
08048498 g O .rodata 00000004 _IO_stdin_used
080495dc g .data 00000000 __data_start
080495e0 g O .data 00000004 i
0804849c g O .rodata 00000000 .hidden __dso_handle
080494f0 g O .dtors 00000000 .hidden __DTOR_END__
080483e0 g F .text 00000069 __libc_csu_init
00000000 F *UND* 00000039 printf@@GLIBC_2.0
080495e8 g *ABS* 00000000 __bss_start
080495e4 g O .data 00000004 j
080495f0 g *ABS* 00000000 _end
080495e8 g *ABS* 00000000 _edata
08048449 g F .text 00000000 .hidden __i686.get_pc_thunk.bx
08048384 g F .text 00000044 main
08048250 g F .init 00000000 _init
2. 管理用户空间进程内存的结构体
当一个程序被载入内存,内核就会为其建立一个名为task_struct的结构体来管理这个进程。
struct task_struct {
struct list_head tasks;
struct mm_struct *mm, *active_mm;//字段mm为该进程的内存管理。
pid_t pid;
struct fs_struct *fs;
struct files_struct *files;
......
};
task_struct
struct mm_struct {
struct vm_area_struct * mmap; /* list of VMAs ,一个进程有一个mm_struct,多个
vm_area_struct*/
pgd_t * pgd;
int map_count; /* number of VMAs */
......
};
mm_struct
struct vm_area_struct {
struct mm_struct * vm_mm; /* The address space we belong to. */
unsigned long vm_start; /* Our start address within vm_mm. */
unsigned long vm_end; /* The first byte after our end address
within vm_mm. */
}
vm_area_struct
小结:整个内存管理的体系可以这么理解,在32位系统上,所有的地址(0-4G),页表、页框等都统一由内核管理。而内核中的服务(这里的服务包括内运行在内核中的进程,内核变量等)只是占用了3-4G这个地址空间段。对于用户空间而言,进程的地址是由编译器决定的,编译器在链接各个库文件时,用了0-3G的地址。
对于硬件MMU模块而言,它并不关心Linux给它的地址属于哪个空间,MMU只是根据所设定的规则(内存映射表)找到对应的物理地址。
各结构体之间关系图
3. 用户空间malloc的实现
由前面的小结知道,内存管理都在内核空间进行。对于malloc申请的一块内存也是一样,先来看一下malloc调用的图示:
malloc->brk()->SYSCALL_DEFINE1(brk, unsigned long, brk)->__get_free_pages()
SYSCALL_DEFINE1这个系统调用,将执行的状态从用户空间转向内核空间。