2017-2018-1 20179205《Linux內核原理與設計》第八週做業

《Linux內核原理與設計》第八週做業

視頻學習及操做分析

預處理、編譯、連接和目標文件的格式

可執行程序是怎麼來的?linux

以C語言爲例,通過編譯器預處理、編譯成彙編代碼、彙編器編譯成目標代碼,而後連接成可執行文件,再將可執行程序加載到內存中執行,過程能夠經過下圖展現(其中預處理已省略):shell

可執行文件的建立--預處理、編譯和連接:windows

cd Code
vi hello.c
gcc –E –o hello.cpp hello.c –m32             //預處理,把include的文件包含進來及宏替換等工做
vi hello.cpp                                 //cpp爲預處理的中間文件
gcc -x cpp-output -S -o hello.s hello.cpp -m32  //編譯成彙編代碼
vi hello.s
gcc -x assembler -c hello.s -o hello.o -m32     //編譯成目標代碼
vi hello.o                                      //獲得二進制.o文件,ELF格式
gcc -o hello hello.o -m32                    //連接成可執行文件hello
vi hello                                     //也是二進制文件,ELF格式
gcc -o hello.static hello.o -m32 -static     //靜態編譯,佔用內存較大
ls -l

常見的目標文件格式,最古老的目標文件格式是A.out,而後發展成coff,如今咱們經常使用的pe(windows系統運用較多)、elf(linux系統中應用較多)。ELF全稱爲EXECUTABLE AND LINKABLE FORMAT,便可執行和可連接模式,是一個文件格式的標準。目標文件咱們通常也叫它ABI(應用程序二進制接口),實際上在目標文件裏面它已是二進制兼容的格式了,也就是說它這個目標文件已是適應到某一種cpu體系結構上的二進制指令。好比說咱們在一個32位x86編譯出來的目標文件連接成ARM平臺上的可執行文件確定是不能夠的。服務器

elf文件格式中的三種主要目標文件:數據結構

一、一個可重定位(relocatable)文件保存着代碼和適當的數據,用來和其餘的object文件一塊兒來建立一個可執行文件或者是一個共享文件。(主要是.o文件)編輯器

二、一個可執行(executable)文件保存着一個用來執行的程序;該文件指出了exec(BA_OS)如何來建立程序進程映象。函數

三、一個共享object文件保存着代碼和合適的數據,用來被下面的兩個連接器連接。第一個是鏈接編輯器[請參看ld(SD_CMD)],能夠和其餘的可重定位和共享object文件來建立其餘的object。第二個是動態連接器,聯合一個可執行文件和其餘的共享object文件來建立一個進程映象。(主要是.so文件)學習

當建立或增長一個進程映像的時候,系統在理論上將拷貝一個文件的段到一個虛擬的內存段。如圖可執行文件的格式和進程地址空間有一個映射關係:.net

靜態連接的ELF可執行文件與進程的地址空間的聯繫:命令行

當elf文件加載到內存的時候,他把代碼的數據加載到一塊內存中來,其中有不少段代碼。加載進來以後默認從0x8048000開始加載,前面是elf頭部的一些信息,通常頭部的大小會有不一樣,加載的入口點的位置多是0x8048300,即程序的實際入口。當啓動一個剛加載過可執行文件的進程的時候,開始執行的入口點。文件是一個elf的靜態鏈接文件,連接的時候已經連接好了。從這(0x8048300)開始執行,壓棧出棧,從main函數到結束,全部的連接在靜態連接時候已經設定好了。正常須要用到共享庫或動態連接的時候,狀況會更復雜一點。

裝載可執行程序以前,先了解一下可執行程序的執行環境。通常咱們執行一個程序的shell環境,它自己不限制命令行參數的個數,命令行參數的個數受限於命令自身,好比 int main(int argc,char *argv[]) ,shell會調用execve將命令行參數和環境參數傳遞給可執行程序的main函數。命令行參數和環境串都放在用戶態的堆棧中。Shell程序->execve -> sys_execve,而後在初始化新程序堆棧時拷貝進去。

可執行程序、共享庫和動態加載

動態連接有可執行裝載時的動態連接和運行時的動態連接,下面演示了兩種動態連接:

共享庫shilibexample.c實現SharedLibApi()函數:

#include <stdio.h>
#include "shlibexample.h"

/*
 * Shared Lib API Example
 * input    : none
 * output   : none
 * return   : SUCCESS(0)/FAILURE(-1)
 *
 */
int SharedLibApi()
{
printf("This is a shared libary!\n");
return SUCCESS;
}

shilibexample.h:

