(总结供新手参考)向Linux 内核中添加user-defined system call 的一些思考

来源:互联网 发布:php 判断是否包含数字 编辑:程序博客网 时间:2024/05/21 10:11

linux 发展接近20年了,当年Linus Torvalds 在大学的宿舍里写了Linux kernel 1.0版本, 借着internet的传播,在全世界程序高手的不断修改添加功能之后变得越发的强大,现在linux服务器os领域占有相当大的份额,并且不断的增长中,在嵌入式领域,由于linux的灵活内核配置的特性,使linux成为最常用的嵌入式os,当然手机操作系统也有是linux的,很多的笔记本厂商预装的os也是linux的某个发行版本,著名的movie 泰坦尼克号 的特效镜头就是利用n个装有linux os的Computer协同处理出来的,linux拥有强大的力量,让OS龙头老大的盖茨大叔倍感压力,而之前盖茨根本不把Linus Torvalds 这个人放在眼里,当然这个倒无所谓,因为我们的大黑客Linus Torvalds 本来就是个低调的人。虽然说是GNU 和 internet造就了 Linus Torvalds 的传奇,时势造英雄,有时势没英雄也造不出来什么。对Linus Torvalds 我有无尽的钦佩之情。

   内核是操作系统最核心的部分,负责的是最核心的任务,内核向应用层提供了应用接口,即系统调用。 linux内核90%以上的代码是用c写的,剩下的是asm,在内核编程这块,c可以说是万国通语言,在csdn论坛里就有人评论说,虽然c是过程化的语言,但是感觉linux内核写的很oo,从这里也许我们能够看出内核代码是何其的高明。 c语言把内核提供的这些low level functions 进行了 wrap ,进而成为libc,貌似也有个glibc。就比如说 libc里的 printf() 和 scanf()两个函数就是利用 kernel system calls里的 write() 和 read() 来实现的, linux下用户程序执行的时候会自动打开三个文件 stdin stdout stderr, file descriptor 分别是 0, 1, 2,write()的第一个参数就是file descriptor 如果取 1, 就可以实现printf()的功能,当然应该这样说 printf()功能的实现是借助write().

   都知道linux是个open source的os,open source 是个好东西。我们可以查看到linux的内核源码, 一般在/usr/src/linux下。知道了内核源码我们就可以根据我们的实际情况,比如pc的硬件配置和应用需求,对kernel进行DIY的裁剪,你想要什么不想要什么,都可以自己决定,当然想要游刃有余的DIY需要相当的硬件知识和对linux 内核的研究。

   还是步入正题吧。

  如何向kernel里添加自己的system call 呢?有两种方法:一种是把自己的system call 直接build in 内核中,另一种是 add to kernel use module。第一种方法是添加了自己的系统调用然后编译进内核成为内核的一部分,而第二种是借助模块把自己定义的system call动态的加载进内核,当然也可以move出来。第一种需要reboot,以新的内核加载启动,而第二种就不需要reboot,直接可以使用。第二种还没有摸过,作为模块编译有一定语法要求,有空再实现一下。还是用大家都用的方法看看。

  要向内核中添加系统调用,首先要对linux内核系统调用的机制有个大概的了解。这里需要说一下自己的感受,编译内核是一件简单的事,说简单是因为编译内核的过程本身并不复杂,就那几个命令 tar -zxvf ***.tar.gz; make clean; make dep; make menuconfig;make bzImage; make module ; make modules_install; 然后再改改grub或者lilo什么的。 但是编译内核不是一件看起来那么简单的事,首先不同版本的饿内核比如2.4 和 2.6内核的编译方法就有不同。当然编译的环境不同,方法也会有不同,在Ubuntu上编译和在Fedora上的编译就有不同,最起码的两个不同的linux destribution用的软件包就不一样。一个是deb一个是rpm。最关键的是编译内核涉及到一大堆的硬件知识,在进行内核配置的那一步,需要用户自己定制内核,需要把哪些build进内核,哪些做为模块动态加载,哪些不需要。比如文件系统,一般要把ext3 build 进kernel,而如果你知道自己pc的网卡是千兆网卡,你就可以把10/100M 和 10000M去掉。内核配置是最关键和最核心的一块,对于新手一般都是选默认或者加载一个old .config文件。这样有很多没有必要的东西都没有去掉,所以编译的时间相对也会比较长。还没有步入正题,linux的系统调用机制是怎样的呢?首先要描述一个情景,用户程序是在 user mode 下运行的,当遇到一条system call需要执行时就会产生一个soft int,在linux内核实现中是 int 0x80, 转入中断处理入口syscall, 这样用户程序就进入了 kernel mode, 结合系统调用号(stub) 和 syscall_table_32.S系统调用表,内核找到系统调用函数的地址,进行调用,在这其中用到了一个寄存器 %eax, 完成系统调用后,返回值会写入%eax返回给用户程序,回到user mode.这样就完成了系统调用。可以看出系统调用的实现是借助软中断ox80来实现的。

