淺析Linux計算機工作機制


環境:lubuntu 13.04   kernel 3.8  gcc 4.7.3 

作者:卡濤 SA12226265

簡介: 本文將對 Linux™ 系統計算機的工作機制進行簡單概述。文中將為您提供單任務系統如何工作的細節,然后將進一步展示匯編指令在CPU的運行過程,以及由單任務向多任務處理的擴展。

♦ gcc用法參考(*表示文件名)
– gcc –E –o *.cpp *.c 生成預處理文件
– gcc –x cpp-output –S –o *.s *.cpp
• gcc –S –o *.s *.c   編譯成匯編
– gcc –x assembler –c *.s -o *.o
• gcc –c *.c -o *.o   匯編成目標代碼
• as –o *.o *.s
– gcc –o * *.o
• gcc –o * *.c     生成可執行ELF文件格式

 

 


一、編譯處理                                                                                                                                                                 

  測試用源碼如下,該源碼不涉及系統調用,僅適用於用戶態下CPU、堆棧處理流程分析;

1 int g(int x){
2     return x+3;
3 }
4 int f(int x){
5     return g(x);
6 }
7 int main(void){
8     return f(8)+1;
9 }

 

  由於該源碼未使用宏指令和#include指令,所以預處理后的文件內容沒有明顯變化;

 使用命令“gcc -E lab.c -o lab.cpp”,以下為lab.cpp中內容:

1
# 1 "lab.c" 2 # 1 "<命令行>" 3 # 1 "lab.c" 4 int g(int x){ 5 return x+3; 6 } 7 int f(int x){ 8 return g(x); 9 } 10 int main(void){ 11 return f(8)+1; 12 }

 

  使用GCC編譯器產生預處理文件的匯編代碼:

 
           
使用命令“gcc -S lab.cpp -o lab.s”,以下為lab.s中內容:
 
1
.file "lab.cpp" 2 .text 3 .globl _Z1gi 4 .type _Z1gi, @function 5 _Z1gi: 6 .LFB0: 7 .cfi_startproc 8 pushl %ebp 9 .cfi_def_cfa_offset 8 10 .cfi_offset 5, -8 11 movl %esp, %ebp 12 .cfi_def_cfa_register 5 13 movl 8(%ebp), %eax 14 addl $3, %eax 15 popl %ebp 16 .cfi_restore 5 17 .cfi_def_cfa 4, 4 18 ret 19 .cfi_endproc 20 .LFE0: 21 .size _Z1gi, .-_Z1gi 22 .globl _Z1fi 23 .type _Z1fi, @function 24 _Z1fi: 25 .LFB1: 26 .cfi_startproc 27 pushl %ebp 28 .cfi_def_cfa_offset 8 29 .cfi_offset 5, -8 30 movl %esp, %ebp 31 .cfi_def_cfa_register 5 32 subl $4, %esp 33 movl 8(%ebp), %eax 34 movl %eax, (%esp) 35 call _Z1gi 36 leave 37 .cfi_restore 5 38 .cfi_def_cfa 4, 4 39 ret 40 .cfi_endproc 41 .LFE1: 42 .size _Z1fi, .-_Z1fi 43 .globl main 44 .type main, @function 45 main: 46 .LFB2: 47 .cfi_startproc 48 pushl %ebp 49 .cfi_def_cfa_offset 8 50 .cfi_offset 5, -8 51 movl %esp, %ebp 52 .cfi_def_cfa_register 5 53 subl $4, %esp 54 movl $8, (%esp) 55 call _Z1fi 56 addl $1, %eax 57 leave 58 .cfi_restore 5 59 .cfi_def_cfa 4, 4 60 ret 61 .cfi_endproc 62 .LFE2: 63 .size main, .-main 64 .ident "GCC: (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3" 65 .section .note.GNU-stack,"",@progbits

 

   使用已有的匯編代碼,生成可執行程序:

  將匯編代碼匯編為可執行代碼:
1 執行"gcc -c example.s -o example.o"命令生成.o文件。
 
  將目標代碼轉換成可執行ELF文件
2 執行"gcc example.o -o example"命令生成可執行文件。

                    

 上圖為生成所有文件的索引及可執行文件的詳細信息。


二、程序運行分析                                                                                                                                                                 

  操作環境:edb

  32位CPU所含有的寄存器有:

  指針寄存器(ebp):指向當前所用棧的基址;

  指針寄存器(esp):指向當前所用棧的頂部;

  數據寄存器(eax、ebx、ecx和edx) :通常用來保存API的返回值,由於操作的效率比較高,因而使用的頻率也比較高;

  變址寄存器(esi和edi):主要用於存放存儲單元在段內的偏移量,用它們可實現多種存儲器操作數的尋址方式,為以不同的地址形式訪問存儲單元提供方便;

 

  段寄存器(es、cs、ss、ds、fs和gs) :段寄存器是根據內存分段的管理模式而設置的,內存單元的物理地址由段寄存器的值和一個偏移量組合而成的,這樣可用兩個                       較少位數的值組合成一個可訪問較大物理空間的內存地址;

  指令指針寄存器(eip):記錄的是CPU將要執行的下一條指令地址;

  標志寄存器(eflags):記錄各種標志位;  

  使用EDB打開lab可執行文件(File->Open),之后可以看到如下畫面。  

           

 

  edb調試可根據每一步指令,進行debug,上圖可以看出初始化可執行文件后寄存器、堆棧的值及所有可執行匯編指令,在調試過程中可以觀察當前每步過程中寄存器的變化,其中0xbfa9397c是程序執行時esp的值,也就是當前棧頂指針位置.ebp寄存器為0,eip初始值為0804840a,說明程序的執行的第一條指令的地址為0X0804840a。在堆棧區跟蹤esp地址到達棧頂,值為0xb75d2935.

  


