欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

首頁綜合 正文
目錄

柚子快報邀請碼778899分享:ICS大作業(yè)程序人生

柚子快報邀請碼778899分享:ICS大作業(yè)程序人生

http://yzkb.51969.com/

計算機系統(tǒng)

大作業(yè)

題 ????目??程序人生-Hello’s P2P ?

專 ??????業(yè) ?????網絡空間安全??????????????????

學   ??號 ??????2022112241?????????????????

班 ??級????????2203901????????????????

學 ??????生 ????????孫浩馨?????????

指 導 教 師????????史先俊?????????????

計算機科學與技術學院

2024年5月

摘 ?要

本文圍繞hello.c程序而展開,研究了這一最基本c語言代碼在Linux系統(tǒng)下的P2P,020的生命周期,對hello.c文件經過計算機的預處理、編譯、匯編、鏈接、加載運行、執(zhí)行、訪問內存、動態(tài)申請內存、信號處理、終止與回收的全過程,從而了解hello.c的完整一生。通過對hello.c的研究,對計算機系統(tǒng)課程的體系進行了回顧與串聯,實現了對計算機體系的深入理解。

關鍵詞:計算機系統(tǒng);??hello.c;計算機體系結構???;程序生命周期???;????????????????????

目 ?錄

第1章 概述

1.1 Hello簡介

1.2 環(huán)境與工具

1.3 中間結果

1.4 本章小結

第2章 預處理

2.1?預處理的概念與作用

2.2在Ubuntu下預處理的命令

2.3 Hello的預處理結果解析

2.4 本章小結

第3章 編譯

3.1 編譯的概念與作用

3.2 在Ubuntu下編譯的命令

3.3 Hello的編譯結果解析

3.4 本章小結

第4章 匯編

4.1 匯編的概念與作用

4.2 在Ubuntu下匯編的命令

4.3 可重定位目標elf格式

4.4 Hello.o的結果解析

4.5 本章小結

第5章 鏈接

5.1 鏈接的概念與作用

5.2 在Ubuntu下鏈接的命令

5.3 可執(zhí)行目標文件hello的格式

5.4 hello的虛擬地址空間

5.5 鏈接的重定位過程分析

5.6 hello的執(zhí)行流程

5.7 Hello的動態(tài)鏈接分析

5.8 本章小結

第6章 hello進程管理

6.1 進程的概念與作用

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

6.3 Hello的fork進程創(chuàng)建過程

6.4 Hello的execve過程

6.5 Hello的進程執(zhí)行

6.6 hello的異常與信號處理

6.7本章小結

第7章 hello的存儲管理

7.1 hello的存儲器地址空間

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

7.3 Hello的線性地址到物理地址的變換-頁式管理

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

7.5 三級Cache支持下的物理內存訪問

7.6 hello進程fork時的內存映射

7.7 hello進程execve時的內存映射

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

7.9動態(tài)存儲分配管理

7.10本章小結

第8章 hello的IO管理

8.1 Linux的IO設備管理方法

8.2 簡述Unix IO接口及其函數

8.3 printf的實現分析

8.4 getchar的實現分析

8.5本章小結

結論

附件

參考文獻

第1章 概述

1.1 Hello簡介

P2P P2P即 From Program to Progress,從程序到進程。當hello被輸入到計算機中,會被存儲為hello.c的源代碼文件。hello.c源代碼經過cpp預處理后,解決了包括頭文件引用等問題,生成了文本文件hello.i。hello.i通過cc1編譯生成了匯編語言文件hello.s。hello.s經過as匯編將代碼轉換為了可執(zhí)行的機器碼,再通過ld將編譯后的目標文件與系統(tǒng)庫連接,形成可執(zhí)行文件hello。 進程管理中,操作系統(tǒng)(OS)通過fork()系統(tǒng)調用為hello創(chuàng)建了一個新的進程。OS調用execve()加載了hello的可執(zhí)行文件,并開始執(zhí)行。內存管理單元(MMU)和操作系統(tǒng)為hello提供了虛擬地址到物理地址的映射,以及內存管理的支持,包括TLB,頁表等。IO管理和信號處理允許hello與外部設備進行交互。hello利用CPU、RAM和IO資源,在計算機硬件上運行,并通過OS提供的各種服務實現其功能。020 020即From Zero to Zero,從“零”到“零”。hello在被創(chuàng)造出來時,是一張白紙,即“From Zero”。當hello經過了P2P的過程后,成為了一個完整的程序,在程序執(zhí)行完成后,hello進程被回收,與hello相關的所有狀態(tài)信息與數據被清除,即“to Zero”。

1.2 環(huán)境與工具

1.2.1 硬件環(huán)境 CPU:11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz ??2.30 GHz RAM:16GB

1.2.2 軟件環(huán)境

Windows11 64位

VMware Workstation Pro 17.5.2

Ubuntu 22.04.3

1.2.3 調試工具

Visual Studio 2022

cpp

gcc

as

ld

readelf

gdb

edb

1.3 中間結果

文件名 作用 hello.i hello.c預處理后得到的文本文件 hello.s hello.i編譯后得到的匯編代碼 hello.o hello.s匯編得到的可重定位目標文件 helloo.elf readelf讀取hello.o得到的文本 helloo.asm objdump反匯編hello.o得到的反匯編文件 hello hello.o鏈接后得到的可執(zhí)行文件 hello.elf readelf讀取hello得到的文本 hello.asm objdump反匯編hello得到的反匯編文件

1.4 本章小結

本章首先簡要解釋了P2P,020的含義,然后列出了本文研究所使用的軟硬件環(huán)境,最后列出了研究中所生成的中間結果文件以及其作用。

第2章 預處理

2.1?預處理的概念與作用

預處理(Preprocessing)是編譯過程中的第一個階段,在源代碼被編譯之前執(zhí)行。它的主要作用是對源代碼進行一些文本上的處理,以準備后續(xù)的編譯工作。預處理器會掃描源代碼文件,并根據預定義的規(guī)則執(zhí)行以下一系列操作:

宏替換(Macro substitution): 處理源代碼中的宏定義,將宏名稱替換為其對應的值,使代碼更加易讀并減少代碼的重復性。

