一個程序經過編譯連接后形成的地址空間是一個虛擬地址空間,而Linux在內存尋址時簡化了分段
機制,使得虛擬地址與線性地址是一致的,比如程序test_wait.c代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int i;
unsigned char *buff;
buff = (char *)malloc(sizeof(char)*1024);
printf("pid is :%d\n", getpid());
for (i = 0; i < 60; i++) {
sleep(60);
}
return 0;
}
經過編譯后形成的文件是test_wait,然后用命令objdump反匯編后如下(只取部分代碼):
$ objdump -d test_wait
test_wait: file format elf32-i386
Disassembly of section .init:
08048304 <_init>:
8048304: 55 push %ebp
8048305: 89 e5 mov %esp,%ebp
8048307: 53 push %ebx
8048308: 83 ec 04 sub $0x4,%esp
804830b: e8 00 00 00 00 call 8048310 <_init+0xc>
8048310: 5b pop %ebx
8048311: 81 c3 e4 1c 00 00 add $0x1ce4,%ebx
8048317: 8b 93 fc ff ff ff mov -0x4(%ebx),%edx
804831d: 85 d2 test %edx,%edx
8048301: e8 2e 00 00 00 call 8048334 <__gmon_start__@plt>
8048306: e8 15 01 00 00 call 8048420
可以看到,其中的地址就是虛擬地址,整個虛擬地址空間大小為3GB,再加上可以通過系統調用進入
內核的1GB空間,于是每個進程可以擁有4GB的虛擬地址空間(也叫虛擬內存)。某個進程的虛擬地
址空間可以通過/proc文件系統看到:
$ ./test_wait
pid is :9840
重新開一個終端:
cat /proc/9840/maps
08048000-08049000 r-xp 00000000 08:01 212891 /home/chen/mem/test_wait
08049000-0804a000 r--p 00000000 08:01 212891 /home/chen/mem/test_wait
0804a000-0804b000 rw-p 00001000 08:01 212891 /home/chen/mem/test_wait
096d5000-096f6000 rw-p 096d5000 00:00 0 [heap]
b7dee000-b7def000 rw-p b7dee000 00:00 0
b7def000-b7f47000 r-xp 00000000 08:01 409724 /lib/tls/i686/cmov/libc-2.8.90.so
b7f47000-b7f49000 r--p 00158000 08:01 409724 /lib/tls/i686/cmov/libc-2.8.90.so
b7f49000-b7f4a000 rw-p 0015a000 08:01 409724 /lib/tls/i686/cmov/libc-2.8.90.so
b7f4a000-b7f4d000 rw-p b7f4a000 00:00 0
b7f59000-b7f5c000 rw-p b7f59000 00:00 0
b7f5c000-b7f76000 r-xp 00000000 08:01 392460 /lib/ld-2.8.90.so
b7f76000-b7f77000 r-xp b7f76000 00:00 0 [vdso]
b7f77000-b7f78000 r--p 0001a000 08:01 392460 /lib/ld-2.8.90.so
b7f78000-b7f79000 rw-p 0001b000 08:01 392460 /lib/ld-2.8.90.so
bf964000-bf979000 rw-p bffeb000 00:00 0 [stack]
關于此文件的詳細信息可以參看:
http://www.kerneltravel.net/?p=287
由上面的信息可以看到
08048000-08049000地址段的標志是r-xp(讀,執行)是代碼段,
08049000-0804a000的標志是rw-p(讀寫)是數據段
096d5000-096f6000是堆也叫空洞,只有當程序中調用malloc()申請空間時才有堆段。
bf964000-bf979000 是堆棧段
這樣我們可以看到進程的用戶空間的分配了。如下圖:
可以看出代碼段在最低地址依次往上是數據段,空洞、堆棧段在最高地址,棧指針向下移動。
進程的虛擬地址在保存在內核中的task_struct(PCB)結構中,定義如下:
struct task_struct { //進程結構體
//……
struct mm_struct *mm;//描述進程的整個用戶空間
}
而stuct mm_struct 結構中包含了虛擬空間的結構體字段
mmap(struct vm_area_struct * mmap),所以可以通過模塊編程來查看進程的虛擬地址空間。
關于模塊編程可以看這里:
http://www.kerneltravel.net/?p=80,程序清單如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
static int pid;
module_param(pid,int,0644);
static int __init memtest_init(void)
{
struct task_struct *p;
struct vm_area_struct *temp;
printk("My module worked!\n");
p = find_task_by_vpid(pid);
temp = p->mm->mmap;
while(temp) {
printk("start:%p\tend:%p\n", (unsigned long *)temp->vm_start,
(unsigned long *)temp->vm_end);
temp = temp->vm_next;
}
return 0;
}
static void __exit memtest_exit(void)
{
printk("Unloading my module.\n");
return;
}
module_init(memtest_init);
module_exit(memtest_exit);
MODULE_LICENSE("GPL");
編譯模塊,運行剛才的程序test_wait,然后帶參數插入模塊,如下:
$ ./test_wait &
pid is :9413
$ sudo insmod mem.ko pid=9413
[ 2690.715913] My module worked!
[ 2690.715992] start:08048000 end:08049000
[ 2690.716005] start:08049000 end:0804a000
[ 2690.717029] start:0804a000 end:0804b000
[ 2690.717065] start:096d5000 end:096f6000
[ 2690.717096] start:b7dee000 end:b7def000
[ 2690.717126] start:b7def000 end:b7f47000
[ 2690.717157] start:b7f47000 end:b7f49000
[ 2690.717187] start:b7f49000 end:b7f4a000
[ 2690.717217] start:b7f4a000 end:b7f4d000
[ 2690.717248] start:b7f59000 end:b7f5c000
[ 2690.717304] start:b7f5c000 end:b7f76000
[ 2690.717334] start:b7f76000 end:b7f77000
[ 2690.717364] start:b7f77000 end:b7f78000
[ 2690.717395] start:b7f78000 end:b7f79000
[ 2690.717425] start:bf964000 end:bf979000
可以看出和剛才/proc文件系統中的地址是一樣的。
在任意一個時刻,一個CPU只有一個進程在運行,所以雖然有時候很多進程的虛擬地址值有相同的
,但是由于每次只有一個進程運行,在當某個進程運行時cpu就將其虛擬地址也切換進來,這樣就保
證了每個進程都擁有4GB的地址空間。