【版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet。文章仅供学习交流,请勿用于商业用途】

        一个进程的内存映象由以下几部分组成:代码段、数据段、BSS段和堆栈段。以及内存映射的区域等部分,内存映射函数mmap(),
负责把文件内容映射到进程的虚拟内存空间, 通过对这段内存的读取和改动。来实现对文件的读取和改动,而文件能够是设备驱动文件节点。

通过把内核驱动的内存空间映射到应用层。能够实现应用和内核空间的数据交换。

        linux设备分三种,字符设备、块设备、网络接口设备。每个字符设备或块设备都在/dev文件夹下相应一个设备文件。

linux用户态程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备。

        本节使用字符设备驱动为例来实现映射。有关字符设备驱动相关内容可參考作者这篇文章:

http://blog.csdn.net/shallnet/article/details/17734309

实现内存映射的关键在于实现字符设备驱动的mmap()函数,mmap()函数原型为:

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
该函数负责把文件内容映射到进程的虚拟地址空间,通过对这段内存的读取和改动来实现对文件的读取和改动,而不须要再调用read和write;

mmap方法是file_operations结构的成员,在mmap系统调用的发出时被调用。mmap设备方法所须要做的就是建立虚拟地址到物理地址的页表。

事实上在在我们调用系统调用mmap时,内核中的sys_mmap函数首先依据用户提供给mmap的參数(如起始地址、空间大小、行为修饰符等)创建新的vma。然后再调用对应文件的file_operations中的mmap函数。

进程虚拟地址空间相关内容可參考作者这篇文章:

http://blog.csdn.net/shallnet/article/details/47701225
      使用remap_pfn_range()函数将设备内存线性地映射到用户地址空间中。该函数原型为:

/**
* 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为虚拟内存区域。在一定范围内的页将被映射到该区域内。

addr为又一次映射时的起始地址。该函数为处于addr和addr+size之间的虚拟地址建立页表。

pfn为与物理内存对于的页帧号,页帧号仅仅是将物理地址右移PAGE_SHIFT位。

size为以字节为单位,被又一次映射的大小。

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内核与应用数据通信(四):映射设备内核空间到用户态的更多相关文章

  1. 例说linux内核与应用数据通信系列【转】

    转自:http://blog.csdn.net/shallnet/article/details/47865169 版权声明:本文为博主原创文章,未经博主允许不得转载.如果您觉得文章对您有用,请点击文 ...

  2. 例说linux内核与应用数据通信系列

    [版权声明:尊重原创.转载请保留出处:blog.csdn.net/shallnet.文章仅供学习交流,请勿用于商业用途] 本系列通过源代码演示样例解说linux内核态与用户态数据通信的各种方式: 例说 ...

  3. Linux 内核空间与用户空间

    本文以 32 位系统为例介绍内核空间(kernel space)和用户空间(user space). 内核空间和用户空间 对 32 位操作系统而言,它的寻址空间(虚拟地址空间,或叫线性地址空间)为 4 ...

  4. (转)linux用户态和内核态理解

    原文:https://blog.csdn.net/buptapple/article/details/21454167 Linux探秘之用户态与内核态-----------https://www.cn ...

  5. linux 用户态和内核态以及进程上下文、中断上下文 内核空间用户空间理解

    1.特权级         Intel x86架构的cpu一共有0-4四个特权级,0级最高,3级最低,ARM架构也有不同的特权级,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查.硬件已经提 ...

  6. linux用户态和内核态理解

    1.特权级         Intel x86架构的cpu一共有0-4四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查.硬件已经提供了一套特权级使用的相关机制 ...

  7. Linux操作系统,为什么需要内核空间和用户空间?

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 本文以 32 位系统为例介绍内核空间(kernel sp ...

  8. linux内核态和用户态小结

    一 内核态和用户态的区别 当进程执行系统调用而陷入内核代码中执行时,我们就称进程处于内核状态.此时处理器处于特权级最高的(0级)内核代码.当进程处于内核态时,执行的内核代码会使用当前的内核栈.每个进程 ...

  9. Linux内存管理 —— 内核态和用户态的内存分配方式

    1. 使用buddy系统管理ZONE我的这两篇文章buddy系统和slab分配器已经分析过buddy和slab的原理和源码,因此一些细节不再赘述.所有zone都是通过buddy系统管理的,buddy ...

随机推荐

  1. 紫书 习题 10-12 UVa 557(概率计算)

    开始的时候我没有考虑1/2的概率,直接一波组合数,然后WA 后来去看题解发现我们可以反过来想,求最后两个人不一样的情况 这个时候肯定会抛到最后的 所以每一种可能就是(0.5)^(n - 2),然后一共 ...

  2. 【Henu ACM Round#16 F】Om Nom and Necklace

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] KMP算法可以把"i前缀"pre[i] 分成ssssst的形式 这里t是s的前缀. 然后s其实就是pre[i]中 ...

  3. 新一代企业即时通信系统 -- 傲瑞通(OrayTalk)

    傲瑞通(OrayTalk)是我们为企业专门打造的新一代企业即时通讯平台,功能强大丰富.像组织结构.文字/语音/视频会话.文件传送.远程协助.消息记录等功能都有,而且留有接口可与企业遗留系统进行集成. ...

  4. BootstrapDialog模态框

    5最近是比较烦直接使用Bootstrap里面的模态框,满屏都是模态框代码,看得心烦.然后想起以前使用的BootstrapDialog.show()的方式,挺简单好用的.然后就拿出来分享一下. 1.下载 ...

  5. Android自定义组件系列【16】——最帅气的自动滚动广告条

    前一段时间要实现一个滚动的广告条,参考了一下网上许多实现,发现实现都很麻烦,所以我决定自己使用ViewFlipper来实现一个,在此将代码贴出来,与大家共享. 转载请说明出处:http://blog. ...

  6. Tomcat部署项目修改浏览器上猫咪头像

    一.发现问题用tomcat部署项目,在浏览器标签也上发现了tomcat猫咪图.要把这个图修改掉. 二.解决问题apache-tomcat-5.5.28\webapps\ROOT下的ico文件,将需要替 ...

  7. web.config添加identity impersonate="true"导致拒绝访问

    例:<identity impersonate="tr" userName="AD\name" password="word"/> ...

  8. MyEclipse的代码自动提示功能

     一般默认情况下,Eclipse ,MyEclipse的代码提示功能是比Microsoft Visual Studio的差很多的,主要是Eclipse ,MyEclipse本身有很多选项是默认关闭的, ...

  9. 紫书 例题 9-8 UVa 1625 (滚动数组+公共字符串处理)

    这题看题解看了很久,学到了挺多(自己还是太弱,唉!) (1)这道题的思路非常的巧妙.我一开始看到就觉得不好来记录开始位置以及 结束位置.但是题解换了一个思路,记录每一次开始了但还没有结束的字符有多少个 ...

  10. 手把手教你用vue-cli构建一个简单的路由应用

    上一章说道:十分钟上手-搭建vue开发环境(新手教程)https://www.jianshu.com/p/0c6678671635 开发环境搭建好之后,那么开始新添加一些页面,构建最基本的vue项目, ...