HIT CSAPP大作业论文
計算機系統
大作業
題 目 程序人生-Hello’s P2P
專 業 計算機類
學 號 1190201816
班 級 1903012
學 生 樊紅雨
指 導 教 師 史先俊
計算機科學與技術學院
2021年5月
摘 要
本論文旨在研究 hello 在 linux 系統下的整個生命周期。結合 CSAPP 課本,通過 gcc
等工具進行實驗,從而將課本知識落實、融會貫通,通過一個程序深入挖掘知識點,對于學生對于課程的理解以及知識的升華有很大幫助。
**關鍵詞:**hello的一生;計算機系統;編譯;鏈接;重定位;內存;CPU;深入理解計算機系統。
(摘要0分,缺失-1分,根據內容精彩稱都酌情加分0-1分)
**
**
目 錄
第1章 概述 - 4 -
1.1 Hello簡介 - 4 -
1.2 環境與工具 - 4 -
1.3 中間結果 - 4 -
1.4 本章小結 - 4 -
第2章 預處理 - 5 -
2.1 預處理的概念與作用 - 5 -
2.2在Ubuntu下預處理的命令 - 5 -
2.3 Hello的預處理結果解析 - 5 -
2.4 本章小結 - 5 -
第3章 編譯 - 6 -
3.1 編譯的概念與作用 - 6 -
3.2 在Ubuntu下編譯的命令 - 6 -
3.3 Hello的編譯結果解析 - 6 -
3.4 本章小結 - 6 -
第4章 匯編 - 7 -
4.1 匯編的概念與作用 - 7 -
4.2 在Ubuntu下匯編的命令 - 7 -
4.3 可重定位目標elf格式 - 7 -
4.4 Hello.o的結果解析 - 7 -
4.5 本章小結 - 7 -
第5章 鏈接 - 8 -
5.1 鏈接的概念與作用 - 8 -
5.2 在Ubuntu下鏈接的命令 - 8 -
5.3 可執行目標文件hello的格式 - 8 -
5.4 hello的虛擬地址空間 - 8 -
5.5 鏈接的重定位過程分析 - 8 -
5.6 hello的執行流程 - 8 -
5.7 Hello的動態鏈接分析 - 8 -
5.8 本章小結 - 9 -
第6章 hello進程管理 - 10 -
6.1 進程的概念與作用 - 10 -
6.2 簡述殼Shell-bash的作用與處理流程 - 10 -
6.3 Hello的fork進程創建過程 - 10 -
6.4 Hello的execve過程 - 10 -
6.5 Hello的進程執行 - 10 -
6.6 hello的異常與信號處理 - 10 -
6.7本章小結 - 10 -
第7章 hello的存儲管理 - 11 -
7.1 hello的存儲器地址空間 - 11 -
7.2 Intel邏輯地址到線性地址的變換-段式管理 - 11 -
7.3 Hello的線性地址到物理地址的變換-頁式管理 - 11 -
7.4 TLB與四級頁表支持下的VA到PA的變換 - 11 -
7.5 三級Cache支持下的物理內存訪問 - 11 -
7.6 hello進程fork時的內存映射 - 11 -
7.7 hello進程execve時的內存映射 - 11 -
7.8 缺頁故障與缺頁中斷處理 - 11 -
7.9動態存儲分配管理 - 11 -
7.10本章小結 - 12 -
第8章 hello的IO管理 - 13 -
8.1 Linux的IO設備管理方法 - 13 -
8.2 簡述Unix IO接口及其函數 - 13 -
8.3 printf的實現分析 - 13 -
8.4 getchar的實現分析 - 13 -
8.5本章小結 - 13 -
結論 - 14 -
附件 - 15 -
參考文獻 - 16 -
第1章 概述
1.1 Hello簡介
P2P(Program to Process):
? Program:在 editor 中鍵入代碼得到 hello.c 程序。
?
Process:源文件hello.c在Linux中經過cpp的預處理、ccl的編譯、as的匯編、ld的鏈接最終成為可執行目標文件hello.o。在shell中輸入./hello.o后,shell為其fork,產生子進程。
020(Zero to Zero):
? shell 為 hello 進程
execve,映射虛擬內存,進入程序入口后程序開始載入物理內存。
? 進入 main 函數執行目標代碼, CPU 為運行的 hello分配時間片執行
邏輯控制流。
? 當程序運行結束后, shell 父進程負責回收 hello
進程,內核刪除相關數據結構。
從一開始計算機中不存在hello到執行hello,最終將hello回收。從無到有再到無。
1.2 環境與工具
硬件環境:
?處理器:Intel? Core? i5-93000H @ 2.4GHz
?RAM:16.00G
?系統類型:基于x64的處理器
?軟件環境:Windows10 64位;Ubuntu 20.10
?開發與調試工具:gcc,as,ld,vim,edb,readelf,VSCode
1.3 中間結果
| 預處理后的文件 | hello.i |
| 編譯之后的匯編文件 | hello.s |
| 匯編之后的可重定位目標文件 | hello.o |
| 鏈接之后的可執行目標文件 | Hello |
| hello.o 的 ELF 格式 | Elf.txt |
| hello.o 的反匯編代碼 | Dishello.s |
| hello的ELF 格式 | hello1.elf |
| hello 的反匯編代碼 | hello_objdump.s |
1.4 本章小結
本章對hello做一個總體的概括,首先介紹了P2P、O2O的概念。介紹了實現該論文的軟硬件環境,開發工具。最后列出了從.c文件到可執行目標文件的過程。
(第1章0.5分)
第2章 預處理
2.1 預處理的概念與作用
預處理的概念:
預處理中會展開以#起始的行,試圖解釋為預處理指令(preprocessing directive) ,其中
ISO
C/C++要求支持的包括#if、#ifdef、#ifndef、#else、#elif、#endif(條件編譯)、
#define(宏定義)、 #include(源文件包含)、 #line(行控制)、
#error(錯誤指令)、#pragma(和實現相關的雜注)以及單獨的#(空指令)。預處理指令一般被用來使源代碼在不同的執行環境中被方便的修改或者編譯。
預處理的作用:
1.將源文件中用#include 形式聲明的文件復制到新的程序中。比如 hello.c第 6-8
行中的#include 等命令告訴預處理器讀取系統頭文件stdio.h unistd.h stdlib.h
的內容,并把它直接插入到程序文本中。
2. 用實際值替換用#define 定義的字符串
3. 根據#if 后面的條件決定需要編譯的代碼
特殊符號,預編譯程序可以識別一些特殊的符號,預編譯程序對于在源程序中出現的這些串將用合適的值進行替換。
2.2在Ubuntu下預處理的命令
cpp hello.c > hello.i
圖1 預處理命令
圖2 預處理后的hello文件
2.3 Hello的預處理結果解析
經過預處理后,hello.i文件變為了3069行,而原文件hello.c只有23行。其中,hello.c的程序出現在3056行之后,在這之前是頭文件stdio.h、unisted.h和stdlib.h的展開。
圖3 hello.c源代碼
圖4 hello.i中的hello.c程序
對hello.i其他部分進行解析,在3056行之前的內容為頭文件的展開。以stdio.h展開為例,stdio.h是標準庫文件,cpp到Ubuntu
中默認的環境變量下尋找
stdio.h,打開文件/usr/include/stdio.h,發現其中依然使用了#define語句,cpp對stdio中define的宏定義遞歸地進行展開。所以最終的.i文件中是沒有#define語句的。同時還有大量的#ifdef,#ifndef條件編譯的語句,cpp會對條件進行判斷,最后決定其中的運行邏輯。
庫文件部分截圖:
圖5 hello.i文件中庫文件部分
標準庫中預置的函數:
圖6 hello.i文件中聲明函數部分
2.4 本章小結
本章主要介紹了預處理(頭文件的展開,去掉注釋,宏替換,條件編譯等)的概念以及應用功能。還介紹了Ubuntu下預處理的指令,具體到hello.c文件的預處理結果hello.i文件的文本解析,了解了預處理的內涵。
(第2章0.5分)
第3章 編譯
3.1 編譯的概念與作用
編譯的概念:
編譯程序所要作得工作就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之后,將其翻譯成等價的中間代碼表示或匯編代碼。
編譯器將文本文件 hello.i 翻譯成文本文件 hello.s。
編譯的作用:
編譯包括以下基本流程:
語法分析:編譯程序的語法分析器以單詞符號作為輸入,分析單詞符號串是否形成符合語法規則的語法單位,方法分為兩種:自上而下分析法和自下而上分析法。
中間代碼:源程序的一種內部表示,或稱中間語言。中間代碼的作用是可使編譯程序的結構在邏輯上更為簡單明確,特別是可使目標代碼的優化比較容易實現中間代碼。
代碼優化:指對程序進行多種等價變換,使得從變換后的程序出發,能生成更有效的目標代碼。
目標代碼:生成是編譯的最后一個階段。目標代碼生成器把語法分析后或優化后的中間代碼變換成目標代碼。此處指匯編語言代碼,須經過匯編程序匯編后,成為可執行的機器語言代碼。
3.2 在Ubuntu下編譯的命令
命令:gcc -S hello.i -o hello.s
圖7 Ubuntu下編譯的命令
圖8 進行編譯后得到的文件
3.3 Hello的編譯結果解析
3.3.1匯編目錄
| .file | 聲明源文件 |
| .text | 以下是代碼段 |
| . section .rodata | 以下是 rodata 節 |
| .globle | 聲明一個全局變量 |
| .type | 用來指定是函數類型或是對象類型 |
| .size | 聲明大小 |
| .long、.string | 聲明一個 long、string 類型 |
| .align | 聲明對指令或者數據的存放地址進行對齊的方式 |
3.3.2數據類型
hello.s中用到的數據類型有:整數、字符串、數組、指針。
3.3.2.1整數
1)整數常量
如圖8,9,在紅線處的if語句中,整數常量4被保存在.text中,作為指令的一部分:
圖8 紅線部分的匯編語言
圖9 if語句的常量
同理,for語句中整數常量0,8的保存方式與前面相同:
圖10 for語句中的整數常量
圖11 匯編語句中for中的常量
需要注意的是,匯編語言中對常量8進行了修改,源代碼中判斷的條件是<8,匯編代碼中改為了<=7。
這些整數常量都被存儲到了.text節中。
2)局部整數變量
局部變量整數變量都存儲在寄存器或棧中。如程序中的局部整數變量i:
圖11 局部變量i
它在匯編代碼的體現為:
圖12 為局部變量i賦值
可以看到局部變量是存儲在棧中的,他的地址為-4(%rbp)。
3)全局整數變量
在hello.c中并未出現全局變量,但我們根據書中知識可知,初始化的全局變量存儲在.data節中,它的初始化不需要匯編語句,而是直接完成的。
3.3.2.2 字符串
在第一個printf語句中有字符串:
printf(“用法: Hello 學號 姓名 秒數!\n”);
printf()、scanf()中的字符串則被存儲在.rodata節中:
.LC0:
.string “\347\224\250\346\263\225: Hello \345\255\246\345\217\267
\345\247\223\345\220\215 \347\247\222\346\225\260\357\274\201”
同理,在另一行語句:
printf(“Hello %s %s\n”,argv[1],argv[2]);
該字符串同樣被保存到.rodata中:
圖13 .rodata節中保存的字符串
3.3.2.3 數組/指針
主函數main的參數中有指針數組char *argv[]:
圖14 main函數的數組
在argv數組中,argv[0]指向程序的名稱,argv[1]和argv[2]分別表示兩個字符串。
因為char *數據類型占8個字節,根據匯編代碼中:
圖15 匯編函數中將數組儲存到棧中
argc存儲在%edi argv存儲在%rsi
可知通過??梢苑謩e得到argv[1]和argv[2]兩個字符串。
3.3.3 賦值操作以及算術操作
程序中涉及的賦值操作有:
圖16 賦值操作
該語句為i賦初值為0。同時,i++為自加操作符:
在每次循環執行的內容結束后,對i進行一次自加,棧上存儲變量i的值加1。
在匯編語言中,使用movl指令實現賦值操作:
movl $0, %eax
使用addl指令實現自加操作:
可以看出,本地變量i儲存到寄存器%eax中。
3.3.4 關系操作和控制轉移
程序第14行中判斷傳入參數argc是否等于4,源代碼為:
圖17 判斷
匯編代碼為:
je用于判斷cmpl產生的條件碼,若兩個操作數的值不相等則跳轉到指定地址;
for循環中的循環執行條件。
3.3.5 函數操作
X86-64中,過程調用傳遞參數規則:第1~6個參數一次儲存在%rdi、%rsi、%rdx、%rcx、%r8、%r9這六個寄存器中,剩下的參數保存在棧當中。
函數包括如下內容:
函數表達式:函數作為表達式中的一項出現在表達式中,以函數返回值參與表達式的運算。這種方式要求函數是有返回值的。
函數語句:函數調用的一般形式加上分號即構成函數語句。
函數實參:函數作為另一個函數調用的實際參數出現。這種情況是把該函數的返回值作為實參進行傳送,因此要求該函數必須是有返回值的。
調用函數的動作如下:
傳遞控制:進行過程 Q 的時候,程序計數器必須設置為 Q
的代碼的起始地址,然后在返回時,要把程序計數器設置為 P 中調用 Q
后面那條指令的地址。
傳遞數據:P 必須能夠向 Q 提供一個或多個參數,Q 必須能夠向 P 中返回一個值。
分配和釋放內存:在開始時,Q
可能需要為局部變量分配空間,而在返回前,又必須釋放這些空間。
main函數:
參數傳遞:傳入參數argc和argv[],分別用寄存器%rdi和%rsi存儲。
函數調用:被系統啟動函數調用。
函數返回:設置%eax為0并且返回,對應return 0 。
源代碼:
printf(“Hello %s %s\n”, argv[1], argv[2]);
匯編代碼:
.L4:
movq -32(%rbp), %rax
addq $16, %rax
movq (%rax), %rdx
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
leaq .LC1(%rip), %rdi
movl $0, %eax
call printf@PLT
exit函數:
參數傳遞:傳入的參數為1,再執行退出命令
函數調用:if判斷條件滿足后被調用.
源代碼:
exit(1);
匯編代碼:
.LFB6:
movl $1, %edi
call exit@PLT
sleep函數:
參數傳遞:傳入參數atoi(argv[3]),
函數調用:for循環下被調用,call sleep
源代碼:
sleep(atoi(argv[3]));
匯編代碼:
.L4:
movq -32(%rbp), %rax
addq $24, %rax
movq (%rax), %rax
movq %rax, %rdi
call atoi@PLT
movl %eax, %edi
call sleep@PLT
getchar函數:
函數調用:在main中被調用,call getchar
源代碼:
getchar();
匯編代碼:
.L3:
call getchar@PLT
3.4 本章小結
本章主要介紹了編譯的概念以及過程?;径际窍冉o出原理然后結合 hello.c C 程序到
hello.s 匯編代碼之間的映射關系作出合理解釋。編譯器將.i 的拓展程序編譯為.s
的匯編代碼。經過編譯之后,我們的 hello 自C
語言解構為更加低級的匯編語言。介紹了匯編代碼如何實現變量、常量、傳遞參數以及分支和循環。編譯程序所做的工作,就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之后,將其翻譯成等價的中間代碼表示或匯編代碼表示。
(第3章2分)
第4章 匯編
4.1 匯編的概念與作用
概念
驅動程序運行匯編器as,將匯編語言(這里是hello.s)翻譯成機器語言(hello.o)的過程稱為匯編,同時這個機器語言文件也是可重定位目標文件。
作用
匯編就是將高級語言轉化為機器可直接識別執行的代碼文件的過程,匯編器將.s
匯編程序翻譯成機器語言指令,把這些指令打包成可重定位
目標程序的格式,并將結果保存在.o 目標文件中,.o 文件是一個二進制文件,它
包含程序的指令編碼。
4.2 在Ubuntu下匯編的命令
命令:as hello.s -o hello.o
圖18 Ubuntu下匯編命令與生成的文件
4.3 可重定位目標elf格式
命令:
readelf -a hello.o > ./elf.txt
指令獲得 hello.o 文件的 ELF 格式。
ELF頭:
包含了系統信息,編碼方式,ELF頭大小,節的大小和數量等等一系列信息。Elf頭內容如下:
ELF Header:
圖19 ELF頭
描述了.o文件中出現的各個節的類型、位置、所占空間大小等信息。
圖20 節頭目標
4.3.5重定位節:
一個.text 節中位置的列表,包含.text
節中需要進行重定位的信息,當鏈接器把這個目標文件和其他文件組合時,需要修改這些位置。8
條重定位信息分別是對.L0(第一個 printf 中的字符串)、puts 函數、exit
函數、.L1(第二個 printf 中的字符串)、printf 函數、sleepsecs、sleep
函數、getchar 函數進行重定位聲明。
圖21 重定位節
4.3.6符號表:
.symtab是一個符號表,它存放在程序中定義和引用的函數和全局變量的信息。
圖22 符號表
4.4 Hello.o的結果解析
使用 objdump -d -r
hello.o獲得反匯編代碼。Hello.s和使用objdump獲得的反匯編代碼除去顯示格式之外兩者差別不大,主要差別如下:
反匯編代碼跳轉指令的操作數使用的不是段名稱如.L3,因為段名稱只是在匯編語言中便于編寫的助記符,所以在匯編成機器語言之后顯然不存在,而是確定的地址。
圖23
反匯編代碼的jmp指令和hello.s文件中的jmp指令
在.s 文件中,函數調用之后直接跟著函數名稱,而在反匯編序中,call
的目標地址是地址。這是因為 hello.c
中調用的函數都是共享庫中的函數,最終需要通過動態鏈接器才能確定函數的運行時執行地址,在匯編成為機器語言的時候,對于這些不確定地址的函數調用,將其
call 指令后的相對地址設置為全 0(目標地址正是下一條指令),然后在.rela.text
節中為其添加重定位條目,等待靜態鏈接的進一步確定。
圖24 反匯編代碼的call指令和hello.s文件中的call指令
在.s 文件中,訪問 rodata(printf 中的字符串),使用段名稱+%rip,在反匯編代碼中
0+%rip,因為 rodata
中數據地址也是在運行時確定,故訪問也需要重定位。所以在匯編成為機器語言時,將操作數設置為全
0 并添加重定位條目。
圖25 反匯編代碼的全局變量訪問和hello.s文件中的變量訪問
4.5 本章小結
本章對匯編結果進行了詳盡的介紹。經過匯編器的操作,匯編語言轉化為機器語言,hello.o可重定位目標文件的生成為后面的鏈接做了準備。通過對比hello.s和hello.o反匯編代碼的區別。間接了解到從匯編語言映射到機器語言匯編器需要實現的轉換。使得我們對該內容有了更加深入地理解。
(第4章1分)
第5章 鏈接
5.1 鏈接的概念與作用
鏈接器概念:
鏈接是將各種不同文件的代碼和數據部分收集(符號解析和重定位)起來并組合成一個單一文件的過程。
鏈接器作用:
令源程序節省空間而未編入的常用函數文件(如printf.o)進行合并,生成可以正常工作的可執行文件。這令分離編譯成為可能,節省了大量的工作空間。
5.2 在Ubuntu下鏈接的命令
動態鏈接器和鏈接的目標文件都是64位的。
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
圖26 鏈接命令
5.3 可執行目標文件hello的格式
**命令:**readelf -a hello > hello1.elf
(1)ELF頭:
以16B的序列Magic開始,Magic描述了生成該文件的系統的字的大小和字節順序,ELF頭剩下的部分包含幫助鏈接器語法分析和解釋目標文件的信息,其中包括ELF頭的大小、目標文件的類型、機器類型、字節頭部表(section
header table)的文件偏移,以及節頭部表中條目的大小和數量等信息。
與鏈接前的ELF header比較,可見除系統決定的基本信息不變外,section
header和program header均增加,并獲得了入口地址。
圖27 ELF頭
節頭:
在 ELF 格式文件中,節頭對 hello 中所有的節信息進行了聲明,其中包括大小 Size
以及在程序中的偏移量
Offset,因此根據節頭中的信息我們就可以定位各個節所占的區間(起始位置,大小)。
圖28 部分節頭
5.4 hello的虛擬地址空間
使用edb加載hello,查看本進程的虛擬地址空間各段信息。
Data Dump 窗口可以查看加載到虛擬地址中的 hello 程序。
圖29 edb中Data Dump
窗口顯示虛擬地址由0x400000開始,到0x400fff結束,這之間的每一個節對應5.3中的每一個節頭表的聲明。
圖30 edb中Symbols
觀察edb的Sympols小窗口。我們發現確實從虛擬地址從0x400000開始和5.3節中的節頭表是一一對應的
5.5 鏈接的重定位過程分析
**命令:**objdump -d -r hello > hello_objdump.s
圖31 hello_objdump.s的_init節
不同之處:
增加了節:
hello中增加了.init和.plt節(hello.o反匯編得到的hello.asm中只有.text節)。
增加了共享庫函數:
hello中增加了一些節中定義的函數,如puts@plt共享庫函數,printf@plt共享庫函數以及getchar@plt函數等。
圖32 增加的函數
函數調用:
hello中無hello.o中的重定位條目,跳轉和函數調用的地址在hello_objdump.s中是虛擬內存地址。對于hello.o的反匯編代碼,函數只有在鏈接之后才能確定運行執行的地址,因此在.rela.text節中為其添加了重定位條目。
圖33 call進行函數調用
地址訪問:
鏈接器解析重定條目時以R_X86_64_PC32類型對.rodata 兩個重定位(printf
中的兩個字符串),確定.rodata 與.text
節之間的相對距離,call之后的值被鏈接器直接修改為目標地址與下一條指令的地址之差,指向相應的字符串。
鏈接的過程:
根據hello和hello.o的不同,分析出鏈接的過程為:
鏈接就是鏈接器(ld)將各個目標文件(各種.o文件)組裝在一起,文件中的各個函數段按照一定規則累積在一起。
5.6 hello的執行流程
使用edb執行hello,觀察函數執行流程,執行hello時輸入參數1190201816 樊紅雨 1。
圖34 輸入的參數
列出所有過程:
圖35 過程的各個函數及其地址
上圖可通過edb中的symbols表格進行查看。
5.7 Hello的動態鏈接分析
由于無法預測函數的運行時地址,對于動態共享鏈接庫中 PIC
函數,編譯器需要添加重定位記錄,等待動態鏈接器處理。鏈接器采用延遲綁定的策略,防止運行時修改調用模塊的代碼段。
對于變量而言,我們利用代碼段和數據段的相對位置不變的原則計算正確地址。對于庫函數而言,需要plt、got合作,plt初始存的是一批代碼,它們跳轉到got所指示的位置,然后調用鏈接器。初始時got里面存的都是plt的第二條指令,隨后鏈接器修改got,下一次再調用plt時,指向的就是正確的內存地址。plt就能跳轉到正確的區域。
圖36 ELF文件中偏移
在dl_init調用之前,0x601008 和 0x601010 處的兩個 8B
數據為空。對于每一條PIC函數調用,調用的目標地址都實際指向PLT
中的代碼邏輯,GOT存放的是PLT中函數調用指令的下一條指令地址。
在dl_init調用之后,0x601008和0x601010處的兩個8B數據分別發生改變為0x7fba
871a8170和0x7fba 86f96680。
其中GOT[1]指向重定位表(另一次運行,不過真的是根據GOT[1]指向的)(依次為.plt節需要重定位的函數的運行時地址)用來確定調用的函數地址。
在dl_init調用之后的函數調用時,跳轉到PLT執行.plt中邏輯,下一條指令壓棧,第一次訪問跳轉時GOT地址為函數序號,然后跳轉到PLT[0]。在PLT[0]中將重定位表地址壓棧,然后訪問動態鏈接器。
在動態鏈接器中使用函數序號和重定位表確定函數運行時地址,重寫GOT,再將控制傳遞給目標函數。根據jmp的原理(執行完目標函數之后的返回地址為最近call指令下一條指令地址),之后如果對同樣函數調用,第一次訪問跳轉直接跳轉到目標函數。
5.8 本章小結
本章結合實驗中的hello可執行程序依此介紹了鏈接的概念及作用,在Ubuntu下鏈接的命令行;并對hello的elf格式進行了詳細的分析對比,同時注意到了hello的虛擬地址空間知識;并通過反匯編hello文件,將其與hello.o反匯編文件對比,詳細了解了重定位過程;遍歷了整個hello的執行過程,在最后對hello進行了動態鏈接分析。不過,鏈接遠不止本章所涉及的這么簡單,就像是hello會在它運行時要求動態鏈接器加載和鏈接某個共享庫,而無需在編譯時將那些庫鏈接到應用中。
(第5章1分)
第6章 hello進程管理
6.1 進程的概念與作用
-
概念
狹義定義:進程是正在運行的程序的實例(an instance of a computer program that
is being executed)。廣義定義:進程是一個具有一定獨立功能的程序關于某個數據集合的一次運行活動。它是操作系統動態執行的基本單元,在傳統的操作系統中,進程既是基本的分配單元,也是基本的執行單元。
-
作用
1)在現代計算機中,進程為用戶提供了以下假象:我們的程序好像是系統中當前運行的唯一程序
一樣,我們的程序好像是獨占的使用處理器和內存,處理器好像是無間斷的執行
我們程序中的指令,我們程序中的代碼和數據好像是系統內存中唯一的對象。2)每次用戶通過向shell 輸入一個可執行目標文件的名字,運行程序時, shell
就會創建一個新的進程,然后在這個新進程的上下文中運行這個可執行目標文件。應用程序也能夠創建新進程,并且在這個新進程的上下文中運行它們自己的代碼或其他應用程序。3)進程提供給應用程序兩個關鍵抽象:一個獨立的邏輯控制流;一個私有的地址空間。
6.2 簡述殼Shell-bash的作用與處理流程
-
作用
Shell是用戶與操作系統之間完成交互式操作的一個接口程序,它為用戶提供簡化了的操作。而NU組織發現sh是比較好用的又進一步開發Borne
Again Shell,簡稱bash,它是Linux系統中默認的shell程序。 -
處理流程
1)將用戶輸入的命令行進行解析,分析是否是內置命令;
2)若是內置命令,直接執行;若不是內置命令,則bash在初始子進程的上下文中加載和運行它。
3)本質上就是shell在執行一系列的讀和求值的步驟,在這個過程中,他同時可以接受來自終端的命令輸入。
6.3 Hello的fork進程創建過程
根據shell的處理流程,可以推斷,輸入命令執行hello后,父進程如果判斷不是內部指令,即會通過fork函數創建子進程。子進程與父進程近似,并得到一份與父進程用戶級虛擬空間相同且獨立的副本——包括數據段、代碼、共享庫、堆和用戶棧。父進程打開的文件,子進程也可讀寫。二者之間最大的不同或許在于PID的不同。Fork函數只會被調用一次,但會返回兩次,在父進程中,fork返回子進程的PID,在子進程中,fork返回0。
6.4 Hello的execve過程
-
execve函數在當前進程的上下文中加載并運行新程序hello。函數原型為:int
execve(const char *filename, const char *argv[], const char
*envp[]);如果成功,則不返回;如果錯誤,則返回-1。 -
在execve加載了hello之后,它調用啟動代碼。啟動代碼設置棧,并將控制傳遞給hello的主函數(即main函數),該函數有以下原型:
-
int main(int argv, char **argv, char **envp)或者等價的:
-
int main(int argc, char *argv[], char *envp).
結合虛擬內存和內存映射過程,可以更詳細地說明execve函數實際上是如何加載和執行程序Hello:
刪除已存在的用戶區域(自父進程獨立)。
映射私有區:為Hello的.bss、代碼、數據和棧區域創建新的區域結構,所有這些區域都是私有的、寫時才復制的。
映射共享區:比如Hello程序與標準C庫libc.so鏈接,這些對象都是動態鏈接到Hello的,然后再用戶虛擬地址空間中的共享區域內。
設置PC:execve做的最后一件事就是設置當前進程的上下文中的程序計數器,使之指向代碼區域的入口點。
6.5 Hello的進程執行
-
進程時間片
一個進程執行他的控制流的一部分的每一個時間段叫做時間片(time
slice),多任務也叫時間分片(time slicing) -
進程上下文切換
調度:在進程執行的某些時刻,內核可以決定搶占當前進程,并重新開始一個先前被強占的進程。這種決策就叫調度(是由內核中的調度器的代碼處理的)。
上下文切換:在內核調度了一個新的進程運行時,它就搶占當前進程,并使用一種上下文切換的機制來控制轉移到新的進程。
1)保存當前進程的上下文
2)恢復某個先前被強占的進程被保存的上下文
3)將控制傳遞給這個新恢復的進程
-
具體的用戶態核心態轉換
進程hello初始運行在用戶模式中,直到它通過執行系統調用函數sleep或者exit時便陷入到內內核。內核中的處理程序完成對系統函數的調用。之后,執行上下文切換,將控制返回給進程hello系統調用之后的那條語句。
6.6 hello的異常與信號處理
正常執行hello程序的結果:當程序執行完成之后(以鍵入回車結束),進程回收。
異常類型:
| 中斷 | 來自I/O設備的信號 | 異步 | 總是返回到下一條指令 |
| 陷阱 | 有意的異常 | 同步 | 總是返回到下一條指令 |
| 故障 | 潛在可恢復的錯誤 | 同步 | 可能返回到當前指令 |
| 終止 | 不可恢復的錯誤 | 同步 | 不會返回 |
處理方式:
中斷處理方式
陷阱處理方式
故障處理方式
終止處理方式
在程序輸出2條字符串之后按下ctrl-z :
當按下 ctrl-z之后,shell 父進程收到 SIGSTP
信號,信號處理程序將hello進程掛起。
通過 ps 命令查看 hello 進程,此時他的后臺job 號是1。
調用fg 1將其調到前臺,此時 shell
首先打印hello的命令行命令,hello繼續運行打印剩下的字符串,之后輸入字串,程序結束,同時進程被回收。
按下Ctrl +Z使程序掛起
使用fg 1將其調到前臺
在程序輸出2條字符串之后按下ctrl-c
:當按下ctrl-c之后,shell父進程收到SIGINT信號,信號處理函數的邏輯是結束hello,并回收hello進程。
使用Ctrl+C將程序終止
中途亂按,只是將屏幕的輸入緩存到緩沖區。亂碼被認為是命令。
Hello程序運行中途亂按
hello結束后,stdin中的其他字串會當做shell若干條命令行輸入。
kill函數,掛起的進程被終止,在ps中無法查到到其PID。
使用kill函數向hello程序發送信號
6.7本章小結
本章了解了hello進程的執行過程。闡明了進程的定義與作用,介紹了Shell的一般處理流程,調用
fork 創建新進程,調用 execve 執行 hello,hello的進程執行,hello
的異常與信號處理。主要講hello的創建、加載和終止,通過鍵盤輸入。程序是指令、數據及其組織形式的描述,進程是程序的實體??梢哉f,進程是運行的程序。在hello運行過程中,內核有選擇對其進行管理,決定何時進行上下文切換。也同樣是在hello的運行過程中,當接受到不同的異常信號時,異常處理程序將對異常信號做出相應,執行相應的代碼,每種信號都有不同的處理機制,對不同的異常信號,hello也有不同的處理結果。
(第6章1分)
第7章 hello的存儲管理
7.1 hello的存儲器地址空間
-
邏輯地址
邏輯地址(Logical
Address)是指由程序hello產生的與段相關的偏移地址部分(hello.o)。 -
線性地址
線性地址(Linear
Address)是邏輯地址到物理地址變換之間的中間層。程序hello的代碼會產生邏輯地址,或者說是(即hello程序)段中的偏移地址,它加上相應段的基地址就生成了一個線性地址。 -
虛擬地址
有時我們也把邏輯地址稱為虛擬地址。因為與虛擬內存空間的概念類似,邏輯地址也是與實際物理內存容量無關的,是hello中的虛擬地址。
-
物理地址
物理地址(Physical
Address)是指出現在CPU外部地址總線上的尋址物理內存的地址信號,是地址變換的最終結果地址。如果啟用了分頁機制,那么hello的線性地址會使用頁目錄和頁表中的項變換成hello的物理地址;如果沒有啟用分頁機制,那么hello的線性地址就直接成為物理地址了。
7.2 Intel邏輯地址到線性地址的變換-段式管理
一個邏輯地址由兩部分組成,段標識符,段內偏移量。段標識符是一個16位長的字段組成,稱為段選擇符,其中前13位是一個索引號。后面三位包含一些硬件細節。
索引號,可以通過段標識符的前13位,直接在段描述符表中找到一個具體的段描述符,這個描述符就描述了一個段。
這里面,我們只用關心Base字段,它描述了一個段的開始位置的線性地址。
全局的段描述符,放在“全局段描述符表(GDT)”中,一些局部的段描述符,放在“局部段描述符表(LDT)”中。
GDT在內存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT則在ldtr寄存器中。
給定一個完整的邏輯地址段選擇符+段內偏移地址,
判斷段選擇符的T1=0還是1,知道當前要轉換是GDT中的段,還是LDT中的段,再根據相應寄存器,得到其地址和大小。我們就有了一個數組了。
利用段選擇符中前13位,可以在這個數組中,查找到對應的段描述符,這樣,它了Base,即基地址就知道了。
把Base + offset,就是要轉換的線性地址了
7.3 Hello的線性地址到物理地址的變換-頁式管理
頁式管理是一種內存空間存儲管理的技術,頁式管理分為靜態頁式管理和動態頁式管理。將各進程的虛擬空間劃分成若干個長度相等的頁(page),頁式管理把內存空間按頁的大小劃分成片或者頁面(page
frame),然后把頁式虛擬地址與內存地址建立一一對應頁表,并用相應的硬件地址變換機構,來解決離散地址變換問題。頁式管理采用請求調頁或預調頁技術實現了內外存存儲器的統一管理。
優點:
動態頁式管理提供了內存和外存統一管理的虛存實現方式,使用戶可以利用的存儲空間大大增加。這既提高了主存的利用率,又有利于組織多道程序執行。
由于它不要求作業或進程的程序段和數據在內存中連續存放,從而有效地解決了碎片問題。
缺點:
要求有相應的硬件支持。例如地址變換機構,缺頁中斷的產生和選擇淘汰頁面等都要求有相應的硬件支持。這增加了機器成本。
增加了系統開銷,例如缺頁中斷處理機,
請求調頁的算法如選擇不當,有可能產生抖動現象。
7.4 TLB與四級頁表支持下的VA到PA的變換
·使用K級頁表的地址翻譯
K級頁表的地址翻譯
每次CPU產生一個虛擬地址,MMU(內存管理單元)就必須查閱一個PTE(頁表條目),以便將虛擬地址翻譯為物理地址。在最糟糕的情況下,這會從內存多取一次數據,代價是幾十到幾百個周期。如果PTE碰巧緩存在L1中,那么開銷就會下降到1或2個周期。許多系統在MMU中包括了一個關于PTE的小的緩存,稱為翻譯后備緩存器(TLB)。
多級頁表:
將虛擬地址的VPN劃分為相等大小的不同的部分,每個部分用于尋找由上一級確定的頁表基址對應的頁表條目。
解析VA,利用前m位vpn1尋找一級頁表位置,接著一次重復k次,在第k級頁表獲得了頁表條目,將PPN與VPO組合獲得PA
7.5 三級Cache支持下的物理內存訪問
前提:只討論 L1 Cache 的尋址細節,L2 與 L3Cache 原理相同。L1 Cache 是 8路 64
組相聯。塊大小為 64B。
共64(2e6)組需要 6bit CI 進行組尋址。共有8路,塊大小為64B需要6bit
CO表示數據偏移位置。因為 VA 共 52bit,所以CT 共 40bit。
根據上一步獲得的物理地址
VA,使用CI(后六位再后六位)進行組索引,每組8路,對8路的塊分別匹配
CT(前40位)。
如果匹配成功且塊的 valid 標志位為1,則命中(hit),根據數據偏移量
CO(后六位)取出數據返回。
如果沒有匹配成功或者匹配成功但是標志位是
1,則不命中(miss),向下一級緩存中查詢數據(L2 Cache->L3 Cache->主存)。
根據一種常見的簡單策略,查詢到數據之后,如果映射到的組內有空閑塊,則直接放置;否則組內都是有效塊,產生沖突(evict),則采用最近最少使用策略LFU進行替換。
7.6 hello進程fork時的內存映射
當 fork 函數被 shell
進程調用時,內核為新進程創建各種數據結構,并分配給它一個唯一的
PID,為了給這個新進程創建虛擬內存,它創建了當前進程的mm_struct、區域結構和頁表的原樣副本。在hello進程中返回時,hello進程擁有與調用fork進程相同的虛擬內存。
它將這兩個進程的每個頁面都標記為只讀,并將兩個進程中的每個區域結構都標記為私有的寫時復制。隨后的寫操作通過寫時復制機制創建新頁面**。**
7.7 hello進程execve時的內存映射
1)在bash中的進程中執行了如下的execve調用:execve(“hello”,NULL,NULL);
2)execve函數在當前進程中加載并運行包含在可執行文件hello中的程序,用hello替代了當前bash中的程序。
下面是加載并運行hello的幾個步驟:
3)映射私有區域
4)映射共享區域,hello 程序與共享對象 libc.so 鏈接,libc.so
是動態鏈接到這個程序中的,然后再映射到用戶虛擬地址空間中的共享區域內。
5)設置程序計數器(PC)
execve做的最后一件事是設置當前進程的上下文中的程序計數器,是指指向代碼區域的入口點(init)。
7.8 缺頁故障與缺頁中斷處理
頁面命中完全是由硬件完成的,而處理缺頁是由硬件和操作系統內核協作完成的。
缺頁故障:當指令引用一個虛擬地址,在MMU
中查找頁表時發現與該地址相對應的物理地址不在內存中發生了故障,需要調用異常處理子程序從磁盤中取出。
缺頁中斷處理:缺頁處理程序是系統內核中的代碼,選擇一個犧牲頁面,如果這個犧牲頁面被修改過,那么就將它交換出去,換入新的頁面并更新頁表。當缺頁處理程序返回時,CPU
重新啟動引起缺頁的指令,這條指令再次發送 VA 到MMU,這次 MMU 就能正常翻譯 VA 了。
7.9動態存儲分配管理
Printf會調用malloc,動態內存管理的基本方法與策略:
動態內存分配器維護著一個進程的虛擬內存區域,稱為堆。分配器將堆視為一組不同大小的塊的集合來維護。
每個塊就是一個連續的虛擬內存片,要么是已分配的,要么是空閑的。已分配的塊顯式地保留為供應用程序使用。
空閑塊可用來分配??臻e塊保持空閑,直到它顯式地被應用所分配。
一個已分配的塊保持已分配狀態,直到它被釋放。
帶邊界標簽的隱式空閑鏈表分配器管理:
帶邊界標記的隱式空閑鏈表的每個塊是由一個字的頭部、有效載荷、可能的額外填充以及一個字的尾部組成的。
隱式空閑鏈表:在隱式空閑鏈表中,因為空閑塊是通過頭部中的大小字段隱含地連接著的。分配器可以通過遍歷堆中所有的塊,從而間接地遍歷整個空閑塊的集合。其中,一個設置了已分配的位而大小為零的終止頭部將作為特殊標記的結束塊。當一個應用請求一個k字節的塊時,分配器搜索空閑鏈表,查找一個足夠大的可以放置所請求塊的空閑塊。分配器有三種放置策略:首次適配、下一次適配合最佳適配。分配完后可以分割空閑塊減少內部碎片。同時分配器在面對釋放一個已分配塊時,可以合并空閑塊,其中便利用隱式空閑鏈表的邊界標記來進行合并。
隱式空閑鏈表結構示意圖
在內存塊中增加4B的頭部和4B的腳部。腳部的設計是專門為了合并空閑塊的。因為頭部和腳部大小已知,所以利用頭部和腳部中存放的塊大小就可以尋找上下
block。
顯式空閑鏈表管理:
顯式空閑鏈表是將空閑塊組織為某種形式的顯式數據結構。因為根據定義,程序不需要一個空閑塊的主體,所以實現這個數據結構的指針可以存放在這些空閑塊的主體里面,在每個空閑塊中,都包含一個前驅與一個后繼指針。
顯式空閑鏈表:在顯式空閑鏈表中??梢圆捎煤筮M先出的順序維護鏈表,將最新釋放的塊放置在鏈表的開始處,也可以采用按照地址順序來維護鏈表,其中鏈表中每個塊的地址都小于它的后繼地址,在這種情況下,釋放一個塊需要線性時間的搜索來定位合適的前驅。
顯式空閑鏈表的示意圖
7.10本章小結
本章主要介紹了hello的存儲器地址空間、intel的段式管理、hello 的頁式管理,在intel
Core7在環境下介紹了VA 到PA
的翻譯、物理內存訪問,還介紹hello進程fork時的內存映射、execve時的內存映射、缺頁故障與缺頁中斷處理、動態存儲分配管理。
并且我們知道了以下事實:
虛擬內存是對主存的一個抽象。
虛擬內存提供三個功能:簡化了內存保護;簡化了內存管理;在主存中自動緩存最近使用的存放在磁盤上的虛擬地址空間的內容。
地址翻譯的過程必須和系統中的所有的硬件緩存的操作集成在一起。
動態內存分配器直接操作內存,無需類型系統的很多幫助。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO設備管理方法
設備的模型化
文件(所有的I/O設備都被模型化為文件,甚至內核也被映射為文件)
設備管理
unix io接口
這種將設備優雅地映射為文件的方式,允許Linux內核引出一個簡單、低級的應用接口,稱為Unix
I/O。
我們可以對文件的操作有:打開關閉操作open和close;讀寫操作read和write;改變當前文件位置lseek等
8.2 簡述Unix IO接口及其函數
接口
1.打開文件。一個應用程序通過要求內核打開相應的文件,來宣告它想要訪問一個 I/O
設備,內核返回一個小的非負整數,叫做描述符,它在后續對此文件的所有操作中標識這個文件,內核記錄有關這個打開文件的所有信息,應用程序只需要記住這個描述符。
**2.linux shell 創建的每個進程開始時都有三個打開的文件:**標準輸入(描述符為0)
、標準輸出(描述符為1) 和標準錯誤(描述符為2) 。頭文件< unistd.h>
定義了常量STDIN_FILENO 、STOOUT_FILENO 和STDERR_FILENO,
它們可用來代替顯式的描述符值。
3.改變當前的文件位置:對于每個打開的文件,內核保持著一個文件位 置 k,初始為
0,這個文件位置是從文件開頭起始的字節偏移量,應用 程序能夠通過執行
seek,顯式地將改變當前文件位置 k。
4.讀寫文件。一個讀操作就是從文件復制n>0 個字節到內存,從當前文件位置k
開始,然后將k增加到k+n 。給定一個大小為m 字節的文件,當k~m
時執行讀操作會觸發一個稱為end-of-file(EOF)
的條件,應用程序能檢測到這個條件。在文件結尾處并沒有明確的“EOF 符號”
。類似地,寫操作就是從內存復制n>0
個字節到一個文件,從當前文件位置k開始,然后更新k 。
5.關閉文件。當應用完成了對文件的訪問之后,它就通知內核關閉這個文件。作為響應,內核釋放文件打開時創建的數據結構,并將這個描述符恢復到可用的描述符池中。無論一個進程因為何種原因終止時,內核都會關閉所有打開的文件并釋放它們的內存資源.
函數
1.打開和關閉文件。
**打開文件函數原型:**int open(char* filename,int flags,mode_t mode)
返回值:若成功則為新文件描述符,否則返回-1;
flags:O_RDONLY(只讀),O_WRONLY(只寫),O_RDWR(可讀寫)
**mode:**指定新文件的訪問權限位。
**關閉文件函數原型:**int close(fd)
**返回值:**成功返回0,否則為-1
2,讀和寫文件
讀文件函數原型:ssize_t read(int fd,void *buf,size_t n)
**返回值:**成功則返回讀的字節數,若EOF則為0,出錯為-1
**描述:**從描述符為fd的當前文件位置復制最多n個字節到內存位置buf
**寫文件函數原型:**ssize_t wirte(int fd,const void *buf,size_t n)
**返回值:**成功則返回寫的字節數,出錯則為-1
**描述:**從內存位置 buf 復制至多n個字節到描述符為 fd 的當前文件位置
8.3 printf的實現分析
(1)查看 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**;**
}
(2)其中引用的vsprintf函數:
int vsprintf(char *buf, const char *fmt, va_list args)
{
char *p;
chartmp[256];
va_listp_next_arg = args;
for (p = buf; *fmt; fmt++)
{
if (*fmt != ‘%’)
{
*p++ = *fmt;
continue;
}
fmt++;
switch (*fmt)
{
case ‘x’:
itoa(tmp, *((int *)p_next_arg));
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case ‘s’:
break;
default:
break;
}
return (p - buf);
}
}
vsprintf 程序按照格式 fmt 結合參數 args
生成格式化之后的字符串,并返回字串的長度。
系統函數write:
mov eax, _NR_write
mov ebx, [esp + 4**]**
mov ecx, [esp + 8**]**
int INT_VECTOR_SYS_CALL
在 write 函數中,將棧中參數放入寄存器,ecx 是字符個數,ebx 存放第一個字符地址。
在 Linux 下,write 函數的第一個參數為 fd,也就是描述符,而 1
代表的就是標準輸出。查看 write
函數的匯編實現可以發現,它首先給寄存器傳遞了幾個參數,然后調用 syscall
結束。write 通過執行 syscall
指令實現了對系統服務的調用,從而使內核執行打印操作。
內核會通過字符顯示子程序,根據傳入的 ASCII 碼到字模庫讀取字符對應的
點陣,然后通過 vram(顯存)對字符串進行輸出。顯示芯片將按照刷新頻率逐行 讀取
vram,并通過信號線向液晶顯示器傳輸每一個點(RGB 分量),最終實現
printf中字符串在屏幕上的輸出。
8.4 getchar的實現分析
int getchar(void)
{
char c;
return (read(0,&c,1)==1)?(unsigned char)c:EOF
}
可以看到,getchar 函數通過調用 read 函數返回字符。其中 read 函數的第一個
參數是描述符 fd,0代表標準輸入。第二個參數輸入內容的指針,這里也就是字符 c
的地址,最后一個參數是 1,代表讀入一個字符,符號 getchar函數讀一個字符的
設定。read 函數的返回值是讀入的字符數,如果為 1 說明讀入成功,那么直接返
回字符,否則說明讀到了 buf 的最后。 read 函數同樣通過 sys_call
中斷來調用內核中的系統函數。鍵盤中斷處理子程序會接受按鍵掃描碼并將其轉換為 ASCII
碼后保存在緩沖區。然后 read 函數調用 的系統函數可以對緩沖區 ASCII
碼進行讀取,直到接受回車鍵返回。 這樣,getchar 函數通過 read
函數返回字符,實現了讀取一個字符的功能。
8.5本章小結
本章我們就 hello 里面的函數對應 unix 的 I/O 來細致地分析了一下 I/O
對接口以及操作方法,這有助于我們以后在寫函數的時候在標準 I/O
庫沒有的時候我們可以編寫自己的 I/O 函數。
(第8章1分)
結論
P2P:
編寫:通過編輯器將代碼鍵入 hello.c
預處理:將hello.c調用的所有外部的庫展開,所有的宏定義替換,合并到一個hello.i文件中
編譯:將 hello.i 編譯成為匯編文件 hello.s
匯編:將 hello.s 會變成為可重定位目標文件 hello.o
鏈接:將 hello.o 與可重定位目標文件和動態鏈接庫鏈接成為可執行目標程序
hello。
020:
運行:在shell(終端)中輸入./hello 1190201816 樊紅雨 1
創建子進程:shell父進程調用fork函數為hello創建子進程
運行程序:shell調用execve,execve調用啟動加載器,加映射虛擬內存,進入程序入口后程序,開始載入物理內存,然后CPU進入main函數執行程序。
執行指令:CPU為hello分配時間片,在一個時間片中,hello享有CPU資源,順序執行自己的控制邏輯流,當
CPU 訪問 hello 時,請求一個虛擬地址,MMU 把虛擬地址轉換成物理地址并通過三級
cache 訪存。
訪問內存:MMU將程序中使用的虛擬內存地址,通過頁表映射成物理地址。
動態申請內存:printf會調用malloc向動態內存分配器申請堆中的內存。
信號:如果運行途中鍵入ctr-c、ctr-z,則調用shell的信號處理函數分別停止、掛起。
結束:shell父進程回收子進程,內核刪除為這個進程創建的所有數據結構。
感悟:
Hello 的一生如此精妙復雜,但展現給我們的只是屏幕輸出 hello
等字符串的一瞬,不深入了解它(計算機系統),可能就丟生了一座寶庫。
本來以為程序從編輯到編譯,運行、最后結束的過程是很簡單,尤其對于一個只有幾行代碼的小程序?,F在再看hello的一生,才發現他是如此的復雜,學習的越多,就越知道自己的渺小,從前以為計算機系統的內容會很容易理解,很容易掌握?,F在再看,自己真的是年少輕狂,現在看到了這些復雜又精密巧妙的系統,知道了自己其實還差的很遠。
(結論0分,缺失 -1分,根據內容酌情加分)
附件
列出所有的中間產物的文件名,并予以說明起作用。
| 預處理后的文件 | hello.i |
| 編譯之后的匯編文件 | hello.s |
| 匯編之后的可重定位目標文件 | hello.o |
| 鏈接之后的可執行目標文件 | Hello |
| hello.o 的 ELF 格式 | Elf.txt |
| hello.o 的反匯編代碼 | Dishello.s |
| hello的ELF 格式 | hello1.elf |
| hello 的反匯編代碼 | hello_objdump.s |
(附件0分,缺失 -1分)
參考文獻
為完成本次大作業你翻閱的書籍與網站等
[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.
(參考文獻0分,缺失 -1分)
堆中的內存。
信號:如果運行途中鍵入ctr-c、ctr-z,則調用shell的信號處理函數分別停止、掛起。
結束:shell父進程回收子進程,內核刪除為這個進程創建的所有數據結構。
感悟:
Hello 的一生如此精妙復雜,但展現給我們的只是屏幕輸出 hello
等字符串的一瞬,不深入了解它(計算機系統),可能就丟生了一座寶庫。
本來以為程序從編輯到編譯,運行、最后結束的過程是很簡單,尤其對于一個只有幾行代碼的小程序?,F在再看hello的一生,才發現他是如此的復雜,學習的越多,就越知道自己的渺小,從前以為計算機系統的內容會很容易理解,很容易掌握?,F在再看,自己真的是年少輕狂,現在看到了這些復雜又精密巧妙的系統,知道了自己其實還差的很遠。
(結論0分,缺失 -1分,根據內容酌情加分)
附件
列出所有的中間產物的文件名,并予以說明起作用。
| 預處理后的文件 | hello.i |
| 編譯之后的匯編文件 | hello.s |
| 匯編之后的可重定位目標文件 | hello.o |
| 鏈接之后的可執行目標文件 | Hello |
| hello.o 的 ELF 格式 | Elf.txt |
| hello.o 的反匯編代碼 | Dishello.s |
| hello的ELF 格式 | hello1.elf |
| hello 的反匯編代碼 | hello_objdump.s |
(附件0分,缺失 -1分)
參考文獻
為完成本次大作業你翻閱的書籍與網站等
[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.
(參考文獻0分,缺失 -1分)
總結
以上是生活随笔為你收集整理的HIT CSAPP大作业论文的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 来点热闹的生活吧!!!O(∩_∩)O哈哈
- 下一篇: 100部好看的校园喜剧片(可练习英文)+