linux系統呼叫
1、為什麼要分成核心空間和使用者空間
為了安全。在核心空間,可以對cpu進行任意訪問,也就是擁有最高的許可權。如果使用者程式也擁有了這個許可權,如果應用程式不安全(攜帶病毒),就會影響核心的安全。因此,分成核心空間和使用者空間(使用者空間的許可權受限)。
注:這裡涉及到保護模式的概念。總得來說,就是:在保護模式下,程式不能再隨意的訪問實體記憶體了,有些記憶體地址CPU做了明確的保護限制。保護機制能有效的實現不同任務之間和同一任務內的保護。
詳見部落格:
https://blog.csdn.net/gatieme/article/details/50646461
2、應用程式如何訪問核心資源
應用程式要想訪問核心資源,有三種方法:系統呼叫、異常(軟/硬中斷),和陷入。
這裡重點分析系統呼叫。
eg:
應用程式工作在使用者空間,驅動執行在核心空間。當用戶想要實現對核心的操作,比如,使用open()函式來開啟/dev/led這個驅動,因為使用者空間不能直接對核心進行訪問,因此,必須使用一個叫“系統呼叫”的方法來實現從使用者空間“陷入(其實就是軟中斷)”到核心空間,這樣才能實現對底層驅動的操作。
系統呼叫其實也是一堆的函式。每一個系統呼叫都有一個系統呼叫號(eg:如下圖所示:read()的系統呼叫號是3;write()的系統呼叫號是4)。從使用者空間“陷入”到核心以後,會提供一個系統呼叫號(eg: 我的核心是4.1.15,系統呼叫號在arch\arm64\include\asm\ unistd_32.h檔案存放)。核心知道這是一個系統呼叫後,根據系統呼叫號呼叫核心裡面對用的函式。
系統呼叫號相當關鍵,一旦分配就不能再有任何變更,否則編譯好的應用程式就會崩潰。
3、系統呼叫概述
linux核心中設定了一組用於實現系統功能的子程式,稱為系統呼叫。系統呼叫和普通庫函式呼叫非常相似,只是系統呼叫由作業系統核心提供,運行於核心態,而普通的函式呼叫由函式庫或使用者自己提供,運行於使用者態。
應用程式執行在使用者空間,而 Linux 驅動屬於核心的一部分,因此驅動運行於核心空間。
當我們在使用者空間想要實現對核心的操作,比如使用 open 函式開啟/dev/led 這個驅動,因為使用者空間不能直接對核心進行操作,因此必須使用一個叫做“系統呼叫”的方法來實現從使用者空間“陷入”到核心空間,這樣才能實現對底層驅動的操作。在linux中系統呼叫是使用者空間訪問核心的唯一手段,除異常和陷入外,他們是核心唯一的合法入口。
在Unix世界,最流行的API是基於POSIX標準的。
注:
1)、一個API可能會需要一個或多個系統呼叫來完成特定功能。通俗點說就是並不是所有的API函式都一一對應一個系統呼叫,有時,一個API函式會需要幾個系統呼叫來共同完成函式的功能,甚至還有一些API函式不需要呼叫相應的系統呼叫(因此它所完成的不是核心提供的服務)
2)、API是一個提供給應用程式的介面,一組函式,是與程式設計師進行直接互動的。
3)、系統呼叫則不與程式設計師進行互動的,它根據API函式,通過一個軟中斷機制向核心提交請求,以獲取核心服務的介面。
4、系統呼叫的實現
系統呼叫要從使用者空間進入核心空間,通過一些處理器支援的特殊機制----軟中斷。arm上,就是彙編指令SWI。
4.1什麼是軟中斷
1). 軟中斷的處理非常像硬中斷。然而,它們僅僅是由當前正在執行的程序所產生的。
2). 通常,軟中斷是一些對I/O的請求。這些請求會呼叫核心中可以排程I/O發生的程式。對於某些裝置,I/O請求需要被立即處理,而磁碟I/O請求通常可以排隊並且可以稍後處理。根據I/O模型的不同,程序或許會被掛起直到I/O完成,此時核心排程器就會選擇另一個程序去執行。I/O可以在程序之間產生並且排程過程通常和磁碟I/O的方式是相同。
3). 軟中斷僅與核心相聯絡。而核心主要負責對需要執行的任何其他的程序進行排程。一些核心允許裝置驅動的一些部分存在於使用者空間,並且當需要的時候核心也會排程這個程序去執行。
4). 軟中斷並不會直接中斷CPU。也只有當前正在執行的程式碼(或程序)才會產生軟中斷。這種中斷是一種需要核心為正在執行的程序去做一些事情(通常為I/O)的請求。有一個特殊的軟中斷是Yield呼叫,它的作用是請求核心排程器去檢視是否有一些其他的程序可以執行。
轉自: http://www.linuxidc.com/Linux/2014-03/98013.htm
4.2 硬中斷和軟中斷的區別
1)、軟中斷是執行中斷指令產生的,而硬中斷是由外設引發的。
2)、硬中斷的中斷號是由中斷控制器提供的,軟中斷的中斷號由指令直接指出,無需使用中斷控制器。
3)、硬中斷是可遮蔽的,軟中斷不可遮蔽。
4)、硬中斷處理程式要確保它能快速地完成任務,這樣程式執行時才不會等待較長時間,稱為上半部。軟中斷處理硬中斷未完成的工作,是一種推後執行的機制,屬於下半部。
擴充套件:
為了滿足實時系統的要求,中斷處理應該是越快越好。linux為了實現這個特點,當中斷髮生的時候,硬中斷處理那些短時間就可以完成的工作,而將那些處理事件比較長的工作,放到中斷之後來完成,也就是軟中斷來完成。
4.3
軟中斷是通過一條具體指令SWI,引發中斷操作,也就是說使用者程式裡可以通過寫入SWI指令來切換到特權模式,當CPU執行到SWI指令時會從使用者模式切換到管理模式下,執行軟體中斷處理(eg:當執行應用程式open()函式的時候,就已經進入了核心態 )。由於SWI指令由作業系統提供的API封裝起來,並且軟體中斷處理程式也是作業系統編寫者提前寫好的,因此使用者程式呼叫API時就是將操作許可權交給了作業系統,所以使用者程式還是不能隨意訪問硬體。
4.4系統呼叫的實現原理
4.4.1從使用者空間通過C庫訪問系統呼叫
在Linux中每個系統呼叫都有相應的系統呼叫號作為唯一的標識,核心維護一張系統呼叫表,sys_call_table,表中的元素是系統呼叫函式的起始地址,而系統呼叫號就是系統呼叫在呼叫表的偏移量(對於系統呼叫表中的表項是以32位(4位元組)型別存放的,所以核心需要將給定的系統呼叫號乘以4,然後用所得的結果在該表中查詢其位置)。通過這種方式,找到對應的系統呼叫響應函式的入口地址。
因為所有的系統呼叫陷入核心的方式都一樣,所以僅僅是陷入核心空間是不夠的。因此必須把系統呼叫號一併傳給核心。對於ARM來說,以r7作為傳遞系統呼叫號的暫存器。
系統呼叫的陷入:
4.4.2從使用者空間通過C庫訪問系統呼叫例項
作業系統使用系統呼叫表將系統呼叫編號翻譯為特定的系統呼叫。系統呼叫表包含有實現每個系統呼叫的函式的地址。例如,read() 系統呼叫函式名為sys_read。read()系統呼叫編號是 3,所以sys_read() 位於系統呼叫表的第四個條目中(因為系統呼叫起始編號為0)。從地址 sys_call_table + (3 * word_size) 讀取資料,得到sys_read()的地址。
找到正確的系統呼叫地址後,它將控制權轉交給那個系統呼叫。我們來看定義sys_read()的位置,即fs/read_write.c檔案。這個函式會找到關聯到 fd 編號(傳遞給 read() 函式的)的檔案結構體。那個結構體包含指向用來讀取特定型別檔案資料的函式的指標。進行一些檢查後,它呼叫與檔案相關的 read() 函式,來真正從檔案中讀取資料並返回。與檔案相關的函式是在其他地方定義的 —— 比如套接字程式碼、檔案系統程式碼,或者裝置驅動程式程式碼。這是特定核心子系統最終與核心其他部分協作的一個方面。
讀取函式結束後,從sys_read()返回,它將控制權切換給 ret_from_sys。它會去檢查那些在切換回使用者空間之前需要完成的任務。如果沒有需要做的事情,那麼就恢復使用者程序的狀態,並將控制權交還給使用者程式。
4.4.3從使用者空間直接訪問系統呼叫例項
通常,系統呼叫靠C庫支援。使用者程式通過包含標準標頭檔案並和C庫連結,就可以使用系統呼叫(或者呼叫庫函式,再由庫函式實際呼叫)。但如果你僅僅寫出系統呼叫,glibc庫恐怕並不提供支援。值得慶幸的是,Linux本身提供了一組巨集,用於直接對系統呼叫進行訪問。它會設定好暫存器並呼叫陷人指令。這些巨集是_syscalln(),其中n的範圍從0到6。代表需要傳遞給系統呼叫的引數個數,這是由於該巨集必須瞭解到底有多少引數按照什麼次序壓入暫存器。舉個例子,open()系統呼叫的定義是:
long open(const char filename, int flags, int mode)
而不靠庫支援,直接呼叫此係統呼叫的巨集的形式為:
#define NR_ open 5
syscall3(long, open, const char,filename, int, flags, int, mode)
這樣,應用程式就可以直接使用open()。
5、原始碼分析
在Linux下系統呼叫是用軟中斷實現的,下面以一個簡單的open例子簡要分析一下應用層的open是如何呼叫到核心中的sys_open的。在glibc庫中,通過封裝例程(Wrapper Routine)將API和系統呼叫關聯起來。API是標頭檔案中所定義的函式介面,而位於glibc中的封裝例程則是對該API對應功能的具體實現。事實上,我們知道介面open()所要完成的功能是通過系統呼叫open()完成的,因此封裝例程要做的工作就是先將介面open()中的引數複製到相應的暫存器中,然後引發一個異常,從而系統進入核心區執行sys_open(),最後當系統呼叫執行完畢後,封裝例程還要將錯誤碼返回到應用程式中。
1)、先來看一下下面這段程式:
int main(){
int fd;
fd=open(".",O_RDWR);
}
2)、然後對該程式進行靜態編譯,並且反彙編:
000082f0 <main>:
82f0: e92d4800 push {fp, lr}
82f4: e28db004 add fp, sp, #4 ; 0x4
82f8: e24dd010 sub sp, sp, #16 ; 0x10
82fc: ebffffd5 bl 8258 <test>
8300: e1a03000 mov r3, r0
8304: e50b300c str r3, [fp, #-12]
8308: e59f002c ldr r0, [pc, #44] ; 833c <main+0x4c>
830c: e3a01002 mov r1, #2 ; 0x2
8310: eb002e82 bl 13d20 <__libc_open>
8314: e1a03000 mov r3, r0
8318: e50b3008 str r3, [fp, #-8]
831c: e59f301c ldr r3, [pc, #28] ; 8340 <main+0x50>
可以看到在main 函式中,呼叫了__libc_open函式,再看一下__libc_open:
00013d20 <__libc_open>:
13d20: e51fc028 ldr ip, [pc, #-40] ; 13d00 <___fxstat64+0x50>
13d24: e79fc00c ldr ip, [pc, ip]
13d28: e33c0000 teq ip, #0 ; 0x0
13d2c: 1a000006 bne 13d4c <__libc_open+0x2c>
13d30: e1a0c007 mov ip, r7
13d34: e3a07005 mov r7, #5 ; 0x5 //open的系統呼叫號5
13d38: ef000000 svc 0x00000000 //產生軟中斷
13d3c: e1a0700c mov r7, ip
13d40: e3700a01 cmn r0, #4096 ; 0x1000
13d44: 312fff1e bxcc lr
13d48: ea0008b0 b 16010 <__syscall_error>
__libc_open的實現應該在glib封裝函式中,以r7作為傳遞系統呼叫號的暫存器,並且使用svc 0x00000000命令陷入核心,這種處理方式說明使用的是比較新的EABI方式的系統呼叫。
注:好多資料上說陷入軟中斷是使用SWI指令,ARM官網解釋:
作為 ARM 組合語言開發成果的一部分,SWI 指令已重新命名為 SVC。 在此版本的 RVCT 中,SWI 指令反彙編為 SVC,並提供註釋以指明這是以前的 SWI。
此 ARM 指令可用於所有版本的 ARM 體系結構。
參考部落格:
【1】
https://blog.csdn.net/gatieme/article/details/50779184?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522160921617116780299028620%252522%25252C%252522scm%252522%25253A%25252220140713.130102334.pc%25255Fall.%252522%25257D&request_id=160921617116780299028620&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-26-50779184.nonecase&utm_term=linux%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E8%B0%83%E7%94%A8%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E7%9A%84%E8%BF%87%E7%A8%8B
【2】
https://blog.csdn.net/oqqYuJi12345678/article/details/100746436?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522160923189116780271118297%252522%25252C%252522scm%252522%25253A%25252220140713.130102334.pc%25255Fall.%252522%25257D&request_id=160923189116780271118297&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-6-100746436.nonecase&utm_term=swi%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E5%8F%B7%E6%98%AF%E8%87%AA%E5%B7%B1%E5%AE%9A%E4%B9%89%E7%9A%84%E5%90%97