哈工大 计算机系统大作业 程序人生-Hello’s P2P From Program to Process
計算機系統
?
大作業
題 ????目 ?程序人生-Hello’s P2P?
專?????? 業 ??計算學部?????????????
學 ?? 號 ??120L020512??????????
班 ?? 級 ??2003004?????????????
學?????? 生 ? ? ? 黃鵬程? ? ? ??
指 導 教 師 ????史先俊?????????
計算機科學與技術學院
2022年5月
摘? 要
??? 本次大作業旨在通過對hello程序生命歷程中各個環節的實驗與分析,將計算機系統課程的整體知識進行串聯與復現,從而加深對課程內容的理解。
關鍵詞:計算機;匯編;進程;存儲管理;??????????????????????
目? 錄
第1章 概述................................................................................................................ - 4 -
1.1 Hello簡介......................................................................................................... - 4 -
1.2 環境與工具........................................................................................................ - 4 -
1.3 中間結果............................................................................................................ - 4 -
1.4 本章小結............................................................................................................ - 5 -
第2章 預處理............................................................................................................ - 6 -
2.1 預處理的概念與作用........................................................................................ - 6 -
2.2在Ubuntu下預處理的命令............................................................................. - 6 -
2.3 Hello的預處理結果解析................................................................................. - 6 -
2.4 本章小結............................................................................................................ - 7 -
第3章 編譯................................................................................................................ - 9 -
3.1 編譯的概念與作用............................................................................................ - 9 -
3.2 在Ubuntu下編譯的命令................................................................................ - 9 -
3.3 Hello的編譯結果解析..................................................................................... - 9 -
3.4 本章小結.......................................................................................................... - 14 -
第4章 匯編.............................................................................................................. - 15 -
4.1 匯編的概念與作用.......................................................................................... - 15 -
4.2 在Ubuntu下匯編的命令.............................................................................. - 15 -
4.3 可重定位目標elf格式.................................................................................. - 15 -
4.4 Hello.o的結果解析........................................................................................ - 17 -
4.5 本章小結.......................................................................................................... - 18 -
第5章 鏈接.............................................................................................................. - 19 -
5.1 鏈接的概念與作用.......................................................................................... - 19 -
5.2 在Ubuntu下鏈接的命令.............................................................................. - 19 -
5.3 可執行目標文件hello的格式...................................................................... - 19 -
5.4 hello的虛擬地址空間................................................................................... - 21 -
5.5 鏈接的重定位過程分析.................................................................................. - 22 -
5.6 hello的執行流程........................................................................................... - 24 -
5.7 Hello的動態鏈接分析................................................................................... - 25 -
5.8 本章小結.......................................................................................................... - 26 -
第6章 hello進程管理....................................................................................... - 27 -
6.1 進程的概念與作用.......................................................................................... - 27 -
6.2 簡述殼Shell-bash的作用與處理流程........................................................ - 27 -
6.3 Hello的fork進程創建過程......................................................................... - 28 -
6.4 Hello的execve過程..................................................................................... - 28 -
6.5 Hello的進程執行........................................................................................... - 28 -
6.6 hello的異常與信號處理............................................................................... - 29 -
6.7本章小結.......................................................................................................... - 31 -
第7章 hello的存儲管理................................................................................... - 32 -
7.1 hello的存儲器地址空間............................................................................... - 32 -
7.2 Intel邏輯地址到線性地址的變換-段式管理............................................... - 32 -
7.3 Hello的線性地址到物理地址的變換-頁式管理......................................... - 33 -
7.4 TLB與四級頁表支持下的VA到PA的變換................................................ - 34 -
7.5 三級Cache支持下的物理內存訪問............................................................. - 34 -
7.6 hello進程fork時的內存映射..................................................................... - 35 -
7.7 hello進程execve時的內存映射................................................................. - 36 -
7.8 缺頁故障與缺頁中斷處理.............................................................................. - 36 -
7.9動態存儲分配管理.......................................................................................... - 36 -
7.10本章小結........................................................................................................ - 37 -
第8章 hello的IO管理.................................................................................... - 38 -
8.1 Linux的IO設備管理方法............................................................................. - 38 -
8.2 簡述Unix IO接口及其函數.......................................................................... - 38 -
8.3 printf的實現分析........................................................................................... - 38 -
8.4 getchar的實現分析....................................................................................... - 40 -
8.5本章小結.......................................................................................................... - 40 -
結論............................................................................................................................ - 40 -
附件............................................................................................................................ - 42 -
參考文獻.................................................................................................................... - 43 -
第1章 概述
1.1 Hello簡介
P2P:
?????? 如圖1.11,為c語言代碼源文件,即hello.c變成可執行文件hello的過程。預處理器對源文件進行宏替換、條件編譯的預處理操作后,生成hello.i文件;.i文件檢查語法后生成匯編文件hello.s;匯編文件經過匯編被轉換為機器碼,生成可重定位文件hello.o;然后連接器將源代碼中用到的庫函數與可重定位文件合并為可執行文件hello;我們在shell中鍵入命令后,其fork子進程、調用execve加載hello并運行。
?
?????????????????????? 圖1.11 ?P2P過程
O2O:
?????? 在shell中fork子進程后調用execve加載并執行hello,分配虛擬內存空間并映射到物理內存;隨后依照CPU中邏輯控制流開始執行;在程序結束后,shell通過hello父進程或祖先進程將其回收,釋放內存空間。
1.2 環境與工具
硬件環境:處理器Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz?? 2.30 GHz;RAM 8GB; 系統類型:系統類型:64位操作系統,基于x64的處理器;
????????????????????
軟件環境:Windows10 64位;Ubuntu 20.04
開發與調試工具:gcc,as,ld,vim,edb,readelf,VS
1.3 中間結果
hello.i:預處理得到的文本文件
hello.s:編譯得到的匯編文件
hello.o:匯編得到的可重定位目標文件
hello:鏈接得到的可執行文件
objdump_hello.s:hello反匯編得到的代碼
1.4 本章小結
本章在對P2P、O2O的介紹中概括了hello從誕生到執行再到死亡的過程;給出本次作業的實驗環境與用到的中間結果。
第2章 預處理
2.1 預處理的概念與作用
概念:
程序設計領域中,預處理一般是指在程序源代碼被翻譯為目標代碼的過程中,生成二進制代碼之前的過程。預處理中會展開以#起始的行,試圖解釋為預處理指令。包括#if/#ifdef/#ifndef/#else/#elif/#endif(條件編譯)、#define(宏定義)、#include(源文件包含)、#line(行控制)、#error(錯誤指令)、#pragma(和實現相關的雜注)以及單獨的#(空指令)。預處理指令一般被用來使源代碼在不同的執行環境中被方便的修改或者編譯。
作用:
將源文件中用#include形式聲明的文件復制至新的程序中;
用實際值替換用#define 定義的字符串,即將宏定義進行替換;
2.2在Ubuntu下預處理的命令
gcc -m64 -no-pie -fno-PIC hello.c -E -o hello.i如下圖2.21,生成文件如圖2.22;
?
?
??????????????? ?????圖2.21 執行命令
?????????? ?圖2.22 生成文件
2.3 Hello的預處理結果解析
打開hello.i,發現程序已經擴展為3060行,如圖2.31,hello.c中main函數代碼出現在3047行以后,在此之前的代碼為.c源文件中含有三個庫:#include <stdio.h>、#include <unistd.h>、#include <stdlib.h>的展開,將頭文件的內容插入到該命令所在的位置,從而把頭文件和當前源文件連接成一個源文件,如圖2.32。
?
?
?? ????????????????圖2.31 main函數
?
????????????????? 圖2.32 預處理#include
2.4 本章小結
本章使用gcc -m64 -no-pie -fno-PIC hello.c -E -o hello.i將hello.c預處理為hello.i,發現hello.i中插入了大量源文件包含的庫文件
第3章 編譯
3.1 編譯的概念與作用
概念:
編譯是指編譯器做詞法分析、語法分析、語義分析等,在檢查無錯誤后,將代碼翻譯成匯編語言的過程。?編譯器將文本文件 hello.i 翻譯成文本文件 hello.s。
作用:
注意:這兒的編譯是指從 .i 到 .s 即預處理后的文件到生成匯編語言程序
???????
3.2 在Ubuntu下編譯的命令
gcc -m64 -no-pie -fno-PIC -S hello.i -o hello.s
?
????????????????? 圖3.21 編譯的命令
?
?????????????????? 圖3.22 生成hello.s
3.3 Hello的編譯結果解析
3.3.1 數據
數字:如圖3.11,3.12源文件中的常量4與8被作為立即數保存在圖3.13、3.14中,hello.s的代碼節.data中;
?
??????????????????? ??圖 3.11
圖 3.12
?
?
???? ?圖 3.13 立即數4
?
?圖 3.14 立即數7(借助7來判斷小于8)
字符串:如圖3.15,源文件中的字符串"用法: Hello 學號 姓名 秒數!\n"被保存在.rodata節中,因為其為只讀字符串;
?
????????????????????????????????? ?? 圖 3.15 保存在.rodata的字符串
局部變量:源文件中使用局部變量i:
???????????????????????????????????????????????? for ( i = 0; i < 8 ; i++ )
被保存在棧中%rsp-4位置,如圖3.16,該位置每次加1后與7進行比較,依此決定是否再次進入循環。
?
圖 3.16 寄存器中的局部變量i
3.3.2 賦值
上文中,局部變量i被賦初值為0,而我們已經知道了它的存儲位置,故容易找到其在hello.s中的賦值語句,如圖3.21
?
圖 3.21 ?i賦初值為0
????????????
3.3.2 算術操作
對于上文提到的循環,步長為1,每次i自增1,易找到其在hello.s中的操作如圖3.22
?
???? ??圖 3.22 ?i++
3.3.3 關系操作、控制轉移
源文件中出現了兩次關系判斷,如圖3.31中的13行、17行:
?
?????????? ??圖 3.31
第13行在hello.s中對應操作如圖3.32,為argc與4進行比較,若相等則進行跳轉操作:
?
??? 圖 3.32 判斷相等與跳轉操作
上圖中,24行為argc與4進行比較,比較的結果保存在寄存器中,25行je根據比較結果決定是否跳轉到.L2;
?
? 圖 3.33 ?循環中i的比較與跳轉操作
3.3.4 數組/指針/結構操作
指針數組char *argv[ ] 首地址保存在-32(%rbp)位置,如圖3.41,print函數打印argv[1]與argv[2],則在第35、38行分別將數組首地址加上偏移獲得數組元素;
?
???????????? 圖 3.41
3.3.5 函數操作
參數傳遞:第1~6個參數儲存在%rdi、%rsi、%rdx、%rcx、%r8、%r9這六個寄存器中,剩下的參數保存在棧當中。
調用函數:每個函數的每次調用,都有它自己獨立的一個棧幀,這個棧幀中維持著所需要的各種信息。寄存器%ebp指向當前的棧幀的底部(高地址),寄存器%esp指向當前的棧幀的頂部(低地址)。調用函數的棧底將會被保存,而棧頂將作為被調用函數的棧底。
函數返回:函數返回值保存在%ax中。
下對hello.s中函數分析:
參數傳遞:傳入參數argc和argv[],分別用寄存器%rdi和%rsi存儲
函數調用:C程序總是從mian函數開始執行
函數返回:結束時更改%eax為0
?
??? 圖 3.51 main入口
?
??? 圖 3.52 main出口
參數傳遞: call printf時傳入了 argv[1]和argc[2]的地址
?
圖 3.53 調用printf時傳入的argv[1]和
argc[2]保存在%rdx、%rsi中
參數傳遞:將%edi 設置為 1
參數傳遞:將%rdi 設置為 argv[3]
參數傳遞:將%edi 設置為atoi處理后的argv[3]
參數傳遞:將%edi 設置為 1,執行exit(1)
3.4 本章小結
編譯是指編譯器做詞法分析、語法分析、語義分析等,在檢查無錯誤后,將代碼翻譯成匯編語言的過程。本章對hello.i編譯后得到的hello.s進行分析,探究了編譯器處理C語言的各個數據類型以及各類操作的過程。
第4章 匯編
4.1 匯編的概念與作用
概念:把匯編語言翻譯成機器語言的過程稱為匯編。
作用:用匯編語言編寫的程序,機器不能直接識別,要由一種程序將匯編語言翻譯成機器語言,這種起翻譯作用的程序叫匯編程序。
4.2 在Ubuntu下匯編的命令
gcc -m64 -no-pie -fno-PIC hello.s -c -o hello.o
?
???????????????? 圖4.21 匯編的命令
?
?????????????? 圖 4.22 匯編得到hello.o
4.3 可重定位目標elf格式
readelf -a hello.o > helloo.elf 生成文本文件
ELF頭:
以16B的序列 Magic 開始,Magic描述了生成該文件的系統的字的大小和字節順序,ELF頭剩下的部分包含幫助鏈接器語法分析和解釋目標文件的信息,其中包括 ELF 頭的大小、目標文件的類型、機器類型、 字節頭部表(section header table)的文件偏移,及節頭部表中條目的大小和數量等信息。根據頭文件的信息,可以知道該文件是可重定位目標文件,有14個節。
?
????????? ????圖 4.31 ELF頭
節頭:
?????? 描述了.o文件中出現的各個節的類型、位置、所占空間大小等信息。
?
?????????????? ?圖 4.32 節頭
重定位節:
?????? 當匯編器生成一個目標模塊時,它并不知道數據和代碼最終將放在內存中的什么位置。它也不知道這個模塊引用的任何外部定義的函數或者全局變量的位置。所以,無論何時匯編器遇到最終未知未知的目標引用,它就會生成一個重定位條目,告訴鏈接器在將目標文件合并到可執行文件時如何修改新的引用。
?????? 本程序需要重定位的信息有:.rodata中的模式串,puts,exit,printf,slepsecs,sleep,getchar這些符號。
????????????????? ??圖 4.33 重定位節
符號表:
?????? 符號表中存放程序定義和引用的函數和全局變量的信息。但其中不包含局部變量條目。
?
?
??????????????? 圖 4.34 符號表
??? 分析hello.o的ELF格式,用readelf等列出其各節的基本信息,特別是重定位項目分析。
4.4 Hello.o的結果解析
?
?
???????????????????????? ?????? 圖 4.41 hello.o反匯編
將其與hello.s進行對照分析:
- 操作數:hello.s中操作數為十進制,而反匯編代碼中為十六進制;
- 分支轉移:hello.s中地址使用段名稱如 je .L2,而反匯編代碼中則使用相對偏移地址,如 je 2d <main+0x2d>;
- 函數調用:hello.s中,call指令使用的是函數名稱,反匯編代碼中call指令使用相對偏移地址。原因是hello.s中調用的函數都是共享庫中的函數,故需要通過等待調用動態鏈接將重定位的函數目標地址鏈接到共享庫程序中,最終需通過動態鏈接器確定函數的運行時地址;
- 除上述以外,二者沒有什么不同,這表明了匯編語言能與機器碼建立一一對應的關系。
4.5 本章小結
把匯編語言翻譯成機器語言的過程稱為匯編,hello.s匯編得到hello.o后,我們閱讀了ELF文件,然后通過hello.o反匯編得到的代碼與hello.s進行比較,發現機器碼與匯編語言的映射關系,以及它相對匯編語言所具有的特點。
第5章 鏈接
5.1 鏈接的概念與作用
概念:鏈接是將各種代碼和數據片段收集并組合成一個單一文件的過程,這個文件可被加載(復制)到內存并執行。
作用:節省源程序空間;將可重定位文件連接成為可執行文件。
5.2 在Ubuntu下鏈接的命令
ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o
?
??????????????????? 圖 5.21 鏈接的命令
?
?? ?圖 5.22 生成可執行文件
5.3 可執行目標文件hello的格式
?ELF文件頭:
??? ?????????
?
??????????????????????? 圖5.31 ELF文件頭
節頭:
節頭部表中包含了hello中所有節的信息,其中包括名稱、類型、大小、地址和偏移量等信息,其中地址為程序被載入到虛擬地址的起始地址,偏移量為各個節在程序中的偏移量。根據節頭部表的信息可以使用HexEdit定位各個節的起始位置及大小。
?
???? ????圖5.32 節頭
程序頭:
?????? elf可執行文件易加載到內存,可執行文件連續的片被映射到連續的內存段,程序頭部表描述了這一映射關系。程序頭部表包括各程序頭的偏移量、內存地址、對其要求、目標文件與內存中的段大小及運行時訪問權限等信息。
????? ??????圖5.33 程序頭
重定位節:
?????? 重定位節包含.text節中一些需對目標進行重定位的函數信息,鏈接器把函數的目標位置文件與其他目標文件組合在一起時,需要修改這些函數的位置。
?
?????????? ?????圖5.34 重定位節
5.4 hello的虛擬地址空間
在edb的memory regions窗口中,可以看到hello的虛擬地址空間,如圖5.41,由0x400000到0x405000;
?
???????????????????????????????????????????????? 圖5.41 memory regions窗口
edb加載hello后, Data Dump 窗口可以查看加載到虛擬地址中的 hello 程序,如圖5.42;
?
?????????? ??圖5.42 Data Dump 窗口
根據5.3中的節頭部表中的地址,可在edb中找到各個節。例如:.text節的地址為0x4010f0,大小為0x145,用edb查找結果如圖5.43;
?
??????????? ????圖5.43
.data節的地址為0x404040,大小為0x4,如圖5.44
?
??????? 圖5.44
.rodata節的地址為0x402000,大小為0x3b,如圖5.45
??????????????? 圖5.45
5.5 鏈接的重定位過程分析
使用命令:objdump -d -r hello >helloobjdump.txt,獲得hello的反匯編代碼,如圖5.51;
?
?????????? ?????圖5.51 hello反匯編
對照hello與hello.o,不同點有:
?
?????? 圖5.52 ?hello的main函數
?
?????????????????????????? ??? ????? 圖5.53? .init節.plt段
鏈接過程:
?????? 經以上分析可知,鏈接就是將多個可重定位目標文件合并到一起,生成可執行文件。鏈接需要進行符號解析、重定位及計算符號引用地址三個步驟。
重定位:
重定位將合并輸入模塊。并為每個符號分配運行地址。重定位由兩個步驟組成:重定位節與符號定義、重定位節中的符號引用。
定位節與符號定義,鏈接器將相同類型的節合并為同一類型的新的聚合節,此后鏈接器將運行時內存地址賦值新的聚合節、輸入模塊定義的每個節,還有輸入模塊定義的每個符號。
重定位節中的符號引用,鏈接器修改代碼節與數據節中對每個符號的引用,使他們指向正確的運行地址,這一步依賴hello.o中的重定位條目。
5.6 hello的執行流程
Edb逐步執行并記錄調用的函數,如圖5.61
?
???????????? 圖5.61 即將調用hello!_init
得到調用函數順序如下:
ld-2.27.so!_dl_start
ld-2.27.so!_dl_init
hello!_start
libc-2.27.so!__libc_start_main
-libc-2.27.so!__cxa_atexit
-libc-2.27.so!__libc_csu_init
hello!_init
libc-2.27.so!_setjmp
-libc-2.27.so!_sigsetjmp
–libc-2.27.so!__sigjmp_save
hello!main
hello!puts@plt
hello!exit@plt
hello!printf@plt
hello!atoi@plt
hello!sleep@plt
hello!getchar@plt
ld-2.27.so!_dl_runtime_resolve_xsave -ld-2.27.so!_dl_fixup
–ld-2.27.so!_dl_lookup_symbol_x
libc-2.27.so!exit
5.7 Hello的動態鏈接分析
在elf文件中可以找到動態鏈接調用的函數的位置,如圖5.71:
?
??????????????? ?圖5.71 函數位置
??????????
動態鏈接調用的函數的位置為0x404000,進入edb內存窗口查看:
?
圖5.71 ?init之前
?
圖5.72 ?init之后
對于變量而言,利用代碼段和數據段的相對位置不變的原則計算得到正確地址。對于庫函數而言,需要plt與got合作,plt初始存的是一批代碼,它們跳轉到got所指示位置,接著調用鏈接器。初始時got里面存的都為plt的第二條指令,隨后鏈接器會修改got,當下一次再次調用plt時,指向的就是正確的內存地址。plt就能跳轉到正確的區域。
5.8 本章小結
本章分析了hello的鏈接過程,hello.o經過鏈接生成可執行文件hello,通過對比hello反匯編文件與hello.o之間的差別,我們可以總結出重定位的一些特點。在edb中逐步調試hello,我們能看到hello逐個調用的函數。
第6章 hello進程管理
6.1 進程的概念與作用
概念:
進程是計算機中的程序關于某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。
作用:
(1) 給程序創造這樣的假象: 我們的程序好像是系統中當前運行的唯一程序一樣,我們的程序好像是獨占的使用處理器和內存。
(2) 處理器好像是無間斷的執行我們程序中的指令,我們程序中的代碼和數據好像是系統內存中唯一的對象。
6.2 簡述殼Shell-bash的作用與處理流程
作用:shell是系統的用戶界面,它接收并解釋用戶輸入的命令,再將其送入內核執行。
處理流程:
若為內置命令則立即執行;
否則將命令行的參數改造為系統調用execve()內部處理所要求的形式終端進程調用fork()來創建子進程,自身則用系統調用wait()來等待子進程完成;
6.3 Hello的fork進程創建過程
父進程在讀取命令后,首先判斷該命令是否為內置命令,若非內置命令,則會調用fork命令創建子進程。子進程除PID外,與父進程完全一致,獲得與父進程虛擬地址空間相同但獨立的副本,其用戶棧、寄存器、代碼段等也與父進程一致,子進程可以讀寫父進程打開的任何文件。Fork函數在父進程中返回子進程PID,在子進程中則返回0。
6.4 Hello的execve過程
當創建了一個新運行的子進程后,子進程調用execve函數在當前子進程的上下文中加載并運行hello程序。execve函數加載并運行可執行目標文件hello,且帶參數列表argv和環境變量列表envp。只有當出現錯誤時,例如找不到hello,execve才會返回到調用程序。所以execve調用一次且從不返回。
argv是一個參數字符串指針數組,argv[0]是可執行目標文件的名字,而envp的元素則指向環境變量。
6.5 Hello的進程執行
進程給hello程序提供的關鍵抽象:
- 一個獨立的邏輯控制流,它提供一個假象,好像我們的進程獨占的使用處理器。
- 一個私有的地址空間,它提供一個假象,好像我們的程序獨占的使用內存系統。
上下文信息:由程序正確運行所需的狀態組成的,它包括存放在內存中的代碼與數據,它的棧、通用目的寄存器的內容、程序計數器、環境變量以及打開文件描述符的集合;
進程時間片:多個邏輯控制流并發執行時,其中一個進程執行它的控制流的一部分的一個時間段叫做一個時間片;
用戶模式和內核模式:處理器通常使用一個寄存器提供兩種模式的區分,該寄存器描述了進程當前享有的特權,當沒有設置模式位時,進程就處于用戶模式中,用戶模式的進程不允許執行特權指令,也不允許直接引用地址空間中內核區內的代碼和數據;設置模式位時,進程處于內核模式,該進程可以執行指令集中的任何命令,并且可以訪問系統中的任何內存位置。
進程調度過程:
以hello為例,作為一個獨立的進程與其他進程并發執行,內核為hello維持一個上下文,在hello的某個時間片內,若內核判斷它已經運行了足夠長的時間,那么內核可以決定搶占hello進程,并重新開始一個之前被搶占了的進程,并使用上下文切換的機制將控制轉移到新的進程,該機制具體執行分為三步:1)保存當前進程的上下文,2)恢復被搶占進程被保存的上下文,3)將控制轉移給這個新的進程;這樣,內核就完成了對hello與其他進程的調度。
用戶態與核心態轉換:
?????? Hello初始是在用戶模式中的,進程從用戶模式變為內核模式的唯一方法是通過諸如中斷、故障或者陷入系統調用這樣的異常。當hello執行過程中異常發生時,如鍵盤按下ctrl-c,控制傳遞到異常處理程序,處理器將用戶模式變為內核模式。處理器運行在內核模式中,當他返回到用戶代碼時,處理器就把模式從內核模式改回用戶模式,以上。
6.6 hello的異常與信號處理
下表為hello運行過程中可能出現的異常種類:
| 異常類別 | 可能的誘發原因 | 處理方式 |
| 中斷 | 收到I/O設備的信號,如鍵盤輸入 | 處理器讀取異常號,調用中斷處理程序,返回下一條指令 |
| 陷阱 | Hello的父進程執行syscall指令fork一個hello | 陷阱處理程序在內核態中完成fork工作,返回syscall之后的指令 |
| ????? 故障 | 缺頁異常 | 缺頁異常處理程序從磁盤加載適當的頁面,然后重新執行當前指令 |
| ????? 終止 | 出現DRAM或SRAM位損壞的奇偶錯誤 | Abort例程終止該程序 |
????????????????????????????????????????? ? 表6.61 ?hello執行中出現的異常
Hello執行中輸入命令及其運行結果:
亂按:
?
?
??????????? ??圖6.61? 亂按輸入結果
如圖6.61,將屏幕的輸入緩存到緩沖區。亂碼被認為是命令,并自動執行光標選中行;
Ctrl-Z + 其他命令:
?????? ??圖6.62 ?Ctrl-Z + ps、jobs、fg
如圖6.62,按下CtrlZ后hello停止運行,并輸出其作業號為1,此時輸入ps,屏幕輸出三個進程號及其名字;然后輸入jobs,屏幕輸出作業信息;最后輸入fg加上hello的作業號1,hello繼續執行。
?
圖6.63 Ctrl-Z + Ctrl-C、kill
如圖6.63,hello運行中按下ctrl C,然后輸入ps,并未發現hello,說明其已經結束;然后再次運行hello,輸入ctrl Z,使用ps得知hello進程號為2621,然后再輸入kill -9 2621殺死hello,此時ps發現已找不到hello,證明其被殺死。
6.7本章小結
本章對進程展開研究,hello在處理器中可能與其他進程并發執行,這就會涉及進程調度與上下文維護。進程運行過程中可能會發生各種異常,針對不同的異常有著不同的處理方式,但都是在內核態完成對異常的處理。進程同樣會對信號做出響應,例如在shell中輸入crtl z時hello會停止運行,而輸入fg命令又會恢復其運行狀態。
第7章 hello的存儲管理
7.1 hello的存儲器地址空間
邏輯地址:
?????? 邏輯地址即程序中的段地址,邏輯地址由兩部份組成,段標識符和段內偏移量。段標識符是由一個16位長的字段組成,稱為段選擇符。其中前13位是一個索引號。
線性地址:
?????? 線性地址是邏輯地址到物理地址之間的一個中間層變換,程序代碼會產生邏輯地址,或者說是段中的偏移地址,加上相應段的基地址就生成了一個線性地址。如果啟用了分頁機制,那么MMU內存管理單元會在內存映射表里尋找與線性地址對應的物理地址。若沒有啟用分頁機制,那么線性地址直接就是物理地址。
虛擬地址:
虛擬地址是CPU保護模式下的一個概念,保護模式是80286系列和之后的x86兼容CPU操作模式,在CPU引導完操作系統內核后,操作系統內核會進入一種CPU保護模式,也叫虛擬內存管理,在這之后的程序在運行時都處于虛擬內存當中,虛擬內存里的所有地址都是不直接的,所以你有時候可以看到一個虛擬地址對應不同的物理地址,比如hello進程里的call函數入口虛擬地址是0x001,而另一個進程也是,但是它倆對應的物理地址卻是不同的,操作系統采用這種內存管理方法
物理地址:
?????? 物理地址就是內存中每個內存單元的編號,這個編號是順序排好的,物理地址的大小決定了內存中有多少個內存單元,物理地址的大小由地址總線的位寬決定。
7.2 Intel邏輯地址到線性地址的變換-段式管理
段偏移量加上基地址的和,構成線性地址。其中,段偏移量為邏輯地址的組成部分;基地址存儲在段描述符表中,該表存儲有多個描述符,每個描述符都描述了某個段的起始位置與大小等信息;而邏輯地址中的另一部分:段標識符的高13位為段選擇符,段選擇符能對應上段描述表中的一個描述符。
綜上,邏輯地址到線性地址的變換過程為,取邏輯地址的段標識符中的段選擇符,到段描述表中找到對應的描述符,描述符中存有段開始的線性地址,即段基址;段基址加上邏輯地址中的段偏移量就是線性地址。
7.3 Hello的線性地址到物理地址的變換-頁式管理
線性地址即hello程序虛擬地址空間中的虛擬地址,虛擬內存空間與物理內存空間都被劃分為頁,與頁號相對應。虛擬地址由虛擬頁號 + 虛擬頁偏移量組成。頁表是建立虛擬頁號與物理頁號映射關系的表結構,頁表項包含有有效位、物理頁號、磁盤地址等信息。如下圖7.31
?
? ??圖7.31 物理內存與虛擬內存在頁表上的對應
??????????????? ??? (圖片來源CSDN)
虛擬頁號 + 頁表起始地址能找到相對應的頁表項,頁表起始地址存儲在頁表基址寄存器中,頁表項存儲的頁表狀態有三種:未分配,已緩沖,未緩沖。當對應狀態為已緩沖時,說明虛擬頁所對應的物理頁已經存儲在內存中,此時頁表項存儲的物理頁號 + 物理頁偏移量即為物理地址,而物理頁偏移量與虛擬頁偏移量相同,可以從虛擬地址中直接得出。頁表項狀態為已緩沖時,對應頁式管理過程如下圖7.32
?
?圖7.32 頁表項狀態為已緩沖的頁式管理過程
???????? ???? ???? (圖片來源CSDN)
當頁表項中狀態為未緩存時,若要讀取該頁,會引發缺頁中斷異常,缺頁異常處理程序根據頁置換算法,選擇出一個犧牲頁,如果這個頁面已經被修改了,則寫出到磁盤上,最后將這個犧牲頁的頁表項有效位設置為0,存入磁盤地址。缺頁異常程序處理程序調入新的頁面,如果該虛擬頁尚未分配磁盤空間,則分配磁盤空間,然后磁盤空間的頁數據拷貝到空閑的物理頁上,并更新頁表項狀態為已緩存,更新物理頁號,缺頁異常處理程序返回后,再回到發生缺頁中斷的指令處,重新按照頁表項命中的步驟執行。
7.4 TLB與四級頁表支持下的VA到PA的變換
多級頁表可以減小翻譯地址時的時間開銷。多級頁表中,頁表基址寄存器存儲一級頁表的地址,1到3的頁表的每一項存儲的下一級頁表的起始地址,4級頁表的每一項存儲的是物理頁號或磁盤地址。解析VA時,其前m位vpn1尋找一級頁表中的頁表項,接著一次重復k次,在第k級頁表獲得了頁表條目,將PPN與VPO組合獲得物理地址PA。
7.5 三級Cache支持下的物理內存訪問
高速緩存的組織方式如圖7.51,高速緩存的結構將地址劃分成了t個標記位、s個組索引位和b個塊偏移位。當cpu執行一條讀內存字w的指令時,它會首先向一級cache請求這個字,如果緩存命中,那么高速緩存會很快將該字返回給cpu,若不命中,則向下一級緩存發起請求。
?
圖7.51 高速緩存組織方式
以組相聯高速緩存為例,判斷緩存是否命中,然后取出字的過程分為三步:
高速緩存從w的地址中抽取出s個組索引位,這些位被解釋為一個對應于一個組號的無符號整數,用于在緩存中進行組選擇。
確定了緩存中的組i后,緩存將搜索組中的每一行,直到某行標記位與地址中的標記位一致,如果能找到這樣的一行,那么即為命中。
如果w不在組中的任何一行,那么就是緩存不命中,緩存會從下一級存儲空間(例如L1的下一級為L2)中取出包含這個字的塊,并依照特定的行替換策略將該行放入緩存中,行替換策略保證被替換行的被引用概率最低。
?在命中的行中,使用塊偏移選中字w返回給cpu。
7.6 hello進程fork時的內存映射
當fork函數被父進程調用時,內核為子進程創建各種數據結構,并分配它唯一的一個PID。為給這個新進程創建虛擬內存,它創建當前進程的mm_struct、區域結構與頁表的原樣副本;它將兩個進程中的每個頁面都標記為只讀,并把兩個進程中的每個區域結構都標記為私有的寫時復制。
當fork在子進程中返回時,其現在的虛擬內存剛好和調用fork時存在的虛擬內存相同。當這兩個進程的任一者進行后續寫操作時,寫時復制機制就會創建新頁面,也就為每個進程保持私有地址空間的抽象概念。
7.7 hello進程execve時的內存映射
在子進程中調用execve函數加載hello時,將完成以下工作
1)刪除已存在的用戶區域。
2)映射私有區域
3)映射共享區域
4)設置程序計數器(PC)
?
?
?????????????????? 圖7.71 虛擬內存空間(來源CSDN)
最后exceve設置當前進程的上下文中的程序計數器,指向代碼區域的入口點。而下一次調度這個進程時,他將從這個入口點開始執行。Linux將根據需要換入代碼和數據頁面。
7.8 缺頁故障與缺頁中斷處理
當指令引用一個地址,而與該地址相應的物理頁面不在內存中,即PTE中的有效位是0,所以MMU出發了一次異常,會觸發缺頁故障,內核調用缺頁處理程序。通過查詢頁表PTE可以知該頁在磁盤的位置。缺頁處理程序從指定的地址加載頁面到物理內存中,然后更新PTE。再將控制返回給引起缺頁故障的指令。當該指令再次執行時,相應的物理頁面已加載在內存中,因此能夠命中。
7.9動態存儲分配管理
所有動態申請的內存都存在堆上面,用戶通過保存在棧上面的一個指針來使用該內存空間。動態內存分配器維護著堆,堆頂指針是brk。有兩種風格,一種叫顯式分配器,使用兩個函數,malloc和free,分別用于執行動態內存分配和釋放。
malloc的作用是向系統申請分配堆中指定size個字節的內存空間。也就是說函數返回的指針為指向堆里的一塊內存。并且,操作系統中有一個記錄空閑內存地址的鏈表。當操作系統收到程序申請時,就會遍歷該鏈表,然后尋找第一個空間大于所申請空間的堆結點,將該結點從空閑結點鏈表中刪除后,將該結點的空間分配給到程序。在使用malloc()分配內存空間后,需釋放內存空間,否則就會出現內存泄漏。
free()釋放的是指針指向的內存,而不是指針。指針并沒有被釋放,它仍然指向原來的存儲空間。因此指針需要手動釋放,指針是一個變量,只有當程序結束時才被銷毀。釋放了內存空間后,原本指向這塊空間的指針仍然存在。但此時指針指向的內容為垃圾,是未定義的。因此,釋放內存后要把指針指向NULL,防止該指針后續被解引用。
7.10本章小結
本章主題是hello的存儲管理,在不同的存儲空間中有著不同的地址,虛擬地址空間中有虛擬地址,物理存儲空間中有物理地址,而程序中使用的是邏輯地址,段式管理將邏輯地址轉換為線性地址,頁式管理將線性地址轉換為物理地址。處理器借助地址向內存請求數據,多級高速緩存讓計算機整體的存儲結構既擁有趨近于L1cache的高速,又有巨大的存儲空間。當指令引用一個地址,而與該地址相應的物理頁面不在內存中時,就要調用缺頁故障處理程序。此外,本章還分析了hello進程fork與execve時的內存映射,并在最后介紹了動態存儲分配管理的方法與原理。
第8章 hello的IO管理
8.1 Linux的IO設備管理方法
一個Linux文件就是一個m個字節的序列:
B0,B1,B2……Bk……Bm-1
所有的I/O設備(例如網絡、磁盤和終端)都被模型化為文件,而所有的輸入和輸出都被當做對相應文件的讀和寫來執行,這種將設備優雅地映射為文件的方式,允許Linux內核引出一個簡單、低級的應用接口,稱為Unix I/O,這使得所有的輸入和輸出都能以一種統一且一致的方式來執行。
設備的模型化:文件
設備管理:unix io接口
8.2 簡述Unix IO接口及其函數
Unix I/O 接口:
Shell 創建的每個進程都有三個打開的文件:標準輸入,標準輸出,標準錯誤。
8.3 printf的實現分析
研究printf的實現,首先來看看printf函數的函數體
int printf(const char fmt, …)
{
int i;
char buf[256];
va_list arg = (va_list)((char)(&fmt) + 4);
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
printf程序按照格式fmt結合參數args生成格式化之后的字符串,并返回字串長度。
接下來我們追蹤write:
write:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL
這里是給幾個寄存器傳了幾個參數,然后以int結束。將棧中的參數放入寄存器,ecx為字符個數,ebx存放第一個字符地址。
再來看看sys_call的實現:
sys_call:
call save
push dword [p_proc_ready]
sti
push ecx
push ebx
call [sys_call_table + eax * 4]
add esp, 4 * 3
mov [esi + EAXREG - P_STACKBASE], eax
cli
ret
syscall將字符串中的字節從寄存器中通過總線復制到顯卡的顯存中,顯存中存儲的是字符的ASCII碼。
字符顯示驅動子程序:從ASCII到字模庫到顯示vram(存儲每一個點的RGB顏色信息)。
顯示芯片按照刷新頻率逐行讀取vram,并通過信號線向液晶顯示器傳輸每一個點(RGB分量)。
完成了printf。
8.4 getchar的實現分析
getchar代碼部分:
int getchar(void)
{
static char buf[BUFSIZ];
static char* bb=buf;
static int n=0;
if(n==0)
{
n=read(0,buf,BUFSIZ);
bb=buf;
}
return (–n>=0)?(unsigned char)*bb++:EOF;
}
當使用getchar時,程序發生陷阱的異常。當按鍵盤時會產生中斷。
異步異常-鍵盤中斷的處理:鍵盤中斷處理子程序。接受按鍵掃描碼轉成ascii碼,保存到系統的鍵盤緩沖區。
getchar等調用read系統函數,通過系統調用讀取按鍵ascii碼,直到接受到回車鍵才返回。
8.5本章小結
本章介紹了Linux I/O設備的基本概念和管理方法,簡述Unix IO接口及其函數,以及分析printf 函數和 getchar 函數的實現。
結論
Hello的一生:
Hello的一生結束了,可我的修行才剛剛開始。計算機如同集結人類智慧的山峰,CSAPP將我引到了這山腳下,讓我得以一窺其巍峨。長路漫漫,歸途何期?我認為程序員的求索之路是無窮無盡的,無數的科學家將計算機締造,未來也將由后人不斷發展,屏幕上閃動的光標,既是藝術的綻放,更是技術的碩果。而我應該做的,無非虛懷若谷,小心謹慎地一路登攀。
附件
hello.c 源文件
hello.i 預處理后的文件
hello.s 編譯后的匯編文件
hello.o 匯編后的可重定位文件
hello 鏈接后的可執行文件
helloelf.txt ?hello的elf文件
hellooelf.txt ?hello.o的elf文件
helloobjdump.txt ?hello的反匯編代碼
參考文獻
為完成本次大作業你翻閱的書籍與網站等
[1]? 林來興. 空間控制技術[M]. 北京:中國宇航出版社,1992:25-42.
[2]? 辛希孟. 信息技術與信息服務國際研討會論文集:A集[C]. 北京:中國科學出版社,1999.
[3]? 趙耀東. 新時代的工業工程師[M/OL]. 臺北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4]? 諶穎. 空間交會控制理論與方法研究[D]. 哈爾濱:哈爾濱工業大學,1992:8-13.
[5]? KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6]? CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.
[7]? Randal E. Bryant. 深入理解計算機系統[M],第三版,龔奕利,機械工業出版社,2016:1-640.
[8]? 王爽. 匯編語言[M]. 第3版,清華大學出版社,2013:14-172.
[9]? CSDN博客 https://blog.csdn.net/daocaokafei/article/details/116207148?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165235562016781683965613%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165235562016781683965613&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-1-116207148-null-null.142^v9^pc_search_result_control_group,157^v4^new_style&utm_term=%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80&spm=1018.2226.3001.4187
總結
以上是生活随笔為你收集整理的哈工大 计算机系统大作业 程序人生-Hello’s P2P From Program to Process的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 虚函数绕过 GS保护 学习
- 下一篇: 滑窗优化、边缘化、舒尔补、FEJ及fil