1. 程式人生 > >2018-2019-1 20189219《Linux內核原理與分析》第五周作業

2018-2019-1 20189219《Linux內核原理與分析》第五周作業

新的 文件系統 因此 The 如同 新版本 重要 變量 restore

以前學習計算機操作系統的時候也學習過系統調用的三層機制,但是當時都是純理論學習,沒有親身實踐,很多都理解的比較模糊,這裏借助老師的方法使用內嵌匯編加深理解。

系統調用

要想理解系統調用的具體含義,我們需要先了解用戶態內核態中斷三個概念。簡單的來說:

在用戶態下,我們可以運行用戶態進程,而在內核態下,我們不僅僅可以運行用戶態下的進程,還可以運行更高級別的內核態進程。如果在用戶態下我們需要使用內核態下的進程,那麽我們可以借助中斷操作來從用戶態進入內核態。

用戶態進程

上述的用戶態可能概念比較模糊,這裏我們舉個栗子:

  • C
#include<stdio.h>
int main(){
    int input,output,temp;
    input=1;
    temp=0;
    output=input;
    printf("%d%d",temp,output);
    return 0;
}
  • 內嵌匯編
#include<stdio.h>
int main(){
    int input,output,temp;
    input=1;
    asm volatile(
        "movl $0, %%eax\n\t"
        "movl %%eax, %1\n\t"
        "movl %2, %%eax\n\t"
        "movl %%eax, %0\n\t"
        :"=m"(output),"=m"(temp)
        :"r"(input)
        :"eax"
    );
    printf("%d%d",temp,output);
    return 0;
}

這個例子是書上用來講解內嵌匯編代碼的寫法,這裏我們就不再過多討論。首先我們觀察C語言下的代碼,我們發現這就是個簡單的賦值操作,核心代碼為:

temp=0;
output=input;

觀察其匯編代碼,我們得知這裏使用了eax寄存器,將0和intput變量值傳遞給內存中的temp和output,那麽我們把這個過程叫做用戶態。可以看到,這裏的賦值操作無需借助任何的高級權限,申明變量之後直接賦值就好了。那麽什麽叫做內核態?

內核態進程

這裏我們依然以具體的C語言代碼為例。

#include<stdio.h>
#include<time.h>
int main()
{
    time_t tt;
    struct tm *t;
    __asm__ __volatile__(
        "movl $0, %%ebx\n\t"
        "movl $0xd, %%eax\n\t"
        "int $0x80\n\t"
        "movl %%eax, %0\n\t"
        :"=m"(tt)
    );
    t = localtime(&tt);
    printf("time:%d/%d/%d\n",t->tm_year+1900,t->tm_mon+1,t->tm_mday);
    return 0;
}

當你的程序需要使用到系統函數time()的時候,我們把這個系統函數以及它的執行過程叫做內核態,因為它使用了高級別指令time。當然,這裏的

"movl $0, %%ebx\n\t"
"movl $0xd, %%eax\n\t"
"int $0x80\n\t"
"movl %%eax, %0\n\t"
:"=m"(tt)

語句就包含了從用戶態使用系統調用這一特殊中斷陷入內核態的整個過程。

中斷以及系統調用

想要從用戶態進入到內核態不是平白無故就能實現的,這裏我們需要借助中斷的力量。這裏我們僅僅討論系統調用這一特殊中斷。

系統調用中斷處理過程

  • SAVE_ALL
    當中斷標誌出現時,先保存用戶態的cs:eip&ss:esp&eflags(current)至內核棧中,然後將系統調用的中斷服務程序的入口加載到cs:eip中,把當前的內核態ss:esp也加載到cpu中。這樣,當前cpu的下一條指令即為中斷程序的入口。在linux中使用int 0x80語句來觸發系統調用的執行,即執行中斷向量0x80所對應的服務system_call
  • restore_all & INTERRUPT_RETURN
    中斷結束後,執行restore_all & INTERRUPT_RETURN,此時將pop之前存儲的用戶態的cs:eip&ss:esp&eflags(current),從而恢復到之前的用戶態中。
    至此,系統調用過程就結束了。

    API

    對應上述的匯編代碼,我們給出C語言代碼
#include<stdio.h>
#include<time.h>
int main()
{
        time_t tt;
        struct tm *t;
        tt = time(NULL);
        t = localtime(&tt);
        printf("time:%d/%d/%d\n",t->tm_year+1900,t->tm_mon,t->tm_mday);
        return 0;
}

