精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

系統調用是如何實現的?

Linux閱碼場 ? 來源:Linuxer ? 作者:Linuxer ? 2021-02-20 16:46 ? 次閱讀

這張圖畫了挺久的,主要是想讓大家可以從全局角度,看下linux內核中系統調用的實現。

4156d6e4-71ad-11eb-8b86-12bb97331649.png

在講具體的細節之前,我們先根據上圖,從整體上看一下系統調用的實現。

系統調用的實現基礎,其實就是兩條匯編指令,分別是syscall和sysret。

syscall使執行邏輯從用戶態切換到內核態,在進入到內核態之后,cpu會從 MSR_LSTAR 寄存器中,獲取處理系統調用內核代碼的起始地址,即上面的 entry_SYSCALL_64。

在執行 entry_SYSCALL_64 函數時,內核代碼會根據約定,先從rax寄存器中獲取想要執行的系統調用的編號,然后根據該編號從sys_call_table數組中找到對應的系統調用函數。

接著,從 rdi, rsi, rdx, r10, r8, r9 寄存器中獲取該系統調用函數所需的參數,然后調用該函數,把這些參數傳入其中。

在系統調用函數執行完畢之后,執行結果會被放到rax寄存器中。

最后,執行sysret匯編指令,從內核態切換回用戶態,用戶程序繼續執行。

如果用戶程序需要該系統調用的返回結果,則從rax中獲取。

總體流程就是這樣,相對來說,還是比較簡單的,主要就是先去理解syscall和sysret這兩條匯編指令,在理解這兩條匯編指令的基礎上,再去看內核源碼,就會容易很多。

有關syscall和sysret指令的詳細介紹,請參考Intel 64 and IA-32 Architectures Software Developer’s Manual。

有了上面對系統調用的整理理解,我們接下來看下其具體的實現細節。

以write系統調用為例,其對應的內核源碼為:

419dd24c-71ad-11eb-8b86-12bb97331649.png

在內核中,所有的系統調用函數都是通過 SYSCALL_DEFINE 等宏定義的,比如上面的write函數,使用的是 SYSCALL_DEFINE3。

將該宏展開后,我們可以得到如下的函數定義:

41e59078-71ad-11eb-8b86-12bb97331649.png

由上可見,SYSCALL_DEFINE3宏展開后為三個函數,其中只有__x64_sys_write是外部可訪問的,其它兩個都有被static修飾,不能被外部訪問,所以注冊到上文中提到的sys_call_table數組里的函數,應該就是這個函數。

那該函數是怎么注冊到這個數組的呢?

我們先不說答案,先來看下sys_call_table數組的定義:

4213a346-71ad-11eb-8b86-12bb97331649.png

由上可見,該數組各元素的默認值都是 __x64_sys_ni_syscall:

425994a0-71ad-11eb-8b86-12bb97331649.png

該函數也非常簡單,就是直接返回錯誤碼-ENOSYS,表示系統調用非法。

sys_call_table數組定義的地方好像只設置了默認值,并沒有設置真正的系統調用函數。

我們再看看其他地方,看是否有代碼會注冊真正的系統調用函數到sys_call_table數組里。

可惜,并沒有。

這就奇怪了,那各系統調用函數到底是在哪里注冊的呢?

我們再回頭仔細看下sys_call_table數組的定義,它在設置完默認值之后,后面還include了一個名為asm/syscalls_64.h的頭文件,這個位置include頭文件還是比較奇怪的,我們看下它里面是什么內容。

但是,這個文件居然不存在。

那我們只能初步懷疑這個頭文件是編譯時生成的,帶著這個疑問,我們去搜索相關內容,確實發現了一些線索:

4298af50-71ad-11eb-8b86-12bb97331649.png

這個文件確實是編譯時生成的,上面的makefile中使用了syscalltbl.sh腳本和syscall_64.tbl模板文件來生成這個syscalls_64.h頭文件。

我們來看下syscall_64.tbl模板文件的內容:

42bb097e-71ad-11eb-8b86-12bb97331649.png

這里確實定義了write系統調用,且標明了它的編號是1。

我們再來看下生成的syscalls_64.h頭文件:

431eb82a-71ad-11eb-8b86-12bb97331649.png

這里面定義了很多好像宏調用一樣的東西。

__SYSCALL_COMMON,這個不就是sys_call_table數組定義那里define的那個宏嘛。

