例说linux内核与应用数据通信(四):映射设备内核空间到用户态
一个进程的内存映象由以下几部分组成:代码段、数据段、BSS段和堆栈段。以及内存映射的区域等部分,内存映射函数mmap(),
负责把文件内容映射到进程的虚拟内存空间, 通过对这段内存的读取和改动。来实现对文件的读取和改动,而文件能够是设备驱动文件节点。
通过把内核驱动的内存空间映射到应用层。能够实现应用和内核空间的数据交换。
linux用户态程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备。
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
事实上在在我们调用系统调用mmap时,内核中的sys_mmap函数首先依据用户提供给mmap的參数(如起始地址、空间大小、行为修饰符等)创建新的vma。然后再调用对应文件的file_operations中的mmap函数。
/**
* remap_pfn_range - remap kernel memory to userspace
* @vma: user vma to map to
* @addr: target user address to start at
* @pfn: physical address of kernel memory
* @size: size of map area
* @prot: page protection flags for this mapping
*
* Note: this is only safe if the mm semaphore is held when called.
*/
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
unsigned long pfn, unsigned long size, pgprot_t prot)
当中vma为虚拟内存区域。在一定范围内的页将被映射到该区域内。
prot为新VMA要求的“保护”属性。
以下看一看file_operations中的mmap成员的实现:
static struct vm_operations_struct sln_remap_vm_ops = {
.open = sln_vma_open,
.close = sln_vma_close
}; static int chrmem_dev_mmap(struct file*filp, struct vm_area_struct *vma)
{
struct mem_dev *dev = filp->private_data; if (remap_pfn_range(vma,vma->vm_start,virt_to_phys(dev->data)>>PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot))
return -EAGAIN; vma->vm_ops = &sln_remap_vm_ops; sln_vma_open(vma);
return 0;
}
该函数中函数page_to_pfn(shm_page)将表示物理页面的page结构转换为其相应的页帧号。该字符设备驱动的主要思想是建立一个字符设备,在它的驱动程序中申请一块物理内存区域,并利用mmap将这段物理内存区域映射到进程的地址空间中。该驱动源代码例如以下:
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h> #include <linux/kernel.h>
#include "chr_memdev.h" int chrmem_major;
struct chrmem_dev *chrmem_devp; int chrmem_open(struct inode *inode, struct file *filp)
{
filp->private_data = chrmem_devp; return 0;
} ...... void sln_vma_open(struct vm_area_struct *vma)
{
printk("===vma_open: %s===\n", chrmem_devp->data);
} void sln_vma_close(struct vm_area_struct *vma)
{
printk("===vma_close: %s===\n", chrmem_devp->data);
} static struct vm_operations_struct sln_remap_vm_ops = {
.open = sln_vma_open,
.close = sln_vma_close
}; int chrmem_release(struct inode *inode, struct file *filp)
{
return 0;
} static int chrmem_dev_mmap(struct file*filp, struct vm_area_struct *vma)
{
struct chrmem_dev *dev = filp->private_data; if (remap_pfn_range(vma,vma->vm_start,virt_to_phys(dev->data)>>PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot))
return -EAGAIN; vma->vm_ops = &sln_remap_vm_ops; sln_vma_open(vma);
return 0;
} static const struct file_operations chrmem_fops =
{
.owner = THIS_MODULE,
.open = chrmem_open,
.release = chrmem_release,
.read = chrmem_read,
.write = chrmem_write,
.llseek = chrmem_llseek,
.ioctl = chrmem_ioctl,
.mmap = chrmem_dev_mmap }; static int chrmem_dev_init(void)
{
int result;
dev_t devno; /* 分配设备号 */
result = alloc_chrdev_region(&devno, 0, 1, "chrmem_dev");
if (result < 0) {
return result;
} // 为自己定义设备结构体分配内存空间
mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
if (!mem_devp) {
result = - ENOMEM;
goto err;
}
memset(mem_devp, 0, sizeof(struct mem_dev)); /*初始化字符设备*/
cdev_init(&mem_devp->cdev, &mem_fops);
mem_devp->cdev.owner = THIS_MODULE; /*加入注冊字符设备 */
mem_major = MAJOR(devno);
cdev_add(&mem_devp->cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS); /*初始化自己定义设备数据内容*/
mem_devp->data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
memset(mem_devp->data, '*', MEMDEV_SIZE / 100 ); return 0; err:
unregister_chrdev_region(devno, 1); return result;
} static int chrmem_dev_init(void)
{
int result;
dev_t devno; /* 分配设备号 */
result = alloc_chrdev_region(&devno, 0, 1, "chrmem_dev");
if (result < 0) {
return result;
} // 为自己定义设备结构体分配内存空
chrmem_devp = kmalloc(CHR_MEMDEV_NUM * sizeof(struct chrmem_dev), GFP_KERNEL);
if (!chrmem_devp) {
result = - ENOMEM;
goto err;
}
memset(chrmem_devp, 0, sizeof(struct chrmem_dev)); /*初始化字符设备*/
cdev_init(&chrmem_devp->cdev, &chrmem_fops);
chrmem_devp->cdev.owner = THIS_MODULE; /*加入注冊字符设备 */
chrmem_major = MAJOR(devno);
cdev_add(&chrmem_devp->cdev, MKDEV(chrmem_major, 0), CHR_MEMDEV_NUM); /*初始化自己定义设备数据内容*/
chrmem_devp->data = kmalloc(CHR_MEMDEV_DATA_SIZE, GFP_KERNEL);
memset(chrmem_devp->data, '*', CHR_MEMDEV_DATA_SIZE / 100 ); return 0; err:
unregister_chrdev_region(devno, 1); return result;
} static void chrmem_dev_exit(void)
{
cdev_del(&chrmem_devp->cdev); //delete device
kfree(chrmem_devp); // release device memory
unregister_chrdev_region(MKDEV(chrmem_major, 0), 1); // unregister char device No.
} module_init(chrmem_dev_init);
module_exit(chrmem_dev_exit); MODULE_LICENSE("GPL");
MODULE_AUTHOR("shallnet");
MODULE_DESCRIPTION("blog.csdn.net/shallnet");
在应用程序中调用mmap来实现内存映射,应用程序代码例如以下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h> #include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h> #define SHR_MEMSIZE 4096
#define MEM_CLEAR 0x0
#define MEM_RESET 0x1
#define MEM_DEV_FILENAME "/dev/sln_memdev" int main()
{
int fd;
char *shm = NULL; fd = open(MEM_DEV_FILENAME, O_RDWR);
if (fd < 0) {
printf("open(): %s\n", strerror(errno));
return -1;
} shm = mmap(NULL, SHR_MEMSIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (MAP_FAILED == shm) {
printf("mmap: %s\n", strerror(errno));
} printf("Before Write, shm = %s\n", shm); strcpy(shm,"User write to share memory!"); printf("After write, shm = %s\n", shm); if (0 > ioctl(fd, MEM_CLEAR, NULL)) {
printf("ioctl: %s\n", strerror(errno));
return -1;
} printf("After clear, shm = %s\n", shm); if (0 > ioctl(fd, MEM_RESET, NULL)) {
printf("ioctl: %s\n", strerror(errno));
return -1;
}
printf("After reset, shm = %s\n", shm); munmap(shm, SHR_MEMSIZE);
close(fd);
return 0;
}
应用程序在实现映射之后,首先读取输出共享内存内容,然后写入。然后清空该共享内存内容以及重设共享内存。
在编译驱动和应用程序之后首先插入驱动。在创建设备节点,最后执行应用程序看是否成功。例如以下:
# insmod memdev.ko
# cat /proc/devices | grep chrmem_dev
248 chrmem_dev
# mknod /dev/sln_memdev c 248 0
# ls
app app_read drv Makefile mem_app memdev.ko read_app
# ./mem_app
Before Write, shm = ****************************************
After write, shm = User write to share memory!
After clear, shm =
After reset, shm = hello, user!
#
能够看到字符设备驱动的内核空间被成功映射到用户态,如今用户空间的一段内存关联到设备内存上。对用户空间的读写就相当于对字符设备的读写。
例说linux内核与应用数据通信(四):映射设备内核空间到用户态的更多相关文章
- 例说linux内核与应用数据通信系列【转】
转自:http://blog.csdn.net/shallnet/article/details/47865169 版权声明:本文为博主原创文章,未经博主允许不得转载.如果您觉得文章对您有用,请点击文 ...
- 例说linux内核与应用数据通信系列
[版权声明:尊重原创.转载请保留出处:blog.csdn.net/shallnet.文章仅供学习交流,请勿用于商业用途] 本系列通过源代码演示样例解说linux内核态与用户态数据通信的各种方式: 例说 ...
- Linux 内核空间与用户空间
本文以 32 位系统为例介绍内核空间(kernel space)和用户空间(user space). 内核空间和用户空间 对 32 位操作系统而言,它的寻址空间(虚拟地址空间,或叫线性地址空间)为 4 ...
- (转)linux用户态和内核态理解
原文:https://blog.csdn.net/buptapple/article/details/21454167 Linux探秘之用户态与内核态-----------https://www.cn ...
- linux 用户态和内核态以及进程上下文、中断上下文 内核空间用户空间理解
1.特权级 Intel x86架构的cpu一共有0-4四个特权级,0级最高,3级最低,ARM架构也有不同的特权级,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查.硬件已经提 ...
- linux用户态和内核态理解
1.特权级 Intel x86架构的cpu一共有0-4四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查.硬件已经提供了一套特权级使用的相关机制 ...
- Linux操作系统,为什么需要内核空间和用户空间?
点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 本文以 32 位系统为例介绍内核空间(kernel sp ...
- linux内核态和用户态小结
一 内核态和用户态的区别 当进程执行系统调用而陷入内核代码中执行时,我们就称进程处于内核状态.此时处理器处于特权级最高的(0级)内核代码.当进程处于内核态时,执行的内核代码会使用当前的内核栈.每个进程 ...
- Linux内存管理 —— 内核态和用户态的内存分配方式
1. 使用buddy系统管理ZONE我的这两篇文章buddy系统和slab分配器已经分析过buddy和slab的原理和源码,因此一些细节不再赘述.所有zone都是通过buddy系统管理的,buddy ...
随机推荐
- Android如何从外部跳进App
博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved ! 这个问题解决了两天时间,因为网上没有完整的解决方案,解决后分享 ...
- XWIKI的搭建
原文地址:https://my.oschina.net/gywbest/blog/780569 一 应用背景描述 在平时的运维工作中,把常规工作进行文档整理非常重要,无论是平时工作处理或是工作交接,实 ...
- BZOJ1901 ZOJ2112 线段树+treap (线段树套线段树)
BZOJ1901: 线段树套线段树做法: (外层线段树 里层动态开节点的权值线段树) 有一个小小的trick 可以省掉二分变成nlog^2n的 就是把查询的区间都取出来- logn个一起走- 2016 ...
- Sqoop Import原理和详细流程讲解
Sqoop Import原理 Sqoop Import详细流程讲解 Sqoop在import时,需要指定split-by参数.Sqoop根据不同的split-by参数值来进行切分,然后将切分出来的区域 ...
- ASP.net Web API允许跨域访问解决办法
来源 http://blog.csdn.net/wxg_kingwolfmsncn/article/details/48545099 遇到此跨域访问问题,解决办法如下: 方法一: 1. 在we ...
- sql中的 SET QUOTED_IDENTIFIER OFF、SET ANSI_NULLS ON
代码 Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-- ...
- 整个shuffle的流程图
整个shuffle的流程图 Paste_Image.png Map Shuffle的作用以及相应的设置 partition 过程:输入的<key,value>对经过map()处理后输出 ...
- 反斜杠处理函数addslashes()和stripslashes()
addslashes():对输入字符串中的某些预定义字符前添加反斜杠,这样处理是为了数据库查询语句等的需要.这些预定义字符是:单引号 (') ,双引号 (") ,反斜杠 (\) ,NULL. ...
- javascript 基础篇 随课笔记
!DOCTYPE HTML><html><head><meta name="content-type" content="text/h ...
- vim7.4官方源码在vs2013的编译方法及问题总结
vim7.4发布也有一段时候了,也该是把之前编译的7.3重新编译一下了,于是考虑着到最新的visual studio 2013编译一下,也顺便看看有没有其它问题. 1.安装vs2013,这个应该不用说 ...