linux內核分析(網課期末&地面課期中)

堆棧變化過程:html

Linux內核分析——計算機是如何工做的linux

計算機是如何工做的?(總結)——三個法寶git

 

 

 

  • 存儲程序計算機工做模型,計算機系統最最基礎性的邏輯結構;程序員

  • 函數調用堆棧,高級語言得以運行的基礎,只有機器語言和彙編語言的時候堆棧機制對於計算機來講並不那麼重要,但有了高級語言及函數,堆棧成爲了計算機的基礎功能;github

    • enter  算法

      • pushl %ebpshell

      •  movl %esp,%ebp編程

    • leave  數據結構

      • movl %ebp,%esp架構

      • popl %ebp

    • 函數參數傳遞機制和局部變量存儲

  • 中斷,多道程序操做系統的基點,沒有中斷機制程序只能從頭一直運行結束纔有可能開始運行其餘程序。

 

反彙編

 

 

 

  gcc -g  生成可執行文件

 

  

 

  objdump  -S  得到反彙編文件

 

  C語言內嵌彙編語言(模板):

 

      asm volatile(

 

        輸出:

 

        輸入:

 

        破壞描述部分

 

        );

 

1、Linux內核源代碼介紹

 

一、根目錄

 

arch/x86目錄下的代碼是咱們重點關注的,arch中包括支持不一樣CPU的源代碼。

 

init目錄下包含內核啓動相關的代碼,如main.c(start_kernel函數至關於普通C程序的main函數,是Linux內核初始化的起點)。

 

ipc:進程間通訊

 

kernel:Linux內核的核心代碼

 

關注readme文件

 

 

 

2、構造一個簡單的Linux系統MenuOS

 

一、在實驗樓環境下:

 

cd LinuxKernel/

 

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img

 

便可啓動內核,完成後進入menu程序,支持三個命令help、version和quit。

 

二、使用本身的Linux系統環境搭建MenuOS的過程

 

# 下載內核源代碼編譯內核

 

cd ~/LinuxKernel/

 

wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz

 

xz -d linux-3.18.6.tar.xz

 

tar -xvf linux-3.18.6.tar(解壓)

 

cd linux-3.18.6

 

make i386_defconfig

 

make # 通常要編譯很長時間,少則20分鐘多則數小時

 

 

 

# 製做根文件系統

 

cd ~/LinuxKernel/

 

mkdir rootfs

 

git clone https://github.com/mengning/menu.git  # 若是被牆,可使用附件menu.zip

 

cd menu

 

gcc -o init linktable.c menu.c test.c -m32 -static –lpthread(init是第一個用戶態進程,是1號進程,採用的是靜態編譯的方式)

 

cd ../rootfs

 

cp ../menu/init ./

 

find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img(img鏡像文件)

 

 

 

# 啓動MenuOS系統

 

cd ~/LinuxKernel/

 

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img

 

三、從新配置編譯Linux使之攜帶調試信息

 

(1)在原來配置的基礎上,make menuconfig選中以下選項從新配置Linux,使之攜帶調試信息

 

kernel hacking—>

 

[*] compile the kernel with debug info

 

(2)make從新編譯(時間較長)

 

 

 

四、使用gdb跟蹤調試內核

 

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S # 關於-s和-S選項的說明:

 

# -S freeze CPU at startup (use ’c’ to start execution)

 

# -s shorthand for -gdb tcp::1234 若不想使用1234端口,則可使用-gdb tcp:xxxx來取代-s選項

 

另開一個shell窗口

 

gdb

 

(gdb)file linux-3.18.6/vmlinux # 在gdb界面中target remote以前加載符號表    file home/shiyanlou/LinuxKernel/vmlinux

 

(gdb)target remote:1234 # 創建gdb和gdbserver之間的鏈接,按c 讓qemu上的Linux繼續運行

 

(gdb)break start_kernel # 斷點的設置能夠在target remote以前,也能夠在以後

Linux內核分析實驗二:mykernel實驗指導(操做系統是如何工做的)

 Linux內核分析實驗三----跟蹤分析Linux內核的啓動過程

 