文件包含(File inclusion): 處理源代碼中的文件包含指令(如#include),將指定的文件內容插入到當前文件中,提高代碼的可維護性和重復性。

條件編譯(Conditional compilation): 根據條件判斷指令(如#ifdef、#ifndef、#if、#elif、#else和#endif)來選擇性地包含或排除部分代碼,根據不同的編譯選項或環(huán)境控制代碼的編譯行為。

行連接(Line splicing): 將跨越多行的代碼連接成一行,增加代碼的可讀性。

注釋刪除(Comment stripping): 刪除源代碼中的注釋,減少編譯后的文件大小。

其他預處理指令處理: 處理其他預處理指令,如#error、#pragma等。

預處理完成后,生成的輸出通常是另一個經過預處理后的源代碼文件,其中已經包含了預處理器所執(zhí)行的所有操作。這個經過預處理的文件將作為編譯器的輸入,進行后續(xù)的編譯工作。

2.2在Ubuntu下預處理的命令

在Ubuntu下可以通過cpp hello.c > hello.i

來預處理hello.c,得到hello.i文本。

預處理執(zhí)行命令截圖如下:

圖2-1 預處理命令截圖

2.3 Hello的預處理結果解析

hello.c源文件只含有如下的24行代碼。

圖2-2 hello.c文件截圖

經過預處理后得到hello.i。

圖2-3 hello.i文件截圖

經過預處理后,代碼擴展至3061行。

1——7行代碼中是源代碼文件中相關信息,14——3047行為預處理擴展的內容。hello.i中3048——3061行代碼對應hello.c中11——24行主函數代碼。

圖2-4 hello.i 1-7行代碼截圖

14——58行為部分文件包含信息。

圖2-5 文件包含信息截圖

61——94行為部分類型定義信息

圖2-6 類型定義信息

329——378行為部分函數聲明信息。

圖2-7 函數聲明信息

14——3047行代碼為hello.c中引用的頭文件stdio.h,unistd.h,stdlib.h的展開。

以stdio.h論述展開的具體流程如下:

stdio.h為標準庫文件,當被引用時,原有的#inlcude 會被刪除,cpp會在系統(tǒng)默認的環(huán)境變量中尋找stdio.h,找到其路徑為/usr/include/stdio.h后打開stdio.h,用stdio.h替換被刪除掉的#include段,并對stdio.h中的#include,#define段代碼遞歸執(zhí)行上述操作直至所有#include,#define全部被替換。同時,cpp還會將代碼中的注釋與空白字符部分進行刪除,并對一些量進行替換。

圖2-8 默認的環(huán)境變量

2.4 本章小結

本章主要對預處理部分進行了說明,介紹了預處理的概念以及作用,展示了對hello.c文件進行預處理的過程,并對得到的hello.i文件進行了分析。

第3章 編譯

3.1 編譯的概念與作用

編譯(Compilation)是計算機程序開發(fā)中的重要步驟,它將高級編程語言(如C、C++、Java等)編寫的源代碼轉換為低級的機器語言或匯編語言程序。在這個過程中,編譯器扮演著關鍵的角色。

編譯的主要作用如下:

語法分析與詞法分析:編譯器首先對經過預處理的源代碼進行詞法分析和語法分析。詞法分析器將源代碼分解成一個個的詞法單元(tokens),而語法分析器則將這些詞法單元組合成語法結構,以驗證語法的正確性。

語義分析:編譯器進行語義分析,檢查源代碼中的語義錯誤,并生成相應的錯誤信息。

優(yōu)化:在編譯過程中,編譯器可能會對源代碼進行優(yōu)化,以提高程序的性能和效率。這包括控制流優(yōu)化、數據流優(yōu)化、指令級優(yōu)化等。

代碼生成: 經過語法和語義分析后,編譯器將源代碼轉換成等價的機器語言或匯編語言程序。這個階段的輸出可以是目標機器的匯編代碼或是中間表示(Intermediate Representation),它們可以被進一步轉換成目標機器的機器碼。

生成匯編語言程序: 在某些編譯器中,編譯器可能會直接生成匯編語言程序,而不是目標機器的機器碼。生成匯編語言程序的過程是將高級語言的源代碼轉換成匯編語言的等效表示,其中每一條指令都對應著目標機器上的一條指令。

編譯的結果是一個與源代碼等價的、可執(zhí)行的程序,它可以被計算機硬件直接執(zhí)行,或者通過進一步的鏈接和加載過程轉換成可執(zhí)行文件??偟膩碚f,編譯是將高級語言的源代碼轉換成機器語言或匯編語言的過程,使得計算機可以理解和執(zhí)行程序。

????????

3.2 在Ubuntu下編譯的命令

在Ubuntu下通過gcc -S -o hello.i hello.s將hello.i編譯成hello.s。

?

3.3 Hello的編譯結果解析

3.3.1 文件結構

內容 含義 .file 源文件 .text 代碼段 .globl 全局變量 .data 存放已經初始化的全局和靜態(tài)C變量 .section .rodata 存放只讀變量 .align 對齊方式 .type 表示是函數類型/對象類型 .size 表示大小 .long 表示是long類型 .string 表示是string類型

3.3.2 數據類型

(1)常量數據

a)printf中的輸出,格式字符串都被存放在.rodata中

hello.c源程序中代碼:

hello.s匯編程序中代碼:

b)if條件判斷值、for循環(huán)終止條件值在.text段

hello.c源程序中代碼:

hello.s匯編程序中代碼:

變量數據

全局變量:程序中無全局變量局部變量:局部變量i(4字節(jié)int型)在運行時保存在棧中,使用一條movl指令進行賦值,使用一條addl指令進行增一。

hello.c源程序中代碼:

hello.s匯編程序中代碼:

局部變量i在賦初值后被保存在地址為%rbp-4的棧位置上。

3.3.3 算數操作

for循環(huán)體中,對循環(huán)變量i的更新使用了++自增運算,匯編代碼翻譯成addl指令(4字節(jié)int型對應后綴“l(fā)”):

hello.c源程序中代碼:

hello.s匯編程序中代碼:

3.3.4 關系操作與控制轉移

(1)if條件判斷

hello.c源程序中代碼:

hello.s匯編程序中代碼:

je使用cmpl設置的條件碼(ZF),若ZF = 0,說明argc等于5,條件不 成立,控制轉移至.L2(for循環(huán)部分,程序主體功能);若ZF?= 1,說明argc 不等于5(即執(zhí)行程序時傳入的參數個數不符合要求),繼續(xù)執(zhí)行輸出提示信 息并退出。

(2)for循環(huán)條件終止

hello.c源程序中代碼:

hello.s匯編程序中代碼:

此處jle使用cmpl設置的條件碼(ZF?SF OF),若(SF^OF) | ZF = 1,說明循環(huán)終止條件不成立(變量i的值小于或等于9),控制轉移至.L4,繼續(xù)執(zhí)行循環(huán)體;若(SF^OF) | ZF = 0,則循環(huán)終止條件成立(變量i的值達到10),不再跳轉至循環(huán)體開始位置,繼續(xù)向后執(zhí)行直至退出。

值得注意的是,源程序代碼的邏輯與編譯器翻譯生成的邏輯有細微的差別。源代碼中判斷i<10,而編譯器將其調整為判斷i<=9,但實際上二者等價。

