(六)嵌入式:Linux下新增系統調用的實現

開發環境見前面聲明!!!!
1.知識擴展
1.1 Linux內核中設置了一組用於實現系統功能的子程序,稱爲系統調用。系統調用和普通庫函數調用非常相似,只是系統調用由操作系統核心提供,運行於內核態,而普通的函數調用由函數庫或用戶自己提供,運行於用戶態。
用戶空間的程序無法直接執行內核代碼。它們不能直接調用內核空間中的函數,因爲內核駐留在受保護的地址空間上。如果進程可以直接在內核的地址空間上讀寫的話,系統安全就會失去控制。所以,應用程序應該以某種方式通知系統,告訴內核自己需要執行一個系統調用,希望系統切換到內核態,這樣內核就可以代表應用程序來執行該系統調用了。
系統調用,說的是操作系統提供給用戶程序調用的一組“特殊”接口。用戶程序可以通過這組“特殊”接口來獲得操作系統內核提供的服務,比如用戶可以通過文件系統相關的調用請求系統打開文件、關閉文件或讀寫文件等。從邏輯上來說,系統調用可被看成是一個內核與用戶空間程序交互的接口——它好比一箇中間人,把用戶進程的請求傳達給內核,待內核把請求處理完畢後再將處理結果送回給用戶空間。
1.2 系統調用在用戶空間進程和硬件設備之間添加了一箇中間層。該層主要作用有三個:
1.它爲用戶空間提供了一種統一的硬件的抽象接口。比如當需要讀些文件的時候,應用程序就可以不去管磁盤類型和介質,甚至不用去管文件所在的文件系統到底是哪種類型。
2.系統調用保證了系統的穩定和安全。作爲硬件設備和應用程序之間的中間人,內核可以基於權限和其他一些規則對需要進行的訪問進行裁決。舉例來說,這樣可以避免應用程序不正確地使用硬件設備,竊取其他進程的資源,或做出其他什麼危害系統的事情。
3.每個進程都運行在虛擬系統中,而在用戶空間和系統的其餘部分提供這樣一層公共接口,也是出於這種考慮。如果應用程序可以隨意訪問硬件而內核又對此一無所知的話,幾乎就沒法實現多任務和虛擬內存,當然也不可能實現良好的穩定性和安全性。在Linux中,系統調用是用戶空間訪問內核的唯一手段;除異常和中斷外,它們是內核惟一的合法入口。
1.3 通知內核的機制是靠軟件中斷實現的。首先,用戶程序爲系統調用設置參數。其中一個參數是系統調用編號。參數設置完成後,程序執行“系統調用”指令。x86系統上的軟中斷由int產生。這個指令會導致一個異常:產生一個事件,這個事件會致使處理器切換到內核態並跳轉到一個新的地址,並開始執行那裏的異常處理程序。此時的異常處理程序實際上就是系統調用處理程序。它與硬件體系結構緊密相關。
1.4 補充:
一般情況下,應用程序通過應用編程接口(API)而不是直接通過系統調用來編程。這點很重要,因爲應用程序使用的這種編程接口實際上並不需要和內核提供的系統調用一一對應。一個API定義了一組應用程序使用的編程接口。它們可以實現成一個系統調用,也可以通過調用多個系統調用來實現,而完全不使用任何系統調用也不存在問題。實際上,API可以在各種不同的操作系統上實現,給應用程序提供完全相同的接口,而它們本身在這些系統上的實現卻可能迥異。
在Unix世界中,最流行的應用編程接口是基於POSIX標準的,其目標是提供一套大體上基於Unix的可移植操作系統標準。POSIX是說明API和系統調用之間關係的一個極好例子。在大多數Unix系統上,根據POSIX而定義的API函數和系統調用之間有着直接關係。
2.使用系統調用實現重啓的功能
實現一個新的系統調用的第一步是決定他的用途。本實驗要實現系統調用的重啓功能,就要在內核代碼中更新系統調用的系統調用表。 然後要決定系統調用的名字,這個名字就是你編寫用戶程序想使用的名字,比如我們取一個簡單的名字:mysyscall。一旦這個名字確定下來了,那麼在系統調用中的幾個相關名字也就確定下來了。 系統調用的編號名字:__NR_mysyscall;內核中系統調用的實現程序的名字:sys_mysyscall;本實驗取名爲restart.系統調用的編號名字:__NR_restart;內核中系統調用的實現程序的名字:sys_restart;