再去上面看下__SYSCALL_COMMON這個宏定義,它的作用是將sym表示的函數賦值到sys_call_table數組的nr下標處。

所以對于__SYSCALL_COMMON(1, sys_write)來說,它就是注冊__x64_sys_write函數到sys_call_table數組下標為1的槽位處。

而這個__x64_sys_write函數,正是我們上面猜測的,SYSCALL_DEFINE3定義的write系統調用,展開之后的一個外部可訪問的函數。

這樣就豁然開朗了,原來真正的系統調用函數的注冊,是通過先定義__SYSCALL_COMMON宏,再include那個根據syscall_64.tbl模板生成的syscalls_64.h頭文件來完成的,非常巧妙。

系統調用函數注冊到sys_call_table數組的過程,到這里已經非常清楚了。

下面我們繼續來看下哪里在使用這個數組:

4348f482-71ad-11eb-8b86-12bb97331649.png

do_syscall_64在使用,方式是先通過nr在sys_call_table數組中找到對應的系統調用函數,然后再調用該函數,將regs傳入其中。

這個流程和我們上面預估的一樣,且傳入的regs參數類型,和我們上面注冊的系統調用函數所需的類型也一樣。

那也就是說,regs參數的字段里,是帶著各系統調用函數所需的參數的,SYSCALL_DEFINE等宏展開出來的一系列函數,會從這些字段中提取出真正的參數,然后對其進行類型轉換,最后這些參數被傳入到最終的系統調用函數中。

對于上面的write系統調用宏展開后的那些函數,__x64_sys_write會先從regs中提取出di, si, dx字段作為真正參數,然后__se_sys_write會將這些參數轉成正確的類型,最后__do_sys_write函數被調用,轉換后的這些參數被傳入其中。

在系統調用函數執行完畢后,其結果會被賦值到了regs的ax字段里。

由上可見,系統調用函數的參數及返回值的傳遞,都是通過regs來完成的。

但文章開始的時候不是說,系統調用的參數及返回值的傳遞,是通過寄存器來完成的嗎,這里怎么是通過struct pt_regs的字段呢?

先別急,先來看下struct pt_regs的定義:

43838782-71ad-11eb-8b86-12bb97331649.png

你有沒有發現,這里面的字段名都是寄存器的名字。

那是不是說,在執行系統調用的代碼里,有邏輯把各寄存器里的值放到了這個結構體的對應字段里,在結束系統調用時,這些字段里的值又被賦值到各個對應的寄存器里呢?

離真相越來越近。

我們繼續看使用了do_syscall_64的地方:

43e020fa-71ad-11eb-8b86-12bb97331649.png

上圖中的entry_SYSCALL_64方法,就是系統調用流程中最重要的一個方法了,為了便于理解,我對該方法做了很多修改,并添加了很多注釋。

這里需要注意的是100行到121行這段邏輯,它將各寄存器的值壓入到棧中,以此來構建struct pt_regs對象。

這就能構建出一個struct pt_regs對象了?

是的。

我們回上面看下struct pt_regs的定義,看其字段名字及順序是不是和這里的壓棧順序正好相反。

我們再想下,當我們要構建一個struct pt_regs對象時,我們要為其在內存中分配一塊空間,然后用一個地址來指向這段空間,這個地址就是該struct pt_regs對象的指針,這里需要注意的是,這個指針里存放的地址,是這段內存空間的最小地址。

再看上面的壓棧過程,每一次壓棧操作我們都可以認為是在分配內存空間并賦值,當r15被最終壓入到棧中后,整個內存空間分配完畢,且數據也初始化完畢,此時,rsp指向的棧頂地址,就是這段內存空間的最小地址,因為壓棧過程中,棧頂的地址是一直在變小的。

綜上可知,在壓棧完畢后,rsp里的地址就是一個struct pt_regs對象的地址,即該對象的指針。

在構建完struct pt_regs對象后,123行將rax中存放的系統調用編號賦值到了rdx里,124行將rsp里存放的struct pt_regs對象的地址,即該對象的指針,賦值到了rsi中,接著后面執行了call指令,來調用do_syscall_64方法。

調用do_syscall_64方法之前,對rdi和rsi的賦值,是為了遵守c calling convention,因為在該calling convention中約定,在調用c方法時,第一個參數要放到rdi里,第二個參數要放到rsi里。