分析main函數執行過程:

                                初始:                                                  pushl %ebp:                movl %esp, %ebp:

                                                 

  

 

  剛開始棧頂指針esp指向初始棧頂位置,然后將之前的棧底指針ebp入棧並且新的ebp和esp同時指向了地址減少4字節后的位置即第三個圖中所示,很明顯這一步的作用就是保存之前棧環境的前提下建立新的堆棧框架,堆棧變化如上圖所示。


subl $4, %esp : movl $8, (%esp): call _Z1fi:

  將esp向低地址增長4字節並將8入棧,CPU執行call指令時通常做兩步操作,第一步將eip壓棧做保存,第二步跳轉,堆棧變化如圖所示。 


 調用f(int)函數:

 pushl %ebp: movl %esp, %ebp: subl $4, %esp:

  將當前eip入棧並跳轉到f中去。跳到f后做的第一件事兒仍然是保存之前棧環境並建立新的堆棧環境,將esp增長4位,指向下一地址,堆棧變化如上圖。

 movl 8(%ebp), %eax: movl %eax, (%esp): call _Z1gi:

  將ebp增加8位的地址中的參數傳入eax中,並將eax的值壓入棧,調用call指令,將eip值壓入棧,調轉入g函數,堆棧變化如上圖。


  分析g函數的執行活動:

 pushl %ebp: movl %esp, %ebp: movl 8(%ebp), %eax:

     ebp壓棧,初始化棧環境,將之前保存的變量再次轉移到eax中,堆棧變化如上圖。

 

 addl $3, %eax: popl %ebp: ret:

  將eax加3,使得eax中的值變為11。然后ebp出棧,返回到上次保存的ebp地址,esp逐次返回棧底,堆棧變化如上圖。


 

  函數執行結束后,指令執行過程分析:

 leave: ret: addl $1, %eax: leave: ret:

   
執行leave操作ebp返回上次保存的地址,esp指向下一棧頂,ret操作esp逐次返回棧底,add操作使得eax再加1,生成新的值12,再次leave退出該進程的棧空間,eip返回到初始時候的值,至此,程序執行完畢,最終的結果保存在eax寄存器中。



三、Linux計算機工作機制分析

單任務
  
在linux計算機中,我們討論最簡單的單個任務模型,即處理是順序的不考慮中斷和多任務情況下的工作過程。首先由單一進程開始執行,所需要的可執行代碼均存儲在代碼區,而每個進程會專門開辟一段堆棧來支持該進程運行,該棧的增長方向是從高地址向低地址增長,棧中保存程序的局部變量,以及函數的返回地址等信息。進程執行過程中堆棧的控制由棧頂指針寄存器esp與棧基址寄存器ebp完成。
  單個任務執行過程中,堆棧的控制單元按執行函數來分配,進制的每個函數都特定的棧空間,函數中的數據操作結果保存在數據寄存器(
前文有介紹)中,從不同的函數進入其他函數,需要call指令來執行對應函數多在代碼段的地址,在執行過程中程序會先初始化該函數的棧空間,保存之前的現場,例如eip,ebp等寄存器的值。一般eip來確定所要執行的指令的地址即壓棧之前所在函數的入口地址。在新的棧空間中按esp來確定棧空間的大小。
  根據前面任務執行過程的分析,在調用函數f或者g時,
將函數參數從數據寄存器中壓入堆棧,然后調用call,轉入對應函數空間,並將eip與ebp保存,eip為前一函數的call調用的操作數被保存,最后將esp,ebp重新指向新的棧基址,從而完成棧的初始化操作。
  函數執行結束時一般都會執行ret和leave指令,leave指令一般就是返回上級棧的ebp與esp,ret指令是講eip賦值為返回地址的值,使得函數結束后能夠返回到調用該函數的父函數,從而使得單一進程能夠在多個函數調用過程中順序執行並能夠在執行結束后及時的銷毀內存空間並結束進程。
多任務
  
多任務系統執行原理就是所有的應用程序以進程的方式以較低級別運行在Linux操作系統中,每個進程有獨立的堆棧空間,代碼段和靜態存儲區,CPU在執行進程的時候有操作系統統一調度,所有執行進程按優先級的高低
分配獲得CPU。
   由於多任務處理過程類似單任務處理過程中各函數間轉換的過程,多任務系統中引入了中斷機制。任何進程在運行時,有新的優先級更高的進程進入執行狀態時,操作系統會發送一個中斷信號到CPU,使得CPU轉而去運行正常的控制流之外的進程代碼。由於操作系統中存在多個進程,在進程優先級一直的情況下,所有任務如單任務系統中執行機制一樣順序的執行完成,當優先級不一致時,各個進程由操作系統統一調度,當調用一個進程時,我們將前一任務相關的執行信息(如esp,eip,任務執行的斷點,當前的程序flag寄存器等等)保存起來,當該進程執行完畢后再去執行之前所未完成的進程,從而實現多任務的切換與分時控制。


  2013-05-17 以上均為個人理解,由於個人水平有限,如有錯誤希望能夠共同討論。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM