话说用户态访问内核内存很简单,将这块内核内存映射到用户地址空间即可。依托一个字符设备,实现其mmap回调函数,在用户进程打开那个设备,mmap之,很容易实现用户进程直接访问内核内存。
但是反过来呢?内核访问用户内存。理论上也很简单,因为所有进程的内核态地址空间都是共享的,所以想访问哪个进程的内存,就切换到那个进程的地址空间,这将丝毫不会影响当前的执行流。事实上也是这么简单,use_mm就是干这个的,不过既然要切换地址空间,那么当前task的地址空间就必须是明确的,因此就不能在任意上下文调用use_mm(这从user mm会缺页导致睡眠之外的另一个视角说明了任意上下文不好调用use_mm)。但是这么用的很少,AIO算一个,以至于use_mm竟然没有被EXPORT出来...不过有办法。我写了一个例子
配合一个用户态程序:
我在先运行了用户态程序test之后,获取它的pid为3881,pshare指针地址为0x22d7010,我将这两个值写入内核模块,编译,加载,然后内核就可以和用户态进程共享这块内存了。当然,标准的做法肯定没有这么龌龊,我只是写点代码...
但是反过来呢?内核访问用户内存。理论上也很简单,因为所有进程的内核态地址空间都是共享的,所以想访问哪个进程的内存,就切换到那个进程的地址空间,这将丝毫不会影响当前的执行流。事实上也是这么简单,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,我将这两个值写入内核模块,编译,加载,然后内核就可以和用户态进程共享这块内存了。当然,标准的做法肯定没有这么龌龊,我只是写点代码...