3.3.5 數組、指針、結構操作

數組*argv[]為參數字符串數組指針,在數組*argv[]中,argv[0]為輸入程序的路徑和名稱字符串起始位置,argv[1]、argv[2]、argv[3]依次為后面三個參數字符串的起始位置。

hello.s匯編程序中代碼:

將main()的第二個參數從寄存器寫到了棧空間中。

從棧上取這一參數,并按照基址-變址尋址法訪問argv[1]、argv[2]、argv[3](由于指針char*大小為8字節(jié),分別偏移8、16、24字節(jié)來訪問)。

3.3.6 函數操作

hello.c源文件中含有main主函數、printf()函數、exit()函數、sleep()函數、atoi()函數。

main函數

a) 參數傳遞:

匯編代碼:

在寫入棧空間時,第一個參數通過寄存器EDI傳遞,第二個參數通過寄存器RSI傳遞。

b) 函數調用

此處對程序入口進行標記。

c) 函數返回

參數不正確返回值為1,調用exit()函數。

參數正確返回0。

printf()函數

a) 參數傳遞:需要輸出的字符串

源代碼:

匯編代碼:

b) 函數調用:由主函數通過call調用。

c) 函數返回:返回輸出的字符數量。

exit()函數

a) 參數傳遞:退出狀態(tài)值1

b) 函數調用:由主函數通過call調用。

c) 函數返回:無返回值,正常終止程序,返回一個狀態(tài)代碼給調用程序或操作系統(tǒng)

sleep()函數

參數傳遞:

源代碼:

匯編代碼:

函數調用:由主函數通過call調用。函數返回:返回實際休眠時間。

atoi()函數

參數傳遞:

源代碼:

匯編代碼:

函數調用:由主函數通過call調用。函數返回:參數字符串被轉換后的整數值。

getchar()函數

參數傳遞:

源代碼:

匯編代碼:

函數調用:由主函數通過call調用。函數返回:返回char類型值。

3.4 本章小結

本章由對編譯進行簡要的介紹開始,然后展示了編譯的過程,后又對編譯的結果進行了逐步的分析。

第4章 匯編

4.1 匯編的概念與作用

匯編(Assembly)是將匯編語言程序轉換成機器語言二進制程序的過程。在這個過程中,匯編器(Assembler)扮演著關鍵的角色。

匯編的主要作用包括:

轉換為機器語言: 匯編器將匯編語言程序翻譯成對應的機器語言指令序列,這些指令直接由計算機硬件執(zhí)行。

生成目標文件: 匯編器生成的輸出通常是目標文件,其中包含了轉換后的機器語言指令。目標文件通常是二進制格式的文件,可以被進一步處理。

符號解析和地址計算: 匯編器負責解析程序中的符號(如標簽、變量名等),并計算它們的地址。這是為了確保生成的機器代碼能夠正確地引用和訪問這些符號所代表的內存位置。

代碼和數據段分配: 匯編器將程序中的代碼段(包含可執(zhí)行指令)和數據段(包含靜態(tài)數據)分配到內存中的不同區(qū)域,并生成相應的指令和數據的存儲地址。

處理偽指令和指令格式: 匯編器處理匯編語言中的偽指令(如宏指令、偽操作符等)以及指令格式(如操作碼、操作數等),將它們轉換成對應的機器語言表示。

生成可鏈接的目標文件: 匯編器生成的目標文件通常是可鏈接的,可以被鏈接器進一步處理,與其他目標文件進行鏈接,形成最終的可執(zhí)行文件。

總的來說,匯編的作用是將匯編語言程序轉換成機器語言二進制程序,使得計算機可以直接執(zhí)行。它是編譯過程中的一個重要階段,負責將高級語言或匯編語言轉換成機器可執(zhí)行的指令序列。

4.2 在Ubuntu下匯編的命令

通過gcc -c hello.s -o hello.o將hello.s匯編成hello.o。

4.3 可重定位目標elf格式

通過readelf -a hello.o>helloo.elf得到hello.o的elf格式:

4.3.1 elf頭:

以 16字節(jié)序列 Magic 開始,其描述了生成該文件的系統(tǒng)的字的大小和字節(jié)順序,ELF 頭剩下的部分包含幫助鏈接器語法分析和解釋目標文件的信息,其中包括 ELF 頭大小、目標文件類型、機器類型、節(jié)頭部表的文件偏移,以及節(jié)頭部表中條目的大小和數量等相關信息。

4.3.2 節(jié)頭:

包含了文件中出現的各個節(jié)的意義,包括節(jié)的類型、位置和大小等信息。

4.3.3 重定位節(jié):

重定位節(jié)記錄了各段引用的符號相關信息,在鏈接時,需要通過重定位節(jié)對這些位置的地址進行重定位。鏈接器會通過重定位條目的類型判斷如何計算地址值并使用偏移量等信息計算出正確的地址。本程序需要重定位的符號有:.rodata,puts,exit,printf,atoi,sleep,getchar及.text。注意到重定位類型僅有R_X86_64_PC32(PC相對尋址)和R_X86_64_PLT32(使用PLT表尋址)兩種,而未出現R_X86_64_32(絕對尋址)。

4.3.4 符號表

符號表中保存著定位、重定位程序中符號定義和引用的信息,所有重定位需要引用的符號都在其中聲明。

4.4 Hello.o的結果解析

通過objdump -d -r hello.o > helloo.asm?得到hello.o的反匯編,并與第3章的 hello.s進行對照分析。

4.4.1 分支轉移:

在 hello.s 中,分支跳轉的目標位置是通過 .L1、.L2 這樣的助記符來實現的,而 hello.o中,跳轉的目標位置是具體的數值。但注意這個數值還不是具體的一個地址,因為此時還沒進行鏈接,它是通過重定位條目進行計算得來的,是一個相對的地址值,由于不同文件代碼鏈接合并和,一個文件本身的代碼的相對地址不會改變,所以不需要與外部重定位,而可以直接計算出具體的數值,因此這里就已經完成了所有的操作,這條語句將以這種形式加載到內存中被cpu讀取與執(zhí)行。 ???????????????????????

4.4.2 函數調用:

在hello.s中,用call指令進行調用函數時,總會在call指令后直接加上函數名,而通過.o文件反匯編得到的匯編代碼中,call指令后會跟著的是函數通過重定位條目指引的信息,由于調用的這些函數都是未在當前文件中定義的,所以一定要與外部鏈接才能夠執(zhí)行。在鏈接時,鏈接器將依靠這些重定位條目對相應的值進行修改,以保證每一條語句都能夠跳轉到正確的運行時位置。

4.4.3 操作數:

