linux系统下关于进程映射空间,linux驱动-映射进程空间

简述:

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

接口:

接口要包含的头文件:

#include

函数接口:

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

#include

#include

#include

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;ipage_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;iif (! PageReserved(aiopages[i]))

SetPageDirty(aiopages[i]);

page_cache_release(aiopages[i]);

}

...

..

}

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值