实验3 系统调用
系統(tǒng)調(diào)用
實(shí)驗(yàn)?zāi)康?/h2> - 建立對(duì)系統(tǒng)調(diào)用接口的深入認(rèn)識(shí)
- 掌握系統(tǒng)調(diào)用的基本過(guò)程
- 能完成系統(tǒng)調(diào)用的全面控制
- 為后續(xù)實(shí)驗(yàn)做準(zhǔn)備
實(shí)驗(yàn)內(nèi)容
此次實(shí)驗(yàn)的基本內(nèi)容是:在Linux 0.11上添加兩個(gè)系統(tǒng)調(diào)用,并編寫(xiě)兩個(gè)簡(jiǎn)單的應(yīng)用程序測(cè)試它們。
iam()
第一個(gè)系統(tǒng)調(diào)用是iam(),其原型為:
int iam(const char * name);
完成的功能是將字符串參數(shù)name的內(nèi)容拷貝到內(nèi)核中保存下來(lái)。要求name的長(zhǎng)度不能超過(guò)23個(gè)字符。返回值是拷貝的字符數(shù)。如果name的字符個(gè)數(shù)超過(guò)了23,則返回“-1”,并置errno為EINVAL。
在kernal/who.c中實(shí)現(xiàn)此系統(tǒng)調(diào)用。
whoami()
第二個(gè)系統(tǒng)調(diào)用是whoami(),其原型為:
int whoami(char* name, unsigned int size);
它將內(nèi)核中由iam()保存的名字拷貝到name指向的用戶地址空間中,同時(shí)確保不會(huì)對(duì)name越界訪存(name的大小由size說(shuō)明)。返回值是拷貝的字符數(shù)。如果size小于需要的空間,則返回“-1”,并置errno為EINVAL。
也是在kernal/who.c中實(shí)現(xiàn)。
測(cè)試程序
運(yùn)行添加過(guò)新系統(tǒng)調(diào)用的Linux 0.11,在其環(huán)境下編寫(xiě)兩個(gè)測(cè)試程序iam.c和whoami.c。最終的運(yùn)行結(jié)果是:
\$ ./iam lizhijun
\$ ./whoami
lizhijun
實(shí)驗(yàn)報(bào)告
在實(shí)驗(yàn)報(bào)告中回答如下問(wèn)題:
- 從Linux 0.11現(xiàn)在的機(jī)制看,它的系統(tǒng)調(diào)用最多能傳遞幾個(gè)參數(shù)?你能想出辦法來(lái)擴(kuò)大這個(gè)限制嗎?
- 用文字簡(jiǎn)要描述向Linux 0.11添加一個(gè)系統(tǒng)調(diào)用foo()的步驟。
評(píng)分標(biāo)準(zhǔn)
- 將 testlab2.c(在/home/teacher目錄下) 在修改過(guò)的Linux 0.11上編譯運(yùn)行,顯示的結(jié)果即內(nèi)核程序的得分。滿分50%
- 只要至少一個(gè)新增的系統(tǒng)調(diào)用被成功調(diào)用,并且能和用戶空間交換參數(shù),可得滿分
- 將腳本 testlab2.sh(在/home/teacher目錄下) 在修改過(guò)的Linux 0.11上運(yùn)行,顯示的結(jié)果即應(yīng)用程序的得分。滿分30%
- 實(shí)驗(yàn)報(bào)告,20%
實(shí)驗(yàn)提示
首先,請(qǐng)將Linux 0.11的源代碼恢復(fù)到原始狀態(tài)。
操作系統(tǒng)實(shí)現(xiàn)系統(tǒng)調(diào)用的基本過(guò)程(在MOOC課程中已經(jīng)給出了詳細(xì)的講解)是:
- 應(yīng)用程序調(diào)用庫(kù)函數(shù)(API);
- API將系統(tǒng)調(diào)用號(hào)存入EAX,然后通過(guò)中斷調(diào)用使系統(tǒng)進(jìn)入內(nèi)核態(tài);
- 內(nèi)核中的中斷處理函數(shù)根據(jù)系統(tǒng)調(diào)用號(hào),調(diào)用對(duì)應(yīng)的內(nèi)核函數(shù)(系統(tǒng)調(diào)用);
- 系統(tǒng)調(diào)用完成相應(yīng)功能,將返回值存入EAX,返回到中斷處理函數(shù);
- 中斷處理函數(shù)返回到API中;
-
API將EAX返回給應(yīng)用程序。
-
應(yīng)用程序如何調(diào)用系統(tǒng)調(diào)用
在通常情況下,調(diào)用系統(tǒng)調(diào)用和調(diào)用一個(gè)普通的自定義函數(shù)在代碼上并沒(méi)有什么區(qū)別,但調(diào)用后發(fā)生的事情有很大不同。調(diào)用自定義函數(shù)是通過(guò)call指令直接跳轉(zhuǎn)到該函數(shù)的地址,繼續(xù)運(yùn)行。而調(diào)用系統(tǒng)調(diào)用,是調(diào)用系統(tǒng)庫(kù)中為該系統(tǒng)調(diào)用編寫(xiě)的一個(gè)接口函數(shù),叫API(Application Programming Interface)。API并不能完成系統(tǒng)調(diào)用的真正功能,它要做的是去調(diào)用真正的系統(tǒng)調(diào)用,過(guò)程是:
- 把系統(tǒng)調(diào)用的編號(hào)存入EAX
- 把函數(shù)參數(shù)存入其它通用寄存器
- 觸發(fā)0x80號(hào)中斷(int 0x80)
0.11的lib目錄下有一些已經(jīng)實(shí)現(xiàn)的API。Linus編寫(xiě)它們的原因是在內(nèi)核加載完畢后,會(huì)切換到用戶模式下,做一些初始化工作,然后啟動(dòng)shell。而用戶模式下的很多工作需要依賴一些系統(tǒng)調(diào)用才能完成,因此在內(nèi)核中實(shí)現(xiàn)了這些系統(tǒng)調(diào)用的API。我們不妨看看lib/close.c,研究一下close()的API:
#define __LIBRARY__
#include "unistd.h"
_syscall1(int,close,int,fd)
其中_syscall1是一個(gè)宏,在include/unistd.h中定義。將_syscall1(int,close,int,fd)進(jìn)行宏展開(kāi),可以得到:
int close(int fd) { long __res; __asm__ volatile ("int $0x80" : "=a" (__res) : "0" (__NR_close),"b" ((long)(fd))); if (__res >= 0)return (int) __res; errno = -__res; return -1; }這就是API的定義。它先將宏NR_close存入EAX,將參數(shù)fd存入EBX,然后進(jìn)行0x80中斷調(diào)用。調(diào)用返回后,從EAX取出返回值,存入res,再通過(guò)對(duì)res的判斷決定傳給API的調(diào)用者什么樣的返回值。其中NR_close就是系統(tǒng)調(diào)用的編號(hào),在include/unistd.h中定義:
#define NR_close 6
所以添加系統(tǒng)調(diào)用時(shí)需要修改include/unistd.h文件,使其包含NR_whoami和__NR_iam。而在應(yīng)用程序中,要有:
#define __LIBRARY__ / 有它,_syscall1等才有效。詳見(jiàn)unistd.h /
#include "unistd.h" / 有它,編譯器才能獲知自定義的系統(tǒng)調(diào)用的編號(hào) /
_syscall1(int, iam, const char, name); / iam()在用戶空間的接口函數(shù) /
_syscall2(int, whoami,char,name,unsigned int,size); / whoami()在用戶空間的接口函數(shù) /
在0.11環(huán)境下編譯C程序,包含的頭文件都在/usr/include目錄下。該目錄下的unistd.h是標(biāo)準(zhǔn)頭文件(它和0.11源碼樹(shù)中的unistd.h并不是同一個(gè)文件,雖然內(nèi)容可能相同),沒(méi)有NR_whoami和NR_iam兩個(gè)宏,需要手工加上它們,也可以直接從修改過(guò)的0.11源碼樹(shù)中拷貝新的unistd.h過(guò)來(lái)。
- 從“int 0x80”進(jìn)入內(nèi)核函數(shù)
int 0x80觸發(fā)后,接下來(lái)就是內(nèi)核的中斷處理了。先了解一下0.11處理0x80號(hào)中斷的過(guò)程。
在內(nèi)核初始化時(shí),主函數(shù)(在init/main.c中,Linux實(shí)驗(yàn)環(huán)境下是main(),Windows下因編譯器兼容性問(wèn)題被換名為start())調(diào)用了sched_init()初始化函數(shù):
void main(void) { ……time_init();sched_init();buffer_init(buffer_memory_end);…… }sched_init()在kernel/sched.c中定義為:
void sched_init(void) {……set_system_gate(0x80,&system_call); }set_system_gate是個(gè)宏,在include/asm/system.h中定義為:
\ _set_gate的定義是: \ __asm__ ("movw %%dx,%%ax\n\t" \"movw %0,%%dx\n\t" \"movl %%eax,%1\n\t" \"movl %%edx,%2" \: \: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \"o" (*((char *) (gate_addr))), \"o" (*(4+(char *) (gate_addr))), \"d" ((char *) (addr)),"a" (0x00080000))雖然看起來(lái)挺麻煩,但實(shí)際上很簡(jiǎn)單,就是填寫(xiě)IDT(中斷描述符表),將system_call函數(shù)地址寫(xiě)到0x80對(duì)應(yīng)的中斷描述符中,也就是在中斷0x80發(fā)生后,自動(dòng)調(diào)用函數(shù)system_call。具體細(xì)節(jié)請(qǐng)參考《注釋》的第4章。
接下來(lái)看system_call。該函數(shù)純匯編打造,定義在kernel/system_call.s中:
…… nr_system_calls = 72 #這是系統(tǒng)調(diào)用總數(shù)。如果增刪了系統(tǒng)調(diào)用,必須做相應(yīng)修改 …… .globl system_call .align 2 system_call:cmpl \$nr_system_calls-1,%eax #檢查系統(tǒng)調(diào)用編號(hào)是否在合法范圍內(nèi)ja bad_sys_callpush %dspush %espush %fspushl %edxpushl %ecx pushl %ebx # push %ebx,%ecx,%edx,是傳遞給系統(tǒng)調(diào)用的參數(shù)movl $0x10,%edx # 讓ds,es指向GDT,內(nèi)核地址空間mov %dx,%dsmov %dx,%esmovl $0x17,%edx # 讓fs指向LDT,用戶地址空間mov %dx,%fscall sys_call_table(,%eax,4)pushl %eaxmovl current,%eaxcmpl $0,state(%eax)jne reschedulecmpl $0,counter(%eax)je reschedulesystem_call用.globl修飾為其他函數(shù)可見(jiàn)。Windows實(shí)驗(yàn)環(huán)境下會(huì)看到它有一個(gè)下劃線前綴,這是不同版本編譯器的特質(zhì)決定的,沒(méi)有實(shí)質(zhì)區(qū)別。call sys_call_table(,%eax,4)之前是一些壓棧保護(hù),修改段選擇子為內(nèi)核段,call sys_call_table(,%eax,4)之后是看看是否需要重新調(diào)度,這些都與本實(shí)驗(yàn)沒(méi)有直接關(guān)系,此處只關(guān)心call sys_call_table(,%eax,4)這一句。根據(jù)匯編尋址方法它實(shí)際上是:
call sys_call_table + 4 * %eax # 其中eax中放的是系統(tǒng)調(diào)用號(hào),即__NR_xxxxxx
顯然,sys_call_table一定是一個(gè)函數(shù)指針數(shù)組的起始地址,它定義在include/linux/sys.h中:
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,……
增加實(shí)驗(yàn)要求的系統(tǒng)調(diào)用,需要在這個(gè)函數(shù)表中增加兩個(gè)函數(shù)引用——sys_iam和sys_whoami。當(dāng)然該函數(shù)在sys_call_table數(shù)組中的位置必須和__NR_xxxxxx的值對(duì)應(yīng)上。同時(shí)還要仿照此文件中前面各個(gè)系統(tǒng)調(diào)用的寫(xiě)法,加上:
extern int sys_whoami();
extern int sys_iam();
不然,編譯會(huì)出錯(cuò)的。
- 實(shí)現(xiàn)sys_iam()和sys_whoami()
添加系統(tǒng)調(diào)用的最后一步,是在內(nèi)核中實(shí)現(xiàn)函數(shù)sys_iam()和sys_whoami()。
每個(gè)系統(tǒng)調(diào)用都有一個(gè)sys_xxxxxx()與之對(duì)應(yīng),它們都是我們學(xué)習(xí)和模仿的好對(duì)象。比如在fs/open.c中的sys_close(int fd):
int sys_close(unsigned int fd) {……return (0); }它沒(méi)有什么特別的,都是實(shí)實(shí)在在地做close()該做的事情。所以只要自己創(chuàng)建一個(gè)文件:kernel/who.c,然后實(shí)現(xiàn)兩個(gè)函數(shù)就萬(wàn)事大吉了。
- 修改Makefile
要想讓我們添加的kernel/who.c可以和其它Linux代碼編譯鏈接到一起,必須要修改Makefile文件。Makefile里記錄的是所有源程序文件的編譯、鏈接規(guī)則,《注釋》3.6節(jié)有簡(jiǎn)略介紹。我們之所以簡(jiǎn)單地運(yùn)行make就可以編譯整個(gè)代碼樹(shù),是因?yàn)閙ake完全按照Makefile里的指示工作。
Makefile在代碼樹(shù)中有很多,分別負(fù)責(zé)不同模塊的編譯工作。我們要修改的是kernel/Makefile。需要修改兩處。一處是:
OBJS = sched.o system_call.o traps.o asm.o fork.o \panic.o printk.o vsprintf.o sys.o exit.o \signal.o mktime.o 改為: OBJS = sched.o system_call.o traps.o asm.o fork.o \panic.o printk.o vsprintf.o sys.o exit.o \signal.o mktime.o who.o另一處:
\### Dependencies: exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \../include/asm/segment.h改為:
\### Dependencies: who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \../include/asm/segment.hMakefile修改后,和往常一樣“make all”就能自動(dòng)把who.c加入到內(nèi)核中了。如果編譯時(shí)提示who.c有錯(cuò)誤,就說(shuō)明修改生效了。所以,有意或無(wú)意地制造一兩個(gè)錯(cuò)誤也不完全是壞事,至少能證明Makefile是對(duì)的。
- 用printk()調(diào)試內(nèi)核
oslab實(shí)驗(yàn)環(huán)境提供了基于C語(yǔ)言和匯編語(yǔ)言的兩種調(diào)試手段。除此之外,適當(dāng)?shù)叵蚱聊惠敵鲆恍┏绦蜻\(yùn)行狀態(tài)的信息,也是一種很高效、便捷的調(diào)試方法,有時(shí)甚至是唯一的方法,被稱為“printf法”。
要知道到,printf()是一個(gè)只能在用戶模式下執(zhí)行的函數(shù),而系統(tǒng)調(diào)用是在內(nèi)核模式中運(yùn)行,所以printf()不可用,要用printk()。它和printf的接口和功能基本相同,只是代碼上有一點(diǎn)點(diǎn)不同。printk()需要特別處理一下fs寄存器,它是專用于用戶模式的段寄存器。看一看printk的代碼(在kernel/printk.c中)就知道了:
int printk(const char *fmt, ...) {……__asm__("push %%fs\n\t""push %%ds\n\t""pop %%fs\n\t""pushl %0\n\t""pushl $buf\n\t""pushl $0\n\t""call tty_write\n\t""addl $8,%%esp\n\t""popl %0\n\t""pop %%fs"::"r" (i):"ax","cx","dx");…… }顯然,printk()首先push %fs保存這個(gè)指向用戶段的寄存器,在最后pop %fs將其恢復(fù),printk的核心仍然是調(diào)用tty_write()。查看printf()可以看到,它最終也要落實(shí)到這個(gè)函數(shù)上。
- 編寫(xiě)測(cè)試程序
激動(dòng)地運(yùn)行一下由你親手修改過(guò)的“Linux 0.11 pro++”!然后編寫(xiě)一個(gè)簡(jiǎn)單的應(yīng)用程序進(jìn)行測(cè)試。比如在sys_iam()中向終端printk()一些信息,讓?xiě)?yīng)用程序調(diào)用iam(),從結(jié)果可以看出系統(tǒng)調(diào)用是否被真的調(diào)用到了。
可以直接在Linux 0.11環(huán)境下用vi編寫(xiě)(別忘了經(jīng)常執(zhí)行“sync”以確保內(nèi)存緩沖區(qū)的數(shù)據(jù)寫(xiě)入磁盤(pán)),也可以在Ubuntu或Windows下編完后再傳到Linux 0.11下。無(wú)論如何,最終都必須在Linux 0.11下編譯。編譯命令是:
# gcc -o iam iam.c -Wall
gcc的“-Wall”參數(shù)是給出所有的編譯警告信息,“-o”參數(shù)指定生成的執(zhí)行文件名是iam,用下面命令運(yùn)行它:
# ./iam
如果如愿輸出了你的信息,就說(shuō)明你添加的系統(tǒng)調(diào)用生效了。否則,就還要繼續(xù)調(diào)試,祝你好運(yùn)!
- 在用戶態(tài)和核心態(tài)之間傳遞數(shù)據(jù)
指針參數(shù)傳遞的是應(yīng)用程序所在地址空間的邏輯地址,在內(nèi)核中如果直接訪問(wèn)這個(gè)地址,訪問(wèn)到的是內(nèi)核空間中的數(shù)據(jù),不會(huì)是用戶空間的。所以這里還需要一點(diǎn)兒特殊工作,才能在內(nèi)核中從用戶空間得到數(shù)據(jù)。
要實(shí)現(xiàn)的兩個(gè)系統(tǒng)調(diào)用參數(shù)中都有字符串指針,非常象open(char *filename, ……),所以我們看一下open()系統(tǒng)調(diào)用是如何處理的。
int open(const char * filename, int flag, ...) { ……__asm__("int $0x80":"=a" (res):"0" (__NR_open),"b" (filename),"c" (flag),"d" (va_arg(arg,int)));…… }可以看出,系統(tǒng)調(diào)用是用eax、ebx、ecx、edx寄存器來(lái)傳遞參數(shù)的。其中eax傳遞了系統(tǒng)調(diào)用號(hào),而ebx、ecx、edx是用來(lái)傳遞函數(shù)的參數(shù)的,其中ebx對(duì)應(yīng)第一個(gè)參數(shù),ecx對(duì)應(yīng)第二個(gè)參數(shù),依此類推。如open所傳遞的文件名指針是由ebx傳遞的,也即進(jìn)入內(nèi)核后,通過(guò)ebx取出文件名字符串。open的ebx指向的數(shù)據(jù)在用戶空間,而當(dāng)前執(zhí)行的是內(nèi)核空間的代碼,如何在用戶態(tài)和核心態(tài)之間傳遞數(shù)據(jù)?接下來(lái)我們繼續(xù)看看open的處理:
system_call: //所有的系統(tǒng)調(diào)用都從system_call開(kāi)始……pushl %edxpushl %ecx pushl %ebx # push %ebx,%ecx,%edx,這是傳遞給系統(tǒng)調(diào)用的參數(shù)movl $0x10,%edx # 讓ds,es指向GDT,指向核心地址空間mov %dx,%dsmov %dx,%esmovl $0x17,%edx # 讓fs指向的是LDT,指向用戶地址空間mov %dx,%fscall sys_call_table(,%eax,4) # 即call sys_open由上面的代碼可以看出,獲取用戶地址空間(用戶數(shù)據(jù)段)中的數(shù)據(jù)依靠的就是段寄存器fs,下面該轉(zhuǎn)到sys_open執(zhí)行了,在fs/open.c文件中:
int sys_open(const char * filename,int flag,int mode) //filename這些參數(shù)從哪里來(lái)? /*是否記得上面的pushl %edx, pushl %ecx, pushl %ebx?實(shí)際上一個(gè)C語(yǔ)言函數(shù)調(diào)用另一個(gè)C語(yǔ)言函數(shù)時(shí),編譯時(shí)就是將要傳遞的參數(shù)壓入棧中(第一個(gè)參數(shù)最后壓,…),然后call …,所以匯編程序調(diào)用C函數(shù)時(shí),需要自己編寫(xiě)這些參數(shù)壓棧的代碼…*/ {……if ((i=open_namei(filename,flag,mode,&inode))<0) {……}…… }它將參數(shù)傳給了open_namei()。再沿著open_namei()繼續(xù)查找,文件名先后又被傳給dir_namei()、get_dir()。在get_dir()中可以看到:
static struct m_inode * get_dir(const char * pathname) {……if ((c=get_fs_byte(pathname))=='/') {……}…… }處理方法就很顯然了:用get_fs_byte()獲得一個(gè)字節(jié)的用戶空間中的數(shù)據(jù)。所以,在實(shí)現(xiàn)iam()時(shí),調(diào)用get_fs_byte()即可。但如何實(shí)現(xiàn)whoami()呢?即如何實(shí)現(xiàn)從核心態(tài)拷貝數(shù)據(jù)到用心態(tài)內(nèi)存空間中呢?猜一猜,是否有put_fs_byte()?有!看一看include/asm/segment.h:
extern inline unsigned char get_fs_byte(const char * addr) {unsigned register char _v;__asm__ ("movb %%fs:%1,%0":"=r" (_v):"m" (*addr));return _v; } extern inline void put_fs_byte(char val,char *addr) {__asm__ ("movb %0,%%fs:%1"::"r" (val),"m" (*addr)); }他倆以及所有put_fs_xxx()和get_fs_xxx()都是用戶空間和內(nèi)核空間之間的橋梁,在后面的實(shí)驗(yàn)中還要經(jīng)常用到。
- 運(yùn)行腳本程序
Linux的一大特色是可以編寫(xiě)功能強(qiáng)大的shell腳本,提高工作效率。本實(shí)驗(yàn)的部分評(píng)分工作由腳本 testlab2.sh 完成。它的功能是測(cè)試iam.c和whoami.c。
首先將iam.c和whoami.c分別編譯成iam和whoami,然后將 testlab2.sh 拷貝到同一目錄下。用下面命令為此腳本增加執(zhí)行權(quán)限:
chmod +x testlab2.sh
然后運(yùn)行之:
./testlab2.sh
根據(jù)輸出,可知iam.c和whoami.c的得分。
- errno
errno是一種傳統(tǒng)的錯(cuò)誤代碼返回機(jī)制。當(dāng)一個(gè)函數(shù)調(diào)用出錯(cuò)時(shí),通常會(huì)返回-1給調(diào)用者。但-1只能說(shuō)明出錯(cuò),不能說(shuō)明錯(cuò)是什么。為解決此問(wèn)題,全局變量errno登場(chǎng)了。錯(cuò)誤值被存放到errno中,于是調(diào)用者就可以通過(guò)判斷errno來(lái)決定如何應(yīng)對(duì)錯(cuò)誤了。各種系統(tǒng)對(duì)errno的值的含義都有標(biāo)準(zhǔn)定義。Linux下用“man errno”可以看到這些定義。
====================實(shí)驗(yàn)報(bào)告=======================
大致的實(shí)驗(yàn)思路在李老師的實(shí)驗(yàn)提示中已經(jīng)十分詳細(xì)了,基本上一步步的做就可以了,個(gè)別要點(diǎn)要注意一下,具體實(shí)驗(yàn)步驟如下:
1,模仿close的API,在~/oslab/linux-0.11/lib目錄下建立iam,whoami的API,代碼如下:
/* * linux/lib/iam.c * * (C) 2017 Fibonacci */ #define __LIBRARY__ #include <unistd.h> _syscall1(int, iam, const char*, name);/* * linux/lib/whoami.c* * (C) 2017 Fibonacci */#define __LIBRARY__ #include <unistd.h>_syscall2(int, whoami,char*,name,unsigned int,size);
說(shuō)明:要注意的是實(shí)驗(yàn)提示中的_syscall1,_syscall2的參數(shù)寫(xiě)的有點(diǎn)問(wèn)題,char *寫(xiě)成了char;
2,修改~/oslab/linux-0.11/include下的unistd.h文件,修改的地方有兩處,具體位置如下:
#define __NR_ssetmask 69 #define __NR_setreuid 70 #define __NR_setregid 71/* added by Fibonacci as flowing two lines*/ #define __NR_iam 72 #define __NR_whoami 73 #define _syscall0(type,name) \ type name(void) \pid_t getpgrp(void); pid_t setsid(void);/* added by Fibonacci as flowing two lines*/ int iam(const char * name); int whoami(char* name, unsigned int size);#endif
3,修改~/oslab/linux-0.11/kernel/system_call.s,下面第61行: 59 sa_restorer = 12 60 61 nr_system_calls = 7462 63 /* 64 * Ok, I get parallel printer interrupts while using the floppy for some65 * stra
4,修改文件include/linux/sys.h,修改有兩個(gè)地方: extern int sys_setregid();/* added by Fibonacci as flowing two lines*/ extern int sys_iam(); extern int sys_whoami();fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read, sys_write, sys_ope
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid, sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask, sys_setreuid,sys_setregid, sys_iam, sys_whoami};
5,在~/oslab/linux-0.11/kernel實(shí)現(xiàn),即是在內(nèi)核中實(shí)現(xiàn)函數(shù)sys_iam()和sys_whoami(),所以只要自己創(chuàng)建一個(gè)文件:kernel/who.c即可。 /*************************************************************************> File Name: who.c> Author: Fibonacci> Created Time: Sun 10 Dec 2017 08:47:01 PM CST************************************************************************/#define __LIBRARY__ #include <unistd.h> #include <errno.h> #include <asm/segment.h> #include <linux/kernel.h>char string_buf[64] = {0};int sys_iam(const char * name) {/* just for debug */printk("debug: iam is called successfully\n");int i = 0;char char_buf;while ((char_buf=get_fs_byte(name+i)) != '\0' && i < 63){string_buf[i] = char_buf;i++;}string_buf[i] = '\0';if (i > 23){errno = EINVAL;return -1;}else{return i;} }int sys_whoami(char* name, unsigned int size) {int i = 0;char char_buf;while (i < 23){char_buf = string_buf[i];put_fs_byte(char_buf, name+i);i++;}put_fs_byte('\0', name+i);if (string_buf[i] != '\0'){errno = EINVAL;return -1;}else{return i;} }
6,Makefile文件 OBJS = sched.o system_call.o traps.o asm.o fork.o \panic.o printk.o vsprintf.o sys.o exit.o \signal.o mktime.o who.o
### Dependencies: who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h
7,編寫(xiě)測(cè)試程序,我把它放在了~/oslab/hdc/usr/root,具體代碼如下: /************************************************************************* > File Name: iam.c> Author: Fibonacci> Created Time: Sun 10 Dec 2017 03:23:06 PM CST************************************************************************/ #define __LIBRARY__ #include <unistd.h> #include <errno.h> #include <asm/segment.h> #include <linux/kernel.h> _syscall1(int, iam, const char*, name);int main(int argc, char *argv[]) {iam(argv[1]);return 0; }
/************************************************************************* > File Name: whoami.c> Author: Fibonacci> Created Time: Sun 10 Dec 2017 03:23:06 PM CST************************************************************************/ #define __LIBRARY__ #include <unistd.h> #include <errno.h> #include <asm/segment.h> #include <linux/kernel.h> #include <stdio.h>_syscall2(int, whoami,char *,name,unsigned int,size);int main(int argc, char *argv[]) {char username[64] = {0};whoami(username, 24);printf("%s\n", username);return 0; }
testlab2.sh#!/bin/bashgcc iam.c -o iam gcc whoami.c -o whoami ./iam Lizhijun ./whoami
8,程序運(yùn)行結(jié)果:
9,實(shí)驗(yàn)中要點(diǎn)思考
10,實(shí)驗(yàn)體會(huì)
實(shí)驗(yàn)做出來(lái)之前,感覺(jué)好難,雖然李老師已經(jīng)提示的很詳細(xì)明白了,感覺(jué)還是無(wú)從下手,經(jīng)過(guò)參考別人的思路,自己不斷的摸索,終于做出來(lái)了,再回頭看提示,感覺(jué)每一步都已經(jīng)很明顯了,只是自己做的不對(duì)或者理解的不對(duì)。學(xué)習(xí)這個(gè)課程沒(méi)有它法,只有仔細(xì)聽(tīng)課,閱讀實(shí)驗(yàn)提示,結(jié)合網(wǎng)上的資料,尋找蛛絲馬跡,不斷嘗試。
總結(jié)
- 上一篇: ArcPy常用基础功能
- 下一篇: java信息管理系统总结_java实现科