反匯編代碼中的立即數是十六進制數,而?hello.s?文件中的數是十進制的。寄存器尋址兩者相同。內存引用?hello.s?中會用偽指令(如.LC0)代替,而反匯編則是基址加偏移量尋址:0x0(%rip)。

4.5 本章小結

本章首先簡要介紹了匯編的概念與作用,并展示了匯編的具體過程,并對匯編后的文件進行分析,介紹了可重定位文件的格式以及各部分的作用,最后對hello.o的結果進行解析,對hello.o文件和hello.s差異進行辨析。

第5章 鏈接

5.1 鏈接的概念與作用

鏈接(Linking)是指將多個目標文件或庫文件鏈接在一起,形成一個可執(zhí)行文件或共享目標文件的過程。在這個過程中,鏈接器(Linker)會解析各個目標文件之間的符號引用和定義關系,然后將它們合并成一個整體,最終生成一個可執(zhí)行文件或共享目標文件。過程包括解析符號、解決符號引用、地址重定位等步驟,最終生成一個完整的可執(zhí)行文件,其中包含了程序的所有代碼和數據,以便在計算機上執(zhí)行。

鏈接的作用包括:

符號解析:鏈接器通過符號表解析各個目標文件中使用的符號,并將其與相應的定義進行關聯。這包括全局變量、函數、外部庫函數等。

符號重定位:在鏈接過程中,鏈接器會調整各個目標文件中的符號地址,以便將它們正確地映射到最終的內存地址上。

庫鏈接:鏈接器還負責將程序所需的庫文件鏈接到程序中,以便在運行時能夠正確地調用庫中的函數和符號。

5.2 在Ubuntu下鏈接的命令

通過ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o hello.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o -o hello命令得到hello

5.3 可執(zhí)行目標文件hello的格式

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

通過readelf -a hello>hello.elf得到hello.elf。

5.3.1 elf頭

5.3.2 節(jié)頭

5.3.3 程序頭

5.3.4 段節(jié)

5.3.5 動態(tài)section

5.3.6 重定位節(jié)

5.3.7 符號表

5.4 hello的虛擬地址空間

用EDB查看hello的虛擬地址?。

虛擬地址范圍為0x401000至0x402000。

通過symbol一一對照,得到虛擬地址與節(jié)頭部表的對應關系。

5.5 鏈接的重定位過程分析

通過objdump -d -r hello > hello.asm得到反匯編文件hello.asm。

不同之處:

hello.o的反匯編中只含有.text節(jié),而hello的反匯編中還有.init,.plt,.plt.sec。在hello中鏈接加入了exit、printf、sleep、getchar等在hello.c中用到的庫函數。hello中不再存在hello.o中的重定位條目,并且跳轉和函數調用的地址在hello中都變成了虛擬內存地址。

5.6 hello的執(zhí)行流程

(以下格式自行編排,編輯時刪除)

使用gdb/edb執(zhí)行hello,說明從加載hello到_start,到call main,以及程序終止的所有過程(主要函數)。請列出其調用與跳轉的各個子程序名或程序地址。

使用EDB開始執(zhí)行hello。

根據symbol說明程序執(zhí)行的函數以及其地址。

函數名 函數地址 .init 0x401000 .plt 0x401020 puts@plt 0x401030 printf@plt 0x401040 getchar@plt 0x401050 atoi@plt 0x401060 exit@plt 0x401070 sleep@plt 0x401080 _start 0x4010f0 main 0x401125 .fini 0x401248

5.7 Hello的動態(tài)鏈接分析

???編譯器沒有辦法預測函數的運行時地址,所以需要添加重定位記錄,等待動態(tài)鏈接器處理,為避免運行時修改調用模塊的代碼段,鏈接器采用延遲綁定的策略。動態(tài)鏈接器使用過程鏈接表PLT+全局偏移量表GOT實現函數的動態(tài)鏈接,在GOT中存放函數目標地址,PLT使用GOT中地址跳轉到目標函數,在加載時,動態(tài)鏈接器會重定位GOT中的每個條目,使得它包含目標的正確的絕對地址。

調用dl_init之前.got.plt段的內容:

調用dl_init之后.got.plt段的內容:

5.8 本章小結

本章首先介紹了鏈接的概念與作用,然后對文件進行鏈接,并對可執(zhí)行文件hello的格式進行了分析,然后查看了hello的虛擬地址,并對鏈接的重定位過程進行了分析,概述了hello的執(zhí)行流程,并對hello的動態(tài)鏈接進行了分析。

第6章 hello進程管理

6.1 進程的概念與作用

進程是程序執(zhí)行的一個實例。一個程序在運行時會被操作系統(tǒng)加載到內存中,并分配一個獨立的執(zhí)行環(huán)境,這個執(zhí)行環(huán)境就是一個進程。進程是操作系統(tǒng)進行資源分配和調度的基本單位,它具有以下特點和作用:

獨立性: 每個進程都擁有獨立的地址空間,使得它們彼此之間不會相互干擾。進程之間通常是隔離的,一個進程的崩潰不會影響其他進程的正常運行。

并發(fā)執(zhí)行: 操作系統(tǒng)可以同時運行多個進程,每個進程都在自己的執(zhí)行環(huán)境中獨立執(zhí)行。這種并發(fā)執(zhí)行的方式使得計算機系統(tǒng)可以更有效地利用多核處理器和其他硬件資源。

資源分配: 操作系統(tǒng)為每個進程分配了一定的系統(tǒng)資源,包括內存空間、CPU時間、文件描述符等。進程可以通過操作系統(tǒng)提供的接口來請求和釋放這些資源。

調度和管理: 操作系統(tǒng)負責對進程進行調度和管理,以確保系統(tǒng)資源被合理地分配和利用。這包括進程的創(chuàng)建、銷毀、掛起、恢復以及切換等操作。

通信與同步: 進程之間可以通過各種機制進行通信和同步,包括共享內存、消息隊列、管道、信號量、鎖等。這使得不同進程之間可以進行數據交換和協(xié)作,實現復雜的任務分解和并發(fā)處理。

程序執(zhí)行環(huán)境: 每個進程都有自己的程序執(zhí)行環(huán)境,包括代碼、數據、堆棧、寄存器狀態(tài)等。操作系統(tǒng)負責管理這些執(zhí)行環(huán)境,并在需要時進行切換和調度。

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

殼是用戶與操作系統(tǒng)內核之間的接口,它接收用戶輸入的命令并將其轉換成操作系統(tǒng)內核能夠理解和執(zhí)行的指令。

殼的主要作用是將我們的指令翻譯給OS內核,讓內核來進行處理,并把處理的結果反饋給用戶。(Windows下的殼程序就是圖形化界面)shell的存在使得用戶不會直接操作OS,保證了OS的安全性。