1、用戶態、內核態

  權限分級——爲了系統自己更穩定,使系統不宜崩潰。(並非全部程序員縮寫的代碼都很健壯!!)

  x86 CPU四種不一樣的執行級別:0(內核態)—3(用戶態)

  區分方法:CS:EIP(CPU的每條指令都是經過這裏執行)(代碼段選擇寄存器:偏移量寄存器)

       CS寄存器的最低兩位代表了代碼級別——邏輯地址(不是物理地址)

       內核態:能夠訪問4G地址空間的任意地址    用戶態:0x00000000—0xbfffffff空間內的地址

2、中斷——從用戶態進入內核態

  系統調用是一種特殊的中斷(用戶態進程與硬件設備的接口)

  切換時就會涉及到上下文的問題?

       即中斷int指令在堆棧上會保存一些值:1.用戶態棧頂地址  2.當時狀態字  3.當時CS:EIP的值

        save—cs:eip  ss:eip  eflag  

        load—cs:eip  ss:eip

  過程:1.SAVE_ALL——保存現場

      判斷是否有進程調度?

     (無進程調度)last.RESTORE_ALL+iret(popl cs:eip/ss:eip/efalg)

     (有進程調度)last.暫時保存當前的值直到該進程再次被調度

3、API——應用程序編程接口(不一樣於系統調用)

  • API是一個函數定義
  • 系統調用請求一個軟中斷
  • 觸發系統調用經過傳遞系統調用號實現(壓棧傳遞參數)

 

Linux內核分析實驗四

 

1、給MenuOS增長time和time-asm命令

 

1. 克隆並自動編譯MenuOS

 

rm menu -rf強制刪除原menu文件

 

git clone http:

 

makecd menurootfs運行自動編譯腳本,生成根文件系統,啓動MenuOS

 

2. 給MenuOS增長time和time-asm命令

 

更新menu代碼到最新版

 

test.c中main函數裏,增長MenuConfig

 

增長對應的兩個函數:Time和TimeAsm

 

make rootfs

2、調試內核

1. 使用gdb跟蹤調試內核

-3.18.6/arch//boot/qemu -kernel linuxx86bzImage -initrd rootfs.img -s -S
gdb
-3.18.6/vmlinux   (gdb)file linux
remote:1234 (gdb)target

b sys_time:在系統調用time的位置設置斷點

c:在MenuOs裏使用time,停在斷點處

s 單步調試—一步一步執行(PS:沒法執行time_asm中的int 0X80部分)

3、系統調用在內核代碼中的處理過程

1. 系統調用在內核代碼中的工做機制和初始化

  • main.c中start_kernel函數:trap_init()
  • set_system_trap_gate(SYSCALL_VECTOR,&system_call)
    • SYSCALL_VECTOR:系統調用的中斷向量
    • &system_call:彙編代碼入口
  • 一執行int 0x80,系統直接跳轉到system_call。

2. 系統調用——一個特殊的中斷          

 

SAVE_ALL:保存現場

call *sys_call_table(,%eax,4)調用了系統調度處理函數,eax存的是系統調用號,是實際的系統調度程序。

sys_call_table:系統調用分派表

syscall_after_all:保存返回值

         如有sys_exit_work,則進入sys_exit_work:會有一個進程調度時機。

      work_pending -> work_notifysig,用來處理信號

可能call schedule:進程調度代碼

可能跳轉到restore_all,恢復現場。

若無sys_exit_work,就執行restore_all恢復,返回用戶態。

INTERRUPT_RETURN <=> iret,結束。

- - - 在系統調用返回以前,可能發生進程調度,進程調度裏就會出現進程上下文的切換。進程間通訊可能有信號須要處理。能夠將內核視爲一系列中斷指令的集合。

Linux內核分析實驗五

進程控制塊PCB——task_struct(進程描述符)

 

爲了管理進程,內核必須對每一個進程進行清晰的描述,進程描述符提供了內核所需瞭解的進程信息。

struct task_struct數據結構很龐大

  • Linux進程的狀態與操做系統原理中的描述的進程狀態彷佛有所不一樣,好比就緒狀態和運行狀態都是TASK_RUNNING,爲何呢?

    TASK_RUNN有沒有在CPU上執行決定他是就緒仍是運行狀態。

        和操做系統相似:就緒態,運行態,阻塞態

進程的標示pid

    SMP條件編譯器

鏈表代碼——雙向鏈表

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Linux內核分析實驗六

