天天看點

程式人生-hello`s P2P

計算機系統

大作業

題 目 程式人生-Hello’s P2P

專 業 計算機系

學   号 1170300921

班   級 1703009

學 生 王江瑞    

指 導 教 師 史先俊

計算機科學與技術學院

2018年12月

摘 要

本文主要介紹了hello程式的預處理、編譯、彙編、連結以及hello運作是的程序、信号、異常處理、存儲處理,介紹了有關程序、記憶體、IO管理的相關知識,旨在闡述程式從建立到執行的所有過程,結合計算機系統的知識,進行詳細解釋。

關鍵詞:代碼、預處理、編譯、彙編、連結、運作、建立子程序、運作程式、指令、記憶體、信号

(摘要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簡介

在電腦中寫入代碼得到Hello.c程式(program),将其用cpp預處理,ccl編譯,as彙編,ld連結,最終成為可執行程式,然後在shell中啟動,shell将該程式fork,産生子程序,變成process,這就是P2P的過程。然後execve,映射虛拟記憶體,然後載入實體記憶體,然後執行目标代碼,CPU為hello配置設定時間片執行邏輯控制流。運作結束後,父程序回收hello子程序,核心執行删除操作,這就是O2O。

1.2 環境與工具

硬體環境:Intel®Core™i7-6700HQ [email protected] 2.59GHz 8G RAM 64位作業系統 128SSD+1T HDD。

軟體環境:Ubuntu

開發與調試:vim,gcc,as,ld,ebd,readelf,HexEdit

1.3 中間結果

檔案名稱 檔案作用

Hello.i 預處理之後文本檔案

Hello.s 編譯之後的彙編檔案

Hello.o 彙編之後的可重定位目标執行

Hello 連結之後的可執行檔案

Hello2.c 測試程式代碼

Hello2 測試程式

Helloo.objdmp Hello.o的反彙編代碼

Helloo.elf Hello.o的ELF格式

Hello.objdmp hello的反彙編代碼

Hello.elf Hellode ELF格式

Tmp.txt 存放臨時資料

1.4 本章小結

主要介紹了hello的p2p,o2o過程,實驗所需的環境與工具,列出了中間結果檔案的名字和作用

(第1章0.5分)

第2章 預處理

2.1 預處理的概念與作用

概念:預處理器(cpp)根據以字元#開頭的指令,修改原始的C程式,将所有庫變成一個文本檔案,包括:宏定義,檔案包含,條件編譯。通常以.i作為檔案擴充名。

作用:1.将#include的聲明放到新程式中。比如hello.c中第一行#include <stdio.h>指令告訴預處理器讀取系統頭檔案stdio.h的内容,并把它直接插入程式文本中。

2.宏定義:将符号常量替換成文本。

3.檔案包含:把多個源檔案連接配接成一個源檔案進行編譯,結果将生成一個目标檔案。

4.條件編譯允許隻編譯源程式中滿足條件的程式段,進而減少記憶體的開銷,提高程式效率。

5.有助于程式的修改,閱讀,移植和調試,便于代碼子產品化。

2.2在Ubuntu下預處理的指令

指令:cpp hello.c > hello.i

圖2-1 預處理指令

圖2-2 生成hello.i檔案

2.3 Hello的預處理結果解析

圖2-3 預處理的hello.i檔案(部分)

分析:通過預處理,hello.c檔案變為hello.i檔案,原檔案進行了宏展開,stdio.h,unistd.h,stdlib.h依次展開,檔案擴充為3188行。main函數出現在3102行,如圖。cpp通過#ifdef,#ifndef對條件值來判斷是否執行。

2.4 本章小結

介紹了預處理的概念和功能,并在Ubuntu下的預處理指令将hello.c轉化為hello.i檔案,并對其進行了分析。

(第2章0.5分)

第3章 編譯

3.1 編譯的概念與作用

概念:編譯器(ccl)将文本檔案hello.i翻譯成文本檔案hello.s包含一個彙編語言程式。

作用:1:概念

2:文法檢查、調試措施、修改手段、覆寫處理、目标程式優化、不同語言合用、人及聯系。

3.2 在Ubuntu下編譯的指令

指令:gcc -S hello.i -o hello.s

圖3-1 Ubuntu下的編譯指令生成hello.s檔案

3.3 Hello的編譯結果解析

彙編指令:

指令 含義

.file 聲明源檔案

.text 以下是代碼段

.section.rodata 以下是rodata節

.globl 聲明一個全局變量

.type 指定函數類型和對象類型

.size 聲明大小

.long .string 聲明一個long,string類型

.align 聲明對指令或者資料的存放位址進行對齊的方式

3.3.1 資料

(1)、字元串:在.rodata中隻讀資料節

1.“Usage:hello 學号 姓名!\n”字元串變為UTF-8格式,漢字占三個位元組,一個\占一個位元組。如圖;

2.“hello %s %s\n”,具體如圖:

圖3-2 hello.s聲明在LC0和LC1中的字元串

(2)、整數

1.int sleepsecs:sleepsecs被聲明為全局變量,已經被指派過了,編譯器在.data聲明該變量。在.data中,對齊方式為4,設定類型為對象,設定大小為4位元組,設定為Long型為2。

圖3-3 hello.s中的sleepsecs的聲明

2.Int i:編譯器在hello.s中将i存儲在棧空間-4(%rbp)中,占了棧的4B。

3.Int argc:傳入的第一個參數

4.其他整型資料都是以立即數的性數出現

(3)、數組

Char *argv[]main,執行函數時的輸入指令行。在hello.s中使用兩次(%rax)(兩次rax分别為argv[1]和argv[2]的位址)取出其值,如圖:

圖3-4 計算位址取出數組

3.3.2指派

(1)、int sleepsecs=2.5:sleepsecs是全局變量,是以在.data中将sleepsecs聲明為2的long型。

(2)、i=0:整形指派使用mov指令完成,大小不同有差別:

指令 B W l Q

大小 8b(1B) 16b(2B) 32b(4B) 64b(8B)

i是4B的int類型,是以用movl指派,如圖:

圖3-5 變量i的指派

3.3.3 類型轉化

Int sleepsecs=2.5中,将浮點類型的2.5轉換為int類型。

浮點數類型預設為double,是以是double強制轉化為int類型,遵循向零舍入的原則,将2.5舍入為2。

3.3.4 算術操作

有如下彙編指令:

指令 效果

Leaq S,D D=&S

INC D D+=1

DEC D D-=1

NEG D D=-D

ADD S,D D=D+S

SUB S,D D=D-S

IMULQ S R[%rdx]:R[%rax]=SR[%rax](有符号)

MULQ S R[%rdx]:R[%rax]=SR[%rax](無符号)

IDIVQ S R[%rdx]=R[%rdx]:R[%rax] mod S(有符号)

R[%rax]=R[%rdx]:R[%rax] div S

DIVQ S R[%rdx]=R[%rdx]:R[%rax] mod S(無符号)

R[%rax]=R[%rdx]:R[%rax] div S

程式中的算數操作:

1.i++,i自增,使用指令addl,l為字尾(4B)。

2.彙編中leaq.LC1(%rip),%rdi,使用了加載有效位址指令leaq計算LC1的段位址%rip+LC1并傳遞給%rdi。

3.3.5 關系操作

有如下指令:

指令 效果 描述

CMP S1,S2 S2-S1 比較-設定條件碼

TEST S1,S2 S1&S2 測試-設定條件碼

SET** D D=** 按照将條件碼設定D

J —— 根據**與條件碼進行跳轉

程式中涉及的關系運算為: 1) argc!=3:判斷 argc 不等于 3。hello.s 中使用 cmpl $3,-20(%rbp),計算 argc-3 然後設定條件碼,為下一步 je 利用條件碼進行跳轉作準備。 2) i<10:判斷i小于10。hello.s中使用cmpl $9,-4(%rbp),計算 i-9 然後設定 條件碼,為下一步 jle 利用條件碼進行跳轉做準備。

3.3.6 控制轉移

涉及的控制轉移:(1) if (argv!=3):當 argv 不等于 3 的時候執行程式段中的代碼。如圖,首先 cmpl 比較 argv 和 3,設定條件碼,使用 je 判斷 ZF 标志位,如果為 0,說明 argv-3=0 argv==3,則直接跳轉到.L2,否則順序執行下一條語句。

圖3-6 if語句的編譯

2)for(i=0;i<10;i++);使用計數變量i循環 10 次。如圖,首先無條件跳轉到位于循環體.L4 之後的比較代碼,如果 i<=9,則跳入.L4 for 循環體執行,否則循環結束,執行 for 之後的邏輯。

圖3-7 for循環的編譯

3.3.7 函數操作

函數是一種過程,用一組指定的參數和可選的傳回值實作某種功能。P調用函數包含以下動作:傳遞控制、傳遞資料、配置設定和釋放記憶體。

64 位程式參數存儲順序:

1 2 3 4 5 6 7

%rdi %rsi %rdx %rcx %r8 %r9 棧空間

程式中涉及函數操作的有:

1)main函數:a)傳遞控制,main函數因為被調用call才能執行,call指令将下一條指令的位址dest壓棧,然後跳轉到main函數。b) 傳遞資料,外部調用過程向main函數傳遞參數 argc 和 argv,使用%rdi和%rsi存儲,函數正常出口為 return 0,将%eax 設定 0 傳回。 c) 配置設定和釋放記憶體,使用%rbp記錄棧幀的底,函數配置設定棧幀空間在%rbp之上,程式結束時,調用leave指令,leave 相當于mov %rbp,%rsp,pop%rbp,恢複棧空間為調用之前的狀态,然後ret傳回,ret相當 pop IP,将下一條要執行指令的位址設定為dest。

2)printf 函數: a) 傳遞資料:先 printf 将%rdi 設定為“Usage: Hello 學号姓名!\n”字元串的首位址。再将printf設定%rdi為“Hello %s %s\n” 的首位址,設定%rsi為argv[1],%rdx為argv[2]。b)控制傳遞:第一次 printf 因為隻有一個字元串參數,call [email protected];第二次printf使用call [email protected]。

3)exit 函數:a) 傳遞資料:将%edi設定為 1。b) 控制傳遞:call [email protected]。

4)sleep 函數:傳遞資料:将%edi 設定為sleepsecs。 控制傳遞:call [email protected]。

5)getchar 函數:控制傳遞:call [email protected]

3.4 本章小結

本節完成了對hello.i的編譯工作,成為彙編語言,分别介紹了編譯的資料、指派、類型轉化、算數操作、關系操作、控制轉移、函數操作及相關函數,解釋了以上操作的原理,然後根據hello.s來解釋,最終完全解釋了彙編代碼。

第4章 彙編

4.1 彙編的概念與作用

彙程式設計式是把彙編語言書寫的程式翻譯成與之等價的機器語言程式的翻譯程式。彙程式設計式輸入的是用彙編語言書寫的源程式,輸出的是用機器語言表示的目标程式。彙編語言的指令與機器語言的指令大體上保持一一對應的關系,彙編算法采用的基本政策是簡單的。通常采用兩遍掃描源程式的算法。第一遍掃描源程式根據符号的定義和使用,收集符号的有關資訊到符号表中;第二遍利用第一遍收集的符号資訊,将源程式中的符号化指令逐條翻譯為相應的機器指令。

4.2 在Ubuntu下彙編的指令

指令:as hello.s -o hello.o

圖4-1 生成hello.o檔案

4.3 可重定位目标elf格式

使用 readelf -a hello.o > helloo.elf 指令獲得 hello.o 檔案的 ELF 格式。組成如下:

1)ELF Header:以Magic開始,Magic 描述了生成該檔案的系統的字的大小和位元組順序,ELF 頭剩下的部分包括ELF頭的大小、目标檔案的類型、機器類型、位元組頭部表(section header table)的檔案偏移,以及節頭部表中條目的大小和數量等資訊。

圖4-2 ELF Header

2)Section Headers:節頭部表,包含了檔案中各個節的語義,包括節的類型、位置和大小等。

圖4-3 節頭部表Section Headers

3)重定位節.rela.text ,包含.text節中需要進行重定位的資訊,目标檔案和其他檔案組合時,需要修改這些位置。如圖 4.4,圖中 8 條重定位資訊分别是對.L0、puts 函數、exit 函數、.L1、printf 函數、 sleepsecs、sleep函數、getchar 函數進行重定位聲明。

4)圖4-4重定位節.rela.text

4.4 Hello.o的結果解析

指令:objdump -d -r hello.o > helloo.objdump

圖4-5 hello.s的反彙編代碼

與第三章的圖3-7進行對比,主要有如下差别:

1)分支轉移:反彙編代碼跳轉指令的操作數使用的不是段名稱如,段名稱是彙編語言中便于編寫的助記符,是以在彙編成機器語言之後是确定的位址。

2)函數調用:在.s 檔案中,函數調用之後直接跟着函數名稱,而在反彙程式設計式中,call的目标位址是目前下一條指令。因為 hello.c 中調用的函數需要通過動态連結器才能确定函數的運作時執行位址,在彙編成為機器語言時,将其call指令後的相對位址設定為全0,然 後在.rela.text 節中為其添加重定位條目。

3)全局變量通路:在.s檔案中,通路 rodata,使用段名稱+%rip,在反彙編代碼中 0+%rip,在彙編成為機器語言時,将操作數設定為全0并添加重定位條目。

機器語言程式的是二進制的機器指令序列集合,是純粹的二進制資料表示的語言,是電腦可以真正識别的語言。機器指令由操作碼和操作數組成。彙編語言是以人們比較熟悉的詞句直接表述CPU動作形成的語言,是最接近CPU運作原理的較為通俗的比較容易了解的語言。在不同的裝置中,彙編語言對應着不同的機器語言指令集,通過彙編過程轉換成機器指令。機器語言與彙編語言具有一一對應的映射關系,一條機器語言程式對應一條彙編語言語句,但不同平台之間不可直接移植。

4.5 本章小結

完成了hello.s到hello.o的彙編,轉化為了可重定位檔案,将hello.o的elf格式和通過objdump得到的反彙編與.s彙程式設計式代碼進行比較,了解了差别。

(第4章1分)

第5章 連結

5.1 連結的概念與作用

連結程式将分别在不同的目标檔案中編譯或彙編的代碼收集到一個可直接執行的檔案中。它還連接配接目标程式和用于标準庫函數的代碼,以及連接配接目标程式和由計算機的作業系統提供的資源。連結工作大緻包含兩個步驟,一是符号解析,二是重定位。在符号解析步驟中,連結器将每個符号引用與一個确定的符号定義關聯起來。将多個單獨的代碼節和資料節合并為單個節。将符号從它們的在.o檔案的相對位置重新定位到可執行檔案的最終絕對記憶體位置。更新所有對這些符号的引用來反映它們的新位置。

5.2 在Ubuntu下連結的指令

指令:ld -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 /usr/lib/gcc/x86_64-linux-gnu/7/crtbegin.o /usr/lib/gcc/x86_64-linux-gnu/7/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o hello.o -lc -z relro -o hello

圖5-1 使用 ld 指令連結生成可執行程式 hello

5.3 可執行目标檔案hello的格式

指令:readelf -a hello > hello.elf 生成hello的elf檔案

圖5-2 hello檔案的檔案頭

根據檔案頭的資訊,可以知道該檔案是可執行目标檔案,有31個節。

在 ELF 格式檔案中,節頭對 hello 中所有的節資訊進行了聲明,包括大小 S以及在程式中的偏移量,是以根據節頭中的資訊我們就可以用 HexEdit 定位各個節所占的區間(起始位置,大小)。其中 Address 是程式被載入到虛拟位址的起始位址。

圖5-3 hello ELF格式的節頭表

由于是可執行目标檔案,是以每個段的起始位址都不相同,它們的起始位址分别對應着裝載到虛拟記憶體中的虛拟位址。這樣可以直接從檔案起始處得到各段的起始位置,以及各段所占空間的大小。同時可以觀察到,代碼段是可執行的,但是不能寫;資料段和隻讀資料段都不可執行,而且隻讀資料段也不可寫。

5.4 hello的虛拟位址空間

使用edb加載hello,檢視本程序的虛拟位址空間各段資訊,

圖5-4 使用edb檢視.txet段

如圖 5-5,每一個項提供了各段在虛拟位址空間和實體位址空間的大小、位置、标志、通路權限和對齊方面的資訊。在下面可以看出,程式包含 8 個段:

1)PHDR 儲存程式頭表。

2)INTERP 指定在程式已經從可執行檔案映射到記憶體之後,必須調用的解釋 器。

3)LOAD 表示一個需要從二進制檔案映射到虛拟位址空間的段。

4)DYNAMIC 儲存由動态連結器使用的資訊。

5)NOTE 儲存輔助資訊。

6)GNU_STACK:權限标志,标志棧是否是可執行的。

7) GNU_RELRO:指定在重定位結束之後那些記憶體區域是需要設定隻讀。

圖5-5 ELF檔案的程式頭

5.5 連結的重定位過程分析

使用 objdump -d -r hello > hello.objdump 獲得 hello 的反彙編代碼。

圖5-6 反彙編檔案

與hello.o的反彙編檔案相比,多出了以下内容:

節名 描述

.interp 儲存ld.so的路勁

.note.ABI-tag linux下特有的節

.hash 符号的哈希表

.gun.hash GNU拓展的符号的哈希表

.dynsym 運作時動态符号表

.dynstr 存放.dynsym節中的符号名稱

.gnu.version 符号版本

.gnu.version_r 符号引用版本

.rela.dyn 運作時動态重定位表

.rela.plt .plt節的重定位條目

.init 程式初始化需要執行的代碼

.plt 動态連結-過程連結表

.fini 當程式正常終止時需執行的代碼

.eh_frame Contains exception unwinding and source language information.

.dynamic 存放被ld.so使用的動态連結資訊

.got 動态連結-全局偏移量表-存放變量

.got.plt 動态連結-全局偏移量表-存放函數

.data 初始化的資料

.comment 一串包含編譯器的NULL-terminated字元串

比較分析連結器如下:

1)函數個數:在使用 ld 指令連結的時,主要定義了main 函數,libc.so中定義了 hello.c 中的 printf、sleep、getchar、exit 函數和_start 中的 __libc_csu_init,__libc_csu_fini,__libc_start_main。連結器将上述函數加入。

2)函數調用:連結器解析重定條目時發現對外部函數調用的類型為 R_X86_64_PLT32 的重定位,此時.text 與.plt 節相對距離已經确定,連結器計算相對距離,将對動态連結庫中函數的調用 值改為 PLT 中相應函數與下條指令的相對位址,指向對應函數。對于此類重定位 連結器為其構造.plt 與.got.plt。

3).rodata 引用:連結器解析重定條目時發現兩個類型為 R_X86_64_PC32 的 對.rodata 的重定位(printf 中的兩個字元串),由(2)知.rodata 與.text 節之間的相對距離确定,是以連結器直接修改 call 之後的值為目标位址與下一條指令的位址之差,指向相應的字元串。例:

refptr = s + r.offset = Pointer to 0x40054A

refaddr=ADDR(s)+r.offset=ADDR(main)+r.offset=0x400532+0x18=0x40054A

*refptr = (unsigned) (ADDR(r.symbol) + r.addend-refaddr) = ADDR(str1)+r.addend-refaddr=0x400644+(-0x4)-0x40054A=(unsigned)0xF6

5.6 hello的執行流程

加載程式 ld-2.23.so!_dl_start ld-2.23.so!_dl_init LinkAddress!_start libc-2.23.so!_libc_start_main libc-2.23.so!_cxa_atexit LinkAddress!_libc_csu.init libc-2.23.so!_setjmp

Call main LinkAddress!main

程式終止 libc-2.23.so!exit

5.7 Hello的動态連結分析

對于動态共享連結庫中 PIC 函數,編譯器沒有辦法預測函數的運作時位址,所 以需要添加重定位記錄,等待動态連結器處理,為避免運作時修改調用子產品的代 碼段,連結器采用延遲綁定的政策。動态連結器使用過程連結表 PLT+全局偏移量 表 GOT 實作函數的動态連結,GOT 中存放函數目标位址,PLT 使用 GOT 中位址 跳轉到目标函數。 在 dl_init 調用之前,對于每一條 PIC 函數調用,調用的目标位址都實際指向 PLT 中的代碼邏輯,GOT 存放的是 PLT 中函數調用指令的下一條指令位址。如在 圖 5.7 (a)。 在 dl_init 調用之後,如圖 5.7 (b),

圖5-7(a) 沒有調用 dl_init 之前的全局偏移量表.got.plt

圖5-7(b) 調用 dl_init 之後的全局偏移量表.got.plt

在之後的函數調用時,首先跳轉到 PLT 執行.plt 中邏輯,第一次通路跳轉時 GOT 位址為下一條指令,将函數序号壓棧,然後跳轉到 PLT[0],在 PLT[0]中将重 定位表位址壓棧,然後通路動态連結器,在動态連結器中使用函數序号和重定位 表确定函數運作時位址,重寫 GOT,再将控制傳遞給目标函數。之後如果對同樣函數調用,第一次通路跳轉直接跳轉到目标函數。

5.8 本章小結

本章讨論了連結過程中對程式的處理。Linux系統使用可執行可連結格式,即ELF,具有.text,.rodata等節,并且通過特定的結構組織。

經過連結,ELF可重定位的目标檔案變成可執行的目标檔案,連結器會将靜态庫代碼寫入程式中,以及動态庫調用的相關資訊,并且将位址進行重定位,進而保證尋址的正确進行。靜态庫直接寫入代碼即可,而動态連結過程相對複雜一些,涉及共享庫的尋址。

連結後,程式便能夠在作為程序通過虛拟記憶體機制直接運作。

總體來說,主要分析了 hello 的 虛拟位址空間、重定位過程、執行流程、動态連結過程。

(第5章1分)

第6章 hello程序管理

6.1 程序的概念與作用

1.概念

程序是計算機中的程式關于某資料集合上的一次運作活動,是系統進行資源配置設定和排程的基本機關,是作業系統結構的基礎。

2.作用

程序作為一個執行中程式的執行個體,系統中每個程式都運作在某個程序的上下文中,上下文是由程式正确運作所需的狀态組成的。

這個狀态包括存放在記憶體中的程式的代碼和資料,它的棧、通用目的寄存器的内容、程式計數器、環境變量以及打開檔案描述符的集合。

6.2 簡述殼Shell-bash的作用與處理流程

Shell 的作用:Shell 是一個用 C 語言編寫的程式,他是使用者使用 Linux 的橋梁。 Shell 是指一種應用程式,Shell 應用程式提供了一個界面,使用者通過這個界面通路 作業系統核心的服務。

處理流程:

1)從終端讀入輸入的指令。

2)将輸入字元串切分獲得所有的參數

3)如果是内置指令則立即執行

4)否則調用相應的程式為其配置設定子程序并運作

5)shell 接受鍵盤輸入信号,并對這些信号進行相應處理

6.3 Hello的fork程序建立過程

shell作為父程序通過fork函數為hello建立一個新的程序,供其執行。通過fork函數,子程序得到與父程序使用者級虛拟位址空間相同的但是獨立的一份副本。

在終端中鍵入 ./hello 1170300921 wangjiangrui,運作的終端程式會對輸入的指令行進行解析,因為 hello 不是一個内置的 shell 指令是以解析之後終端 程式判斷./hello 的語義為執行目前目錄下的可執行目标檔案 hello,之後終端程式 首先會調用 fork 函數建立一個新的運作的子程序,新建立的子程序幾乎但不完全 與父程序相同,子程序得到與父程序使用者級虛拟位址空間相同的(但是獨立的) 一份副本,這就意味着,當父程序調用 fork 時,子程序可以讀寫父程序中打開的 任何檔案。父程序與子程序之間最大的差別在于它們擁有不同的 PID。 父程序與子程序是并發運作的獨立程序,核心能夠以任意方式交替執行它們的 邏輯控制流的指令。在子程序執行期間,父程序預設選項是顯示等待子程序的完成。

shell通過fork進行程序建立的代碼

if ((pid = fork()) < 0)

unix_error(“fork error”);

if (pid == 0)

{

sigprocmask(SIG_UNBLOCK, &mask, NULL);

/* Each new job must get a new process group ID

so that the kernel doesn't send ctrl-c and ctrl-z

signals to all of the shell's jobs */
if (setpgid(0, 0) < 0)
	unix_error("setpgid error");

/* Now load and run the program in the new job */
if (execve(argv[0], argv, environ) < 0) 
{
	printf("%s: Command not found\n", argv[0]);
	exit(0);
}
           

}

6.4 Hello的execve過程

使用execve就是一次系統調用,首先要做的将新的可執行檔案的絕對路徑從調用者(使用者空間)拷貝到系統空間中。在得到可執行檔案路徑後,就找到可執行檔案打開,由于作業系統已經為可執行檔案設定了一個資料結構,就初始化這個資料結構,儲存一個可執行檔案必要的資訊。可執行檔案不是真正上能夠自己運作的,需要有代理人來代理。在系統核心中有一個formats隊列,循環周遊這個隊列,看看現在被初始化的這個資料結構是哪個代理人可以代理的。如果沒有就繼續檢視資料結構中的資訊。按照系統配置了是否可以動态加載子產品,加載一次子產品,再循環周遊看是否有代理人前來認領。找到正确的代理人後,代理人首先要做的就是放棄以前從父程序繼承來的資源。主要是對信号處理表,使用者空間和檔案大資源的處理。将父程序的信号處理表複制過來,放棄原來的使用者空間。然後載入真正的程式代碼和資料段,開辟堆棧,映射執行參數和環境變量。

在execve加載了可執行程式之後,它調用啟動代碼。啟動代碼設定棧,并将控制傳遞給新程式的主函數,即可執行程式的main函數。此時使用者棧已經包含了指令行參數與環境變量,進入main函數後便開始逐漸運作程式。

6.5 Hello的程序執行

上下文資訊:上下文就是核心重新啟動一個被搶占的程序所需要的狀态,它由 通用寄存器、浮點寄存器、程式計數器、使用者棧、狀态寄存器、核心棧和各種内 核資料結構等對象的值構成。

時間片:一個程序執行它的控制流的一部分的每一時間段叫做時間片。

排程:

多個流并發地執行的一般現象被稱為并發。一個程序和其他進輪流運作的概念稱為多任務。一個程序執行它的控制流的一部分的每一時間段叫做時間片。是以,多任務也叫做時間分片。

hello程式執行過程中同樣存儲時間分片,與作業系統的其他進行并發運作。并發執行涉及到作業系統核心采取的上下文交換政策。核心為每個程序維持一個上下文,上下文就是核心重新啟動一個先前被搶占的程序所需的狀态。

在執行過程中,核心可以決定搶占目前程序,并重新開始一個先前被搶占的程序,這個過程稱為排程。

如圖 6-1,hello 初始運作在使用者模式,在 hello 程序調用 sleep 之後陷入核心模 式,核心處理休眠請求主動釋放目前程序,并将 hello 程序從運作隊列中移出加入 等待隊列,定時器開始計時,核心進行上下文切換将目前程序的控制權交給其他 程序,當定時器到時時發送一個中斷信号,此時進入核心狀态執行中斷 處理,将 hello 程序從等待隊列中移出重新加入到運作隊列,成為就緒狀态,hello 程序就可以繼續進行自己的控制邏輯流了。

當 hello 調用 getchar 的時候,實際落腳到執行輸入流是 stdin 的系統調用 read, hello 之前運作在使用者模式,在進行 read 調用之後陷入核心,核心中的陷阱處理程 序請求來自鍵盤緩沖區的 DMA 傳輸,并且安排在完成從鍵盤緩沖區到記憶體的資料 傳輸後,中斷處理器。此時進入核心模式,核心執行上下文切換,切換到其他進 程。當完成鍵盤緩沖區到記憶體的資料傳輸時,引發一個中斷信号,此時核心從其 他程序進行上下文切換回 hello 程序。

圖6-1 hello 程序 sleep 上下文切換的原理示意

6.6 hello的異常與信号處理

hello執行過程中可能出現四類異常:中斷、陷阱、故障和終止。

中斷是來自I/O裝置的信号,異步發生,中斷處理程式對其進行處理,傳回後繼續執行調用前待執行的下一條代碼,就像沒有發生過中斷。

陷阱是有意的異常,是執行一條指令的結果,調用後也會傳回到下一條指令,用來調用核心的服務進行操作。幫助程式從使用者模式切換到核心模式。

故障是由錯誤情況引起的,它可能能夠被故障處理程式修正。如果修正成功,則将控制傳回到引起故障的指令,否則将終止程式。

終止是不可恢複的緻命錯誤造成的結果,通常是一些硬體的錯誤,處理程式會将控制傳回給一個abort例程,該例程會終止這個應用程式。

hello執行過程中,可能會遇到各種異常,信号則是一種通知使用者異常發送的機制。例如較為底層的硬體異常以及較高層的軟體事件,比如Ctrl-Z和Ctrl-C,分别觸發SIGCHLD和SIGINT信号。

收到信号後程序會調用相應的信号處理程式對其進行處理。

圖6-2 正常情況運作

圖6-3 按下ctrl Z的情況

圖6-4 按下ctrlC的情況

圖6-5 亂按的情況

圖6-2:正常情況

圖6-3:,是在程式輸出 2 條 info 之後按下 ctrl-z 的結果,當按下 ctrl-z 之後,shell 父程序收到 SIGSTP 信号,信号處理函數的邏輯是列印螢幕回顯、将 hello 程序挂起,通過 ps 指令我們可以看出 hello 程序沒有被回收,此時他的背景 job 号是 1,調用 fg 1 将其調到前台,此時 shell 程式首先列印 hello 的指令行指令, hello 繼續運作列印剩下的 8 條 info,之後輸入字串,程式結束,同時程序被回收。

圖6-4:是在程式輸出 3 條 info 之後按下 ctrl-c 的結果,當按下 ctrl-c 之 後,shell 父程序收到 SIGINT 信号,信号處理函數的邏輯是結束 hello,并回收 hello 程序。

圖6-5:可見亂按并不影響正常運作。

6.7本章小結

本章介紹了程式在shell執行及程序的相關概念。程式在shell中執行是通過fork函數及execve建立新的程序并執行程式。程序擁有着與父程序相同卻又獨立的環境,與其他系統進并發執行,擁有各自的時間片,在核心的排程下有條不紊的執行着各自的指令。

并且介紹了在運作過程中的異常與信号處理。

(第6章1分)

第7章 hello的存儲管理

7.1 hello的存儲器位址空間

邏輯位址:邏輯位址空間的格式為“段位址:偏移位址”,例如“23:8048000”,在實模式下可以轉換為實體位址:邏輯位址CS:EA = 實體位址CS × 16 + EA。保護模式下以段描述符作為下标,通過在GDT/LDT表獲得段位址,段位址加偏移位址得到線性位址。

線性位址:線性位址指虛拟位址到實體位址變換的中間層,是處理器可尋址的記憶體空間(稱為線性位址空間)中的位址。程式代碼會産生邏輯位址,或者說段中的偏移位址,加上相應段基址就成了一個線性位址。如果啟用了分頁機制,那麼線性位址可以再經過變換産生實體位址。若是沒有采用分頁機制,那麼線性位址就是實體位址。

虛拟位址:此處就是線性位址

實體位址:實體位址是用于記憶體晶片級的單元尋址,與處理器和CPU連接配接的位址總線相對應。現代作業系統都提供了一種記憶體管理的抽像,即虛拟記憶體。程序使用虛拟記憶體中的位址,即虛拟位址,由作業系統協助相關硬體,把它“轉換”成真正的實體位址。hello.s中使用的就是虛拟空間的虛拟位址。

7.2 Intel邏輯位址到線性位址的變換-段式管理

Intel處理器從邏輯位址到線性位址的變換通過段式管理,介紹段式管理就必須了解段寄存器的相關知識。段寄存器對應着記憶體不同的段,有棧段寄存器(SS)、資料段寄存器(DS)、代碼段寄存器(CS)和輔助段寄存器(ES/GS/FS)。其大體對應關系如下圖:

圖7-1 段寄存器

段寄存器用于存放段選擇符,通過段選擇符可以得到對應段的首位址。段選擇符分為三個部分,分别是索引、TI(決定使用全局描述符表還是局部描述符表)和RPL(CPU的目前特權級)。

圖7-2 段選擇符

Intel處理器在通過段式管理尋址時,首先通過段描述符得到段基址,然後與偏移量結合得到線性位址,進而得到了虛拟位址。至于偏移量,基址寄存器還是變址寄存器有不同的計算方法,後者需要經過乘比例因子等處理。

7.3 Hello的線性位址到實體位址的變換-頁式管理

虛拟記憶體系統将虛拟記憶體分割稱為虛拟頁,實體記憶體分割稱為實體頁。

圖7-3 虛拟頁與實體頁

頁表将虛拟頁映射到實體頁,其每一項稱為頁表條目,由有效位和一個n位的位址字段組成。如果設定有效位說明該頁已緩存,否則未緩存,位址字段不為空時指向虛拟頁在磁盤上的起始位址。

虛拟位址到實體位址的翻譯時MMU通過虛拟位址索引到對應的頁表條目,如果已緩存命中,否則不命中稱為缺頁。發生缺頁時,MMU會選擇一個犧牲頁,将之前缺頁的虛拟記憶體對應的資料複制到它的位置,并更新頁表,然後重新觸發虛拟位址翻譯。

通過頁表,MMU可實作從虛拟位址到實體位址的映射。

圖7-4 使用頁表的位址翻譯

CPU中的頁表基址寄存器指向目前頁表,n位的虛拟位址包含兩個部分:一個p位的虛拟頁面偏移(VPO)和一個n- p位的虛拟頁号(VPN)。

MMU利用VPN選擇适當的PTE,然後将實體的PPN虛拟位址中的VPO聯起來,得到實體位址。

如果缺頁需要作業系統核心與硬體合作完成。

圖7-5 頁面命中和缺頁的操作圖

7.4 TLB與四級頁表支援下的VA到PA的變換

在 Intel Core i7 環境下研究 VA 到 PA 的位址翻譯問題。前提如下: 虛拟位址空間 48 位,實體位址空間 52 位,頁表大小 4KB,4 級頁表。TLB 4 路 16 組相聯。CR3 指向第一級頁表的起始位置(上下文一部分)。 解析前提條件:由一個頁表大小 4KB,一個 PTE 條目 8B,共 512 個條目,使 用 9 位二進制索引,一共 4 個頁表共使用 36 位二進制索引,是以 VPN 共 36 位, 因為 VA 48 位,是以 VPO 12 位;因為 TLB 共 16 組,是以 TLBI 需 4 位,因為 VPN 36 位,是以 TLBT 32 位。 CPU 産生虛拟位址 VA,VA 傳送給 MMU,MMU 使用前 36 位 VPN 作為 TLBT(前 32 位)+TLBI(後 4 位)向 TLB 中比對,如果命中,則得到 PPN (40bit)與 VPO(12bit)組合成 PA(52bit)。 如果 TLB 中沒有命中,MMU 向頁表中查詢,CR3 确定第一級頁表的起始地 址,VPN1(9bit)确定在第一級頁表中的偏移量,查詢出 PTE,如果在實體記憶體 中且權限符合,确定第二級頁表的起始位址,以此類推,最終在第四級頁表中查 詢到 PPN,與 VPO 組合成 PA,并且向 TLB 中添加條目。 如果查詢 PTE 的時候發現不在實體記憶體中,則引發缺頁故障。如果發現權限 不夠,則引發段錯誤。

圖7-6 Core i7頁表翻譯

7.5 三級Cache支援下的實體記憶體通路

前提:隻讨論 L1 Cache 的尋址細節,L2 與 L3Cache 原理相同。L1 Cache 是 8 路 64 組相聯。塊大小為 64B。 解析前提條件:因為共 64 組,是以需要 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-7 三級Cache支援下的實體記憶體通路

7.6 hello程序fork時的記憶體映射

虛拟記憶體和記憶體映射解釋了fork函數如何為每個新程序提供私有的虛拟位址空間。Fork函數為新程序建立虛拟記憶體。建立目前程序的的mm_struct, vm_area_struct和頁表的原樣副本,兩個程序中的每個頁面都标記為隻讀,兩個程序中的每個區域結構(vm_area_struct)都标記為私有的寫時複制(COW)。在新程序中傳回時,新程序擁有與調用fork程序相同的虛拟記憶體,随後的寫操作通過寫時複制機制建立新頁面。

7.7 hello程序execve時的記憶體映射

execve函數在shell中加載并運作包含在可執行檔案hello中的程式,用hello程式有效地替代了目前程式。加載hello的過程主要步驟如下:

首先删除已存在的使用者區域,也就是将shell與hello都有的區域結構删除。然後映射私有區域,即為新程式的代碼、資料、bss和棧區域建立新的區域結構,均為私有的、寫時複制的。下一步是映射共享區域,将一些動态連結庫映射到hello的虛拟位址空間,最後設定程式計數器,使之指向hello程式的代碼入口。

經過這個記憶體映射的過程,在下一次排程hello程序時,就能夠從hello的入口點開始執行了。

7.8 缺頁故障與缺頁中斷處理

缺頁故障是一種常見的故障,當指令引用一個虛拟位址,在 MMU 中查找頁表 時發現與該位址相對應的實體位址不在記憶體中,是以必須從磁盤中取出的時候就 會發生故障。其處理流程遵循圖 7-8所示的故障處理流程。

圖7-8 故障處理流程

缺頁中斷處理:缺頁處理程式是系統核心中的代碼,選擇一個犧牲頁面,如果 這個犧牲頁面被修改過,那麼就将它交換出去,換入新的頁面并更新頁表。當缺 頁處理程式傳回時,CPU 重新啟動引起缺頁的指令,這條指令再次發送 VA 到 MMU,這次 MMU 就能正常翻譯 VA 了。

7.9動态存儲配置設定管理

動态存儲配置設定管理由動态記憶體配置設定器完成。動态記憶體配置設定器維護着一個程序的虛拟記憶體區域,稱為堆。堆是一個請求二進制零的區域,它緊接在未初始化的資料區後開始,并向上生長(向更高的位址)。配置設定器将堆視為一組不同大小的塊的集合來維護。

每個塊就是一個連續的虛拟記憶體片,要麼是已配置設定的,要麼是空閑的。已配置設定的塊顯式地保留為供應用程式使用。空閑塊可以用來配置設定。空閑塊保持空閑,直到它顯示地被應用程式所配置設定。

一個已配置設定的塊保持已配置設定狀态,直到它被釋放,這種釋放要麼是應用程式顯式執行的,要麼是記憶體配置設定器自身隐式執行的。

動态記憶體配置設定器從堆中獲得空間,将對應的塊标記為已配置設定,回收時将堆标記為未配置設定。而配置設定和回收的過程中,往往涉及到分割、合并等操作。

動态記憶體配置設定器的目标是在對齊塊的基礎上,盡可能地提高吞吐率及空間占用率,即減少因為記憶體配置設定造成的碎片。其實作常見的資料結構有隐式空閑連結清單、顯式空閑連結清單、分離空閑連結清單,常見的放置政策有首次适配、下一次适配和最佳适配。

為了更好的介紹動态存儲配置設定的實作思想,以隐式空閑配置設定器的實作原理為例進行介紹:

圖7-9 隐式空閑連結清單堆塊結構

隐式空閑連結清單配置設定器的實作涉及到特殊的資料結構。其所使用的堆塊是由一個子的頭部、有效載荷,以及可能的一些額外的填充組成的。頭部含有塊的大小以及是否配置設定的資訊。有效載荷用來存儲資料,而填充塊則是用來對付外部碎片以及對齊要求。

基于這樣的基本單元,便可以組成隐式空閑連結清單。

圖7-10 隐式空閑連結清單結構

通過頭部記錄的堆塊大小,可以得到下一個堆塊的大小,進而使堆塊隐含地連接配接着,進而配置設定器可以周遊整個空閑塊的集合。在連結清單的尾部有一個設定了配置設定位但大小為零的終止頭部,用來标記結束塊。

當請求一個k位元組的塊時,配置設定器搜尋空閑連結清單,查找足夠大的空閑塊,其搜尋政策主要有首次适配、下一次适配、最佳适配三種。

一旦找到空閑塊,如果大小比對的不是太好,配置設定器通常會将空閑塊分割,剩下的部分形成一個新的空閑塊。如果無法搜尋到足夠空間的空閑塊,配置設定器則會通過調用sbrk函數向核心請求額外的堆記憶體。

當配置設定器釋放已配置設定塊後,會将釋放的堆塊自動與周圍的空閑塊合并,進而提高空間使用率。為了實作合并并保證吞吐率,往往需要在堆塊中加入腳部進行帶邊界标記的合并。

7.10本章小結

本章主要介紹了 hello 的存儲器位址空間、intel 的段式管理、hello 的頁式管理, 以 intel Core7 在指定環境下介紹了 VA 到 PA 的變換、實體記憶體通路,還介紹了 hello 程序 fork 時的記憶體映射、execve 時的記憶體映射、缺頁故障與缺頁中斷處理、動态存儲配置設定管理。

(第7章 2分)

第8章 hello的IO管理

8.1 Linux的IO裝置管理方法

一個Linux檔案就是一個m個位元組的序列,所有的I/O裝置都被模型化為檔案,而所有的輸入和輸出都被當作對相應檔案的讀和寫來執行。這個裝置映射為檔案的方式,允許Linux核心引出一個簡單、低級的應用接口,稱為Unix I/O,這使得輸入和輸出都能以一種統一且一緻的方式的來執行。

一個應用程式通過要求核心打開相應的檔案來宣告它想通路一個I/O裝置。核心傳回一個小的非負整數,叫做描述符,而檔案的相關資訊由核心記錄,應用程式隻需要記錄這個描述符。

Linux shell建立的每個程序開始時都包含标準輸入、标準輸出、标準錯誤三個檔案,供其執行過程中使用。

對于每個打開的檔案,核心保持着一個檔案位置k,初始為0,即從檔案開頭起始的位元組偏移量,應用程式能夠通過執行seek操作來顯式的改變其值。

至于讀操作,就是從檔案複制n個位元組到記憶體,并将檔案位置k增加為k + n。當k大于等于檔案大小時,觸發EOF條件,即讀到檔案的尾部。

最後,在結束對檔案的通路後,會通過核心關閉這個檔案,核心将釋放打開這個檔案時建立的資料結構,并将描述符恢複到可用的描述符池中。

8.2 簡述Unix IO接口及其函數

Linux以檔案的方式對I/O裝置進行讀寫,将裝置均映射為檔案。對檔案的操作,核心提供了一種簡單、低級的應用接口,即Unix I/O接口。

Unix I/O接口提供了以下函數供應用程式調用:

打開檔案:int open(char *filename, int flags, mode_t mode);

關閉檔案:int close(int fd);

讀檔案:ssize_t read(int fd, void *buf, size_t n);

寫檔案:ssize_t write(int fd, const void *buf, size_t n);

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.首先 arg 獲得第二個不定長參數,即輸出的時候格式化串對應的值。 vsprintf 代碼如下

int vsprintf(char *buf, const char fmt, va_list args)

{

char p;

char tmp[256];

va_list p_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 生成格式化之後的字元串,并 傳回字串的長度。 在 printf 中調用系統函數 write(buf,i)将長度為 i 的 buf 輸出。在Linux下,write函數的第一個參數為fd,也就是描述符,而1代表的就是标準輸出。檢視write函數的彙編實作可以發現,它首先給寄存器傳遞了幾個參數,然後調用syscall結束。write通過執行syscall指令實作了對系統服務的調用,進而使核心執行列印操作。

syscall 将字元串中的位元組“Hello 1170300921 wangjiangrui”從寄存器中通過總線複 計算機系統課程報告制到顯示卡的顯存中,顯存中存儲的是字元的 ASCII 碼。 字元顯示驅動子程式将通過 ASCII 碼在字模庫中找到點陣資訊将點陣資訊存 儲到 vram 中。 顯示晶片會按照一定的重新整理頻率逐行讀取 vram,并通過信号線向液晶顯示器傳輸每一個點(RGB 分量)。 于是我們的列印字元串“Hello 1170300921 wangjiangrui”就顯示在了螢幕上。

8.4 getchar的實作分析

getchar的實作如下:

Int getchar(void)

{

Char c;

Return (read(0,&c,1)==1)?(unsigned char)c:EOF

}

異步異常-鍵盤中斷的處理:當使用者按鍵時,鍵盤接口會得到一個代表該按鍵 的鍵盤掃描碼,同時産生一個中斷請求,中斷請求搶占目前程序運作鍵盤中斷子 程式,鍵盤中斷子程式先從鍵盤接口取得該按鍵的掃描碼,然後将該按鍵掃描碼 轉換成 ASCII 碼,儲存到系統的鍵盤緩沖區之中。

getchar 函數落實到底層調用了系統函數 read,通過系統調用 read 讀取存儲在鍵盤緩沖區中的 ASCII 碼直到讀到回車符然後傳回整個字串,getchar 進行封裝, 大體邏輯是讀取字元串的第一個字元然後傳回。

8.5本章小結

本章主要介紹了 Linux 的 IO 裝置管理方法、Unix IO 接口及其函數,分析了 printf 函數和 getchar 函數。

(第8章1分)

結論

用計算機系統的語言,逐條總結hello所經曆的過程:

hello.c通過鍵盤滑鼠等I/O裝置輸入計算機,并存儲在記憶體中。然後預處理器将hello.c預處理成為文本檔案hello.i。接着編譯器将hello.i翻譯成彙編語言檔案hello.s。彙編器将hello.s彙編成可重定位二進制代碼hello.o。連結器将外部檔案和hello.o連接配接起來形成可執行二進制檔案hello.out。shell通過fork和execve建立程序,然後把hello加載到其中。shell建立新的記憶體區域,并加載代碼、資料和堆棧。hello在執行的過程中遇到異常,會接受shell的信号完成處理。hello在執行的過程中需要使用記憶體,那麼就通過CPU和虛拟空間進行位址通路。Hello執行結束後,shell回收其僵屍程序,從系統中消失。

你對計算機系統的設計與實作的深切感悟,你的創新理念,如新的設計與實作方法:

計算機系統過于虛拟,虛拟代表進階,設計過程雖然複雜,但是卻無一不展現計算機的思想和進階之處,凡世界之物,均在系統之内。進階語言表達了彙編語言,彙編語言表達了機器語言,并且随着技術的發展,硬體性能的不斷提升,計算機系統還會向着綜合與精分兩方面不斷發展,是以為了計算機的偉大發展,讓我們一起加油吧。

(結論0分,缺失 -1分,根據内容酌情加分)

附件

Hello.c c程式代碼

Hello.i 預處理之後的文本檔案

Hello.s 編譯之後的彙編檔案

Hello.o 彙編之後的可重定位目标程式

Hello 連結之後的可執行目标檔案

Helloo.objdmp hello.o的反彙編代碼

Helloo.elf hello.o的ELF格式

Hello.objdmp hello的反彙編代碼

Hello.elf hello的ELF格式

(附件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.

[7] 蘭德爾E.布萊恩特 大衛R.奧哈拉倫. 深入了解計算機系統(第3版).

機械工業出版社.

[8] https://blog.csdn.net

(參考文獻0分,缺失 -1分)

繼續閱讀