這裏tt = time(NULL);就包含了整個內嵌匯編的所有含義,這就是API的作用。API的全稱為應用程序編程接口,是一個函數定義,如同這裏的time(),其實libc函數庫早已定義好它的系統調用封裝例程,所以我們才可以直接拿來用,這就是我們常說的庫函數。

link函數的系統調用及參數傳遞

了解了完整的系統調用的三層機制和API使用,我們挑選了編號為9的link庫函數進行實驗。

  • C
#include<stdio.h>
#include<unistd.h>
int main(){
    int ret;
    char * oldpath = "time-asm";
    char * newpath = "timetest";
    ret = link(oldpath,newpath);
    if(ret==0)printf("link successfully");
    else printf("Unable to link the file");
    return 0;
}
  • 內嵌匯編
#include<stdio.h>
#include<unistd.h>
int main(){
    int ret;
    char * oldpath = "time-asm";
    char * newpath = "timetest-asm";
    asm volatile(
        "movl %2, %%ecx\n\t"
        "movl %1, %%ebx\n\t"
        "movl $0x09, %%eax\n\t"
        "int $0x80\n\t"
        "movl %%eax, %0"
        :"=m"(ret)
        :"b" (oldpath),"c"(newpath)
    );
    if(ret==0)printf("link successfully\n");
    else printf("Unable to link the file\n");
    return 0;
}

執行過程如圖:
技術分享圖片
技術分享圖片
技術分享圖片

這裏的link函數和教材中所舉的rename函數傳參一樣,均為兩位。這裏我簡單說明下link函數以及ln命令。

首先link分為兩種類型:

  • 【硬連接】
    硬連接指通過索引節點來進行連接。在Linux的文件系統中,保存在磁盤分區中的文件不管是什麽類型都給它分配一個編號,稱為索引節點號(Inode Index)。在Linux中,多個文件名指向同一索引節點是存在的。一般這種連接就是硬連接。硬連接的作用是允許一個文件擁有多個有效路徑名,這樣用戶就可以建立硬連接到重要文件,以防止“誤刪”的功能。其原因如上所述,因為對應該目錄的索引節點有一個以上的連接。只刪除一個連接並不影響索引節點本身和其它的連接,只有當最後一個連接被刪除後,文件的數據塊及目錄的連接才會被釋放。也就是說,文件真正刪除的條件是與之相關的所有硬連接文件均被刪除。
    技術分享圖片

圖中為顯示的硬鏈接數目。

  • 【軟連接】
    另外一種連接稱之為符號連接(Symbolic Link),也叫軟連接。軟鏈接文件有類似於Windows的快捷方式。它實際上是一個特殊的文件。在符號連接中,文件實際上是一個文本文件,其中包含的有另一文件的位置信息。
  • link函數
    頭文件
    #include <unistd.h>
    函數原型
    int link (const char * oldpath,const char * newpath);
    說明
    link()以參數newpath指定的名稱來建立一個新的連接(硬連接)到參數oldpath所指定的已存在文件。如果參數newpath指定的名稱為一已存在的文件則不會建立連接。函數在執行成功時則返回0,失敗時則返回-1,錯誤原因存於errno。 link()所建立的硬連接無法跨越不同文件系統,如果需要請改用symlink()。
    此函數對應著linux中的ln ‘target‘ ‘file‘命令。
  • symlink函數
    頭文件
    #include <unistd.h>
    函數原型
    int symlink(const char *oldpath, const char *newpath);
    說明
    與link函數的返回值一致,可以跨越不同文件系統。

    勘誤

  • 1.書中一直沒有提到關於不同gcc對應不同匯編代碼的問題。在書中的實驗裏,gcc的版本為4.*,而實際中最新版本的gcc已經升級為7.3,所以在對書中提供的內嵌匯編代碼進行編譯的時候總是會出錯。在使用objdump命令對兩種不同版本gcc生成的二進制*.o文件進行反匯編的時候,我們發現兩種不同版本gcc所生成的匯編文件是不同的。因此我們在實驗過程中需要使用低版本的gcc進行編譯。
sudo apt install gcc-4.8
ln -s /usr/lib/gcc-4.8 /usr/lib/gccl
  • 2.書中給出的代碼存在一些手誤。雖然後面有解析。
    技術分享圖片
    技術分享圖片

2018-2019-1 20189219《Linux內核原理與分析》第五周作業