#ifndef _SH_LIB_EXAMPLE_H_
#define _SH_LIB_EXAMPLE_H_

#define SUCCESS 0
#define FAILURE (-1)

#ifdef __cplusplus
extern "C" {
#endif
/*
 * Shared Lib API Example
 * input    : none
 * output   : none
 * return   : SUCCESS(0)/FAILURE(-1)
 *
 */
int SharedLibApi();


#ifdef __cplusplus
}
#endif
#endif /* _SH_LIB_EXAMPLE_H_ */

經過gcc -shared shlibexaple.c -o libshlibexample.so -m32編譯成一個共享庫文件,
下面是一樣使用 gcc -shared dllibexample.c -o libdllibexample.so -m32 獲得動態加載共享庫。

其中dellibexample.c實現了DynamicalLoadingLibApi()函數:

#include <stdio.h>
#include "dllibexample.h"

#define SUCCESS 0
#define FAILURE (-1)

/*
 * Dynamical Loading Lib API Example
 * input    : none
 * output   : none
 * return   : SUCCESS(0)/FAILURE(-1)
 *
 */
int DynamicalLoadingLibApi()
{
printf("This is a Dynamical Loading libary!\n");
return SUCCESS;
}

dellibexample.h:

#ifndef _DL_LIB_EXAMPLE_H_
#define _DL_LIB_EXAMPLE_H_



#ifdef __cplusplus
extern "C" {
#endif
/*
 * Dynamical Loading Lib API Example
 * input    : none
 * output   : none
 * return   : SUCCESS(0)/FAILURE(-1)
 *
 */
int DynamicalLoadingLibApi();


#ifdef __cplusplus
}
#endif
#endif /* _DL_LIB_EXAMPLE_H_ */

main.c()函數:

#include <stdio.h>
#include "shlibexample.h"   
#include <dlfcn.h>

/*
 * Main program
 * input    : none
 * output   : none
 * return   : SUCCESS(0)/FAILURE(-1)
 *
 */
int main()
{
printf("This is a Main program!\n");
/* Use Shared Lib */
printf("Calling SharedLibApi() function of libshlibexample.so!\n");
SharedLibApi();
/* Use Dynamical Loading Lib */
void * handle = dlopen("libdllibexample.so",RTLD_NOW);
if(handle == NULL)
{
printf("Open Lib libdllibexample.so Error:%s\n",dlerror());
return   FAILURE;
}
int (*func)(void);
char * error;
func = dlsym(handle,"DynamicalLoadingLibApi");
if((error = dlerror()) != NULL)
{
printf("DynamicalLoadingLibApi not found:%s\n",error);
return   FAILURE;
}
printf("Calling DynamicalLoadingLibApi() function of libdllibexample.so!\n");
func();  
dlclose(handle);   
return SUCCESS;
}

咱們發現main函數中含有include "shlibexample.h" 以及include dlfcn,而沒有include dllibexample(動態加載共享庫)。當須要調用動態加載共享庫時,使用定義在dlfcn.h中的dlopen。
最後經過 gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
接下來編譯main()函數,注意這裏只提供shlibexample的-L(庫對應的接口頭文件所在目錄)和-l(庫名,如libshlibexample.so去掉lib和.so的部分),並無提供dllibexample的相關信息,只是指明瞭-ldl,而後咱們繼續執行:

$ export LD_LIBRARY_PATH=$PWD #將當前目錄加入默認路徑,不然main找不到依賴的庫文件,固然也能夠將庫文件copy到默認路徑下。
$ ./main

使用gdb跟蹤sys_execve內核函數的處理過程

打開test.c文件:

能夠發現增長了一句,MenuConfig("exec","Execute a program",Exec)

看一下這段代碼,和fork()函數相似,增長了一個fork,子進程增長了一個execlp("/hello","hello",NULL); 啓動hello,看一下hello.c:

看一下Makefile文件,靜態的方式編譯了hello.c,並在生成根文件系統時把init 和hello都放在rootfs裏面:

輸入命令 make rootfs ,在qemu窗口中輸入help 執行一下exec:

先cd .. 返回到上一級,qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S啓動,水平分割,gdb

而後把符號表文件加載進來,gdb服務器使用默認端口號1234來鏈接,並經過gdb跟蹤調試,設置斷點,能夠先停在sys_execve而後再設置其餘斷點:

按c執行:

連續c執行三次,在menuos窗口輸入exec,發現執行到 This is child process! 停下:

進入sys_execve系統調用,list列出來,跟蹤:

接下來經過s進入sys_execve內部