殼的處理流程通常包括以下步驟: 殼從標準輸入(通常是終端)讀取用戶輸入的命令,對用戶輸入的命令進行解析,分析命令的結構和含義。根據解析后的命令調用相應的系統(tǒng)程序或應用程序進行執(zhí)行。如果是內建命令(如cd、echo等),則直接在殼內部執(zhí)行。根據命令中的I/O重定向符號(如<、>、|等)對輸入輸出進行重定向處理。 如果命令中包含管道符號(|),殼將多個命令連接起來,形成管道,將前一個命令的輸出作為后一個命令的輸入。檢測并處理命令執(zhí)行過程中可能出現的錯誤,并將錯誤信息輸出給用戶。等待用戶輸入命令并執(zhí)行,直到用戶退出。

6.3 Hello的fork進程創(chuàng)建過程

輸入命令后,shell會判斷該命令不是內部指令,轉而通過fork函數創(chuàng)建一個子進程hello。hello會得到一份包括數據段、代碼、共享庫、堆、用戶棧等均與父進程相同且獨立的副本。同時子進程還會獲得與父進程打開任何文件描述符相同的副本,這表示當父進程調用fork時子進程可以讀寫父進程的內容。父進程和子進程只有PID不同,在父進程中,fork返回子進程的PID,在子進程中,fork返回0.

6.4 Hello的execve過程

在fork創(chuàng)建子進程后,execve函數會加載并運行可執(zhí)行目標文件hello,刪除當前進程虛擬地址的用戶部分中的已存在的區(qū)域結構。然后為hello程序的代碼、數據、bss和棧區(qū)域創(chuàng)建新的私有的、寫時復制的數據結構,然后將標準庫libc.so映射到用戶虛擬地址空間中的共享區(qū)域內。最后設置當前進程的上下文中的程序計數器,使之指向代碼區(qū)域的入口點,即設置PC指向_start地址。

6.5 Hello的進程執(zhí)行

6.5.1 上下文:

內核重新啟動一個被搶占的進程所需要恢復的原來的狀態(tài),由寄存器、程序計數器、用戶棧、內核棧和內核數據結構等對象的值構成。

6.5.2 進程上下文切換:

在內核調度了一個新的進程運行時,它就搶占當前進程,并使用一種上下文切換的機制來控制轉移到新的進程。具體過程為:①保存當前進程的上下文;②恢復某個先前被搶占的進程被保存的上下文;③將控制傳遞給這個新恢復的進程。

6.5.3 進程時間片:

一個進程執(zhí)行它的控制流的一部分的每一個時間段叫做時間片(time slice),多任務也叫時間分片(time slicing)。

6.5.4 進程調度:

在進程執(zhí)行的某些時刻,內核可以決定搶占當前進程,并重新開始一個先前被搶占的進程,這種決策稱為調度,是由內核中的調度器代碼處理的。當內核選擇一個新的進程運行,我們說內核調度了這個進程。在內核調度了一個新的進程運行了之后,它就搶占了當前進程,并使用上下文切換機制來將控制轉移到新的進程。

hello程序調用sleep函數休眠時,內核將通過進程調度進行上下文切換,將控制轉移到其他進程。當hello程序休眠結束后,進程調度使hello程序重新搶占內核,繼續(xù)執(zhí)行。

6.5.5 用戶態(tài)與核心態(tài)的轉換:

為了保證系統(tǒng)安全,需要限制應用程序所能訪問的地址空間范圍。因而存在用戶態(tài)與核心態(tài)的劃分,核心態(tài)擁有最高的訪問權限,而用戶態(tài)的訪問權限會受到一些限制。處理器使用一個寄存器作為模式位來描述當前進程的特權。進程只有故障、中斷或陷入系統(tǒng)調用時才會得到內核訪問權限,其他情況下始終處于用戶權限之中,一定程度上保證了系統(tǒng)的安全性。

6.5.6 hello程序進程執(zhí)行示意圖:

6.6 hello的異常與信號處理

6.6.1 程序正常執(zhí)行

程序正常運行時,循環(huán)輸出十次消息即停止輸出,并結束程序回收進程。

6.6.2 不停亂按

當程序運行時不停亂按,程序仍能正常輸出十次信息,且程序結束后輸入的信息被當作命令輸入。

6.6.3 輸入回車

輸入回車后程序仍能正常運行,正常輸出十次信息。

6.6.4 輸入ctrl+c

輸入后shell進程收到SIGINT信號,程序直接停止運行,并回收hello進程。

6.6.5 輸入ctrl+z

輸入后shell進程收到SIGSTP信號,程序直接停止運行,并掛起hello進程。

通過ps命令查看證明hello的確不是被回收而是被掛起,且其job代號為1。

輸入pstree命令,以樹狀圖顯示所有的進程。

再輸入kill命令,則可以將該掛起的命令殺死。

使用kill命令前

使用kill命令后

證明該掛起的hello進程已被殺死。

6.7本章小結

本章首先簡述了進程的概念與作用,然后概述了殼的作用與處理流程,并對hello的fork進程創(chuàng)建過程以及hello的execve過程進行了介紹,然后對hello的進程執(zhí)行進行了說明,最后分析了hello的異常與信號處理。

第7章 hello的存儲管理

7.1 hello的存儲器地址空間

邏輯地址(Logical Address):邏輯地址是指程序中使用的地址空間,是相對于進程而言的。它是在程序執(zhí)行過程中使用的抽象地址,通常由程序員或操作系統(tǒng)內核管理,不直接對應到實際的物理內存位置。邏輯地址是在程序運行時生成的,用于訪問進程的虛擬地址空間,而不考慮該地址在物理內存中的具體位置。

線性地址(Linear Address):線性地址是指邏輯地址經過分頁機制或段頁式內存管理機制轉換后得到的地址,也稱為虛擬地址(Virtual Address)。在分頁系統(tǒng)中,邏輯地址被分為頁號和頁內偏移量,通過頁表將頁號轉換為物理頁框號,然后加上頁內偏移量得到線性地址。在段頁式系統(tǒng)中,邏輯地址被分為段選擇符和段內偏移量,通過段描述符將段選擇符轉換為段基址,然后加上段內偏移量得到線性地址。

虛擬地址(Virtual Address):虛擬地址是指在程序執(zhí)行時,由程序中的指針所引用的地址空間,通常是相對于進程而言的,與物理內存的實際地址無直接關系。虛擬地址由邏輯地址經過內存管理單元(MMU)轉換后得到的線性地址。

物理地址(Physical Address):物理地址是指內存中實際的存儲單元的地址,是RAM或其他存儲設備上的真實地址。物理地址是在內存訪問過程中由內存管理單元(MMU)將線性地址轉換為的最終地址,用于實際的數據讀寫操作。

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