2.1實現新的系統調用。無論何種配置,該系統調用都必須編譯到核心的內核映象中去,所以我們把它放進kernel/sys.c文件中。先編寫實現軟件復位的程序,實現系統調用sys_restart()的函數功能。如圖所示:
linux-2.4.x/kernel/sys.c
在這裏插入圖片描述
內核調用函數列表,由指向實現各種系統調用的內核函數的函數指針組成的表。記錄在linux-2.4.x/arch/armnommu/kernel/calls.S 下面。在系統調用表的最後加入一個表項。每種支持該系統調用的硬件體系都必須做這樣的工作。從0開始算起,系統調用在該表中的位置就是它的系統調用號。我把新的系統調用加到這個表的末尾,如圖所示:
linux-2.4.x/arch/armnommu/kernel/calls.S
在這裏插入圖片描述
雖然沒有明確地指定編號,但加入的這個系統調用被按照次序分配給了222這個系統調用號。對於每種需要支持的體系結構,我們都必須將自己的系統調用加人到其系統調用表中去。每種體系結構不需要對應相同的系統調用號。
接下來,添加一個系統調用號,這裏面每一個宏就是一個系統調用號,本實驗添加的對應系統調用表裏面的是222號。
linux-2.4.x/include/asm-armnommu/unistd.h
在這裏插入圖片描述
然後在頭文件裏面聲明外部函數,export出來供內核其他部分共同使用。
linux-2.4.x/include/asm-armnommu/arch-firefox/system.h
在這裏插入圖片描述

uClibc/libc/sysdeps/linux/common/syscalls.c
在這裏插入圖片描述
2.2系統調用添加完成後,內核中有了函數restart()功能,要使用內核的函數,可以在內核,可以修改內核代碼來調用,那麼修改內核user/sash/reboot.c代碼,如圖所示:
在這裏插入圖片描述
使其調用restart,然後在內核配置時使能這個命令,這樣在文件系統裏面就出現了reboot的命令,如圖所示,

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

我們把新的內核和文件系統下載到開發板之後,可以看到有了reboot命令,運行一下,系統自動重啓,實現了軟件復位的功能。
在這裏插入圖片描述

2.3 接下來要編寫測試程序,在用戶態通過系統調用,實現軟件復位的功能。首先在user裏面新建一個文件夾syscall,如圖所示:

在這裏插入圖片描述
修改Makefile 文件,使其能夠被編譯,如圖所示:

在這裏插入圖片描述
然後進入syscall,創建文件syscall.c 和 Makefile,並編寫,如圖所示:
在這裏插入圖片描述
在這裏插入圖片描述

然後make編譯,編譯完成後,查看romfs/bin目錄裏面有沒有這個命令,如圖所示:
在這裏插入圖片描述
這樣再次下載到開發板,測試。如圖示:
在這裏插入圖片描述
這樣第一個實驗結束,實現了內核添加新的系統調用,也實現了用戶態對內核函數的調用。

  1. 按鍵控制實現軟件復位
    由前面的LED實驗,已經瞭解到GPIO的使用,我們查閱了相應的手冊資料之後,可以知道板子上的GPIO0連接到的是按鍵S2,GPIO1連接到的是LED5。如圖3-1所示:
    在這裏插入圖片描述

圖2-1 GPIO的引腳關係

然後查閱手冊知道了GPIO的使用流程,GPIO的基地址,寄存器偏移地址,如圖2-2和2-3所示:
在這裏插入圖片描述
圖2-2 GPIO的基地址
在這裏插入圖片描述
圖2-3 GPIO的寄存器偏移量

那開始添加按鍵功能,開始讀取GPIO的輸入寄存器的值,如果按鍵按下之後,對應的輸入寄存器的對應位會寫1,也就是按鍵按下後會對GPIO口輸入一個高電平,這個1在按鍵對應的位置也就是GPIO1。識別到按鍵按下超過4s後,就執行復位。修改代碼如下所示:


#include<stdio.h>
#include<unistd.h>
#include<sys/syscall.h>
#include<sys/types.h>
int main()
{
	unsigned short inputval;
	int cnt = 1;
	/***設置GPIO模式爲輸出模式**/
	*((unsigned short *)(0x8000d000+0x00)) = 0x5555;
	*((unsigned short *)(0x8000d000+0x04)) = 0x5555;
	/***設置哪一個引腳輸出--(每一位0是輸出模式,1是輸入模式)***/
	*((unsigned short *)(0x8000d000+0x08)) = 0xffff;
	/*檢查輸入寄存器的值,如果裏面對的值與上0x02,即0bxxxx10,結果是0bxxxx10-即第二位是1,那	麼說明按鍵按動*/
	while(1)
	{	
		inputval = *((unsigned short *)(0x8000d000+0x10));
		if((inputval&0x02)!=0x02)
		{
			printf("please press the key\n");
			break;
		}	
		else
			sleep(3);
		printf("the key has pressed 4 second \n");
		if((inputval&0x02)==0x02)
		{
			printf("this is my sysacall\n");
			restart(0xffff0000);
		}
		else 
		{
			printf("Oh,you are not careful,the key is 0\n");
			break;
		}		
	}
	return 0;
}

