Linux内核添加系统调用

1、目的:

在现有的系统中添加一个不用传递参数的系统调用。这个系统调用的功能是实现遍历系统中的所有进程。实验主要内容:

  • 添加系统调用的名字
  • 利用标准 C 库进行包装
  • 添加系统调用号
  • 在系统调用表中添加相应表项
  • sys_mysyscall 的实现
  • 编写用户态测试程序

2、步骤:

a)安装依赖库:

[plain]  view plain  copy
  1. sudo apt-get install libncurses5-dev //如果没有ncurses库,则安装  

b)下载内核源代码(以3.6.8版本为例):

linux-3.6.8.tar.bz2文件,放到/home目录即可。

c)解压内核源代码:

[plain]  view plain  copy
  1. #su //输入密码,用户权限改为root权限。或用sudo命令  
  2. #mv linux-3.6.8.tar.bz2 ~ //把内核代码文件移到主目录。  
  3. # cd ~  //进入主目录  
  4. # tar jxvf linux-3.6.8.tar.bz2 //解压内核包,生成的内核源代码放在linux.3.6.8目录中  
  5. # cd linux-3.6.8  



d)修改内核的系统调用库函数:

Ubuntu12.04(可不用修改):
[plain]  view plain  copy
  1. /usr/include/asm-generic/unistd.h  
Kernel 3.6.8中要修改:
[plain]  view plain  copy
  1. include/asm-generic/unistd.h  
进入后修改如下:
其中223号系统调用在syscall_table中没有使用(unused),所以可以修改为我们的调用
[cpp]  view plain  copy
  1. /* mm/fadvise.c */  
  2. /* 
  3. #define __NR3264_fadvise64 223 
  4. __SC_COMP(__NR3264_fadvise64, sys_fadvise64_64, compat_sys_fadvise64_64) 
  5. */  
  6. #define __NR_rksyscall 223  
  7. __SYSCALL(__NR_rksyscall, sys_rksyscall)  


添加系统调用号之后,系统才能根据这个号,作为索引,去找 syscall_table 中的相应表项。
所以,接下来的一步就是:

e)在系统调用表中添加或修改相应表项

在上面步骤中解压出来的内核源代码包中进行修改相关函数:
进入下列目录:
[plain]  view plain  copy
  1. arch/x86/kernel/syscall_table_32.S  
修改编号223对应的代码段:
[cpp]  view plain  copy
  1. # 222 is unused  
  2. # 223 is used now  
  3. 223 i386 mysyscall sys_rksyscall  
  4. 224 i386 gettid sys_gettid  
  5. 225 i386 readahead sys_readahead  

到现在为止,系统已经能够正确地找到并且调用 sys_mysyscall。剩下的就只有一件事情,那
就是 sys_rksyscall 的实现。

f)sys_rksyscall的实现:

我们把这一小段程序添加在 kernel/sys.c 里面。在这里,我们没有在 kernel 目录下另外
添加自己的一个文件,这样做的目的是为了简单,而且不用修改 Makefile,省去不必要的麻
烦。
rksyscall 系统调用实现遍历系统中所有进程,并打印每个进程的进程名字(name)、进
程标识符(pid) 、进程的状态和父进程的名字。
[cpp]  view plain  copy
  1. asmlinkage int sys_mysyscall(void)  
  2. {  
  3.     //在此处加入遍历进程的代码  
  4.     struct task_struct *p;  
  5.         printk("********************************************\n");  
  6.         printk("------------the output of rkcall------------\n");  
  7.         printk("********************************************\n\n");  
  8.         printk("%-20s %-6s %-6s %-20s\n","Name","pid","state","ParentName");  
  9.         for(p = &init_task; (p = next_task(p)) != &init_task;)  
  10.             printk("%-20s %-6d %-6d %-20s\n",p->comm , p->pid, p->state, p->parent->comm);  
  11.     return 0;  
  12. }  


g)重新编译内核:

[plain]  view plain  copy
  1. cp /boot/config-<Tab> .config // <Tab> 为键盘上<Tab>按键,或使用  
[plain]  view plain  copy
  1. cp /boot/config-`uname -r` .config //使用系统的原配置文件  


h)生成配置文件

[plain]  view plain  copy
  1. make menuconfig // 同时生成.config文件  

接下来一步是以防万一:
[cpp]  view plain  copy
  1. 若在3.6.8内核编译存在错误  
  2. “ERROR:”__modver_version_show” [drivers/staging/rts5319/rts5319.ko] underfined”。  
  3. 则在make menuconfig 做如下修改:  
  4.     Device drivers ---  
  5.         Staging drivers--  
  6.             Realtek RTS5139 USB card reader support  
  7. 中把 [M] 改为 [ ](3.6.8内核此时看说明,按键盘上的N),即不编译成模块  


i)编译内核过程

[plain]  view plain  copy
  1. sudo make 或 sudo make -j2(-j2为开启双线程编译)(此步需要1-2小时不等)  
  2. # sudo make modules_install  
  3. # sudo make install  
  4. 查看及修改启动选项# gedit /boot/grub/grub.cfg  
  5. 重新启动  
  6. sudo reboot  
  7. //启动时忽略错误信息提示  
  8.   
  9. 启动后查看内核版本号  
  10. uname -r  
  11. 3.6.8  


j)编写用户态程序

要测试新添加的系统调用,需要编写一个用户态测试程序(test.c)调用 rksyscall 系统调用。

rksyscall 系统调用中 printk 函数输出的信息在/var/log/messages 文件中。
在用户态测试程序从/var/log/message 文件中读出每个进程的进程名字、进程标识符、进程的状态
( 如 : 运 行 、 可 中 断 等 待 ......) 并 分 析 和 父 进 程 的 名 字 , 在 屏 幕 中 输 出 这 些 信 息 。
/var/log/message 文件中的内容也可以在 shell 下用 dmesg 命令查看到。

在 Linux 2.6.25以 后 内 核 中 , 宏 _syscall0() 至 _syscall6() 不再定义在/usr/include/asm/unistd.h中,
因此需要使用syscall()函数实现系统调用。
用户态测试程序可以用如下方法实现
[cpp]  view plain  copy
  1. #include <linux/unistd.h>  
  2. #include <sys/syscall.h>  
  3. //系统调用号根据实验具体  
  4. //#define __NR_ mysyscall 223  
  5. //数字而定  
  6. int main()  
  7. {  
  8.     syscall(__NR_mysyscall);    /*或 syscall(223) */  
  9.     //在此加入在屏幕输出每个进程相关信息的代码;  
  10.     //要求从/var/log/message 文件中读出信息。  
  11. }  
[cpp]  view plain  copy
  1. //编译运行  
  2. gcc -o test test.c  
  3. ./test  
  4. dmesg   //查看进程信息  

最终输出结果:
可以看到已经按照进程名、进程pid、进程状态、父进程进行了归类。

http://blog.csdn.net/rk2900/article/details/8281335

猜你喜欢

转载自blog.csdn.net/wukery/article/details/79296324