1、獲得一個可執行程序

1. 預處理、編譯、連接

gcc hello.c -o hello.exe
  • gcc編譯源代碼生成最終可執行的二進制程序,GCC後臺隱含執行了四個階段步驟。

    預處理 => 編譯 => 彙編 => 連接
  • 預處理:編譯器將C源代碼中包含的頭文件編譯進來和執行宏替換等工做。

    gcc -E hello.c -o hello.i
  • 編譯:gcc首先要檢查代碼的規範性、是否有語法錯誤等,以肯定代碼的實際要作的工做,在檢查無誤後,gcc把代碼翻譯成彙編語言。

    gcc –S hello.i –o hello.s
    -S:該選項只進行編譯而不進行彙編,生成彙編代碼。
  • 彙編:把編譯階段生成的.s文件轉成二進制目標代碼.

    gcc –c hello.s –o hello.o
  • 連接:將編譯輸出.o文件連接成最終的可執行文件。

    gcc hello.o –o hello
  • 運行:若連接沒有-o指明,則生成可執行文件默認爲a.out

    ./hello

  

2. 目標文件格式

    a.out——COFF——ELF(Linux)或PE(Windows)

  重點介紹ELF——三種重要格式文件

  • 可重定位文件:保存着代碼和適當的數據,用來和其餘的object文件一塊兒來建立一個可執行文件或者是一個共享文件。
  • 可執行文件:保存着一個用來執行的程序;該文件指出了exec(BA_OS)如何來建立程序進程映象。
  • 共享文件:保存着代碼和合適的數據,用來被下面的兩個連接器連接。
    • 第一個是鏈接編輯器[請參看ld(SD_CMD)],能夠和其餘的可重定位和共享object文件來建立其餘的object。
    • 第二個是動態連接器,聯合一個可執行文件和其餘的共享object文件來建立一個進程映象。
  • object文件參與程序的連接(建立)和執行。

3. 靜態連接的ELF可執行文件和進程的地址空間

 

  • 入口點:程序從0x804800開始。
  • 可執行文件加載到內存中開始執行的第一行代碼。
  • 通常靜態連接將會把全部代碼放在同一個代碼段。
  • 動態鏈接的進程會有多個代碼段。

2、可執行程序的執行環境

1. 命令行參數和shell環境

  • 列出/usr/bin下的目錄信息

    $ ls -l /usr/bin
  • Shell自己不限制命令行參數的個數,命令行參數的個數受限於命令自身

    int main(int argc, char *argv[], char *envp[])
  • Shell會調用execve將命令行參數和環境參數傳遞給可執行程序的main函數

    int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
  • 庫函數exec*都是execve的封裝例程

2. 命令行參數和shell環境變量的保存與傳遞

shell程序 => execve => sys_execve
  • 命令行參數和環境串都放在用戶態堆棧中
  • 初始化新程序堆棧時拷貝進去

3. 可執行程序動態連接

(1)動態連接

  • 關注:load_elf_binary

    load_elf_binary(...)
    {
        ...
        kernel_read();//其實就是文件解析
        ...
        //映射到進程空間 0x804 8000地址
        elf_map();//
        ...
        if(elf_interpreter) //依賴動態庫的話
        {
            ...
            //裝載ld的起點  #得到動態鏈接器的程序起點
            elf_entry=load_elf_interp(...);
            ...
        }
        else //靜態連接
        {
            ...
            elf_entry = loc->elf_ex.e_entry;
            ...
        }
        ...
        //static exe: elf_entry: 0x804 8000
        //exe with dyanmic lib: elf_entry: ld.so addr
        start_thread(regs,elf_entry,bprm->p);
    }
  • 實際上,裝載過程是一個廣度遍歷,遍歷的對象是「依賴樹」。
  • 主要過程是動態連接器完成、用戶態完成。

(2)裝載時動態連接

/*準備.so文件*/     
shlibexample.h (1.3 KB) - Interface of Shared Lib Example
shlibexample.c (1.2 KB) - Implement of Shared Lib Example

/*編譯成libshlibexample.so文件*/
$ gcc -shared shlibexample.c -o libshlibexample.so -m32

/*使用庫文件(由於已經包含了頭文件因此能夠直接調用函數)*/
SharedLibApi();

(3)運行時動態連接

