## 實驗步驟
(1)了解應用程序如何調用系統調用
在通常情況下,調用系統調用和調用一個普通的自定義函數在代碼上并沒有什么區別,但調用后發生的事情有很大不同。調用自定義函數是通過call指令直接跳轉到該函數的地址,繼續運行。而調用系統調用,是調用系統庫中為該系統調用編寫的一個接口函數,叫API(Application Programming Interface)(它對應一個宏_syscallx,在unistd.h中)。API并不能完成系統調用的真正功能,它要做的是去調用真正的系統調用,過程是:
- 把系統調用的編號存入EAX
- 把函數參數存入其它通用寄存器
- 觸發0x80號中斷(int 0x80)
- 接下來就是內核的中斷處理了,自動調用函數system_call(在kernel/system_call.s中),到sys_call_table找到系統調用號對用的系統調用sys_xxx,執行它。
(2)閱讀文件lib/close.c、fs/open.c、kernel/system_call.s、include/unistd.h、include/linux/sys.h,找出系統調用close與這些文件之間的關系,清晰close系統調用的過程;
(3)參照系統調用close,在上面一系列文件中添加或修改系統調用iam和whoami相關的內容(系統調用號、系統調用表、系統調用總數等);
(4)創建who.c文件,在其中分別編寫包含具體實現細節的sys_iam()和sys_whoami()函數;
(5)修改Makefile,以便在執行make命令時可以編譯who.c文件;
(6)編譯linux內核,運行bochs;
(7)編寫測試程序iam.c和whoiam.c;
(8)將測試程序、unistd.h及測試腳本復制到?hdc目錄,以便bochs啟動后能夠共享這些文件;
(9)運行bochs;
(10)在虛擬機上編譯測試程序iam.c和whoiam.c,并運行測試;
(11)運行測試腳本,測試得分情況。
此次實驗需要修改unistd.h ? ? ? sys.h ? ? ? ? ? system_call.s???? makefile,并編寫who.c?? iam.c??? whoami.c
(1)修改linux-0.11/include/linux/sys.h
根據Linux調用系統調用的過程,需要把 iam()與whoami()兩個函數加到全局變量,和中斷函數表中就可以了,中斷被調用的時候,先查找中斷向量表,找到相應的函數名,調用其函數。
分別添加聲明到最下面和數組中
~~~
extern int sys_setup();
extern int sys_exit();
extern int sys_fork();
extern int sys_read();
extern int sys_write();
extern int sys_open();
extern int sys_close();
extern int sys_waitpid();
extern int sys_creat();
extern int sys_link();
extern int sys_unlink();
extern int sys_execve();
extern int sys_chdir();
extern int sys_time();
extern int sys_mknod();
extern int sys_chmod();
extern int sys_chown();
extern int sys_break();
extern int sys_stat();
extern int sys_lseek();
extern int sys_getpid();
extern int sys_mount();
extern int sys_umount();
extern int sys_setuid();
extern int sys_getuid();
extern int sys_stime();
extern int sys_ptrace();
extern int sys_alarm();
extern int sys_fstat();
extern int sys_pause();
extern int sys_utime();
extern int sys_stty();
extern int sys_gtty();
extern int sys_access();
extern int sys_nice();
extern int sys_ftime();
extern int sys_sync();
extern int sys_kill();
extern int sys_rename();
extern int sys_mkdir();
extern int sys_rmdir();
extern int sys_dup();
extern int sys_pipe();
extern int sys_times();
extern int sys_prof();
extern int sys_brk();
extern int sys_setgid();
extern int sys_getgid();
extern int sys_signal();
extern int sys_geteuid();
extern int sys_getegid();
extern int sys_acct();
extern int sys_phys();
extern int sys_lock();
extern int sys_ioctl();
extern int sys_fcntl();
extern int sys_mpx();
extern int sys_setpgid();
extern int sys_ulimit();
extern int sys_uname();
extern int sys_umask();
extern int sys_chroot();
extern int sys_ustat();
extern int sys_dup2();
extern int sys_getppid();
extern int sys_getpgrp();
extern int sys_setsid();
extern int sys_sigaction();
extern int sys_sgetmask();
extern int sys_ssetmask();
extern int sys_setreuid();
extern int sys_setregid();
extern int sys_iam();
extern int sys_whoami();
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
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 };</span>
~~~
(2)linux-0.11/kernel/system_call.s
需要把nr_system_calls?由72改為 74? 表示了中斷函數的個數。
~~~
# offsets within sigaction
sa_handler = 0
sa_mask = 4
sa_flags = 8
sa_restorer = 12
nr_system_calls = 74</span>
~~~
(3)修改linux-0.11/include/unistd.h
需要把兩個自定義函數的宏定義在這里,修改后為
~~~
#define __NR_setup 0 /* used only by init, to get system going */
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11
#define __NR_chdir 12
#define __NR_time 13
#define __NR_mknod 14
#define __NR_chmod 15
#define __NR_chown 16
#define __NR_break 17
#define __NR_stat 18
#define __NR_lseek 19
#define __NR_getpid 20
#define __NR_mount 21
#define __NR_umount 22
#define __NR_setuid 23
#define __NR_getuid 24
#define __NR_stime 25
#define __NR_ptrace 26
#define __NR_alarm 27
#define __NR_fstat 28
#define __NR_pause 29
#define __NR_utime 30
#define __NR_stty 31
#define __NR_gtty 32
#define __NR_access 33
#define __NR_nice 34
#define __NR_ftime 35
#define __NR_sync 36
#define __NR_kill 37
#define __NR_rename 38
#define __NR_mkdir 39
#define __NR_rmdir 40
#define __NR_dup 41
#define __NR_pipe 42
#define __NR_times 43
#define __NR_prof 44
#define __NR_brk 45
#define __NR_setgid 46
#define __NR_getgid 47
#define __NR_signal 48
#define __NR_geteuid 49
#define __NR_getegid 50
#define __NR_acct 51
#define __NR_phys 52
#define __NR_lock 53
#define __NR_ioctl 54
#define __NR_fcntl 55
#define __NR_mpx 56
#define __NR_setpgid 57
#define __NR_ulimit 58
#define __NR_uname 59
#define __NR_umask 60
#define __NR_chroot 61
#define __NR_ustat 62
#define __NR_dup2 63
#define __NR_getppid 64
#define __NR_getpgrp 65
#define __NR_setsid 66
#define __NR_sigaction 67
#define __NR_sgetmask 68
#define __NR_ssetmask 69
#define __NR_setreuid 70
#define __NR_setregid 71 /*Linux system_call total 72*/
#define __NR_iam 72 /*new system_call 72 and 73*/
#define __NR_whoami 73
~~~
### 修改Makefile
要想**讓我們添加的kernel/who.c可以和其它Linux代碼編譯鏈接到一起,必須要修改Makefile文件**。Makefile里記錄的是所有源程序文件的編譯、鏈接規則,《注釋》3.6節有簡略介紹。我們之所以簡單地運行make就可以編譯整個代碼樹,是因為make完全按照Makefile里的指示工作。
Makefile在代碼樹中有很多,分別負責不同模塊的編譯工作。我們要修改的是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.h
~~~
**此處實驗指導書上很詳細**
下面進入編寫環節
(1)who.c
who.c 放到linux-0.01/kernel 目錄下?? 執行make all??? who.c 被編譯到內核中
~~~
#define __LIBRARY__
#include <unistd.h>
#include <errno.h>
#include <asm/segment.h>
#include <string.h>
char username[64]={0};
int sys_iam(const char* name)
{
int i = 0;
while(get_fs_byte(&name[i]) != '\0')
i++;
if(i > 23)
return -EINVAL;
i=0;
while(1)
{
username[i] = get_fs_byte(&name[i]);
if(username[i] == '\0')
break;
i++;
}
return i;
}
int sys_whoami(char* name,unsigned int size)
{
int i,len;
len = strlen(username);
if (size < len)
return -1;
i=0;
while(i < len)
{
put_fs_byte(username[i],&name[i]);
i++;
}
return i;
}
~~~
說明:此處get_fs_byte()與put_fs_byte()均是按地址位讀取,即指針操作,且-EINVAL的含義需要掌握,此處不再贅述
(2)iam.c
~~~
#define __LIBRARY__
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
_syscall1(int,iam,const char*,name)
int main(int argc,char* argv[])
{
iam(argv[1]);
return 0;
}</span>
~~~
此處即是讀取你傳進去的參數,不明白的復習一下int main()函數兩個參數的含義
(3)whoami.c
~~~
#define __LIBRARY__
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
_syscall2(int, whoami,char*,name,unsigned int,size);
int main(int argc ,char *argv[])
{
char *username;
int counter;
username = (char *)malloc(sizeof(char)*128);
counter = whoami(username,128);
printf("%s\n",username);
free(username);
return 0;
}
~~~
此處是打印回顯你的輸入(在iam.c中傳入的) 最后一個數據是一個超長數據(坑的一比),注意字符串過長的時候,不要覆蓋之前保存的字符串,保存的字符串是之前存的字符串,不然python腳本的分數不會滿。
附report
1.從Linux 0.11現在的機制看,它的系統調用最多能傳遞幾個參數?你能想出辦法來擴大這個限制嗎?
答:當應用程序經過庫函數向內核發出一個中斷調用int 0x80時,就開始執行一個系統調用。其中寄存器eax中存放著系統調用號,而攜帶的參數可依次存放在寄存器ebx,ecx和edx中。因此Linux 0.11內核中用戶程序能夠向內核最多直接傳遞三個參數,當然也可以不帶參數。為了方便執行,內核源代碼在include/unistd文件中定義了宏函數_syscalln(),其中n代表攜帶的參數個數,可以分別0至3。因此最多可以直接傳遞3個參數。
若需要傳遞多個參數,大塊數據給內核,則可以傳遞這塊數據的指針值。例如系統調用int read(int fd,char *buf,int n)在其宏形式_syscall3(int, read, int, fd, char*, buf, int, n),對于include/unistd中給出的每個系統調用宏,都有2+2*n個參數。其中第一個參數對應系統調用返回值的類型;第2個參數是系統調用的名稱;隨后是系統調用所攜帶參數的類型和名稱。
2.用文字簡要描述向Linux 0.11添加一個系統調用foo()的步驟。
答:1)修改/include/unistd.h,在里面加上新系統調用的宏定義:#define __NR_foo 72為原來從0開始數有72個系統調用,變成73。
2)修改/include/linux/sys.h,在里面加上extern int sys_foo(),并在該文件的fn_ptr sys_call_table[]數組中加上一項sys_foo
3)修改kernel里面的system_call.s文件改變里面的系統調用個數,也即在原來的個數上加1;即nr_system_calls = 73
4)在kernel文件中編寫實現新加的系統調用函數的文件foo.c
5)修改makefile文件,修改OBJS和Dependencies添加新的系統調用,使其在編譯linux時能將新加的調用同時編譯。