linux驱动-映射进程空间

简述:

内核映射进程空间,就是由进程分配好空间(属于进程独占资源)后,将用户空间虚拟地址,传递到内核,然后内核映射成内核虚拟地址直接访问,此时内核访问的物理空间是位于用户空间。这样的好处是,内核直接访问进程空间,减少copy动作。

接口:

  • 接口要包含的头文件:
#include <linux/mm.h>
  • 函数接口:
long get_user_pages(struct task_struct *tsk, struct mm_struct *mm,
            unsigned long start, unsigned long nr_pages,
            int write, int force, struct page **pages,
            struct vm_area_struct **vmas);

功能:内核用来映射进程内存空间
第一个参数: tsk是进程控制块指针,这个参数几乎一直作为 current 传递。
第二个参数: 一个内存管理结构的指针, 描述被映射的地址空间. mm_struct 结构是捆绑一个进程的虚拟地址空间所有部分. 对于驱动的使用, 这个参数应当一直是current->mm。
第三个参数: start 是(页对齐的)用户空间缓冲的地址。要映射进程空间虚拟地址的起始地址。
第四个参数: nr_pages是映射进程空间的大小,单位页。
第五个参数: write是内核映射这段进程空间来读还是写,非0(一般就是1)表示用来写,当然,对于用户空间就只能是读了;为0表示用来读,此时用户空间就只能是写了。
第六个参数: force 标志告知 get_user_pages 来覆盖在给定页上的保护, 来提供要求的
权限; 驱动应当一直传递 0 在这里。
第七个参数: pages是这个函数的输出参数,映射完成后,pages是指向进程空间的页指针数组,如pages[1],下标最大是nr_pages-1。注意,内核要用pages,还需要映射成内核虚拟地址,一般用kmap(),kmap_atomic()
第八个参数: vmas也是个输出参数,vm_area_struct结构体,是linux用来管理虚拟内存的,映射完成后关于虚拟内存的信息就在这结构体里面。如果驱动不用,vmas可以是NULL。
返回值: 返回实际映射的页数,只会小于等于nr_pages。

映射:

  • 映射的时候要上进程读者锁:
    进程旗标(锁/信号量),通过current->mm->mmap_sem来获得。
    如:
down_read(&current->mm->mmap_sem);
result = get_user_pages(current, current->mm, ...);
up_read(&current->mm->mmap_sem);

如果内核映射的空间,用来写,写的时候要上进程写锁,在写的时候去读,就会被阻塞:

down_write(current->mm->mmap_sem);
...
//向映射的空间写数据
//up_write(current->mm->mmap_sem);

current->mm->page_table_lock.rlock也可以用这个自旋锁实现的读者写者,具体情况考虑。

释放映射:

if (! PageReserved(page))
    SetPageDirty(page);
page_cache_release(struct page *page);

PageReserved(): 判断是否为保留页,是保留页返回非0,不是返回0。在我们一般映射的页,没经过处理,都不是保留页,返回0。
SetPageDirty(): 简单来说,就是告诉系统这页被修改。

PageReserved(),SetPageDirty()定义在include/linux/page-flags.h,对他们的作用理解,兵不是很深,在一般的驱动中可有可无,安全起见,就按照上面形式放在哪里。

page_cache_release()定义在include/linux/pagemap.h,只能是一次释放一个page,多个page用循环多次调用。

思考:

这种映射,主要是用在进程直接I/O,进程直接读写用户空间内存,就可以达到读写I/O。但是也要具体情况看,相对这种映射对系统开销,还是比较大的。
get_user_pages的用法例子,可以看drivers/scsi/st.c

用这种映射来实现读者写者,进程是读者,驱动是写者。
在“linux驱动—file_operations异步读写aio_read、aio_write”这章,描述的异步例子基础上做,

原始例子如下:

struct kiocb *aki;
struct iovec *aiov;
loff_t aio_off = 0;
struct workqueue *aiowq;

void data_a(struct work_struct *work);
DECLARE_DELAYED_WORK(aio_delaywork,data_a);

