程序和進程
程序存儲在永久性介質上,被加載到內存後變成進程,兩者最大區別是程序包含加載的信息。進程時Linux的說法。
程序存儲空間
程序鏡像包含五類信息:頭部,程序正文(包括代碼和數據),重定位信息 ,符號 ,調試信息。
只要讓程序可以正常運行,其鏡像格式不限,甚至其內容可以和內存中一模一樣。比如下面一段程序:
org 07c00h ; 告訴彙編程序加載到7c00處,彙編器計算符號地址需要從0x7c00處開始
times 506-($-$$) db 0 ; 填充剩下的空間,使生成的二進制代碼恰好爲512字節
dw 0xeeee ; misc data
dw 0xffff ; misc data
dw 0xaa55 ; 結束標誌
編譯該程序並反彙編查看其指令,寫入floppy鏡像a.img作爲啓動盤
root@hy:/home/work/orange/source/chapter1/a# nasm -o empty.bin empty.asm
root@hy:/home/work/orange/source/chapter1/a# ll empty.bin
-rw-r--r-- 1 root root 512 3月 22 20:17 empty.bin
root@hy:/home/work/orange/source/chapter1/a# ndisasm -o 0x7c00 empty.bin | head -n 5
00007C00 0000 add [bx+si],al
00007C02 0000 add [bx+si],al
00007C04 0000 add [bx+si],al
00007C06 0000 add [bx+si],al
00007C08 0000 add [bx+si],al
root@hy:/home/work/orange/source/chapter1/a# ndisasm -o 0x7c00 empty.bin | tail -n 5
00007DF8 0000 add [bx+si],al
00007DFA EE out dx,al
00007DFB EE out dx,al
00007DFC FF db 0xff
00007DFD FF55AA call [di-0x56]
root@hy:/home/work/orange/source/chapter1/a# hexdump -C empty.bin
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000001f0 00 00 00 00 00 00 00 00 00 00 ee ee ff ff 55 aa |..............U.|
00000200
root@hy:/home/work/orange/source/chapter1/a# dd if=empty.bin of=a.img conv=notrunc
1+0 records in
1+0 records out
512 bytes copied, 0.000567204 s, 903 kB/s
使用bochs從a.img啓動並在0x7c00處斷住,查看empty.bin的內存分佈如下:
root@hy:/home/work/orange/source/chapter1/a# cbochs
========================================================================
Bochs x86 Emulator 2.6.8
Built from SVN snapshot on May 3, 2015
Compiled on Mar 17 2019 at 23:26:49
========================================================================
<bochs:3> x/8hb 0x7DF8
[bochs]:
0x0000000000007df8 <bogus+ 0>: 0x00 0x00 0xee 0xee 0xff 0xff 0x55 0xaa
<bochs:4> x/8hb 0x7c00
[bochs]:
0x0000000000007c00 <bogus+ 0>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
代碼生成的鏡像大小512字節,除了尾部數據,其餘全是0。被加載到內存後,它佔用的空間同樣512字節。並且和鏡像中的數據一模一樣。這是最原始的程序,沒有附加任何鏡像加載需要的信息。MS-DOS的.COM文件和這裏的empty.bin同屬這類文件。上面程序可以運行,是因爲這一條語句:
org 07c00h ; 告訴彙編程序加載到0x7c00處,彙編器計算符號地址需要從0x7c00處開始
作用相當於和加載它的程序約定好,自己需要在0x7c00處開始運行才能成功,間接告訴加載器自己的加載信息。
/*
TODO: 現代C程序中程序鏡像其餘組成字段的作用。
*/
進程地址空間
進程運行在內存上,其目標就是執行代碼段提供的指令,所有進程數據都爲代碼執行服務,包括初始化數據段、BSS段(block started by symbol)、堆、棧等,不同數據類型應用於程序的不同執行場景。
一個hello程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int adder(int a, int b, int c, int d,
int e, int f, int g, int h);
int adder(int a, int b, int c, int d,
int e, int f, int g, int h)
{
return a+b+c+d+e+f+g+h;
}
void main(void)
{
int sum;
char *s = malloc(10);
memcpy(s, "xxxxxxxxx", 9);
s[9] = '\0';
sum = adder(1,2,3,4,5,6,7,8);
printf("s=%s,sum=%d\n",s,sum);
while(1)
{}
}
地址空間分佈如下:
root@hy:/home/work/mm_struct# cat /proc/`pidof hello`/maps
55fc37444000-55fc37445000 r--p 00000000 08:15 921133 /home/work/mm_struct/hello
55fc37445000-55fc37446000 r-xp 00001000 08:15 921133 /home/work/mm_struct/hello
55fc37446000-55fc37447000 r--p 00002000 08:15 921133 /home/work/mm_struct/hello
55fc37447000-55fc37448000 r--p 00002000 08:15 921133 /home/work/mm_struct/hello
55fc37448000-55fc37449000 rw-p 00003000 08:15 921133 /home/work/mm_struct/hello
55fc375b3000-55fc375d4000 rw-p 00000000 00:00 0 [heap]
7efd40e24000-7efd40e46000 r--p 00000000 08:13 267705 /lib/x86_64-linux-gnu/libc-2.28.so
7efd40e46000-7efd40fb7000 r-xp 00022000 08:13 267705 /lib/x86_64-linux-gnu/libc-2.28.so
7efd40fb7000-7efd41003000 r--p 00193000 08:13 267705 /lib/x86_64-linux-gnu/libc-2.28.so
7efd41003000-7efd41004000 ---p 001df000 08:13 267705 /lib/x86_64-linux-gnu/libc-2.28.so
7efd41004000-7efd41008000 r--p 001df000 08:13 267705 /lib/x86_64-linux-gnu/libc-2.28.so
7efd41008000-7efd4100a000 rw-p 001e3000 08:13 267705 /lib/x86_64-linux-gnu/libc-2.28.so
7efd4100a000-7efd41010000 rw-p 00000000 00:00 0
7efd41030000-7efd41031000 r--p 00000000 08:13 267679 /lib/x86_64-linux-gnu/ld-2.28.so
7efd41031000-7efd41051000 r-xp 00001000 08:13 267679 /lib/x86_64-linux-gnu/ld-2.28.so
7efd41051000-7efd41059000 r--p 00021000 08:13 267679 /lib/x86_64-linux-gnu/ld-2.28.so
7efd41059000-7efd4105a000 r--p 00028000 08:13 267679 /lib/x86_64-linux-gnu/ld-2.28.so
7efd4105a000-7efd4105b000 rw-p 00029000 08:13 267679 /lib/x86_64-linux-gnu/ld-2.28.so
7efd4105b000-7efd4105c000 rw-p 00000000 00:00 0
7ffffb479000-7ffffb49a000 rw-p 00000000 00:00 0 [stack]
7ffffb5a3000-7ffffb5a6000 r--p 00000000 00:00 0 [vvar]
7ffffb5a6000-7ffffb5a8000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
進程的地址空間由低到高佈局如下:
正文段:可執行的正文程序,可共享,只讀。進程其餘所有數據都爲此服務。
初始化數據段:正文程序中明確賦值的變量,運行時會用到,所有初始化的全局變量放在這裏。初始化常量也放到這裏。
BSS段:由符號開始的塊,正文程序中聲明但沒有初始化的變量。這段被初始化爲0。
堆:程序運行過程中可能通過調用malloc()這樣的函數申請內存空間,Linux內核爲這類行爲預留了堆供其使用。如果堆中的數據持續增加,Linux內核可能會擴展堆的空間。
棧:數據先入後出,其特點決定程序中函數的遞歸調用,函數參數,局部變量的空間都從此分配。如果棧中的數據持續增加,Linux內核可能會擴展棧空間。
分析hello.c程序
數據分佈情況:
char *s = malloc(10);
malloc從堆中分配數據,所以應指向堆:
55fc375b3000-55fc375d4000 rw-p 00000000 00:00 0 [heap]
memcpy(s, "xxxxxxxxx", 9);
字符常量"xxxxxxxxx"應放在初始化的數據段:
55fc37446000-55fc37447000 r--p 00002000 08:15 921133 /home/work/mm_struct/hello
55fc37447000-55fc37448000 r--p 00002000 08:15 921133 /home/work/mm_struct/hello
/*
TODO: 上面兩段pgoff一樣,是否都爲初始化數據段?
*/
sum = adder(1,2,3,4,5,6,7,8);
x64架構函數調用使用“寄存器+棧”的方式傳參,前6個參數用rdi,rsi,rdx,rcx,r8,r9傳遞,第7個及以上用棧。所以參數7和8在adder調用入棧:
7ffffb479000-7ffffb49a000 rw-p 00000000 00:00 0 [stack]
正文段:
除了hello程序本身編譯產生的程序段,還包括bash加載時的ini程序段,Libc共享庫提供的可重定位程序段,都應放在正文段:
55fc37445000-55fc37446000 r-xp 00001000 08:15 921133 /home/work/mm_struct/hello
正文段屬性r-xp,表示可讀,可執行的私有段(private/share)
驗證:
gdb attach `pidof hello`
(gdb) info registers rip
rip 0x55fc37445219 0x55fc37445219 <main+132>
(gdb) disas main+132
Dump of assembler code for function main:
0x000055fc37445195 <+0>: push %rbp
0x000055fc37445196 <+1>: mov %rsp,%rbp // rbp指向棧頂
0x000055fc37445199 <+4>: sub $0x10,%rsp // rsp棧針下移16byte
0x000055fc3744519d <+8>: mov $0xa,%edi // 調用malloc做準備,參數10傳給edi
0x000055fc374451a2 <+13>: callq 0x55fc37445050 <malloc@plt> // 調用malloc,其地址在落在正文段中
0x000055fc374451a7 <+18>: mov %rax,-0x8(%rbp) // 返回參數s放入rax,保存到rbp-8的棧中,它指向的應該時heap中的地址
0x000055fc374451ab <+22>: mov -0x8(%rbp),%rax // 調用memcpy做準備,s->rax
0x000055fc374451af <+26>: mov $0x9,%edx // 9->edx
0x000055fc374451b4 <+31>: lea 0xe49(%rip),%rsi # 0x55fc37446004 // 字符串常量的地址落在數據段中 “xxxxxxxxx”->rsi
0x000055fc374451bb <+38>: mov %rax,%rdi // s->rdi
0x000055fc374451be <+41>: callq 0x55fc37445040 <memcpy@plt> // 同malloc
0x000055fc374451c3 <+46>: mov -0x8(%rbp),%rax
0x000055fc374451c7 <+50>: add $0x9,%rax
0x000055fc374451cb <+54>: movb $0x0,(%rax)
0x000055fc374451ce <+57>: pushq $0x8 // 調用adder做準備,參數入棧,rsp下移8byte
0x000055fc374451d0 <+59>: pushq $0x7 // rsp再下移8byte
0x000055fc374451d2 <+61>: mov $0x6,%r9d
0x000055fc374451d8 <+67>: mov $0x5,%r8d
0x000055fc374451de <+73>: mov $0x4,%ecx
0x000055fc374451e3 <+78>: mov $0x3,%edx
0x000055fc374451e8 <+83>: mov $0x2,%esi
0x000055fc374451ed <+88>: mov $0x1,%edi
0x000055fc374451f2 <+93>: callq 0x55fc37445155 <adder>
0x000055fc374451f7 <+98>: add $0x10,%rsp // 上移16byte
0x000055fc374451fb <+102>: mov %eax,-0xc(%rbp)
0x000055fc374451fe <+105>: mov -0xc(%rbp),%edx
0x000055fc37445201 <+108>: mov -0x8(%rbp),%rax
0x000055fc37445205 <+112>: mov %rax,%rsi
0x000055fc37445208 <+115>: lea 0xdff(%rip),%rdi # 0x55fc3744600e
0x000055fc3744520f <+122>: mov $0x0,%eax
0x000055fc37445214 <+127>: callq 0x55fc37445030 <printf@plt>
=> 0x000055fc37445219 <+132>: jmp 0x55fc37445219 <main+132>
cpu當前一直在程序末尾處循環,從頭分析,rbp最開始指向棧頂,rsp下移了16byte,之後調用malloc申請內存,返回值保存到rax,rax將其壓棧,所以(rbp-8)/(rsp+8)地址處存放的時s的值。之後rbp一直沒變,rsp由於調用adder函數下移16byte,函數返回後仍然保持原位置。所以直到最後的循環,(rbp-8)/(rsp+8)仍然保存了局部變量s的值:
End of assembler dump.
(gdb) info registers rsp // 查看rsp
rsp 0x7ffffb497240 0x7ffffb497240
(gdb) x/10xg 0x7ffffb497248 // 查看 0x8(%rsp)處的內存信息,這個值是個指向heap的指針。
0x7ffffb497248: 0x000055fc375b3260 0x000055fc37445220
0x7ffffb497258: 0x00007efd40e4809b 0x0000000000000000
0x7ffffb497268: 0x00007ffffb497338 0x0000000100040000
0x7ffffb497278: 0x000055fc37445195 0x0000000000000000
0x7ffffb497288: 0x665a784ccc3224d0 0x000055fc37445070
(gdb) x/1sb 0x000055fc375b3260 // 查看s指向的內容,印證了我們的分析
0x55fc375b3260: "xxxxxxxxx"
(gdb) info registers rbp
rbp 0x7ffffb497250 0x7ffffb497250
內核數據結構
三個關鍵數據結構: task_struct,mm_struct,vm_area_struct
/*
TODO:
- 各個數據結構關係介紹
- 操作各數據結構的接口
- 各數據結構的生命週期
- 實驗驗證
*/
內核工作原理
/*
TODO:
- 內核管理進程地址空間的工作流程
- 實驗驗證
*/
Linux進程空間管理實現原理
/*
TODO
- Intel段機制
- Intel頁機制
*/ - Intel段機制
-
GDT(Global Descriptor Table)
-