• [转] 添加新的系统调用 _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. ”, 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(). ",(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 ", tv.tv_sec);
    printf("tv_nsec: %ld ", tv.tv_usec);
    printf("em..., let me sleep for 2 second.:) ");
    sleep(2);
    pedagogictime(&tv);
    printf("tv_sec : %ld ", tv.tv_sec);
    printf("tv_nsec: %ld ", tv.tv_usec);
    }
    假设这个程序是test.c,那么使用gcc -o test test.c得到test可执行文件,然后你可以执
    行这个test看看结果。

    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/unbutun/archive/2009/09/08/4532583.aspx
  • 相关阅读:
    几种常见sqlalchemy查询:
    Python error: Unable to find vcvarsall.bat
    ES5中的数组方法
    JQuery的API
    异步加载中按需加载的代码
    js和jQuery中ajax的重要步骤
    编写一个JavaScript函数,把URL参数解析为一个对象
    弹性盒子的用法
    js实现飞机大战小游戏
    H5中的canvas完成动态时钟
  • 原文地址:https://www.cnblogs.com/aljxy/p/6047047.html
Copyright © 2020-2023  润新知