操作系統實驗報告-系統調用


實驗內容

在Linux 0.11上添加兩個系統調用,並編寫兩個簡單的應用程序測試它們。

iam()

第一個系統調用是iam(),其原型為:

int iam(const char * name);

完成的功能是將字符串參數name的內容拷貝到內核中保存下來。要求name的長度不能超過23個字符。返回值是拷貝的字符數。如果name的字符個數超過了23,則返回“-1”,並置errno為EINVAL。

在kernal/who.c中實現此系統調用。

whoami()

第二個系統調用是whoami(),其原型為:

int whoami(char* name, unsigned int size);

它將內核中由iam()保存的名字拷貝到name指向的用戶地址空間中,同時確保不會對name越界訪存(name的大小由size說明)。返回值是拷貝的字符數。如果size小於需要的空間,則返回“-1”,並置errno為EINVAL。

也是在kernal/who.c中實現。

實驗步驟

代碼的修改和補充

include/linux/sys.h:

添加聲明:

extern int sys_iam();
extern int sys_whoam();

在sys_call_table數組的最后增加兩個元素:

..., sys_iam, sys_whoami}

include/unistd.h:

定義調用號宏:

#define __NR_iam    72
#define __NR_whoami    73

聲明供用戶調用的函數:

int iam(const char *name);
int whoami(char *name, unsigned int size);

kernel/system_call.s:

修改調用個數(從72改為74,調用號從0開始計數):

nr_system_calls = 74

+kernel/who.c:

添加此文件,實現系統調用:

#define __LIBRARY__
#include <asm/segment.h> #include <errno.h> #include <unistd.h> #define MAX_SIZE 23 char username[MAX_SIZE+1]; int sys_iam(const char *name) { int i; for(i=0; get_fs_byte(name+i)!='\0'; i++);      // 注解1:get_fs_byte() printk("sys_iam:\n\t name size is:%d \n", i); if(i>MAX_SIZE) return -EINVAL; for(i=0; (username[i]=get_fs_byte(name+i))!='\0'; i++); return i; } int sys_whoami(char *name, unsigned int size) { int i; for(i=0; put_fs_byte(username[i], name+i), username[i]!='\0'; i++); printk("sys_whoami:\n\t username size is:%d \n\t given size is:%d\n", i, size); if(i>size) return -EINVAL; return i; }

 

注解1:

// 由於sys_iam()的參數name指針來自用戶空間,sys_iam()運行處於內核態,默認使用的是ds、es段寄存器,  // 注解1.1:發生系統調用時段寄存器的設置
// 要取得用戶空間的數據需要臨時改變為fs段寄存器(用於用戶空間) // segment.h中的get_fs_byte() static inline unsigned char get_fs_byte(const char * addr) { unsigned register char _v; __asm__ ("movb %%fs:%1,%0":"=r" (_v):"m" (*addr));  # => movb *fs:addr, _v 將用戶空間addr處的數據存入_v寄存器 return _v; }
注解1.1:
system_call:
    cmpl $nr_system_calls-1,%eax  # 系統調用時會將調用號存入eax,將其與最大的系統調用號進行比較 ja bad_sys_call          # 如果超過,則跳轉到bad_sys_call(將-1存入eax並返回) push %ds      # 壓棧 push %es push %fs pushl %edx      # 系統調用通過這3個寄存器傳參,壓棧作為參數 pushl %ecx  pushl %ebx movl $0x10,%edx # 這3行將ds、es設置為內核口空間段地址0x10 mov %dx,%ds mov %dx,%es movl $0x17,%edx # 這2行將fs設置為用戶空間段地址0x17 mov %dx,%fs call sys_call_table(,%eax,4)  # 調用sys_call_table+eax*4處的函數
                      # sys_call_table是include/linux/sys.h中的函數入口數組,
                      # 它的類型是fn_ptr,定義在include/linux/sched.h中,
                      # typedef int (*fn_ptr)(); 實際是將一個int類型定義為函數指針
                      # 所以這里根據系統調用號計算調用入口地址時需要*4(int為4個byte) pushl %eax movl current,%eax cmpl $0,state(%eax)        # state和下面的counter分別被初始化為0和4,作為current所屬結構體相應數據偏移 jne reschedule            # 根據比較結果判斷是否重新調度 cmpl $0,counter(%eax) je reschedule

 

