话说用户态访问内核内存很简单,将这块内核内存映射到用户地址空间即可。依托一个字符设备,实现其mmap回调函数,在用户进程打开那个设备,mmap之,很容易实现用户进程直接访问内核内存。
但是反过来呢?内核访问用户内存。理论上也很简单,因为所有进程的内核态地址空间都是共享的,所以想访问哪个进程的内存,就切换到那个进程的地址空间,这
将丝毫不会影响当前的执行流。事实上也是这么简单,use_mm就是干这个的,不过既然要切换地址空间,那么当前task的地址空间就必须是明确的,因此
就不能在任意上下文调用use_mm(这从user
mm会缺页导致睡眠之外的另一个视角说明了任意上下文不好调用use_mm)。但是这么用的很少,AIO算一个,以至于use_mm竟然没有被
EXPORT出来...不过有办法。我写了一个例子
#include <linux/module.h> #include <linux/sched.h> #include <linux/kthread.h> int exit_ = 0; /* * 我所测试的内核版本太老,老到下面两个API还没有被导出。 * (还没有被导出的符号可以叫做API吗??) * 在2.6.34内核版本and newer,就可以直接使用了 **/ void (*use_mm)(struct mm_struct *mm); void (*unuse_mm)(struct mm_struct *mm); static int main_loop(void) { struct task_struct *t = NULL; while (exit_) { /* 正确的做法是:pid应该由netlink或者procfs等U/K通信接口告知内核 */ struct pid *pid = find_get_pid(3881); t = pid_task(pid, PIDTYPE_PID); if (t) { /* 正确的做法是:内存指针应该通过U/K通信接口告知内核 */ char *pshare = (char *)0x22d7010; struct mm_struct *mm = t->mm; /* * 注意!这是在具体的task上下文而不是任意上下文,否则use_mm就不行! * 因为用户内存是可被换出的,也是可能缺页的,缺页的处理可能会睡眠 * 所以不要指望在网络协议栈接收/发送软中断中进行用户内存的操作,除非 * 你能确认它们处在task上下文。 **/ use_mm(mm); /* * 访问用户进程的内存 * 这里仅仅是一个最简单的例子,事实上,在这里可以任意的像在用户进程自身一样 * 访问这个地址空间,关键是你要知道符号名称以及位置,这需要设计一个机制,使 * 符号的位置可以映射到一个名称... * * 此时,只要用户进程维护一个内存表,该内存表的物理内存当然可以在HIGHUSER区域 * 被分配!实际上这只是一块虚拟地址空间,缺页中断会映射物理内存。 * * 最好存些什么呢?我知道,在这里-内核空间可以访问sqlite数据库了... **/ printk("kernel info:%s %p\n", pshare, pshare); unuse_mm(mm); } schedule_timeout(1000); } return 0; } static int __init tt_init(void) { exit_ = 1; struct task_struct *tsk; /* 不要直接用kernel_thread创建内核线程,而应该委托专门的线程来做这件事 */ /* pid = kernel_thread(test_it, NULL, SIGCHLD); */ tsk = kthread_run((void *)main_loop, NULL, "KERNEL-ACCESS-USER"); if (IS_ERR(tsk)) { return -1; } /* 由于接口没有导出,下面的信息来自/proc/kallsyms, * 但是起码也要通过模块参数传递过来吧 ... **/ use_mm = 0xffffffff810c7282; unuse_mm = 0xffffffff810c7240; return 0; } static void tt_cleanup(void) { exit_ = 0; return; } module_init(tt_init); module_exit(tt_cleanup); MODULE_LICENSE("GPL");
配合一个用户态程序:
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char **argv) { char *pshare = (char *)calloc(1, 32); strcpy(pshare, "kernel can read!"); while(1) { sleep(1); printf("user info:%s %p\n", pshare, pshare); } }
我在先运行了用户态程序test之后,获取它的pid为3881,pshare指针地址为0x22d7010,我将这两个值写入内核模块,编译,加载,然后内核就可以和用户态进程共享这块内存了。当然,标准的做法肯定没有这么龌龊,我只是写点代码...
我如果能在内核中很方便地使用HIGHUSER内存该有多好...一个例子
原文地址:http://dog250.blog.51cto.com/2466061/1601783