当前位置:Linux教程 - Linux - Linux内核编程(系统调用)

Linux内核编程(系统调用)



        
    著者:Ori Pomerantz
    翻译:徐辉

    7.系统调用
    到此为止,我们做的事情就是使用定义好的内核机制来注册/proc文件和设备句柄。这在做内核常规处理的事情时是很理想的。但是如果你希望做一些非常规的事情、改变系统的行为的时候该怎么办呢?这就必须依靠自己。
    这就是内核编程变得危险的地方。在写下面的例子的时候,我关闭了open系统调用。这意味着我不能打开任何文件,不能运行任何程序,而且不能关闭计算机。我必须拉住电源开关。幸运的是,没有文件丢失。为确保你也不会丢失任何文件,在做insmod以及rmmod前请执行sync权限,
    忘记/proc文件,忘记设备文件。它们只是不重要的细节。真正的同内核通信的过程机制是被所有进程公用的,这就是系统调用。当一个进程请求内核服务时(比如打开文件、创建一个新进程或者要求更多内存),就需要使用这个机制。如果你想用比较有趣的方法改变内核行为,这就是你所需要的。另外,如果你希望看到程序使用了哪一个系统调用,运行strace
    一般的,进程是不能访问内核的。它不能访问内核所占内存空间也不能调用内核函数。CPU硬件决定了这些(这就是为什么它被称作“保护模式”)。系统调用是这些规则的一个例外。其原理是进程先用适当的值填充寄存器,然后调用一个特殊的指令,这个指令会跳到一个事先定义的内核中的一个位置(当然,这个位置是用户进程可读但是不可写的)。在Intel CPU中,这个由中断0x80实现。硬件知道一旦你跳到这个位置,你就不是在限制模式下运行的用户,而是作为操作系统的内核——所以你就可以为所欲为。
    进程可以跳转到的内核位置叫做sysem_call。这个过程检查系统调用号,这个号码告诉内核进程请求哪种服务。然后,它查看系统调用表(sys_call_table)找到所调用的内核函数入口地址。接着,就调用函数,等返回后,做一些系统检查,最后返回到进程(或到其他进程,如果这个进程时间用尽)。如果你希望读这段代码,它在源文件目录//kernel/entry.S,Entry(system_call)的下一行。
    所以,如果我们希望改变某个系统调用的工作方式,我们需要写我们自己的函数(通常是加一点我们自己的代码然后调用原来的函数)来实现,然后改变sys_call_table中的指针使其指向我们的函数。因为我们可能以后会删除,而且不希望系统处在不稳定状态,所以在cleanup_module中保存该表的原来状态很重要。
    这里的源代码是一个这样的核心模块的例子。我们希望“窥探”一个用户,每当这个用户打开一个文件是就printk一条消息。为达到这个目的,我们把打开文件的系统调用替换为我们自己的函数,our_sys_open。这个函数检查当前进程的uid(用户的id),如果它等于我们要窥探的uid,就调用printk来显示所打开文件的文件名。然后,可以用任何一种方法,用同样的参数调用原来的open函数,或者真正打开文件。
    Init_module函数把sys_call_table中的适当地址上的内容替换,把原来的指针保存在一个变量里。Cleanup_module函数用这些变量恢复所有的东西。这种方法是危险的,因为两个内核模块可能改变了同一个系统调用。设想我们由两个内核模块,A和B。A的open系统调用是A_open,B的open系统调用是B_open。现在,如果A插入内核,系统调用将被替换为A_open,当完成以后调用sys_open。然后,B被插入内核,把系统调用替换为B_open,而完成的时候,它将会调用它认为原始的系统调用的A_open,。
    那么,如果B被首先删除,不会出现任何错误——它只是把系统调用恢复成A_open,A_open再去调用原始的的系统调用。然而,如果先删除A,再删除B,系统就会崩溃。A的删除将会把系统调用恢复成sys_open,而把B切换出了循环。然后,当B被删除时,将会把系统调用恢复成A_open,但是A_open已经不在内存。初看来,似乎我们可以通过检查系统调用是否等于我们的open函数来解决这个问题,如果是就不要改变它(这样B被删除的时候就不会改变系统调用),但是这样会引起一个更加恶劣的问题。当A被删除时,它看到系统调用被改成了B_open而不再指向A_open,所以在它被删除时就不会恢复sys_open。不幸的是,B_open仍然试图恢复A_open,但它已不再内存,这样,即使没有删除B系统也会崩溃。
    我可以提出两个方法来解决这个问题。第一个方法是把调用恢复成原始值,sys_open。不幸的是sys_open不是在/proc/ksyms中的内核系统表中的一部分,所以我们不能访问它。另一个解决办法是使用索引计数器来阻止root 去rmmod这个模块,一旦它被装载。这在生产性模块中是好的,但是对教学里中不是很好——这就是为什么我不在这里这样做。
    ex syscall.c

    /* syscall.c
    *
    * System call \"stealing\" sample
    */


    /* Copyright (C) 1998-99 by Ori Pomerantz */


    /* The necessary header files */

    /* Standard in kernel modules */
    #include /* We\re doing kernel work */
    #include /* Specifically, a module */

    /* Deal with CONFIG_MODVERSIONS */
    #if CONFIG_MODVERSIONS==1
    #define MODVERSIONS
    #include
    #endif

    #include /* The list of system calls */

    /* For the current (process) structure, we need
    * this to know who the current user is. */
    #include




    /* In 2.2.3 /usr/include/linux/version.h includes a
    * macro for this, but 2.0.35 doesn\t - so I add it
    * here if necessary. */
    #ifndef KERNEL_VERSION
    #define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
    #endif



    #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
    #include
    #endif



    /* The system call table (a table of functions). We
    * just define this as external, and the kernel will
    * fill it up for us when we are insmod\ed
    */
    extern void *sys_call_table[];


    /* UID we want to spy on - will be filled from the
    * command line */
    int uid;

    #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
    MODULE_PARM(uid, \"i\");
    #endif

    /* A pointer to the original system call. The reason
    * we keep this, rather than call the original function
    * (sys_open), is because somebody else might have
    * replaced the system call before us. Note that this
    * is not 100% safe, because if another module
    * replaced sys_open before us, then when we\re inserted
    * we\ll call the function in that module - and it
    * might be removed before we are.
    *
    * Another reason for this is that we can\t get sys_open.
    * It\s a static variable, so it is not exported. */
    asmlinkage int (*original_call)(const char *, int, int);



    /* For some reason, in 2.2.3 current->uid gave me
    * zero, not the real user ID. I tried to find what went
    * wrong, but I couldn\t do it in a short time, and
    * I\m lazy - so I\ll just use the system call to get the
    * uid, the way a process would.
    *
    * For some reason, after I recompiled the kernel this
    * problem went away.
    */
    asmlinkage int (*getuid_call)();



    /* The function we\ll replace sys_open (the function
    * called when you call the open system call) with. To
    * find the exact prototype, with the number and type
    * of arguments, we find the original function first
    * (it\s at fs/open.c).
    *
    * In theory, this means that we\re tied to the
    * current version of the kernel. In practice, the
    * system calls almost never change (it would wreck havoc
    * and require programs to be recompiled, since the system
    * calls are the interface between the kernel and the
    * processes).
    */
    asmlinkage int our_sys_open(const char *filename,
    int flags,
    int mode)
    {
    int i = 0;
    char ch;

    /* Check if this is the user we\re spying on */
    if (uid == getuid_call()) {
    /* getuid_call is the getuid system call,
    * which gives the uid of the user who
    * ran the process which called the system
    * call we got */

    /* Report the file, if relevant */
    printk(\"Opened file by %d: \", uid);
    do {
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
    get_user(ch, filename+i);
    #else
    ch = get_user(filename+i);
    #endif
    i++;
    printk(\"%c\", ch);
    } while (ch != 0);
    printk(\"\\n\");
    }

    /* Call the original sys_open - otherwise, we lose
    * the ability to open files */
    return original_call(filename, flags, mode);
    }



    /* Initialize the module - replace the system call */
    int init_module()
    {
    /* Warning - too late for it now, but maybe for
    * next time... */
    printk(\"I\m dangerous. I hope you did a \");
    printk(\"sync before you insmod\ed me.\\n\");
    printk(\"My counterpart, cleanup_module(), is even\");
    printk(\"more dangerous. If\\n\");
    printk(\"you value your file system, it will \");
    printk(\"be \\\"sync; rmmod\\\" \\n\");
    printk(\"when you remove this module.\\n\");

    /* Keep a pointer to the original function in
    * original_call, and then replace the system call
    * in the system call table with our_sys_open */
    original_call = sys_call_table[__NR_open];
    sys_call_table[__NR_open] = our_sys_open;

    /* To get the address of the function for system
    * call foo, go to sys_call_table[__NR_foo]. */

    printk(\"Spying on UID:%d\\n\", uid);

    /* Get the system call for getuid */
    getuid_call = sys_call_table[__NR_getuid];

    return 0;
    }


    /* Cleanup - unregister the appropriate file from /proc */
    void cleanup_module()
    {
    /* Return the system call back to normal */
    if (sys_call_table[__NR_open] != our_sys_open) {
    printk(\"Somebody else also played with the \");
    printk(\"open system call\\n\");
    printk(\"The system may be left in \");
    printk(\"an unstable state.\\n\");
    }

    sys_call_table[__NR_open] = original_call;
    }


    发布人:netbull 来自:LinuxByte