我們再去上面看下do_syscall_64方法的定義,參數類型及順序是不是和我們這里說的是完全一樣的。

在調用完do_syscall_64方法后,系統調用的整個流程基本上就快結束了,上圖中的129行到133行做的都是一些寄存器恢復的工作,比如從棧中彈出對應的值到rax,rip,rsp等等。

這里需要注意的是,棧中rax的值是在上面do_syscall_64方法里設置的,其存放的是系統調用的最終結果。

另外,在棧中彈出的rip和rsp的值,分別是用戶態程序的后續指令地址及其堆棧地址。

最后執行sysret,從內核態切換回用戶態,繼續執行syscall后面邏輯。

到這里,完整的系統調用處理流程就已經差不多說完了,不過這里還差一小步,就是syscall指令在進入到內核態之后,是如何找到entry_SYSCALL_64方法的:

445e0cae-71ad-11eb-8b86-12bb97331649.png

它其實是注冊到了MSR_LSTAR寄存器里了,syscall指令在進入到內核態之后,會直接從這個寄存器里拿系統調用處理函數的地址,并開始執行。

系統調用內核態的邏輯處理就是這些。

下面我們用一個例子來演示下用戶態部分:

44aa1ba8-71ad-11eb-8b86-12bb97331649.png

編譯并執行:

44ec4ee2-71ad-11eb-8b86-12bb97331649.png

我們用syscall來執行write系統調用,寫的字符串為Hi ,syscall執行完畢后,我們直接使用ret指令將write的返回結果當作程序的退出碼返回。

所以在上圖中,輸出了Hi,且程序的退出碼是3。

如果對上面的匯編不太理解,可以把它想像成下面這個樣子:

455a1bfc-71ad-11eb-8b86-12bb97331649.png

在這里,我們使用的是glibc中的write方法來執行該系統調用,其實該方法就是對syscall指令做的一層封裝,本質上使用的還是我們上面的匯編代碼。

這個例子到這里就結束了。

有沒有覺得不太盡興?

我們分析了這么多的代碼,最終就用了這么個小例子就結束了,不行,我們要再做點什么。

要不我們來自己寫個系統調用?

說干就干。

我們先在write系統調用下面定義一個我們自己的系統調用:

458e3fcc-71ad-11eb-8b86-12bb97331649.png

該方法很簡單,就是將參數加10,然后返回。

再把這個系統調用在syscall_64.tbl里注冊一下,編號為442:

45ce44f0-71ad-11eb-8b86-12bb97331649.png

編譯內核,等待執行。

我們再把上面寫的那個hi程序改下并編譯好:

4630f078-71ad-11eb-8b86-12bb97331649.png

然后在虛擬機中啟動新編譯的linux內核,并執行上面的程序:

466ec3f8-71ad-11eb-8b86-12bb97331649.png

看結果,正好就是20。

搞定,收工。

原文標題:精致全景圖 | 系統調用是如何實現的

文章出處:【微信公眾號:Linuxer】歡迎添加關注!文章轉載請注明出處。

責任編輯:haq

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 寄存器
    +關注

    關注

    31

    文章

    5325

    瀏覽量

    120052
  • 系統調用
    +關注

    關注

    0

    文章

    28

    瀏覽量

    8321

