发信人: kevintz@GZ() 
整理人: qiaoqian(2001-12-31 00:01:56), 站内信件
 | 
 
 
 标  题: [转载] 《Linux内核模块编程指南》(八) 发信站: 网易虚拟社区 (Tue Jun 13 12:48:46 2000), 站内信件
  【 以下文字转载自 CLanguage 讨论区 】 【 原文由 我思故我不在 所发表 】 《Linux内核模块编程指南》
 《Linux Kernel Module Programming Guide》
 作者:Ori Pomerantz 中译者:谭志([email protected])
   
 译者注:
 1、LKMPG是一本免费的书,英文版的发行和修改遵从GPL version 2的许可。为了 节省时间,我只翻译了其中的大部分的大意,或者说这只是我学习中的一些中文 笔记吧,不能算是严格上的翻译,但我认为这已经足够了。本文也允许免费发布 ,但发布前请和我联系,但不要把本文用于商业目的。鉴于本人的水平,文章中 难免有错误,请大家不吝指正。
 2、本文中的例子在Linux(kernel version 2.2.10)上调试通过。你用的Linux必 须支持内核模块的加载,如果不支持,请在编译内核时选上内核模块的支持或升 级你的内核到一个支持内核模块的版本。
    
       
 
                            第八章  系统调用
 
     到现在,我们做的只是用定义得很好的内核机制来注册/proc文件和设备驱动 程序。这对于写设备驱动程序已经够了。但如果你想做一些不寻常的事,例如改 变系统的某些行为,那就需要你自己动手了。
 
     这是内核编程的危险之处。下面的例子中,我替换了open系统调用。如果失 败的话,这意味着我不能打开任何文件,我不能运行任何程序和我根本不能关闭 计算机!我必须关掉电源。幸运的时候,没有文件被破坏,为了保证你没有丢失 文件,请在运行insmod和rmmod的前后立刻运行sync。      
 
     忘掉/proc文件吧,忘掉设备文件吧。他们只是次要细节的东西。真正的进程 和内核通信的机制(用于所有进程的)是系统调用。当进程向内核请求一个服务(例 如打开文件、fork一个新的进程和请求分配内存等)时,就是使用这种机制。如果 你想改变你感兴趣的内核行为,这里会教你怎么做。另外,你想知道一个程序用 了那些系统调用,可用strace <command> <arguments>来跟踪。
 
 
     通常进程是不应访问内核的。它不能访问内核的内存,也不能调用内核函数 。这是CPU的硬件所限制的(这是为什么叫"保护模式"的原因)。系统调用通常是一 个异常(陷入)。进程首先会在寄存器里放一个合适的值,然后调用一条特殊的指 令来转跳到内核的某个位置执行(当然,这个位置的内容进程只能读,不能写)。 Intel CPUs族是用中断0x80来作到的。硬件知道你一旦跳到这个位置,你就不再 是运行在有限制的用户模式,而是操作系统内核模式,所以你可以做任何你想做 的事。
          
     进程可以转跳到的内核里的位置叫做system_call函数。这个函数先检查代表 请求哪个服务的系统调用号,然后在系统调用表(sys_call_table)里找到这个系 统调用的地址,调用这个函数,这个函数返回后,做一些检查,然后返回到用户 进程(也有可能是其他进程,因为原来的进程CPU时间片可能已经用完)。如果你想 读这些代码,源文件在/usr/src/linux/arch/<architecture>/kernel/entry.S中 。
 
     如果我们想改变某个系统调用的行为,要做的只是写自己的实现函数(通常是 加一些我们自己的代码,然后调用原来的函数),然后改变在sys_call_table里的 指针指向我们的函数。我们的模块可能稍后被移去,而且不想让系统处于不稳定 的状态,因此在cleanup_module里恢复到原来的状态是很重要的。
 
     这里的例子源码是作为一个内核模块来实现的。我们想侦察某个用户,当他 打开文件时,我们会printk一些信息出来。然后我们用我们的函数our_sys_open 代替打开文件的系统调用sys_open。我们的函数检查当前进程的uid(user's id) ,如果等于要侦察的uid,就用printk输出要打开的文件名,再用相同的参数调用 原来的open函数来打开文件。
 
     init_module函数用our_sys_open代替sys_call_table中合适的位置,并把原 来的函数指针保存到一个变量。cleanup_module函数使用这个变量来恢复到原来 的状态。这种方法是危险的,因为可能有两个内核模块修改了相同的系统调用。 试想我们有A、B两个内核模块,打开函数分别是A_open和B_open。当A被插入内核 ,系统调用被替换为A_open,A_open会调用原来的sys_open函数。然后B被插入内 核,它用B_open替换了以为是原来的sys_open的A_open函数。
 
     现在,如果B模块先被移去,一切都很好,它只是恢复了A_open。但如果A先 于B被移去,系统将会崩溃。A移去时,它恢复了sys_open,然后从内核中删除。 然后B被移去,他恢复了A_open,但此时A_open已经不存在于内核中。表面看上去 ,我们可以通过检查当前这个系统调用的指针是否和我们的代替函数相同来决定 是否替换,如果不同则不替换,并退出。但这会引起一个更糟的问题:当A移去时 ,它看到指针被替换为B_open,不再等于A_open,所以不修改就退出了。B_open 被调用时,它调用以为是原来的sys_open的A_open,但A_open已经不在内核里, 系统会在B不被移去的情况下也会崩溃。
 
     我能想到的避免方法是恢复到原来的sys_open值。不幸的是,sys_open不是 内核系统表/proc/ksyms的一部分,所以我们不能访问它。如果谁有更好的方法, 我很乐意知道。:)
 
 例子syscall.c    
  
 /* syscall.c 
  * System call "stealing" sample
  */
 /* kevintz:the program should be compiled on
    kernel version 2.2.3 or over
 */
 
 /* Copyright (C) 1998-99 by Ori Pomerantz */
 
 
 /* The necessary header files */
 
 /* Standard in kernel modules */
 #include <linux/kernel.h>   /* We're doing kernel work */
 #include <linux/module.h>   /* Specifically, a module */
 
 /* Deal with CONFIG_MODVERSIONS */
 #if CONFIG_MODVERSIONS==1
 #define MODVERSIONS
 #include <linux/modversions.h>
 #endif        
 
 #include <sys/syscall.h>  /* The list of system calls */
 
 /* For the current (process) structure, we need
  * this to know who the current user is. */
 #include <linux/sched.h>  
 
 #include <asm/uaccess.h>
 
 /* 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;  
 
 MODULE_PARM(uid, "i");
 
 /* 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 {
       get_user(ch, filename+i);
       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;
 }  
 
 
 Kevin T.Z注:
 
 编译和测试:
 具体的测试方法我不想给出,其实测试也很简单,但结果却令一些菜鸟大吃一惊 。为什么呢?没想到我们平时more或vi或登陆进来这些动作文件系统会发生那么 多事情的吧:)。建议大家通过这个例子好好品味系统的细节。
  -- 那一刹那,我开始用心去看这个世界,所有的事物真的可以看得前
 所未有的那么清楚…… 
  ※ 来源:.月光软件站 http://www.moon-soft.com.[FROM: 202.105.73.199] -- ※ 转载:.月光软件站 http://www.moon-soft.com.[FROM: 202.105.73.199]
  | 
 
 
 |