按c繼續執行到load_elf_binary,list查看;再按c執行,執行到start_thread,想知道new_ip到底指向哪裏,new_ip是返回到用戶態的第一條指令的地址。再水平分割一個控制檯出來,使用命令readelf -h hello,能夠看到入口點地址和上面po new_ip所顯示的地址同樣:

而後咱們繼續執行s步驟,能夠看到在進行修改內核堆棧的位置,發現原來壓棧的ip和sp都被改爲了新的ip(程序hello的入口點地址)和新的sp,這樣在返回到用戶態的時候程序就有一個新的可執行上下文環境。最後按一下c,exec的執行結束:

教材十3、十四章學習

一、虛擬文件系統(VFS)是Linux內核中的一個軟件層,用於給用戶空間的程序提供文件系統接口;同時,它也提供了內核中的一個抽象功能,容許不一樣的文件系統共存。系統中全部的文件系統不但依賴 VFS共存,並且也依靠VFS協同工做。一個實際的文件系統想要被Linux支持,就必須提供一個符合VFS標準的接口,才能與VFS協同工做,跨文件系統操做才能實現

二、VFS藉助它四個主要的數據結構即超級塊、索引節點、目錄項和文件對象以及一些輔助的數據結構,向Linux中不論是普通的文件仍是目錄、設備、套接字等都提供一樣的操做界面,如打開、讀寫、關閉等。只有當實際的文件系統有了控制權時,實際的文件系統纔會作出區分,對不一樣的文件類型執行不一樣的操做。 因此我以爲「一切皆是文件」這句話的意思就是,不管是普通的文件,仍是特殊的目錄、設備等,VFS都將它們同等看待成文件,Linux都會提供一樣的操做界面,這是不一樣於實際文件系統的根本。

三、文件是具備完整意義的信息項的系列,在Linux中,除了普通文件,其餘諸如目錄、設備、套接字等也以文件被對待;目錄比如一個文件夾,用來容納相關文件。由於目錄能夠包含子目錄,因此目錄是能夠層層嵌套,造成 文件路徑; 目錄項在一個文件路徑中,路徑中的每一部分都被稱爲目錄項;如路徑/home/source/helloworld.c中,目錄 /, home, source和文件 helloworld.c都是一個目錄項;索引節點是用於存儲文件的元數據的一個數據結構。文件的元數據,也就是文件的相關信息,和文件自己是兩個不一樣的概念。它包含的是諸如文件的大小、擁有者、建立時間、磁盤位置等和文件相關的信息; 超級塊用於存儲文件系統的控制信息的數據結構。描述文件系統的狀態、文件系統類型、大小、區塊數、索引節點數等,存放於磁盤的特定扇區中。每次一個實際的文件系統被安裝時, 內核會從磁盤的特定位置讀取一些控制信息來填充內存中的超級塊對象。一個安裝實例和一個超級塊對象一一對應,超級塊經過其結構中的一個域s_type記錄它所屬的文件系統類型。

四、進程與超級塊、文件、索引結點、目錄項的關係

五、兩種基本的設備類型,塊設備和字符設備,它們的區別在因而否能夠隨機的訪問數據。扇區是設備的最小尋址單元;塊是文件系統的最小尋址單元,又稱爲文件塊或者I/O塊。緩衝區(buffer)是存放塊的區域;緩衝頭是存放內核處理數據時須要的一些相關信息的結構描述符(如塊屬於哪個設備,塊對應的是哪一個緩衝區),描述符用buffer_head表示。Linus電梯能執行合併和排序預處理。新提交的請求和原有請求隊列中的請求訪問的扇區相鄰,這種狀況能夠合併;沒有合併的條件,可是有多個請求訪問的扇區比較接近,經過將請求排序,可使得磁頭線性工做。

問題與分析:

VFS把一切都看爲文件,提供一樣的操做界面,跨文件系統的文件操做基本原理是什麼?

好比說,將vfat格式的磁盤上的一個文件a.txt拷貝到ext3格式的磁盤上,命名爲b.txt。這包含兩個過程,對a.txt進行讀操做,對b.txt進行寫操做。讀寫操做前,須要先打開文件。由前面的分析可知,打開文件時,VFS會知道該文件對應的文件系統格式,之後操做該文件時,VFS會調用其對應的實際文件系統的操做方法。因此,VFS調用vfat的讀文件方法將 a.txt的數據讀入內存;在將a.txt在內存中的數據映射到b.txt對應的內存空間後,VFS調用ext3的寫文件方法將b.txt寫入磁盤;從而實現了最終的跨文件系統的複製操做。

參考網址:Linux的虛擬文件系統

相關文章
相關標籤/搜索