下面是添加系统调用的笔记,供新手参考一下:

我的os : Ubuntu 8.04

内核版本: 2.6.24.5

 

需要提醒的是,在写自己的system call 代码的时候,不能使用libc里的函数,只能用内核里implemented 的函数,不然会出错的。

在修改系统调用相关的文件的时候一定要参考你下载的内核文件里的格式,网上搜到的写法可能不时候你pc的环境,或者很多是过时的,对于不同版本的内核有一些细微的调整,不能一概的照葫芦画瓢。2.6之前的内核利用_syscallN()宏指令参与内核系统调用的过程,但是2.6不用宏,直接用函数syscall,这一点需要注意,如果在用户程序里调用自己定义的系统调用就不能用宏_syscallN(),对于2.6的内核是会报错的。

--------------------------------------------------------------------------------

linux/kernel/sys.c
asmlinkage int sys_sum(int a, int b)
{
    return a+b;
}

asmlinkage void sys_helloworld(void)
{
    printk(KERN_EMERG "Hello world!/n");
    return ;
}


linux/arch/x86/kernel/syscall_table_32.S

.long sys_sum
.long sys_helloworld

 

linux/include/linux/syscalls.h

asmlinkage int sys_sum(int a, int b);
asmlinkage void sys_helloworld(void);


linux/include/asm-x86/unistd_32.h

#define NR_syscalls 325 -> #define NR_syscalls 327 这里如果忽视了,就同情你了,做无用功了,如果不改,你之后添加的不会生效的,道理很简单。

add:

#define __NR_sum   325
#define __NR_helloworld 326
 

#make-kpkg clean
#make-kpkg --initrd --append-to-version=-test2.0 kernel_image kernel_headers
#dpkg -i linux-image-2.6.24.5-test2.0_2.6.24.5-test2.0-10.00.Custom_i386.deb
#dpkg -i linux-headers-2.6.24.5-test2.0_2.6.24.5test2.010.00.Custom_i386.deb

这里的命令和一般的命令不一样,因为这不是一般的编译命令,这里用的命令是把内核编译为可以安装的deb包,然后通过deb包安装,这样的好处在于借助deb包,不必自己手动拷贝镜像文件和修改引导文件,当然还可以把deb包拷到其他pc上直接运行install kernel,如果两台pc的硬件配置相似的话。

 

 

 

我的测试程序:

添加了两个系统调用,但是helloworld没有调用,出现了个小问题,就不测试了,

计算两个int值之和的system call还是很良好的。
#include <linux/unistd.h>
#include <sys/syscall.h>
#include <stdio.h>

#define __NR_sum 325
#define __NR_helloworld 326

int main()
{

    int res = syscall(__NR_sum, 2, 3);

    printf("Result is :%d/n", res);
    return 0;
}
-----------------------------------------------------------------------------------

 

以上的总结希望能够对新手有点帮助。

不过咱还有一个问题,哪位看咱日志的大侠,如果知道的话就提示一下小弟吧。

 

csdn上有人说咱的这个不叫系统调用,是一个假系统调用,只是用系统调用的机制运行一个用户程序而已,真正的系统调用是用asm写的,而且还有其他n多的文件需要修改,不知道有没有这么一个问题,这其中有什么道理呢?感觉似乎有点道理。

原创粉丝点击