kernel/Makefile:

修改Makefile,補充與who.c相關規則:

OBJS  = ... who.o
......
### Dependencies:
who.s who.o: who.c ../include/unistd.h \ ../include/asm/segment.h ../include/errno.h ......

測試

添加測試用例

運行oslab目錄下的mount-hdc,掛載虛擬硬盤:

cd ~/workspace/oslab
sudo ./mount-hdc
cd hdc/usr/root

添加測試文件:

sudo vim iam.c

iam.c具體內容:

#include <stdio.h>
#define __LIBRARY__  # 在unistd.h中,調用號與_syscall*宏函數都是在定義了__LIBRARY__的前提下才定義的
#include <unistd.h> _syscall1(int,iam,const char *,name)  # 注解2:_syscall*() int main(int argc, char *argv[]) { if(argc<=1) { printf("error: input your name, please! \n "); return -1; } if(iam(argv[1])==-1) { printf("error: length limit is 23. \n"); return -1; } return 0; }
sudo vim whoami.c
#include <stdio.h>
#define __LIBRARY__
#include <unistd.h> _syscall2(int,whoami,char *,name,unsigned int,size) int main(int argc, char *argv[]) { char name[24]; if(whoami(name, 23)==-1) { printf("error while reading name..."); return -1; } printf("%s\n", name); return 0; }

實驗也給我們提供了測試評分文件:testlab2.c testlab2.sh ,也將它們下載放到當前目錄下。

替換部分庫文件:

由於掛載的虛擬硬盤為oslab目錄下的hdc-0.11.img,里面的文件不會隨着內核的編譯而改變,我們手動替換改動的庫文件:

sudo cp ~/workspace/oslab/linux-0.11/include/linux/sys.h ~/workspace/oslab/hdc/usr/include/linux/sys.h 
sudo cp ~/workspace/oslab/linux-0.11/include/unistd.h ~/workspace/oslab/hdc/usr/include/unistd.h

OK,我們現在可以卸載hdc了:

cd ~/workspace/oslab
sudo umount hdc

編譯與運行

cd linux-0.11
make
../run

開啟時默認的目錄即為/usr/root。

編譯C文件:

gcc -o iam iam.c
gcc -o whoami whoami.c gcc -o testlab2 testlab2.c
sync

在bochs上的linux-0.11中產生或修改的文件需要手動寫入硬盤,否則關閉虛擬機后不會保存。

運行自己的測試用例:

./iam "lg's student"
./whoami

運行實驗給的評分用例(滿分分別為50%和30%,還有20%是寫實驗報告。。。):

./testlab2

./testlab2.sh

系統調用的分析

初始化

調用流程

以iam()函數為例:

思考

從Linux 0.11現在的機制看,它的系統調用最多能傳遞幾個參數?你能想出辦法來擴大這個限制嗎?

Linux-0.11的系統調用通過寄存器ebx、ecx、edx傳遞參數,最多能傳遞3個參數。

擴大傳遞參數的數量的方法:

  1. 增加傳參所用的寄存器,但考慮到寄存器的成本以及它們還需要用於其他地方,這個方法不太可取;
  2. 通過定義結構體,在結構體中存入很多參數,然而只把結構體入口地址作為參數進行傳遞;
  3. 用這3個寄存器循環傳值;
  4. 將寄存器拆分為高位和低位傳遞一直比較小的參數;
  5. 利用堆棧傳遞參數。

用文字簡要描述向Linux 0.11添加一個系統調用foo()的步驟。

我們假設foo()的返回值類型為void。

  1. 在include/unistd.h中定義宏__NR_foo,並添加供用戶調用的函數聲明void foo();
  2. 修改kernel/system_call.s中的nr_system_calls,使其增加1;
  3. 在include/linux/sys.h中添加函數聲明extern void sys_foo(),在系統調用入口表fn_ptr末端添加元素sys_foo;
  4. 添加kernel/foo.c文件,實現sys_foo()函數;
  5. 修改kernel/Makefile,在OBJS中加入foo.o,並添加生成foo.s、foo.o的依賴規則。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM