首页 > 代码库 > 我如果能在内核中很方便地使用HIGHUSER内存该有多好...一个例子

我如果能在内核中很方便地使用HIGHUSER内存该有多好...一个例子

话说用户态访问内核内存很简单,将这块内核内存映射到用户地址空间即可。依托一个字符设备,实现其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内存该有多好...一个例子