[轉] 添加新的系統調用 _syscall0(int, mysyscall)



實驗目的
閱讀 Linux 內核源代碼,通過添加一個簡單的系統調用實驗,進一步理解
Linux操作系統處理系統調用的統一流程。通過用kernel module的方法來實現一
個系統調用實驗,進一步理解Linux的內核模塊和Linux系統調用機制,對通過
module方法添加一個系統調用的步驟有所了解。
實驗內容
1、在現有的系統中添加一個不用傳遞參數的系統調用。調用這個系統調
用,使用戶的uid變成0。
2、用kernel module的方法來實現一個系統調用
實驗提示 1
怎么樣?覺得困難還是覺得太簡單?我們首先承認,每個人接觸Linux的時間長短不一
樣,因此基礎也會有一點差別。那么對於覺得太簡單的人呢,請你迅速地合上書本,然后跑
到電腦前面,開始實現這個題目。來吧,不要眼高手低,做完之后,你就可以跳過這一節,
直接進入下一節的學習了。對於覺得有點困難的人呢,不用着急,這一節就是專門為你准備
的。我們會列出詳細的實現步驟,你一定也沒有問題的。
如果你前面對整個系統調用的過程有一個清晰的理解的話,我們就順着系統調用的流程
思路,給出一個添加新的系統調用的步驟:
一、決定你的系統調用的名字
這個名字就是你編寫用戶程序想使用的名字,比如我們取一個簡單的名字:mysyscall。
一旦這個名字確定下來了,那么在系統調用中的幾個相關名字也就確定下來了。
l 系統調用的編號名字:__NR_mysyscall;
l 內核中系統調用的實現程序的名字:sys_mysyscall;
現在在你的用戶程序中出現了:
#include <linux/unistd.h>
int main()
{
mysyscall();
}
流程轉到標准C庫。
二、利用標准C 庫進行包裝嗎
編譯器怎么知道這個mysyscall 是怎么來的呢?在前面我們分析的時候,我們知道那時
標准C 庫給系統調用作了一層包裝,給所有的系統調用做出了定義。但是顯然,我們可能
不願意去改變標准C庫,也沒有必要去改變。那么我們在自己的程序中來做:
#include <linux/unistd.h>
_syscall0(int,mysyscall) /* 注意這里沒有分號*/
int main()
{
mysyscall();
}
好,由於有了_syscall0 這個宏,mysyscall 將得到定義。但是現在系統會去找系統調用號,
以放入eax。所以,接下來我們定義系統調用號。
三、添加系統調用號
系統調用號在文件unistd.h里面定義。這個文件可能在你的系統上會有兩個版本:一個
是C庫文件版本,出現的地方是在/usr/include/unistd.h和/usr/include/asm/unistd.h;另外還有
一個版本是內核自己的unistd.h,出現的地方是在你解壓出來的2.4.18 內核代碼的對應位置
(比如/usr/src/linux/include/linux/unistd.h和/usr/include/asm-i386/unistd.h)。當然,也有可能
這個C 庫文件只是一個到對應內核文件的連接。至於為什么會存在兩個版本的頭文件,這
個問題將會在5-6較高級主題一節進行說明。現在,你要做的就是在文件unistd.h中添加我
們的系統調用號:__NR_mysyscall,如下所示:
include/asm-i386/unistd.h
/usr/include/asm/unistd.h
240 #define __NR_llistxattr 233
241 #define __NR_flistxattr 234
242 #define __NR_removexattr 235
243 #define __NR_lremovexattr 236
244 #define __NR_fremovexattr 237
245 #define __NR_mysyscall 238 /* mysyscall adds here */
添加系統調用號之后,系統才能根據這個號,作為索引,去找syscall_table中的相應表項。
所以說,我們接下來的一步就是:
四、在系統調用表中添加相應表項
我們前面講過,系統調用處理程序(system_call)會根據eax 中的索引到系統調用表
(sys_call_table)中去尋找相應的表項。所以,我們必須在那里添加我們自己的一個值。
arch/i386/kernel/entry.S
398 ENTRY(sys_call_table)
399 .long SYMBOL_NAME(sys_ni_syscall)
400 .long SYMBOL_NAME(sys_exit)
401 .long SYMBOL_NAME(sys_fork)
402 .long SYMBOL_NAME(sys_read)
403 .long SYMBOL_NAME(sys_write)
……
……
634 .long SYMBOL_NAME(sys_ni_syscall)
635 .long SYMBOL_NAME(sys_ni_syscall)
636 .long SYMBOL_NAME(sys_ni_syscall)
637 .long SYMBOL_NAME(sys_mysyscall)
638
639 .rept NR_syscalls-(.-sys_call_table)/4
640 .long SYMBOL_NAME(sys_ni_syscall)
641 .endr
到現在為止,系統已經能夠正確地找到並且調用sys_mysyscall。剩下的就只有一件事情,那
就是sys_mysyscall的實現。
五、sys_mysyscall 的實現
我們把這一小段程序添加在kernel/sys.c 里面。在這里,我們沒有在kernel 目錄下另外
添加自己的一個文件,這樣做的目的是為了簡單,而且不用修改Makefile,省去不必要的麻
煩。
asmlinkage int sys_mysyscall(void)
{
current->uid = current->euid = current->suid = current->fsuid = 0;
return 0;
}
這個系統調用中,把標志進程身份的幾個變量uid、euid、suid和fsuid都設為0。
到這里為止,我們所要做的添加一個新的系統調用的所有工作就完成了,是不是非常簡
單?的確如此。因為Linux 內核結構的層次性還是非常清楚的,這就使得每一個開發者可以
把精力放在怎么樣實現具體的功能上,而不用在一些接口函數上傷腦筋。
現在所有的代碼添加已經結束,那么要使得這個系統調用真正在內核中運行起來,我們
就需要對內核進行重新編譯。這個問題我們在第一章的時候就討論到了,應該沒有問題,因
此我們在這里略過。
六、編寫用戶態程序
要測試我們新添加的系統調用,我們可以編寫一個用戶程序調用我們自己的系統調用。
我們對自己的系統調用的功能已經很清楚了:使得自己的uid變成0。那么我們看看是不是
如此:
用戶態程序
#include <linux/unistd.h>
_syscall0(int,mysyscall) /* 注意這里沒有分號*/
int main()
{
mysyscall(); /* 這個系統調用的作用是使得自己的uid為0 */
printf(“em…, this is my uid: %d. \n”, getuid());
}
實驗提示2:
在這里,我們把系統調用的知識和Kernel Module的知識結合起來,用kernel module的
方法來實現一個系統調用。這個系統調用是gettimeofday的簡化版本。實驗的目的只是為了
使你對通過module方法添加一個系統調用的步驟有所了解。
具體代碼示例如下:
/* pedagogictime.c */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
/* 在這個頭文件里面包含了所有的系統調用號__NR_... */
#include <linux/unistd.h>
/* for struct time* */
#include <linux/time.h>
/* for copy_to_user() */
#include <asm/uaccess.h>
/* for current macro */
#include <linux/sched.h>
#define __NR_pedagogictime 238
MODULE_DESCRIPTION("My sys_pedagogictime()");
MODULE_AUTHOR("Your Name :), (C) 2002, GPLv2 or later");
/* 用來保存舊系統調用的地址*/
static int (*anything_saved)(void);
/* 這個是我們自己的系統調用函數sys_pedagogictime(). */
static int sys_pedagogictime(struct timeval *tv)
{
struct timeval ktv;
/* 這里我們需要增加模塊使用計數。*/
MOD_INC_USE_COUNT;
do_gettimeofday(&ktv);
if (copy_to_user(tv, &ktv, sizeof(ktv))){
MOD_DEC_USE_COUNT;
return -EFAULT;
}
printk(KERN_ALERT"Pid %ld called sys_gettimeofday().\n",(long)current->pid);
MOD_DEC_USE_COUNT;
return 0;
}
/* 這里是初始化函數。__init標志表明這個函數使用后就可以丟棄了。*/
int __init init_addsyscall(void)
{
extern long sys_call_table[];
/* 保存原來系統調用表中此位置的系統調用*/
anything_saved = (int(*)(void))(sys_call_table[__NR_pedagogictime]);
/* 把我們自己的系統調用放入系統調用表,注意進行類型轉換*/
sys_call_table[__NR_pedagogictime] = (unsigned long)sys_pedagogictime;
return 0;
}
/* 這里是退出函數。__exit標志表明如果我們不是以模塊方式編譯這段程序,則這個標志后的
* 函數可以丟棄。也就是說,模塊被編譯進內核,只要內核還在運行,就不會被卸載。
*/
void __exit exit_addsyscall(void)
{
extern long sys_call_table[];
/* 恢復原先的系統調用*/
sys_call_table[__NR_pedagogictime] = (unsigned long)anything_saved;
}
/* 這兩個宏告訴系統我們真正的初始化和退出函數*/
module_init(init_addsyscall);
module_exit(exit_addsyscall);
使用kernel module的方法來實現的這個系統調用,我們需要做的只是用這條命令:
gcc -Wall -O2 -DMODULE -D__KERNEL__ -DLINUX -c pedagogictime.c.
編譯成.o文件,然后使用insmod pedagogictime.o把它動態地加載到正在運行的內核中。顯
然,這樣的做法比起我們先前的那種要重新編譯內核的辦法更加靈活,更加方便。這也正是
Linux kernel module program如此受歡迎的原因。
測試用這個使用kernel module編寫的系統調用的用戶程序:
測試用的用戶程序
/* for struct timeval */
#include <linux/time.h>
/* for _syscall1 */
#include <linux/unistd.h>
#define __NR_pedagogictime 238
_syscall1(int, pedagogictime, struct timeval *, thetime)
int main()
{
struct timeval tv;
pedagogictime(&tv);
printf("tv_sec : %ld\n", tv.tv_sec);
printf("tv_nsec: %ld\n", tv.tv_usec);
printf("em..., let me sleep for 2 second.:)\n");
sleep(2);
pedagogictime(&tv);
printf("tv_sec : %ld\n", tv.tv_sec);
printf("tv_nsec: %ld\n", tv.tv_usec);
}
假設這個程序是test.c,那么使用gcc -o test test.c得到test可執行文件,然后你可以執
行這個test看看結果。

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/unbutun/archive/2009/09/08/4532583. aspx


免責聲明!

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



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