在Linux可加载内核模块中探秘
第一部分:基础知识
第四章:切入核心
作者:CoolBoy
到现在为止,我们已经学习了如何写一个最基本的LKM程序,并且还大概解释了什么是系统调用和内核符号表,那么,怎样让LKM程序来为我们做点什么呢?在内核空间中,我们又能够让这些程序做什么呢?下面我们就先谈一谈内核空间的编程问题。
在内核空间中进行编程比起用户空间来有很多优势,比如对核心的控制能力强、跟底层更为接近等等,不过也有很多地方会让人大伤脑筋,因为内核空间和用户空间是逻辑上分离的两个存储空间,两个空间不可能直接去访问对方的数据,用怎样的妙法来解决这个问题呢?
举个实际的例子来讲,如果当前我们的LKM程序截获了一个系统调用(怎么截获我们今后谈),这个系统调用需要从用户空间获得参数以采取下一步行动,(假设这个参数是环境变量path,在Linux中用常量const char *path来表示),那么,我们的LKM也必须得到这个参数。怎么去取得呢?如果你用:
printk(\"<1>%s\\n\", path);
那么你就会死得很难看。 因为在内核空间根本就不存在这个path变量。请牢记内核空间是不能这样轻易地访问到用户空间的数据的。不过解决的办法也不复杂,我们只要对这个指针动动手脚就可以了。具体的方法:在内核空间建立一个临时指针,然后把用户空间的*path指针深度拷贝给这个临时指针就行(深度拷贝意思就是把指针带的数据一并拷贝过来)。
既然要拷贝用户空间的数据,那么我们仍然需要访问用户空间。在内核模式下,不是没有能访问用户空间的函数,只是少得可怜,get_user就是其中的一个。
get_user(pointer)其实是一个宏,它包含在#里面,下面我们就利用它写一个转换函数,来达到我们拷贝指针的目的。
/*
函数功能:从用户空间深度拷贝指针到内核空间
dest:内核空间的临时指针
src:用户空间的指针
n:需要拷贝的长度
*/
char *strncpy_fromfs(char *dest, const char *src, int n)
{
char *tmp = src;
int compt = 0;
do
{
dest[compt++] = __get_user(tmp++, 1);
}
while ((dest[compt - 1] != \\\0\) && (compt != n));
/*这里用的停止条件很巧妙:如果拷贝过来的字符里出现了结束符,或者是超界,就停止拷贝*/
return dest;
}
通过这个程序,我们就可以得到了用户空间中path变量的信息,再要打印出来就不困难了。如程序:
char *kernel_space_path;
kernel_space_path = (char *) kmalloc(100, GFP_KERNEL); /*在内核中分配内存*/
(void) strncpy_fromfs(test, path, 20); /*调用指针深拷贝函数*/
printk(\"<1>%s\\n\", kernel_space_path); kfree(test); /*记得还要释放内核内存*/
上述代码是可以十分稳定地运行的,但作为一个通用的转换函数,这样的代码又显得过于复杂;如果我们不仅仅是拷贝字符串的话,那么可以用下面的宏来完成:
#include
void memcpy_fromfs(void *to, const void *from, unsigned long count);
我们看到两个宏get_user和memcpy_fromfs显然都是调用了同一个底层函数,因为他们都包含了相同的头文件。所以基于效率的考虑,如果仅转换字符串,则使用前者,若是要作通用的转换,则后者更好。
现在,任务完成了一半,从用户到内核的问题解决了,那么从内核到用户呢?
这个要稍微麻烦一点。因为站在内核的角度,要在用户空间去分配临时内存,再拷贝数据到这个临时内存中是不大好办的。其实只要把用户空间的内存分配问题解决了,接下来的拷贝工作还是满好做的,比如我们可以用下面的宏来做转换的工作:
#include
void memcpy_tofs(void *to, const void *from, unsigned long count);
这个*to,上哪里去找呢?
答案就是下面这个程序,这个程序解决了在内核里去分配用户空间内存的问题。不过此程序显得有些复杂,下面我们会作解释。
/*这个程序中我们用到了brk系统调用*/
static inline _syscall1(int, brk, void *, end_data_segment);
...
int ret, tmp;
char *truc = OLDEXEC;
char *nouveau = NEWEXEC;
unsigned long mmm;
mmm = current->mm->brk;
ret = brk((void *) (mmm + 256));
if (ret < 0)
return ret;
memcpy_tofs((void *) (mmm + 2), nouveau, strlen(nouveau) + 1);
程序中的current是个指向当前进程的任务结构的指针;mm是指向当前进程的任务结构中的内存管理结构的指针。使用current-> mm->brk,我们则可以增加当前进程的数据段,即内存空间。这段空间就可以用来将内核数据拷贝至用户空间的数据中。
你也许对此程序的第一行感到奇怪,其实这行代码让我们能够象使用用户空间的函数一样在核心空间调用函数,因为用户空间的函数最终还是调用核心空间的系统调用,所以我们干脆就在核心空间自己建立一个核心调用来完成用户空间做相同调用时所做的事情。这里我们创建了一个系统调用的宏brk(...)。
好啦。今天到这里我们学习了内核和用户空间的数据交换,虽然程序的量不多,但是看起来满复杂的,不过用心学习一定会有收获哦!下次我们将接着今天后半部的话题,讲讲怎样在内核空间建立类似于用户空间函数的方法。
发布人:Crystal 来自:Linux专区