在 Intel x86 架構中,邏輯地址到線性地址的變換涉及到段式管理機制。這個過程有以下步驟:

1.邏輯地址的生成:

2.邏輯地址通常由 CPU 中的段寄存器和偏移量組成。在 x86 架構中,有四個段寄存器:CS(代碼段)、DS(數據段)、SS(堆棧段)和ES(附加數據段)。偏移量表示在指定段內的位置。

3.段選擇符解析:

4.CPU 使用邏輯地址中的段選擇符從全局描述符表(GDT)或局部描述符表(LDT)中獲取相應的段描述符。段選擇符包含了段描述符在描述符表中的索引。

5.段描述符解析:

6.從描述符表中獲取到的段描述符包含了段的基址和長度等信息。段基址是一個32位的線性地址,用于指示段在內存中的起始位置。段長度則決定了段的大小。

7.線性地址的計算:

8.將段基址與偏移量相加,得到線性地址。這個線性地址是一個32位地址,用于訪問物理內存。

9.地址轉換和訪問:

10.當 CPU 訪問內存時,將邏輯地址轉換為線性地址。這個線性地址通過內存管理單元(MMU)進行轉換,最終映射到物理內存中的相應位置。

這個過程中,段式管理機制允許操作系統(tǒng)將程序的地址空間劃分為多個段,每個段可以具有不同的訪問權限和大小。這樣可以更靈活地管理內存,并提供更好的安全性和隔離性。

7.3 Hello的線性地址到物理地址的變換-頁式管理

線性地址(VA)到物理地址(PA)的轉換通過虛擬地址內存空間的分頁機制實現。首先,從段式管理中獲得線性地址,然后將其劃分為虛擬頁號(VPN)和虛擬頁偏移量(VPO)。VPN指示了頁面在虛擬地址空間中的位置,而VPO指示了頁面內的具體偏移量。由于虛擬內存與物理內存的頁大小相同,所以VPO與物理頁偏移量(PPO)一致。

然后,需要通過訪問頁表中的頁表條目(PTE)來獲取物理頁號(PPN)。如果PTE的有效位為1,則發(fā)生頁命中,可以直接獲取到物理頁號PPN。PPN與PPO組合成物理地址。

然而,如果PTE的有效位為0,則表示對應的虛擬頁沒有緩存在物理內存中,會觸發(fā)缺頁故障。在這種情況下,操作系統(tǒng)的內核會介入,執(zhí)行缺頁處理程序。該程序會確定犧牲頁,并將新的頁面調入物理內存。隨后,控制返回到原始進程,并重新執(zhí)行導致缺頁的指令。這時候發(fā)生了頁命中,可以獲取到PPN,然后與PPO組合成物理地址。

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

TLB:

每當CPU生成一個虛擬地址時,MMU需要檢索相關的PTE以將其轉換為物理地址。在最糟糕的情況下,這個過程需要額外的內存訪問,消耗幾十到幾百個周期。但如果MMU的TLB中恰好緩存了PTE,這個開銷就能降低到1或2個周期。因此,許多系統(tǒng)都引入了TLB,它是一個小型的PTE緩存,用于減少內存訪問的開銷。

多級頁表:

多級頁表采用了層次結構,以減少對內存的需求。首先,如果一級頁表中的某個PTE為空,那么對應的二級頁表就不需要存在,從而節(jié)省了內存空間。其次,只有最頂層的一級頁表必須一直駐留在主存中,而其他級別的頁表可以根據需要在主存和輔存之間進行調度,最常使用的二級頁表則會被緩存在主存中,有效減輕了主存壓力。

VA到PA的轉換:

對于四級頁表,虛擬地址(VA)被劃分為4個VPN和1個VPO。每個VPN i都是到第i級頁表的索引。在前三級頁表中,每個PTE指向下一級的頁表基址。而在最底層的頁表中,每個PTE則包含某個物理頁面的PPN,或者指向磁盤塊的地址。在構建物理地址之前,MMU需要訪問k個PTE。與單級頁表相似,PPO和VPO具有相同的值。

7.5 三級Cache支持下的物理內存訪問

首先,根據高速緩存的組數和塊大小來確定高速緩存塊的偏移量(CO)、組索引(CI)和高速緩存標記(CT)。通過組索引,將數據行與高速緩存標記進行匹配。如果匹配成功且該數據行的有效位為1,則表示發(fā)生了命中,此時可以根據偏移量取出數據并將其返回給CPU。

如果在當前級別的高速緩存中未找到匹配的數據行,或者找到的數據行的有效位為0,則表示發(fā)生了未命中。此時,系統(tǒng)將會繼續(xù)在下一級高速緩存(如L2)中進行類似的查找操作。如果在L2中仍然未找到匹配項,則繼續(xù)在更低一級的高速緩存(例如L3)中進行查找。如果直到三級高速緩存均未命中,則需要訪問主存來獲取所需數據。

如果發(fā)生了高速緩存未命中,意味著至少有一級高速緩存未能存儲所需數據。在從更低級別的存儲層次獲取數據后,系統(tǒng)需要更新未命中的高速緩存。首先,系統(tǒng)會檢查是否存在空閑的高速緩存塊,即有效位為0的塊。如果存在空閑塊,則可以直接將獲取的數據寫入其中。如果不存在空閑塊,則需要根據替換策略(例如最近最少使用或最不經常使用)選擇一個塊進行替換,然后將獲取的數據寫入該塊中。

7.6 hello進程fork時的內存映射

當fork函數被當前進程hello調用時,內核為新進程hello創(chuàng)建各種數據結構,并分配給它一個唯一的PID。為了給這個新的hello創(chuàng)建虛擬內存,它創(chuàng)建了當前進程的mm_struct、區(qū)域結構和頁表的原樣副本。它將兩個進程中的每個頁面都標記為只讀,并將兩個進程中的每個區(qū)域結構都標記為私有的寫時復制。

當fork在新進程中返回時,新進程現在的虛擬內存剛好和調用fork時存在的虛擬內存相同。當著兩個進程中的任一個后來進行寫操作時,寫時復制機制就會創(chuàng)建新頁面,因此,也就為每個進程保持了私有地址空間的抽象概念。

7.7 hello進程execve時的內存映射

execve函數加載并運行hello需要以下幾個步驟:

1.刪除已存在的用戶區(qū)域

刪除當前進程hello虛擬地址的用戶部分中的已存在的區(qū)域結構。

2.映射私有區(qū)域

為新程序的代碼、數據、bss和棧區(qū)域創(chuàng)建新的私有的、寫時復制的區(qū)域結構。其中,代碼和數據區(qū)域被映射為hello文件中的.text和.data區(qū)。bss區(qū)域是請求二進制零的,映射到匿名文件,其大小包含在hello中。棧和堆區(qū)域也是請求二進制零的,初始長度為零。

