一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

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

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

3天內不再提示

關于Linux內核系統(tǒng)調用是如何實現(xiàn)的與結果

Linux愛好者 ? 來源:面包板社區(qū) ? 作者:Linux愛好者 ? 2021-03-19 10:52 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

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

在講具體的細節(jié)之前,我們先根據(jù)上圖,從整體上看一下系統(tǒng)調用的實現(xiàn)。

系統(tǒng)調用的實現(xiàn)基礎,其實就是兩條匯編指令,分別是syscall和sysret。

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

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

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

在系統(tǒng)調用函數(shù)執(zhí)行完畢之后,執(zhí)行結果會被放到rax寄存器中。

最后,執(zhí)行sysret匯編指令,從內核態(tài)切換回用戶態(tài),用戶程序繼續(xù)執(zhí)行。

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

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

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

有了上面對系統(tǒng)調用的整理理解,我們接下來看下其具體的實現(xiàn)細節(jié)。

以write系統(tǒng)調用為例,其對應的內核源碼為:

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

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

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

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

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

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

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

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

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

可惜,并沒有。

這就奇怪了,那各系統(tǒng)調用函數(shù)到底是在哪里注冊的呢?

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

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

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

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

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

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

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

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

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

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

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

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

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

系統(tǒng)調用函數(shù)注冊到sys_call_table數(shù)組的過程,到這里已經(jīng)非常清楚了。

下面我們繼續(xù)來看下哪里在使用這個數(shù)組:

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

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

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

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

在系統(tǒng)調用函數(shù)執(zhí)行完畢后,其結果會被賦值到了regs的ax字段里。

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

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

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

你有沒有發(fā)現(xiàn),這里面的字段名都是寄存器的名字。

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

離真相越來越近。

我們繼續(xù)看使用了do_syscall_64的地方:

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

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

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

是的。

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

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

再看上面的壓棧過程,每一次壓棧操作我們都可以認為是在分配內存空間并賦值,當r15被最終壓入到棧中后,整個內存空間分配完畢,且數(shù)據(jù)也初始化完畢,此時,rsp指向的棧頂?shù)刂?,就是這段內存空間的最小地址,因為壓棧過程中,棧頂?shù)牡刂肥且恢痹谧冃〉摹?/p>

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

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

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

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

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

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

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

最后執(zhí)行sysret,從內核態(tài)切換回用戶態(tài),繼續(xù)執(zhí)行syscall后面邏輯。

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

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

系統(tǒng)調用內核態(tài)的邏輯處理就是這些。

下面我們用一個例子來演示下用戶態(tài)部分:

編譯并執(zhí)行:

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

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

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

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

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

有沒有覺得不太盡興?

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

要不我們來自己寫個系統(tǒng)調用?

說干就干。

我們先在write系統(tǒng)調用下面定義一個我們自己的系統(tǒng)調用:

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

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

編譯內核,等待執(zhí)行。

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

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

看結果,正好就是20。
編輯:lyn

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

    關注

    31

    文章

    5434

    瀏覽量

    124536
  • LINUX內核
    +關注

    關注

    1

    文章

    317

    瀏覽量

    22409

原文標題:Linux內核:系統(tǒng)調用是如何實現(xiàn)的