ssize_t d_read(struct file *f, char __user *buf, size_t n, loff_t *off)
{
}
void data_a(struct work_struct *work)
{
    int ret = 0;
    ret = d_read(aki->ki_filp,aiov->iov->iov_base,n,&off);
    aio_complete(aki,ret ,0);
}
ssize_t d_aio_read(struct kiocb *ki, const struct iovec *iovs, unsigned long n, loff_t off)
{
    if(is_sync_kiocb(ki))
        return d_read(ki->ki_filp,iovs->iov->iov_base,n,&off);
    else
    {
        aki = ki;
        aiov = iovs;
        aio_off = off;
        queue_delayed_work(aiowq,aio_delaywork,100);
        return -EIOCBQUEUED;//一般都返回这个,
    }
}
void init_aio()
{
    aiowq= create_workqueue("aiowq");
}

用上get_user_pages()后,将变成:

#include <linux/list.h>
#include <linux/aio.h>
#include <linux/workqueue.h>
#include <linux/mm.h>

struct kiocb *aki;
//struct iovec *aiov;
//loff_t aio_off = 0;
struct workqueue *aiowq;
struct page **aiopages;



LIST_HEAD(custom_aa);

struct custom_async{
    list_head list;
    task_struct *tsk;
    struct page **pg;
    long page_num;
    ssize_t size;   
};

void data_a(struct work_struct *work);
DECLARE_DELAYED_WORK(aio_delaywork,data_a);
DECLARE_DELAYED_WORK(aio_delaywork_c,async_d_writedata);

struct file_operations {
    .owner = THIS_MODE,
    .read = d_read,
    .aio_read = d_aio_read,
    ...
    ..
};


ssize_t d_read(struct file *f, char __user *buf, size_t n, loff_t *off)
{
}
ssize_t async_d_writedata(struct work_struct *work)
{
    int i,n;
    void *p = NULL;
    struct page **temp = NULL;
    struct custom_async *t = NULL;
    struct list_head *tmp = NULL;
    list_for_each(custom_aa,tmp){
        t = container_of(tmp,custom_async,list);
        for(i=0;i<t->page_num;i++)
        {
            temp = t->pg;
            p = kmap(temp[i]);
            down_write(&t->tsk->mm->mmap_sem);
            ...
            //向p指定的空间写数据 ,   n为写了多少个字节数据
            ...
            t->size = n;
            up_write(&t->tsk->mm->mmap_sem);
            kunmap(aio_pages[i]);
        }
    }
    queue_delayed_work(aiowq,aio_delaywork_c,100);  
}
void data_a(struct work_struct *work)
{
    int ret = 0;
    struct custom_async *t;
    //ret = d_read(aki->ki_filp,aiov->iov->iov_base,n,&off);

    t = container_of(custom_aa.next,custom_async,list);
    async_d_writedata();     
    ret = t->size;
    aio_complete(aki,ret ,0);
}
ssize_t d_aio_read(struct kiocb *ki, const struct iovec *iovs, unsigned long n, loff_t off)
{   
    if(is_sync_kiocb(ki))
        return d_read(ki->ki_filp,iovs->iov->iov_base,n,&off);
    else
    {
        aki = ki;
        //aiov = iovs;
        //aio_off = off;
        struct custom_async *p;

        p = kmalloc(sizeof(struct custom_async));
        memset(p,0,sizeof(struct custom_async));
        INIT_LIST_HEAD(&p->list);
        list_add(&p->list,&custom_aa); 
        p->tsk = current;       
        down_read(&current->mm->mmap_sem);
        pagenum = get_user_pages(current,current->mm,iovs->iov->iov_base,
                        n/PAGE_SIZE+(n%PAGE_SIZE)?1:0,
                        1,0,p->pg,NULL);
        up_read(&current->mm->mmap_sem);       
        queue_delayed_work(aiowq,aio_delaywork,100);
        return -EIOCBQUEUED;//一般都返回这个,
    }
}
void init_aio()
{
    aiowq= create_workqueue("aiowq");
    INIT_LIST_HEAD(custom_aa);
}
void exit()
{
    int i;
    ...
    ..
    for(i=0;i<pagenum;i++)
    {
        if (! PageReserved(aiopages[i]))
            SetPageDirty(aiopages[i]);
        page_cache_release(aiopages[i]);
    }
    ...
    ..
}

上面的例子,进程都可以通过异步调用来申请映射,当进程收到一次异步调用完成后,不用再次异步调用,只需要读就可以,驱动会自动的向里面写数据。只要来异步调用申请过一次的进程,当有数据时,都会向每个进程空间写数据。典型的读者写者,进程是读者,驱动是写者。

上面例子,只是表达了处理逻辑,没有编译过,模块退出时,释放不完全。

对比一般的异步,内核是直接向进程空间写数据,不用在数据完成后,还需要一次copy,理论上会快些。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值