3.映射共享區(qū)域

若hello程序與共享對象或目標(如標準C庫libc.so)鏈接,則將這些對象動態(tài)鏈接到hello程序,然后再映射到用戶虛擬地址空間中的共享區(qū)域內。

4.設置程序計數器

最后,execve設置當前進程上下文中的程序計數器,使之指向代碼區(qū)域的入口點。

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

缺頁概念:在虛擬內存系統(tǒng)中,當DRAM緩存中未找到所需的頁面時,被稱為缺頁。缺頁是一種異常情況,屬于可恢復錯誤的范疇。相關處理流程可以在本文的第6.6節(jié)中找到。

處理流程:缺頁異常觸發(fā)內核中的缺頁異常處理程序。該程序首先會選擇一個犧牲頁,如果該犧牲頁已經被修改,則內核會將其寫回磁盤。隨后,內核會從磁盤中復制導致缺頁異常的頁面到內存中,并更新相應的頁表項,將其指向這個新復制的頁面。處理程序執(zhí)行完成后,內核將重新啟動導致缺頁的指令。該指令將重新發(fā)送導致缺頁的虛擬地址給地址翻譯硬件,這次訪問會命中頁面。

這樣處理的結果是,在缺頁異常處理程序的調用和重新發(fā)送缺頁地址之后,系統(tǒng)能夠成功地將缺頁的頁面復制到內存中,并更新頁表項,以便后續(xù)的訪問可以命中所需的頁面。

7.9動態(tài)存儲分配管理

動態(tài)內存分配管理是通過動態(tài)內存分配器進行的。這個分配器負責管理進程的虛擬內存區(qū)域,也稱為堆。它將堆看作是一系列不同大小的塊的集合,每個塊都是一段連續(xù)的虛擬內存片段,可能是已分配的,也可能是空閑的。已分配的塊被明確保留供應用程序使用,而空閑塊可以被分配??臻e塊保持空閑狀態(tài),直到應用程序明確分配它們。已分配的塊保持已分配狀態(tài),直到被釋放,釋放可以由應用程序顯式執(zhí)行,也可以由內存分配器隱式執(zhí)行。

內存分配器有兩種風格:顯式和隱式。C語言中的malloc函數屬于顯式分配器。顯式分配器必須滿足一些嚴格的條件:處理任意請求序列,立即響應請求,只使用堆,對齊塊(對齊要求),不修改已分配的塊。在這些限制條件下,分配器的目標是最大化吞吐量和內存利用率。

常見的放置策略包括:

1.首次適配:從空閑鏈表的頭部開始搜索,選擇第一個合適的空閑塊。

2.下一次適配:類似于首次適配,但是從上一次搜索結束的地方開始。

3.最佳適配:選擇所有空閑塊中適合所需請求大小的最小空閑塊。

有幾種組織內存塊的方法:

1.隱式空閑鏈表:空閑塊通過頭部的大小字段隱式連接,可以添加邊界標記以提高合并空閑塊的速度。

2.顯式空閑鏈表:在隱式空閑鏈表的基礎上,每個空閑塊中都添加了一個前驅(pred)指針和一個后繼(succ)指針。

3.分離的空閑鏈表:將塊按大小分類,分配器維護一個空閑鏈表數組,每個大小類一個空閑鏈表,以減少分配時間并提高內存利用率。C語言中的malloc函數采用了這種方法。

4.樹形結構(如紅黑樹等):按塊大小將空閑塊組織成樹形結構,同樣可以減少分配時間和提高內存利用率。

7.10本章小結

本章主要對hello的存儲器地址空間、intel 的段式管理、hello 的頁式管理, VA 到PA 的變換、物理內存訪問,hello進程fork、execve 時的內存映射、缺頁故障與缺頁中斷處理、動態(tài)存儲分配管理進行了介紹。

第8章 hello的IO管理

8.1 Linux的IO設備管理方法

1.設備的模型化——文件

所有的I/O設備(例如網絡、磁盤和終端)都被模型化為文件。

例如:/dev/sda2文件是用戶磁盤分區(qū),/dev/tty2文件是終端。

2.設備管理——Unix IO接口

將設備模型化為文件的方式允許Linux內核引入一個簡單、低級的應用接口,稱為Unix IO,這使得所有的輸入和輸出都能以一種統(tǒng)一且一致的方式來執(zhí)行。

8.2 簡述Unix IO接口及其函數

Unix I/O接口:

1.打開文件

一個應用程序通過要求內核打開相應的文件,來宣告它想要訪問一個I/O設備,內核返回一個小的非負整數,叫做描述符,它在后續(xù)對此文件的所有操作中標識這個文件,內核記錄有關這個打開文件的所有信息。對于Shell創(chuàng)建的每個進程,其都有三個打開的文件:標準輸入,標準輸出,標準錯誤。

2.改變當前的文件位置

對于每個打開的文件,內核保持著一個文件位置k,初始為0,這個文件位置是從文件開頭起始的字節(jié)偏移量,應用程序能夠通過執(zhí)行seek,顯式地將改變當前文件位置k。

3.讀寫文件

一個讀操作就是從文件復制n>0個字節(jié)到內存,從當前文件位置k開始,然后將k增加到k+n,給定一個大小為m字節(jié)的而文件,當k>=m時,觸發(fā)EOF。類似一個寫操作就是從內存中復制n>0個字節(jié)到一個文件,從當前文件位置k開始,然后更新k。

4.關閉文件

內核釋放文件打開時創(chuàng)建的數據結構,并將這個描述符恢復到可用的描述符池中去。

Unix I/O函數:

1.int open(char* filename,int flags,mode_t mode)

進程通過調用open函數來打開一個存在的文件或是創(chuàng)建一個新文件的。open函數將filename轉換為一個文件描述符,并且返回描述符數字,返回的描述符總是在進程中當前沒有打開的最小描述符,flags參數指明了進程打算如何訪問這個文件,mode參數指定了新文件的訪問權限位。

2.int close(fd)

fd是需要關閉的文件的描述符,close返回操作結果。

3.ssize_t read(int fd,void *buf,size_t n)

read函數從描述符為fd的當前文件位置賦值最多n個字節(jié)到內存位置buf。返回值-1表示一個錯誤,0表示EOF,否則返回值表示的是實際傳送的字節(jié)數量。

4.ssize_t wirte(int fd,const void *buf,size_t n)

write函數從內存位置buf復制至多n個字節(jié)到描述符為fd的當前文件位置。

8.3 printf的實現分析

1.printf函數體:

int?printf(const?char?*fmt,?...)

{

????int?i;

????va_list?arg?=?(va_list)((char?*)(&fmt)?+?4);

????i?=?vsprintf(buf,?fmt,?arg);

????write(buf,?i);

????return?i;

}