dllibexample.h (1.3 KB) - Interface of Dynamical Loading Lib Example
dllibexample.c (1.3 KB) - Implement of Dynamical Loading Lib Example

/*編譯成libdllibexample.so文件*/
$ gcc -shared dllibexample.c -o libdllibexample.so -m32

/*使用庫文件*/
void * handle = dlopen("libdllibexample.so",RTLD_NOW);//先加載進來
int (*func)(void);//聲明一個函數指針
func = dlsym(handle,"DynamicalLoadingLibApi");//根據名稱找到函數指針
func(); //調用已聲明函數

(4)運行

$ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
$ export LD_LIBRARY_PATH=$PWD 
/*將當前目錄加入默認路徑,不然main找不到依賴的庫文件,固然也能夠將庫文件copy到默認路徑下。*/

3、可執行程序的裝載

1. sys_execve內核處理過程

(1)新的可執行程序起點

  • 通常是地址空間爲0x8048000或0x8048300

(2)execve與fork

execve和fork都是特殊一點的系統調用:通常的都是陷入到內核態再返回到用戶態。
  • fork兩次返回,第一次返回到父進程繼續向下執行,第二次是子進程返回到ret_from_fork而後正常返回到用戶態。

  • execve執行的時候陷入到內核態,用execve中加載的程序把當前正在執行的程序覆蓋掉,當系統調用返回的時候也就返回到新的可執行程序起點。

execve
- 執行到可執行程序 -> 陷入內核
- 構造新的可執行文件 -> 覆蓋掉原可執行程序
- 返回到新的可執行程序,做爲起點(也就是main函數)
- 須要構造其執行環境;
  • Shell會調用execve將命令行參數和環境參數傳遞給可執行程序的main函數,先函數調用參數傳遞,再系統調用參數傳遞。

(3)靜態連接的可執行程序和動態連接的可執行程序execve系統調用返回時不一樣

  • 靜態連接:elf_entry指向可執行文件的頭部,通常是main函數,是新程序執行的起點。
  • 動態連接:elf_entry指向ld(動態連接器)的起點,加載load_elf_interp

2. 莊周夢蝶

莊周(調用execve的可執行程序)入睡(調用execve陷入內核),醒來(系統調用execve返回用戶態)發現本身是蝴蝶(被execve加載的可執行程序)。

3. 動態連接的可執行程序的裝載

(1)可執行文件開始執行的起點在哪裏?如何才能讓execve系統調用返回到用戶態時執行新程序?

  • 修改int 0x80壓入內核堆棧的EIP,經過修改內核堆棧中EIP的值做爲新程序的起點。

(2)Linux內核是如何支持多種不一樣的可執行文件格式

static struct linux_binfmt elf_format//聲明一個全局變量 = {
.module     = THIS_MODULE,
.load_binary    = load_elf_binary,//觀察者自動執行
.load_shlib = load_elf_library,
.core_dump  = elf_core_dump,
.min_coredump   = ELF_EXEC_PAGESIZE,
};

static int __iit init_elf_binfmt(void)
{n
    register_binfmt(&elf_format);//把變量註冊進內核鏈表,在鏈表裏查找文件的格式
    return 0;
}

(3)動態連接

  • 可執行程序須要依賴動態連接庫,而這個動態連接庫可能會依賴其餘的庫,這樣造成了一個關係圖——動態連接庫會生成依賴樹。
  • 依賴動態連接器進行加載庫並進行解析(這就是一個圖的遍歷),裝載全部須要的動態連接庫;以後ld將CPU的控制權交給可執行程序
  • 動態連接的過程主要是動態連接器在起做用,而不是內核完成的。

LINUX內核分析第七週——可執行程序的裝載

1、進程調度與進程調度的時機分析

一、不一樣類型的進程有不一樣的調度需求

Linux既支持普通的分時進程,也支持實時進程。

Linux中的調度是多種調度策略和調度算法的混合。

二、調度策略:是一組規則,它們決定何時以怎樣的方式選擇一個新進程運行。

Linux的調度基於分時和優先級。

三、內核中的調度算法相關代碼使用了相似OOD的策略模式。

四、進程調度的時機

(1)中斷處理過程當中,schedule函數實現調度:

    中斷處理過程(包括時鐘中斷、I/O中斷、系統調用和異常)中,直接調用schedule(),或者返回用戶態時根據need_resched標記調用schedule()。

