← Back to Index

 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这个系统调用,将执行的状态从用户空间转向内核空间。