分析:

printf函數調用了vsprintf函數,最后通過系統(tǒng)調用函數write進行輸出;

va_list是字符指針類型;

((char?*)(&fmt)?+?4)表示...中的第一個參數。

2.printf調用的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。用格式字符串對個數變化的參數進行格式化,產生格式化輸出寫入buf供系統(tǒng)調用write輸出時使用。

3.write系統(tǒng)調用:

write:?

mov?eax,?_NR_write

mov?ebx,?[esp?+?4]

mov?ecx,?[esp?+?8]

int?INT_VECTOR_SYS_CALL

分析:這里通過幾個寄存器進行傳參,隨后調用中斷門int?INT_VECTOR_SYS_CALL

即通過系統(tǒng)來調用sys_call實現輸出這一系統(tǒng)服務。

4.sys_call部分內容:

sys_call:

?????/*?

??????*?ecx中是要打印出的元素個數

??????*?ebx中的是要打印的buf字符數組中的第一個元素

??????*?這個函數的功能就是不斷的打印出字符,直到遇到:'\0'

??????*?[gs:edi]對應的是0x80000h:0采用直接寫顯存的方法顯示字符串

??????*/

?????xor?si,si

?????mov?ah,0Fh

?????mov?al,[ebx+si]

?????cmp?al,'\0'

?????je?.end

?????mov?[gs:edi],ax

?????inc?si

????loop:

?????sys_call

???

????.end:

?????ret?

分析:通過逐個字符直接寫至顯存,輸出格式化的字符串。

5.最后一部分工作:

字符顯示驅動子程序實現從ASCII到字模庫到顯示vram(即顯存,存儲每一個點的RGB顏色信息)。顯示芯片按照刷新頻率逐行讀取vram,并通過信號線向液晶顯示器傳輸每一個點(RGB分量)。

8.4 getchar的實現分析

異步異常-鍵盤中斷的處理:鍵盤中斷處理子程序。接受按鍵掃描碼轉成ascii碼,保存到系統(tǒng)的鍵盤緩沖區(qū)。

getchar等調用read系統(tǒng)函數,通過系統(tǒng)調用讀取按鍵ascii碼,直到接受到回車鍵才返回。

getchar調用系統(tǒng)函數read,發(fā)送一個中斷信號,內核搶占這個進程,用戶輸入字符串,鍵入回車后(字符串和回車都保存在緩沖區(qū)內),再次發(fā)送信號,內核重新調度這個進程,getchar從緩沖區(qū)讀入字符。

8.5本章小結

本章主要介紹了Linux的IO設備管理方法,并簡要介紹了Unix IO接口及其函數。也對printf和getchar的實現進行了分析。

結論

Hello程序的一生可以描述為以下過程:

預處理階段:Hello程序的源代碼文件hello.c首先經過預處理器處理,其中包括將所有被包含的外部頭文件的內容直接插入到程序文本中,完成字符串的替換等操作,得到調整和展開后的ASCII文本文件hello.i。

編譯階段:經過編譯器的編譯,hello.i被轉換成等價的匯編代碼文件hello.s。這個過程包括詞法分析和語法分析,將源代碼翻譯成相應的匯編語言代碼。

匯編階段:匯編器將hello.s匯編程序翻譯成機器語言指令,并將這些指令打包成可重定位目標程序格式,最終生成可重定位目標文件hello.o。

鏈接階段:鏈接器將hello程序的目標文件hello.o與動態(tài)鏈接庫等收集整理,生成一個單一的文件,即完全鏈接的可執(zhí)行目標文件hello。

加載與運行階段:用戶在shell中輸入命令,shell解釋這個命令并為其創(chuàng)建一個新進程,調用execve加載hello程序到新進程的內存空間,并開始執(zhí)行。

執(zhí)行指令階段:當CPU調度到hello進程時,它分配一個時間片給hello程序,CPU按順序執(zhí)行hello程序中的指令,PC寄存器不斷更新,CPU取指并執(zhí)行。

訪存階段:在執(zhí)行過程中,內存管理單元將邏輯地址映射成物理地址,通過三級高速緩存系統(tǒng)訪問物理內存/磁盤中的數據。

動態(tài)申請內存階段:如果程序中有動態(tài)內存分配的操作,比如調用printf函數,會向動態(tài)內存分配器申請堆中的內存。

信號處理階段:程序可能會接收到各種信號,如Ctrl-C、Ctrl-Z等,在收到這些信號時,操作系統(tǒng)會調用相應的信號處理函數來進行相應的操作。

終止與回收階段:程序執(zhí)行完成后,父進程(通常是shell)等待并回收hello子進程,內核刪除為hello進程創(chuàng)建的所有數據結構,清理資源。

這些階段的協(xié)同工作構成了Hello程序的一生。

附件

文件名 作用 hello.i hello.c預處理后得到的文本文件 hello.s hello.i編譯后得到的匯編代碼 hello.o hello.s匯編得到的可重定位目標文件 helloo.elf readelf讀取hello.o得到的文本 helloo.asm objdump反匯編hello.o得到的反匯編文件 hello hello.o鏈接后得到的可執(zhí)行文件 hello.elf readelf讀取hello得到的文本 hello.asm objdump反匯編hello得到的反匯編文件

參考文獻

[1] Randal E.Bryant, David O'Hallaron. 深入理解計算機系統(tǒng)[M]. 機械工業(yè)出版社.2018.4?

[2] Pianistx.printf 函數實現的深入剖析[EB/OL].2013[2021-6-9].

https://www.cnblogs.com/pianist/p/3315801.html.?

[3] ?ELF文件頭結構.?CSDN博客.

ELF文件頭結構_elf32頭-CSDN博客

[4] ?read和write系統(tǒng)調用以及getchar的實現.?CSDN博客.

read和write系統(tǒng)調用以及getchar的實現_getchar 和read-CSDN博客

[5]? ?GCC online documentation.?GCC online documentation- GNU Project

[6] ?深入理解計算機系統(tǒng)-之-內存尋址(三)--分段管理機制(段描述符,段選擇子,描述符表).?CSDN博客.

深入理解計算機系統(tǒng)-之-內存尋址(三)--分段管理機制(段描述符,段選擇子,描述符表)_ds的段地址是固定的嗎-CSDN博客

柚子快報邀請碼778899分享:ICS大作業(yè)程序人生

http://yzkb.51969.com/

精彩文章

評論可見,查看隱藏內容

本文內容根據網絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。

轉載請注明,如有侵權,聯系刪除。

本文鏈接:http://gantiao.com.cn/post/19503205.html

發(fā)布評論

您暫未設置收款碼

請在主題配置——文章設置里上傳

掃描二維碼手機訪問

文章目錄