Chinaunix首页 | 论坛 | 博客
  • 博客访问: 647787
  • 博文数量: 156
  • 博客积分: 4833
  • 博客等级: 上校
  • 技术积分: 1554
  • 用 户 组: 普通用户
  • 注册时间: 2007-05-21 19:36
文章分类

全部博文(156)

文章存档

2016年(2)

2013年(1)

2012年(13)

2011年(30)

2010年(46)

2009年(29)

2008年(23)

2007年(12)

分类: LINUX

2007-12-05 03:08:37

将用户空间的虚拟地址映射到内核态的逻辑地址。


作者:  发表于:2007-01-22 15:35:36
【】【】【】【】

小弟正在写一个测试程序,程序的目的是用户空间的虚拟地址映射为内核空间的逻辑地址,然后内核空间的驱动程序对该内存空间直接操作,写入相应的字符串,最后再由用户空间读出该字符串以验证程序的正确性。

程序的流程大概是这样的。
-----------------内核空间------------------------
驱动程序注册一个字符设备
实现iotctl调用。
ioctl将用户空间传递的虚拟地址通过进行页表查询转换为物理地址,然后再将物理地址转化为逻辑地址,最后向该逻辑地址中写入“hello kmap”字符串
返回

-----------------用户空间------------------------
使用valloc分配一页内存
调用memset清空该内存
然后打开驱动程序注册的字符设备接口
调用字符设备的iotcl,将上述分配的一页内存的虚拟地址传递给内核驱动程序
此时内存里面应该已有驱动程序写入的字符串信息,调用printf打印该内存。

小弟按照上述思路实现了程序,但是运行以后,发生了oops。不知道问题具体出在哪里,下面贴下代码,请各位指点迷津。谢谢

---------------------------------kernel.h--------------------------------------
/*
* kernel.h 公用头文件
*/
#ifndef KMAP_KERNEL_H
#define KMAP_KERNEL_H

#ifdef KMAP_KERNEL
#define __DEBUG_MSG(a,x...) do{printk(KERN_ALERT"%s %s %d:"a,__FILE__,__FUNCTION__,__LINE__,##x);}while(0)
#else
#define __DEBUG_MSG(a,x...) do{fprintf(stderr, "%s %s %d:"a, __FILE__, __FUNCTION__, __LINE__, ##x);}while(0)
#endif

#define DEVICE_NAME "kmap"

#define KMAP_IOC_MAGIC 'q'
#define KMAP_IOC_SET_PID_AND_VADDR _IOW(KMAP_IOC_MAGIC, 1, char *)

#ifdef KMAP_KERNEL
int init_module(void);
void cleanup_module(void);

static int kmap_open(struct inode *, struct file *);
static int kmap_ioctl(struct inode *, struct file *,
unsigned int, unsigned long);
#endif

struct ioctl_arg
{
pid_t pid;
unsigned long vaddr;
};
#endif

------------------------------------kernel.c-------------------------------------
/*
*kernel.c 内核实现部分
*/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define KMAP_KERNEL 

#include "kernel.h"

static int major = 0;
static struct file_operations fops =
{
.open = kmap_open,
.ioctl = kmap_ioctl
};

int init_module(void)
{
major = register_chrdev(0, DEVICE_NAME, &fops);
if ( major < 0 )
{
printk(KERN_ALERT"register chrdev failed:%d\n", major);
return major;
}

printk(KERN_ALERT"mknod /dev/%s c %d 0\n", DEVICE_NAME, major);
return 0;
}

void cleanup_module(void)
{
/*unregister the device*/
int ret = unregister_chrdev(major, DEVICE_NAME);
if ( ret < 0 )
{
__DEBUG_MSG("Error in unregister_chrdev:%d\n", ret);
}
}