原文標題:精致全景圖 | 系統調用是如何實現的

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    AIGC系統中多個模型的切換調用方案探索

    作者:京東科技 賈玉龍 1 背景 1.1 現狀 AIGC系統中多個模型的切換調用通常指的是在同一個AIGC系統或應用中,可以根據不同的輸入條件或任務需求,動態地選擇并調用不同的機器學習
    的頭像 發表于 11-27 11:43 ?96次閱讀
    AIGC<b class='flag-5'>系統</b>中多個模型的切換<b class='flag-5'>調用</b>方案探索

    京準電鐘解讀:PTP時鐘同步系統及應用是什么?

    京準電鐘解讀:PTP時鐘同步系統及應用是什么?
    的頭像 發表于 10-31 09:35 ?172次閱讀
    京準電鐘解讀:PTP時鐘同步<b class='flag-5'>系統</b>及應<b class='flag-5'>用是</b>什么?

    如何手搓一個自定義的RPC 遠程過程調用框架

    1、RPC(遠程過程調用概述) 遠程過程調用(RPC, Remote Procedure Call)是一種通過網絡從遠程計算機程序上請求服務,而無需了解網絡細節的通信技術。在分布式系統中,RPC
    的頭像 發表于 07-22 12:17 ?837次閱讀
    如何手搓一個自定義的RPC 遠程過程<b class='flag-5'>調用</b>框架

    人員定位系統的主要作用是什么?還有什么常用功能?

    、人員定位系統的主要作用是什么? 其實現在的人員定位系統可提供的服務不僅僅是定位,更主要的作用是減少安全事故,而便于管理者使用的監管功能也只
    的頭像 發表于 07-16 11:17 ?423次閱讀

    pi調節器的作用是什么

    PI調節器,即比例-積分調節器,是一種廣泛應用于工業控制系統中的控制器。它通過比例(P)和積分(I)兩個參數的調整,實現系統輸出的精確控制。以下是關于PI調節器的詳細介紹: 一、PI調節器
    的頭像 發表于 06-30 10:43 ?3053次閱讀

    控制器的主要作用是指什么

    控制器的主要作用是指在自動化控制系統中,對系統的工作狀態進行監控、調節和控制的設備或裝置。控制器是自動化控制系統的核心部件,其性能和可靠性直接影響到整個
    的頭像 發表于 06-30 10:39 ?3183次閱讀

    中性點接地的作用是什么?

    中性點接地的主要作用是確保電氣系統的安全性、穩定性和可靠性,同時保護設備和人身安全。
    的頭像 發表于 03-19 14:16 ?3805次閱讀
    中性點接地的作<b class='flag-5'>用是</b>什么?

    ADS調用spectre網表仿真異常—薛定諤的NetlistInclude

    ADS是支持調用spice/spectre等網表文件進行仿真的,可以用NetlistInclude控件來進行調用
    的頭像 發表于 03-07 09:57 ?2123次閱讀
    ADS<b class='flag-5'>調用</b>spectre網表仿真異常—薛定諤的NetlistInclude

    verilog如何調用其他module

    。 1.2 為什么要調用其他模塊? 在復雜的設計中,我們通常需要實現各種不同的功能,并且這些功能往往可以通過不同的模塊來實現。通過調用其他模塊,我們可以將問題分解為更小的子問題,并且可
    的頭像 發表于 02-22 15:56 ?5467次閱讀

    電源驅動ic的作用是什么 電源IC驅動電路設計圖

    電源驅動IC的作用是提供對電源開關器件的精確控制,同時保護電源和提供反饋控制功能,幫助實現高效、穩定和可靠的電源系統
    發表于 02-05 17:32 ?2496次閱讀
    電源驅動ic的作<b class='flag-5'>用是</b>什么 電源IC驅動電路設計圖

    cybt343026-01的藍牙模塊做的ibeacon的應用,如何確定我的這個應用是基于5.0還是4.2實現的?

    我用的是cybt343026-01的藍牙模塊做的ibeacon的應用。客戶要求該應用是基于Bluetooth 5.0實現。 我想問一下,如何確定我的這個應用是基于5.0還是4.2實現
    發表于 02-02 09:15

    linux用gdb調試遇到函數調用怎么辦?

    linux用gdb調試遇到函數調用怎么辦? 在Linux上使用GDB調試時,遇到函數調用是一個常見的情況。函數調用可能涉及到多個函數、多個文件,這就需要我們仔細審查代碼,理解函數之間的關系和參數傳遞
    的頭像 發表于 01-31 10:33 ?691次閱讀

    Linux內核中信號相關的系統調用

    正如我們所知,運行在用戶態下的程序可以發送和接收信號。這意味著必須定義一組系統調用來允許這類操作。不幸的是,由于歷史原因,有些系統調用可能功能相同。 因此,其中一些
    的頭像 發表于 01-20 09:34 ?663次閱讀

    linux內核系統調用之參數傳遞

    與普通函數一樣,系統調用通常需要一些輸入/輸出參數,這些參數可能包括實際值(即數字)、用戶模式進程地址空間中的變量地址,甚至包括指向用戶模式函數指針的數據結構的地址(參見第11章“信號相關的系統
    的頭像 發表于 12-20 09:32 ?1480次閱讀

    Linux系統調用腳本的常見方法

    在linux系統中有多種方法可以在系統啟動后調用腳本,接下來介紹幾種常見的方法
    的頭像 發表于 12-13 18:16 ?1006次閱讀