【目的】
理解系統調用的概念,熟悉系統調用的用法。
【環境】
PC:ubuntu 12.04
內核:Linux:3.2.0
【要求】
編程創建系統調用sunpluscall(),實現功能是顯示字符串到屏幕上。
編譯3.2.0內核,用新內核引導系統。
編程調用自己創建的系統調用。
【原理】
操作系統是用戶與計算機之間的接口,用戶通過操作系統的幫助,可以快速、有效和安全可靠地使用計算機系統中的各種資源來解決自己的問題。為了使用戶方便的使用操作系統,OS向用戶提供了“用戶與操作系統的接口”。這種接口支持用戶與操作系統之間進行交互,這些接口可以被分為命令和程序接口兩種。前者直接提供給用戶在鍵盤終端上使用;后者則是提供給用戶(主要是程序員)編程時使用。而要學習系統調用,首先要從程序接口入手。
1、 程序接口
程序接口是操作系統專門為用戶程序設置的,也是用戶程序取得OS服務的唯一途徑。程序接口通常由系統調用組成。在每個操作系統中,通常都有幾十上百條系統調用,它們的作用各有不同,有的用于進程控制、有的用于存儲管理、有的用于文件管理等等。在MS WINDOWS下面進行過WIN32編程的人員應該對windows提供的API函數有一定的印象,這些API函數就是windows操作系統提供給程序員的系統調用接口。而Linux作為一個操作系統,當然有它自己的系統調用。
2、 系統調用
通常,在OS的核心中都設置了一組用于實現各種系統功能的子程序,并將它們提供給程序員調用。程序員在需要OS提供某種服務的時候,便可以調用一條系統調用命令,去實現希望的功能,這就是系統調用。各個不同的操作系統有各自的系統調用,正如前文所講的windows API,便是windows的系統調用,linux的系統調用與之不同的是linux由于內核代碼完全公開,所以可以細致的分析出其系統調用的機制。
1、 系統調用和普通過程的區別
(1)運行于不同的系統狀態
如前所述,用戶程序可以通過系統調用進入系統空間,而普通過程則只能在用戶空間當中運行。
(2)通過軟中斷切換
由于用戶程序使用系統調用后要進入系統空間,所以需要調用一個軟中斷;而普通過程在被調用時沒有這個過程。
2、 系統調用的類型
系統調用的作用與它所在的操作系統有密切關系,根據操作系統的性質不同,它們所提供的系統調用會有一定的差異,不過對于普通操作系統而言,應該具有下面幾類系統調用。
(1)進程控制類型。
(2)文件操縱類型。
(3)進程通信類型。
(4)數據管理類型。
3、 系統調用的實現機制。
由于操作系統的不同,其系統調用的實現方式可能不同,然而實現機制應該是大致相同的,一般包含下面幾個步驟:
(1)設置系統調用號
在系統當中,往往設置多條系統調用命令,并賦予每條系統調用命令一個唯一的系統調用號。
(2)處理系統調用
操作系統當中有個一張系統調用入口表。表中的每個表目都對應一條系統調用命令,它包含有該系統調用自帶參數的數目、系統調用命令處理程序的入口地址等等。操作系統內核便是根據所輸入的系統調用號在該表中查找到到相應的系統調用,進而轉入它的入口地址去執行它。
【實現步驟】
1、拷貝源碼
拷貝linux-3.2.tar.bz2 到虛擬機/usr/src目錄下
2、解壓源碼
#tar xvjf linux-3.2.tar.bz2
3、添加源代碼
#vi linux-3.2/kernel/sunplus.c
新建一個文件sunplus.c。在此文件中添加系統調用函數源代碼,該函數的名稱應該是新的系統調用名稱前面加上sys_標志。假設新加的系統調用為sunpluscall,則該函數應該這樣寫
#include
asmlinkage long sys_sunpluscall(void)
{
printk(KERN_EMERG "this is sunpluscall KERN_EMERG\n");
printk(KERN_ALERT "this is sunpluscall KERN_ALERT\n");
printk(KERN_CRIT "this is sunpluscall KERN_CRIT\n");
printk(KERN_ERR "this is sunpluscall KERN_ERR\n");
printk(KERN_WARNING "this is sunpluscall KERN_WARNING\n");
printk(KERN_NOTICE "this is sunpluscall KERN_NOTICE\n");
printk(KERN_INFO "this is sunpluscall KERN_INFO\n");
printk(KERN_DEBUG "this is sunpluscall KERN_DEBUG\n");
return 0;
}
4、修改Makefile(編譯內核時編譯源代碼)
把sunplus.c添加到kernel目錄下的Makefile中,使其在make編譯內核的時候能編譯到內核中。
#vi linux-3.2/kernel/Makefile
在14行 obj-y += groups.o 下面插入一行
obj-y += sunplus.o
如圖:
5、鏈接新的系統調用
添加新的系統調用之后,下一個任務是讓LINUX內核的其余部分知道該程序的存在。增加新函數的鏈接,需要進行下面的操作。
(1)為新的系統調用添加系統調用號
系統調用號的定義格式如下:
#define __NR_name NNN
其中,name用系統調用名稱代替,而NNN是該系統調用對應的號碼。應該將新的系統調用名稱加到清單的最后,并給它分配已經用到的系統調用號后面的一個號碼。
LINUX內核自身用到的系統調用號已經用到348了。而如果讀者還要自行增加系統調用,就必須從349開始。
#vi linux-3.2/arch/x86/include/asm/unistd_32.h
在文件356行#define __NR_process_vm_writev 348 下面插入一行
#define __NR_sunpluscall 349
把 #define NR_syscalls 349
修改成
#define NR_syscalls 350
NR_syscalls 這個宏表示系統調用的總個數。
如圖:
(2)修改系統調用的指針列表
vi linux-3.2/arch/x86/kernel/syscall_table_32.S
在文件350行.long sys_process_vm_writev 下面添加一行
.long sys_sunpluscall
如圖:
(3)vi linux-3.2/arch/x86/ia32/ia32entry.S
在文件854行.quad compat_sys_process_vm_eritev 下面添加一行
.quad sys_sunpluscall
如圖:
6、重新編譯、安裝
(1)清除殘留的.config和.o
在linux-3.2 目錄下 輸入命令
#make mrproper
該命令的功能在于清除當前目錄下殘留的.config和.o文件,這些文件一般是以前編譯時未清理而殘留的。而對于第一次編譯的代碼來說,不存在這些殘留文件,所以可以略過此步,但是如果該源代碼以前被編譯過,那么強烈建議執行此命令,否則后面可能會出現未知的問題。
(2)配置編譯選項
作為操作系統的內核,其內容和功能必然非常繁雜,包括處理器調度,內存管理,文件系統管理,進程通訊以及設備管理等等,而對于不同的硬件,其配置選項也不相同,所以在編譯源代碼之前必須設置編譯選項。
配置命令有 make menuconfig 或者make xconfig。我使用的是make menuconfig,但是前提條件是要裝ncurses。
1)輸入命令:
sudo apt-get install libncurses5-dev
更新安裝ncurses。 注意一定要聯網。
2)輸入命令 :
make menuconfig
注意把vmware放大到全屏終端放大到全屏,因為輸入make menuconfig 命令后會彈出一個窗口出來,如果不放大會出錯,彈不出窗口。
選擇exit,保存默認配置
(3)編譯內核
1)清除以前編譯生成的 .o 等文件。輸入命令:
make clean
2)編譯內核 此步大約需要 一個半到兩個小時(看機器的性能)。 輸入命令:
make bzImage
3)編譯modules 輸入命令:
make modules
4)安裝modules 就是把剛才編譯生產的modules拷到系統文件夾下,以供新內核調用。 輸入命令:
make modules_install
5)建立要載入ramdisk的映像文件
如果linux系統安裝在scsi磁盤上,這步是必須的,否則可以跳過。
切換至/usr/src目錄
輸入命令:
mkinitramfs -o /boot/initrd.img-3.2.0 3.2.0
(4)安裝內核
輸入命令:
make install
此時系統會把linux內核的鏡像文件還有System.map考入到/boot下,然后會自動生成引導菜單。
7、配置grub
配置grub引導程序
ubuntu系統中grub的默認等待時間為0,要想進入grub菜單,就要修改下等待時間。
修改系統文件/etc/default/grub
將GRUB_HIDDEN_TIMEOUT=0改為GRUB_HIDDEN_TIMEOUT=10
如圖:
修改系統文件/etc/default/grub后需用update-grub命令自動生成啟動的選項。
執行
sudo update-grub
要想系統重啟后的grub引導界面有我們安裝好的內核選項我們還需修改/boot/grub/grub.cfg 文件。
屏蔽 124行 的 // submenu "Previous Linux versions" {
在125行 加入一個 {
如圖:
8、重啟ubuntu
重啟后按 “shift”鍵, 選擇 Ubuntu , Linux 3.2.0 選項
如圖:
9、測試
(1)編寫測試代碼
調用系統調用的方式是使用_syscall宏。2.6.18版本之前的內核,在include/asm-i386/unistd.h文件中定義有7個_syscall宏,分別是:
1 _syscall0(type,name)
2 _syscall1(type,name,type1,arg1)
3 _syscall2(type,name,type1,arg1,type2,arg2)
4 _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)
5 _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)
6 _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5)
7 _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5,type6,arg6)
但是自2.6.19版本開始,_syscall宏被廢除,我們需要使用syscall函數,通過指定系統調用號和一組參數來調用系統調用。
syscall函數原型為:
int syscall(int number, ...);
其中number是系統調用號,number后面應順序接上該系統調用的所有參數。
#include <stdio.h>
#include <stdlib.h>
#include
#include "../linux-3.2/arch/x86/include/asm/unistd_32.h"
int main(int argc, char *argv[])
{
syscall(349);
return 0;
}
(2)測試
測試代碼寫完后編譯
gcc test.c -o test
注意 系統調用里的打印printk是有優先級的,在使用printk時可指定printk的優先級,只有printk的優先級大于終端時,終端上才會顯示出printk打印出的內容。
打印方法1:
切換至字符模式
打印方法2:
使用dmesg函數
Ubuntu切換到字符模式下的方法:
按 ctl + alt + F1 (F1~F6)
切換回圖形界面的方法
按 alt + F7