例说linux内核与应用数据通信(四):映射设备内核空间到用户态

【版权声明:尊重原创,转载请保留出处: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!
#

能够看到字符设备驱动的内核空间被成功映射到用户态,如今用户空间的一段内存关联到设备内存上。对用户空间的读写就相当于对字符设备的读写。

本节源代码下载:

http://download.csdn.net/detail/gentleliu/9035831

时间: 2024-08-06 12:25:02

例说linux内核与应用数据通信(四):映射设备内核空间到用户态的相关文章

Linux 设备驱动之 UIO 用户态驱动优缺点分析

[摘要]linux用户态的设备驱动开发:并不是所有的设备驱动程序都要在内核编写,有些情况下,在用户空间编写驱动程序能够更好地解决遇到的问题.本文对用户态驱动优缺点进行分析. 1.用户空间驱动程序的优点 1.可以和整个C库链接. 2.在驱动中可以使用浮点数,在某些特殊的硬件中,可能需要使用浮点数,而linux内核并不提供浮点数的支持.如果能在用户态实现驱动,就可以轻松解决这一问题. 3.驱动问题不会导致整个系统挂起.内核态驱动的一些错误常常导致整个系统挂起. 4.用户态的驱动调试方便. 5.可以给

操作系统--用户空间和内核空间,用户态和内核态

内核空间和用户空间,内核态和用户态(转载) 内核空间和用户空间Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的虚拟地址空间也为0-4G.Linux内核将这4G字节的空间分为两部分.将最高的1G字节(从虚拟地址 0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”.而将较低的 3G字节(从虚拟地址 0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间).因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有

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

[版权声明:尊重原创.转载请保留出处:blog.csdn.net/shallnet.文章仅供学习交流,请勿用于商业用途] 本系列通过源代码演示样例解说linux内核态与用户态数据通信的各种方式: 例说linux内核与应用数据通信(一):加入一个系统调用 例说linux内核与应用数据通信(二):proc虚拟文件系统 例说linux内核与应用数据通信(三):读写内核设备驱动文件 例说linux内核与应用数据通信(四):映射设备内核空间到 (未完待续.. ... . )

Linux 内核空间与用户空间

本文以 32 位系统为例介绍内核空间(kernel space)和用户空间(user space). 内核空间和用户空间 对 32 位操作系统而言,它的寻址空间(虚拟地址空间,或叫线性地址空间)为 4G(2的32次方).也就是说一个进程的最大地址空间为 4G.操作系统的核心是内核(kernel),它独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限.为了保证内核的安全,现在的操作系统一般都强制用户进程不能直接操作内核.具体的实现方式基本都是由操作系统将虚拟地址空间划分

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

原文:https://blog.csdn.net/buptapple/article/details/21454167 Linux探秘之用户态与内核态-----------https://www.cnblogs.com/bakari/p/5520860.html 1.特权级 Intel x86架构的cpu一共有0-4四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查.硬件已经提供了一套特权级使用的相关机制,软件自然要好好利用,这属于操作系统要做的事情,对于

用户态与内核态之间的切换

时间:2014.06.08 地点:基地 说明:本文由网上资料整理而成 -------------------------------------------------------------------------------------- 一.用户态与内核态 程序在运行时会消耗操作系统的物理资源,比如在创建新进程时涉及物理内存的分配,从父进程拷贝相关信息,拷贝设置页目录.页表等.这些都涉及很底层的操作,不可随便让程序去做,而是由更高级的程序完成,以达到对资源的集中管理,减少冲突.在Linux

用户态与内核态的切换

内核态与用户态的理解: 2)特权级 熟悉Unix/Linux系统的人都知道,fork的工作实际上是以系统调用的方式完成相应功能的,具体的工作是由sys_fork负责实施.其实无论是不是Unix或者Linux,对于任何操作系统来说,创建一个新的进程都是属于核心功能,因为它要做很多底层细致地工作,消耗系统的物理资源,比如分配物理内存,从父进程拷贝相关信息,拷贝设置页目录页表等等,这些显然不能随便让哪个程序就能去做,于是就自然引出特权级别的概念,显然,最关键性的权力必须由高特权级的程序来执行,这样才可

关于内核态用户态和信号的思考(其中中断上下段没有看懂)

终于搞懂用户态内核态以及中断.信号的上下文切换关系了,处于内核态的时候用户态的上下文保存在内核栈中,此时如果发生中断或者切换,是不会区分进程处于用户态还是内核态的,直接切之,软中断导致的是内核态和用户态的转化,也即是用户上下文到内核上下文的转化,而中断导致的是用户态或者内核态上下文到中断上下文的转化,进程切换导致的是用户态/内核态上下文到用户态/内核态上下文的转化.关键是每个进程都对应一个用户栈和内核栈. 扩展阅读: http://19880512.blog.51cto.com/936364/2

【APUE】用户态与内核态的区别

当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态).此时处理器处于特权级最高的(0级)内核代码中 执行.当进程处于内核态时,执行的内核代码会使用当前进程的内核栈.每个进程都有自己的内核栈.当进程在执行用户自己的代码时,则称其处于用户运行态(用 户态).即此时处理器在特权级最低的(3级)用户代码中运行. 内核态与用户态是操作系统的两种运行级别,跟intel cpu没有必然的联系, intel cpu提供Ring0-Ring3三种级别的运行模式,Rin