1. 程式人生 > 其它 >linux系統呼叫

linux系統呼叫

技術標籤:Linux驅動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中系統呼叫是使用者空間訪問核心的唯一手段,除異常和陷入外,他們是核心唯一的合法入口。

一般情況下應用程式通過應用程式設計介面API,比如: C庫(C庫包括C語言庫和系統呼叫。通過該介面,應用程式可以訪問硬體裝置和其他作業系統資源。這組介面在應用程式和核心之間扮演了使者的角色,應用程式傳送各種請求,而核心負責滿足這些請求(或者讓應用程式暫時擱置)。實際上提供這組介面主要是為了保證系統穩定可靠,避免應用程式肆意妄行,惹出大麻煩),而不是直接通過系統呼叫來程式設計。
在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