static int kmap_open(struct inode *inode, struct file *filp)
{
__DEBUG_MSG("enter kmap_open\n");
return 0;
}
static int kmap_ioctl(struct inode *inode, struct file *filp,
  unsigned int cmd , unsigned long arg)
{
pid_t pid = 0;
unsigned long vaddr = 0;
unsigned long phy = 0;
unsigned long laddr = 0;

int err = 0, retval = 0;

pgd_t *pgd = NULL;
pud_t *pud = NULL;
pmd_t *pmd = NULL;
pte_t *pte = NULL;

struct page *mypage;

struct ioctl_arg k_arg;

__memset_generic(&k_arg, 0, sizeof(struct ioctl_arg));

/*get arg*/
err = !access_ok(VERIFY_READ, arg, sizeof(struct ioctl_arg));
if ( err )
{
__DEBUG_MSG("access vaild failed\n");
return -EFAULT;
}

err = copy_from_user((void *)&k_arg, (const void *)arg,
sizeof(struct ioctl_arg));
pid = k_arg.pid;
vaddr = k_arg.vaddr;

__DEBUG_MSG("pid:%d vaddr:%lu\n", pid, vaddr);
/*
*convert vaddr to kernel logic addr
*1,get the current task by pid
*2,get the phyaddr
*3,convert phyaddr to kernel logic addr
*4,write sth. in the buffer

*NOTE:step 1 is not necessay,because now current is pointed to task
*/

__DEBUG_MSG("The pid which user passed in is:%d\n"
" The current pid is:%d\n", pid, current->pid);
/*get phyaddr*/
pgd = pgd_offset(current->mm, vaddr);
if ( pgd_none(*pgd) || pgd_bad(*pgd) )
{
__DEBUG_MSG("invalid pgd\n");
retval = -1;
goto error;
}

pud = pud_offset(pgd, vaddr);
if ( pud_none(*pud) || pud_bad(*pud) )
{
__DEBUG_MSG("invalid pud\n");
retval = -1;
goto error;
}

pmd = pmd_offset(pud, vaddr);
if ( pmd_none(*pmd) || pmd_bad(*pmd) )
{
__DEBUG_MSG("invalid pmd\n");
retval = -1;
goto error;
}
__DEBUG_MSG("before pte_offset_map\n");
pte = pte_offset_map(pmd, vaddr);
__DEBUG_MSG("after pte_offset_map\n");
pte_unmap(pte);
if ( pte_none(*pte) )
{
__DEBUG_MSG("bad pte va: %X pte: %p pteval: %lX\n", vaddr, pte, pte_val(*pte));
retval = -1;
goto error;
}

phy = (pte_val(*pte)) & PAGE_MASK;

__DEBUG_MSG("the phy addr is %lu\n", phy);

mypage = pte_page(*pte);
set_bit(PG_locked, &mypage->flags);
atomic_inc(&mypage->_count);

/*convert phy to kernel logic addr*/
laddr = __pa(phy);
strcpy((char *)laddr, "hello kmap");

error:
return retval;
}
------------------------------------user.c-------------------------------------
/*
*user.c 用户空间部分
*/
#include 
#include 
#include 
#include 
#include 
#include 
#include /*getpagesize*/
#include 

#include "kernel.h"

#define CHAR_DEV_PATH "/dev/"DEVICE_NAME

int main(int argc, char **argv)
{
int fd = -1;
pid_t pid = 0;
unsigned int vaddr = 0;
int result = 0;
int page_size =0;
struct ioctl_arg arg;
int retval = 0;

memset(&arg, 0, sizeof(struct ioctl_arg));

/*get page size*/
page_size = getpagesize();

/*get pid*/
pid = getpid();

vaddr = (unsigned long)valloc(page_size);
memset((char *)vaddr, 0, page_size);
strcpy((char *)vaddr, "hello kmap");

/*assemble arg*/
__DEBUG_MSG("The pid and the vaddr is:%d, %lu\n", pid, vaddr);
arg.pid = pid;
arg.vaddr = vaddr;

/*open the device*/
fd = open(CHAR_DEV_PATH, O_RDWR);

if ( fd < 0 )
{
__DEBUG_MSG("error when open device file:%s\n", strerror(errno));
retval = -1;
goto error;
}

/*pass the arg to kernel*/
result = ioctl(fd, KMAP_IOC_SET_PID_AND_VADDR, &arg);
if ( result )
{
__DEBUG_MSG("error when ioctl:%s\n", strerror(errno));
}

printf("after ioctl we get the memory:%s\n", (char *)vaddr);

error:
if ( fd != -1 )
{
close(fd);
}
return retval;
}



  回复于:2007-01-16 20:10:46

