例说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 ...
随机推荐
- thymeleaf 教程
html页面 添加 <html xmlns:th="http://www.thymeleaf.org" > html原有标签都可以用thymeleaf标签替换 1.t ...
- minixml的安装教程
最近想要实现对xml的解析,上网看到有很多库可以直接调用,例如minixml,tinyxml等,我选择了minixml(没有原因,纯属是因为这个找到了中文文档- -) 附上中文文档链接:https:/ ...
- HTML5学习笔记简明版(11):新增的API
HTMLDocument上的扩展 HTML5在DOM Level 2 HTML上扩展了一些HTMLDocument的接口.这些接口在全部实现了Document接口的元素对象上进行了实现. HTML5在 ...
- Windows 7: Update is not applicable to your computer
https://www.sevenforums.com/windows-updates-activation/119088-update-not-applicable-your-computer.ht ...
- numpy 数据类型与 Python 原生数据类型
查看 numpy 数据类型和 Python 原生数据类型之间的对应关系: In [51]: dict([(d, type(np.zeros(1,d).tolist()[0])) for d in (n ...
- 2015北京网络赛 J Scores bitset+分块
2015北京网络赛 J Scores 题意:50000组5维数据,50000个询问,问有多少组每一维都不大于询问的数据 思路:赛时没有思路,后来看解题报告也因为智商太低看了半天看不懂.bitset之前 ...
- codeforces 357C Knight Tournament(set)
Description Hooray! Berl II, the king of Berland is making a knight tournament. The king has already ...
- 温习 socket http tcp
Socket是一个接口,可以实现TCP或者UDP的传输HTTP是协议 资料: 1.TCP/IP连接 手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接. ...
- JS取出两个数组的不同或相同元素
JS合并两个数组的方法 我们在项目过程中,有时候会遇到需要将两个数组合并成为一个的情况.比如: var a = [1,2,3]; var b = [4,5,6]; 有两个数组a.b,需求是将两个数组合 ...
- U-BOOT启动流程分析--start.s(二)
一.概述 u-boot的启动流程: 从文件层面上看主要流程是在两个文件中:cpu/arm920t/start.s,lib_arm/board.c, 先来分析start.s 在flash中执行的引 ...