(2)用戶態進程只能被動調度

    用戶態進程沒法實現主動調度,僅能經過陷入內核態後的某個時機點進行調度,即在中斷處理過程當中進行調度。

(3)內核線程是隻有內核態沒有用戶態的特殊進程

    內核線程能夠直接調用schedule()進行進程切換,也能夠在中斷處理過程當中進行調度,也就是說內核線程做爲一類的特殊的進程能夠主動調度,也能夠被動調度。

 

2、進程上下文切換相關代碼分析

一、爲了控制進程的執行,內核必須有能力掛起正在CPU上執行的進程,並恢復之前掛起的某個進程的執行,這叫作進程切換、任務切換、上下文切換。

二、掛起正在CPU上執行的進程,與中斷時保存現場是不一樣的,中斷先後是在同一個進程上下文中,只是由用戶態轉向內核態執行;進程上下文的切換是兩個進程的切換。

三、進程上下文包含了進程執行須要的全部信息

(1)用戶地址空間:包括程序代碼,數據,用戶堆棧等。

(2)控制信息:進程描述符,內核堆棧等。

(3)硬件上下文(注意中斷也要保存硬件上下文只是保存的方法不一樣)。

四、schedule()函數選擇一個新的進程來運行,並調用context_switch進行上下文的切換,這個宏調用switch_to來進行關鍵上下文切換。

next = pick_next_task(rq, prev);//進程調度算法、策略都封裝這個函數內部

context_switch(rq, prev, next);//完成進程上下文切換

switch_to利用了prev和next兩個參數:prev指向當前進程,next指向被調度的進程。

3、Linux系統的通常執行過程分析

一、最通常的狀況:正在運行的用戶態進程X切換到運行用戶態進程Y的過程

(1)正在運行的用戶態進程X

(2)發生中斷——save cs:eip/esp/eflags(current) to kernel stack,then load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack).

(3)SAVE_ALL //保存現場

(4)中斷處理過程當中或中斷返回前調用了schedule(),其中的switch_to作了關鍵的進程上下文切換

(5)標號1以後開始運行用戶態進程Y(這裏Y曾經經過以上步驟被切換出去過所以能夠從標號1繼續執行)

(6)restore_all //恢復現場

(7)iret - pop cs:eip/ss:esp/eflags from kernel stack

(8)繼續運行用戶態進程Y

二、關鍵:中斷上下文的切換和進程上下文的切換

 

4、Linux系統執行過程當中的幾個特殊狀況

一、幾種特殊狀況

(1)經過中斷處理過程當中的調度時機,用戶態進程與內核線程之間互相切換和內核線程之間互相切換,與最通常的狀況很是相似,只是內核線程運行過程當中發生中斷沒有進程用戶態和內核態的轉換,CS段沒有變化;

(2)內核線程主動調用schedule(),只有進程上下文的切換,沒有發生中斷上下文的切換,與最通常的狀況略簡略;

(3)建立子進程的系統調用在子進程中的執行起點next_ip = ret_from_fork及返回用戶態,如fork;

(4)加載一個新的可執行程序後返回到用戶態的狀況,如execve。

二、X86_32位系統下,每一個進程的地址空間4G。0-3G用戶態,3G-4G僅內核態。

全部的進程3G以上的部分是共享的。

內核是各類中斷處理過程和內核線程的集合。

 

5、Linux操做系統架構概覽

一、操做系統的基本概念

二、典型的Linux操做系統的結構

 

6、最簡單也是最複雜的操做---執行ls命令

 

7、從CPU和內存的角度看Linux系統的執行

一、從CPU的角度

0xc0000000如下是3G的部分,用戶態。

(1)c=gets();系統調用,陷入內核態,將eip/esp/cs/ds等信息壓棧。

(2)進程管理:等待鍵盤敲入指令。

(3)中斷處理:在鍵盤上敲擊ls發生I/O中斷。

進程x陷入內核態後沒有內容執行變成阻塞態,發生I/O中斷後變成就緒態。

(4)系統調用返回。

二、從內存的角度

Linux內核分析實驗八------理解進程調度時機跟蹤分析進程調度與

相關文章
相關標籤/搜索