请大家重点看一下下面的代码片断,问题应该是出在这里的
      /*
        *convert vaddr to kernel logic addr
        *1,get the current task by pid
        *2,get the phyaddr
        *3,convert phyaddr to kernel logic addr
        *4,write sth. in the buffer

        *NOTE:step 1 is not necessay,because now current is pointed to task
        */

        __DEBUG_MSG("The pid which user passed in is:%d\n"
                                " The current pid is:%d\n", pid, current->pid);
        /*get phyaddr*/
        pgd = pgd_offset(current->mm, vaddr);
        if ( pgd_none(*pgd) || pgd_bad(*pgd) )
        {
                __DEBUG_MSG("invalid pgd\n");
                retval = -1;
                goto error;
        }

        pud = pud_offset(pgd, vaddr);
        if ( pud_none(*pud) || pud_bad(*pud) )
        {
                __DEBUG_MSG("invalid pud\n");
                retval = -1;
                goto error;
        }

        pmd = pmd_offset(pud, vaddr);
        if ( pmd_none(*pmd) || pmd_bad(*pmd) )
        {
                __DEBUG_MSG("invalid pmd\n");
                retval = -1;
                goto error;
        }
        __DEBUG_MSG("before pte_offset_map\n");
        pte = pte_offset_map(pmd, vaddr);
        __DEBUG_MSG("after pte_offset_map\n");
        pte_unmap(pte);
        if ( pte_none(*pte) )
        {
                __DEBUG_MSG("bad pte va: %X pte: %p pteval: %lX\n", vaddr, pte, pte_val(*pte));
                retval = -1;
                goto error;
        }

        phy = (pte_val(*pte)) & PAGE_MASK;

        __DEBUG_MSG("the phy addr is %lu\n", phy);

        mypage = pte_page(*pte);
        set_bit(PG_locked, &mypage->flags);
        atomic_inc(&mypage->_count);

        /*convert phy to kernel logic addr*/
        laddr = __pa(phy);
        strcpy((char *)laddr, "hello kmap");


  回复于:2007-01-16 22:31:23

这个问题,难道比较大,你还是多打印点调试信息,慢慢调试.


  回复于:2007-01-16 22:34:21

我找到问题所在了。
用__va代替__pa
将物理地址转化为内核的逻辑地址是用__va而不是__pa,改动以后程序工作正常。


  回复于:2007-01-20 14:17:15

虽然你这个方法是可行的,却是不提倡的方法,
应该用mmap()的方法。
为你的设备file_operations定义一个mmap函数,
然后用户态调用系统调用mmap()把设备分配的内存映射到用户态,
这是标准的做法。


  回复于:2007-01-20 18:33:41

谢谢轮子大侠,mmap的方法我也尝试过了,我也想尝试一下这种方法?
不知道为什么这种方法是不提倡的?能否再深入的指点一下小弟?


  回复于:2007-01-21 13:46:39

我觉得是移植性不好,很多操作和体系结构,内存布局有关系。
需要处理很多特殊的情况。

比如用户态virtual地址和内核可以访问的virtual地址之间的转换
你用__va()来转换,但如果你的机器内存>896M,
这个页面是highmem的话,就是不对的,因为内核没有这种映射。
需要另外建立内核映射。

另外,你的程序还有很多操作也不规范,
比如你设置PG_lock,这个标志通常在改变页面状态时才设置,
并且是短时的,比如要进行磁盘I/O时。
你lock住page后,可能会影响到内核的操作。

还有访问pte应该加锁(pgtable lock)。

mmap()的方法是传统的unix的标准方法,内核有标准的支持。
换句话说,是unix的传统,所以我觉得mmap()的方法是对的。


  回复于:2007-01-22 15:35:36

r u wheelz from CLF?

how about ur health?

俺喜欢忽悠,哈哈。

[ 本帖最后由 sisi8408 于 2007-1-22 15:39 编辑 ]




原文链接:
转载请注明作者名及原文出处

阅读(1657) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~