即首先設置首先要配置GPIOselect寄存器,然後使其的選擇輸出寄存器,那麼配置每一位都是B01,選擇寄存器有兩個,每一個16位,那麼兩個選擇寄存器的值都要設置爲0x5555,跟LED實驗保持一致,然後要設置GPIO的引腳輸入輸出模式,即設置寄存器GPIOOutputEN,沒用到LED,所以全部設置爲輸入模式。然後讀取GPIO的輸入寄存器的值,判斷是否按下按鍵持續4s。
如圖所示,不按按鍵的時候,會提示沒有按下,按住不鬆開超過4秒,就會執行軟件復位功能。

總結:
此次實驗流程,從用戶態到內核態的調用,很好的加深了我們對內核的理解,也明白了什麼是系統調用。用戶態和內核態之間的切換和聯繫,都是我們以前從來沒有過的認識。本次實驗對我們能力的提升有很大的幫助,講解了添加新的系統調用的方法,讓我們自己完成添加系統調用的功能,以及添加按鍵的功能,這都既是對以前知識的複習,也是在加深新知識的理解。很好的提升了自己的開發能力,慢慢的形成自己的開發路線。
這次課程,我對於系統調用的理解是操作系統提供給用戶程序調用的一組“特殊”接口。我們編寫的用戶程序可以通過接口來獲得操作系統內核提供的服務,系統調用相當於一個內核與用戶空間程序交互中間人,把用戶進程的請求傳達給內核,待內核把請求處理完畢後再將處理結果送回給用戶空間。用戶空間和內核空間分開,讓系統的數據和用戶的數據互不干擾,保證系統的穩定性。分開存放,管理上很方便,而更重要的是,將用戶的數據和系統的數據隔離開,就可以對兩部分的數據的訪問進行控制。這樣就可以確保用戶程序不能隨便操作系統的數據,這樣防止用戶程序誤操作或者是惡意破壞系統。

分析系統調用和庫函數:
庫函數也就是我們通常所說的應用編程接口API,它其實就是一個函數定義,比如常見read()、write()等函數說明了如何獲得一個給定的服務,但是系統調用是通過軟中斷向內核發出一個明確的請求,再者系統調用是在內核完成的,而用戶態的函數是在函數庫完成的。系統調用發生在內核空間,因此如果在用戶空間的一般應用程序中使用系統調用來進行文件操作,會有用戶空間到內核空間切換的開銷。事實上,即使在用戶空間使用庫函數來對文件進行操作,因爲文件總是存在於存儲介質上,因此不管是讀寫操作,都是對硬件(存儲器)的操作,都必然會引起系統調用。也就是說,庫函數對文件的操作實際上是通過系統調用來實現的。例如C庫函數fwrite()就是通過write()系統調用來實現的。
庫函數可以理解爲是對系統調用的一層封裝。系統調用作爲內核提供給用戶程序的接口,它的執行效率是比較高效而精簡的,但有時我們需要對獲取的信息進行更復雜的處理,或更人性化的需要,我們把這些處理過程封裝成一個函數再提供給程序員,更方便於編碼。庫函數有可能包含有一個系統調用,有可能有好幾個系統調用,當然也有可能沒有系統調用,比如有些操作不需要涉及內核的功能。可以參考下圖來理解庫函數與系統調用的關係。
這樣的話,使用庫函數也有系統調用的開銷,爲什麼不直接使用系統調用呢?這是因爲,讀寫文件通常是大量的數據(這種大量是相對於底層驅動的系統調用所實現的數據操作單位而言),這時,使用庫函數就可以大大減少系統調用的次數。這一結果又緣於緩衝區技術。在用戶空間和內核空間,對文件操作都使用了緩衝區,例如用fwrite寫文件,都是先將內容寫到用戶空間緩衝區,當用戶空間緩衝區滿或者寫操作結束時,纔將用戶緩衝區的內容寫到內核緩衝區,同樣的道理,當內核緩衝區滿或寫結束時纔將內核緩衝區內容寫到文件對應的硬件媒介。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章