文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    開源系統(tǒng)適配:聚徽分享國產(chǎn)工控平板在 Linux / 鴻蒙系統(tǒng)下的技術優(yōu)化

    工控平板進行技術優(yōu)化,實現(xiàn)Linux、鴻蒙系統(tǒng)的高效適配,成為行業(yè)關注的焦點。 一、Linux 系統(tǒng)下國產(chǎn)工控平板的技術優(yōu)化 (一)
    的頭像 發(fā)表于 06-13 16:29 ?317次閱讀

    如何配置和驗證Linux內核參數(shù)

    Linux系統(tǒng)運維和性能優(yōu)化中,內核參數(shù)(sysctl)的配置至關重要。合理的參數(shù)調整可以顯著提升網(wǎng)絡性能、系統(tǒng)穩(wěn)定性及資源利用率。然而,僅僅修改參數(shù)是不夠的,如何驗證這些參數(shù)是否生
    的頭像 發(fā)表于 05-29 17:40 ?303次閱讀

    Linux系統(tǒng)中通過預留物理內存實現(xiàn)ARM與FPGA高效通信的方法

    管理子系統(tǒng)管理。因此,需要預留一部分物理內存,使其不被內核管理。接下來將為大家詳細介紹在 Linux 系統(tǒng)中通過預留物理內存實現(xiàn) ARM 與
    的頭像 發(fā)表于 04-16 13:42 ?685次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>系統(tǒng)</b>中通過預留物理內存<b class='flag-5'>實現(xiàn)</b>ARM與FPGA高效通信的方法

    如何在Linux內核5.18版本之后和64位架構中從內核空間調用ioctl?

    我嘗試在最近的內核中重新構建以前版本 (4.19) 的 Linux 設備驅動程序,即嵌入式平臺上的 6.1.22,ARM64 架構。 驅動程序管理 tty 設備。 當我調用類似于用戶空間
    發(fā)表于 04-02 06:06

    樹莓派4 性能大比拼:標準Linux與實時Linux 4.19內核的延遲測試

    引言本文是對我之前關于RaspberryPi3同一主題的帖子的更新。與之前的帖子一樣,我使用的是隨Raspbian鏡像提供的標準內核,以及應用了RT補丁的相似內核版本。對于實時版,我
    的頭像 發(fā)表于 03-25 09:39 ?344次閱讀
    樹莓派4 性能大比拼:標準<b class='flag-5'>Linux</b>與實時<b class='flag-5'>Linux</b> 4.19<b class='flag-5'>內核</b>的延遲測試

    基于OpenSBI的linux nommu實現(xiàn)

    Linux內核6.10提供了對沒有mmu的riscv處理器工作在S模式下的內核的支持,本文介紹基于OpenSBI的linuxnommu的實現(xiàn),供大家參考。1、OpenSBI介紹SBI
    的頭像 發(fā)表于 02-08 13:43 ?672次閱讀
    基于OpenSBI的<b class='flag-5'>linux</b> nommu<b class='flag-5'>實現(xiàn)</b>

    騰訊云內核團隊修復Linux關鍵Bug

    騰訊云操作系統(tǒng)(Tencent OS)內核團隊近日在Linux社區(qū)取得了顯著成果。他們提交的兩項改進方案,成功解決了自2021年以來一直困擾眾多一線廠商,并在近期讓多個Linux頂級
    的頭像 發(fā)表于 12-31 10:58 ?663次閱讀

    嵌入式工程師都在找的【Linux內核調試技術】建議收藏!

    在嵌入式系統(tǒng)的開發(fā)中,Linux內核調試是一個至關重要的環(huán)節(jié)。 隨著處理器技術的不斷進步和嵌入式領域的蓬勃發(fā)展,掌握有效的內核調試技術成為了開發(fā)者們的一項必備技能。本文將介紹幾種常見
    發(fā)表于 11-28 15:37

    Linux系統(tǒng)中shell命令解析

    shell是Linux系統(tǒng)的用戶界面,提供了用戶與內核交互的一種接口,它接收用戶輸入的命令并到送到內核去執(zhí)行,因此也被稱為Linux的命令解
    的頭像 發(fā)表于 11-05 15:40 ?938次閱讀

    linux內核中通用HID觸摸驅動

    linux內核中,為HID觸摸面板實現(xiàn)了一個通用的驅動程序,位于/drivers/hid/hid-multitouch.c文件中。hid觸摸驅動是以struct hid_driver實現(xiàn)
    的頭像 發(fā)表于 10-29 10:55 ?2337次閱讀
    <b class='flag-5'>linux</b><b class='flag-5'>內核</b>中通用HID觸摸驅動

    Linux根文件系統(tǒng)的掛載過程

    Linux根文件系統(tǒng)(rootfs)是Linux系統(tǒng)中所有其他文件系統(tǒng)和目錄的起點,它是內核啟動
    的頭像 發(fā)表于 10-05 16:50 ?971次閱讀

    深度解析linux時鐘子系統(tǒng)

    linux內核實現(xiàn)了一個CLK子系統(tǒng),用于對上層提供各模塊(例如需要時鐘信號的外設,USB等)的時鐘驅動接口,對下層提供具體SOC的時鐘操作細節(jié)。
    的頭像 發(fā)表于 09-29 16:46 ?1314次閱讀
    深度解析<b class='flag-5'>linux</b>時鐘子<b class='flag-5'>系統(tǒng)</b>

    linux驅動程序如何加載進內核

    Linux系統(tǒng)中,驅動程序是內核與硬件設備之間的橋梁。它們允許內核與硬件設備進行通信,從而實現(xiàn)對硬件設備的控制和管理。 驅動程序的編寫 驅
    的頭像 發(fā)表于 08-30 15:02 ?1105次閱讀

    Linux內核測試技術

    Linux 內核Linux操作系統(tǒng)的核心部分,負責管理硬件資源和提供系統(tǒng)調用接口。隨著
    的頭像 發(fā)表于 08-13 13:42 ?1316次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>測試技術

    Linux內核中的頁面分配機制

    Linux內核中是如何分配出頁面的,如果我們站在CPU的角度去看這個問題,CPU能分配出來的頁面是以物理頁面為單位的。也就是我們計算機中常講的分頁機制。本文就看下Linux內核是如何管
    的頭像 發(fā)表于 08-07 15:51 ?641次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>中的頁面分配機制