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