Linux驱动开发——物理地址映射(②)

简介: Linux驱动开发——物理地址映射(②)

文章目录

用户空间下的物理地址映射

mmap系统调用函数

mmap系统调用的实现过程

对应的底层驱动的mmap接口

代码示例(使用mmap修改LED示例):


用户空间下的物理地址映射

前一篇讲述了利用imremap函数完成Linux内核空间下的物理地址映射到内核虚拟地址空间上。那么如何能够将外设的物理地址映射到用户空间下的虚拟地址呢,如果一旦完成将外设的物理地址映射到用户空间下的虚拟地址,那么用户就可以直接通过这种映射访问外设的物理地址。


  • 利用mmap函数完成用户空间到物理地址的映射。

mmap系统调用函数

系统中的“文件”其实就是软件上抽象出来的东西,研究文件就是在研究其中额数据。文件最终保存在EMMC(磁盘),也就是文件中的数据保存在EMMC(磁盘),与其说是研究操作文件,不如说是操作文件中数据对应的磁盘存储位置,磁盘的存储位置同样有对应的物理地址(例如EMMC的512字节开始存储uboot数据,那么512就是EMMC磁盘的一个起始物理地址)


20200104071420527.png

20200104071420527.png

例如:


void *addr;
int fd = open("a.txt", O_RDWR);
addr = mmap(0, 0x100, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
memcpy(addr, "hello", 5);//向映射的用户虚拟内存写入数据
//本质就是向文件a.txt写入数据


  • 表面上看是将文件a.txt和当前进程3G的MMAP虚拟内存映射区中的某块空闲的虚拟内存区域做映射,一旦完成这种映射,将来访问映射的用户虚拟内存就是在访问文件。
  • 本质就是将文件对应的磁盘的物理地址和当前进程的虚拟内存映射区中的某块空闲的虚拟内存区域做映射,一旦完成这种映射,应用程序访问映射的虚拟地址就是在访问对应的物理地址。mmap就是将虚拟地址和物理地址完成映射的函数。

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);


参数:


  • start:为0时是告诉内核来帮你在MMAP虚拟内存映射区找一块空闲的用户虚拟内存做映射。
  • length:映射的用户虚拟内存的大小。
  • prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起。
  • PROT_EXEC //页内容可以被执行
  • PROT_READ //页内容可以被读取
  • PROT_WRITE //页可以被写入
  • PROT_NONE //页不可访问
  • flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体。
  • MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
  • MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
  • MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
  • MAP_DENYWRITE //这个标志被忽略。
  • MAP_EXECUTABLE //同上
  • MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
  • MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
  • MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
  • MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。
  • MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。
  • MAP_FILE //兼容标志,被忽略。
  • MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
  • MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
  • MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
  • fd:有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。
  • off_toffset:被映射对象内容的起点。

返回值:


  • addr:就是映射的用户虚拟内存的起始地址。

mmap系统调用的实现过程

  • 应用程序调用mmap,首先是调用C库的mmap函数,C库的mmap函数中做两件事:

  • 保存mmap系统调用号到r7寄存器。
  • 调用svc指定触发软中断异常。
  • 至此,当前进程由用户空间“陷入”内核空间继续执行,首先跑到内核的异常向量表软中断的处理入口地址0x08。接着做以下三件事:
  • 从r7寄存器中取出之前保存的mmap系统调用号。
  • 以系统调用号为下标在系统调用表中找到对应的内核实现函数sys_mmap,然后调用sys_mmap。
  • 内核的sys_mmap将会做如下:
  • 首先在MMAP虚拟内存映射区中帮用户找到一块空闲的用户虚拟内存区域用来映射外设的物理地址。
  • 内核一旦找到空闲的用户虚拟内存区域后,内核会用struct vm_area_struct数据结构定义初始化一个对象来描述这块空闲用户虚拟内存的属性:
  • 内核的sys_mmap最后调用底层驱动的mmap接口并且将第二步创建的struct vm_area_struct对象的首地址传递给底层驱动的mmap接口,这样底层驱动的mmap接口可以通过首地址来访问空闲的用户虚拟内存的相关属性。
  • 底层驱动的mmap接口调用完毕,最终返回到用户空间,
  • 切记:底层驱动的mmap接口永远仅做一件事:就是将已知的物理地址和已知的空闲的用户虚拟地址做映射,一旦完成这种映射,将来应用程序在用户空间访问映射的用户虚拟地址就是在访问对应的物理地址。
struct vm_area_struct {
  unsigned long vm_start; //空闲的用户虚拟内存的首地址,就是mmap函数的返回值addr
  unsigned long vm_end; //空闲的用户虚拟内存的结束地址
  pgprot_t vm_page_prot;//空闲的用户虚拟内存的访问权限,就是PROT_READ|PROT_WRITE
  ...
};


对应的底层驱动的mmap接口

struct file_operations {
  int (*mmap) (struct file *file, struct vm_area_struct *vma);
};


接口功能:永远仅做只做一件事将已知的用户虚拟地址(内核已经帮你找好了嘛),和已知的物理地址(看芯片手册吗)做映射,一旦完成映射,将来硬件的操作都是在用户空间完成。类似:红娘

参数:


  • file:文件指针,指向内核创建的描述文件被打开以后的状态属性的一个对象。
  • vma:指向内核创建的用于描述空闲的用户虚拟内存的属性的一个对象,将来驱动的mmap接口可以通过此指针来获取空闲的用户
  • 虚拟内存的属性:
  • vma->vm_start:获取用户虚拟内存的起始地址
  • vma->vm_end:获取用户虚拟内存的结束地址
  • vma->vm_page_prot:获取用户虚拟内存的访问权限
int remap_pfn_range(
              struct vm_area_struct *vma,
              unsigned long addr,
              unsigned long pfn,
              unsigned long size,
              pgprot_t prot
          )


函数功能:最终完成地址映射

参数:


  • vma:指向内核创建的用于描述空闲的用户虚拟内存的属性的一个对象,将来驱动的mmap接口可以通过此指针来获取空闲的用户,虚拟内存的属性.
  • addr:已知的空闲的用户虚拟内存的起始地址, 就是vma->vm_start。
  • pfn:已知的物理地址>>12,例如:0xC001C000>>12,
  • size:空闲用户虚拟内存的大小, 就是:vma->vm_end-vma->vm_start。
  • prot:空闲用户虚拟内存的访问权限, 就是:vma->vm_page_prot。

代码示例(使用mmap修改LED示例):

使用mmap在用户空间下调用gpio管脚操作led灯的亮灭,修改后的具体实现在led_test.c中,而驱动成了一个桥梁作用。


  • led_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/mm.h> //mmap
//vma:指向内核创建的一个vm_area_struct对象
//此对象来描述内核帮你找的空闲的用户虚拟内存的属性
static int led_mmap(struct file *file,
                    struct vm_area_struct *vma)
{
    //1.切记:只做一件事:将物理地址映射到用户虚拟地址上
    //映射关系:物理地址0xC001C000和vm_start做了映射
    //将来硬件的操作都是在用户空间完成
    remap_pfn_range(vma, vma->vm_start,//起始用户虚拟地址 
                    0xC001C000>>12,//已知起始物理地址>>12
                    vma->vm_end-vma->vm_start,//大小
                    vma->vm_page_prot//权限
            );
    return 0; //执行成功返回0,执行失败返回负值
}
//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .mmap = led_mmap //地址映射接口
};
//定义初始化混杂设备对象
static struct miscdevice led_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "myled",
    .fops = &led_fops
};
static int led_init(void)
{
    //1.注册混杂设备到内核
    misc_register(&led_misc);
    return 0;
}
static void led_exit(void)
{
    //1.卸载混杂设备
    misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");


  • led_test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(int argc, char *argv[])
{
    int fd;
    void *gpiobase;
    //寄存器的用户虚拟地址
    unsigned long *gpiocout; 
    unsigned long *gpiocoutenb;
    unsigned long *gpiocaltfn0;
    if(argc != 2) {
        printf("用法:%s <on|off>\n", argv[0]);
        return -1;
    }
    //打开设备
    fd = open("/dev/myled", O_RDWR);
    if (fd < 0) {
        printf("打开设备失败!\n");
        return -1;
    }
    //利用mmap进行地址映射
    //结果:gpiobase=vm_start对应的物理地址就是0xC001C000
    gpiobase = mmap(0, 0x100, PROT_READ|PROT_WRITE,
                        MAP_SHARED, fd, 0);
    gpiocout = (unsigned long *)(gpiobase + 0x00);
    gpiocoutenb = (unsigned long *)(gpiobase + 0x04);
    gpiocaltfn0 = (unsigned long *)(gpiobase + 0x20);
    //3.配置引脚为GPIO功能,配置为输出,输出1(省电)
    *gpiocaltfn0 &= ~(0x3 << 24);
    *gpiocaltfn0 |= (1 << 24);
    *gpiocoutenb |= (1 << 12);
    *gpiocout |= (1 << 12);
    //4.根据用户命令开关灯
    if(!strcmp(argv[1], "on"))
        *gpiocout &= ~(1 << 12);
    else 
        *gpiocout |= (1 << 12);
    //解除地址映射
    munmap(gpiobase, 0x100);
    //关闭设备
    close(fd);
    return 0;
}


  • Makefile
obj-m += led_drv.o
all:
  make -C /opt/kernel SUBDIRS=$(PWD) modules
clean:
  make -C /opt/kernel SUBDIRS=$(PWD) clean


  • 执行结果:


2020010323322059.png


相关文章
|
3月前
|
缓存 监控 Linux
在Linux中,如何看当前系统有几颗物理CPU和每颗CPU的核数?
在Linux中,如何看当前系统有几颗物理CPU和每颗CPU的核数?
|
4月前
|
存储 网络协议 Ubuntu
【Linux开发实战指南】基于UDP协议的即时聊天室:快速构建登陆、聊天与退出功能
UDP 是一种无连接的、不可靠的传输层协议,位于IP协议之上。它提供了最基本的数据传输服务,不保证数据包的顺序、可靠到达或无重复。与TCP(传输控制协议)相比,UDP具有较低的传输延迟,因为省去了建立连接和确认接收等过程,适用于对实时性要求较高、但能容忍一定数据丢失的场景,如在线视频、语音通话、DNS查询等。 链表 链表是一种动态数据结构,用于存储一系列元素(节点),每个节点包含数据字段和指向下一个节点的引用(指针)。链表分为单向链表、双向链表和循环链表等类型。与数组相比,链表在插入和删除操作上更为高效,因为它不需要移动元素,只需修改节点间的指针即可。但访问链表中的元素不如数组直接,通常需要从
261 2
|
27天前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
75 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
2月前
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】
|
3月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
43 6
|
3月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
48 5
|
3月前
|
存储 监控 安全
Linux存储安全:物理安全基石
【8月更文挑战第17天】在数字化时代,数据安全至关重要。Linux存储安全的物理防护作为基石,通过选择安全的数据中心、实施严格的访问控制、环境监控、物理隔离及设备锁定等措施,有效防范未授权访问和环境威胁。结合具体实施方法与案例代码,能大幅提升系统的物理安全性,确保数据安全无虞。
55 10
|
3月前
|
编解码 安全 Linux
基于arm64架构国产操作系统|Linux下的RTMP|RTSP低延时直播播放器开发探究
这段内容讲述了国产操作系统背景下,大牛直播SDK针对国产操作系统与Linux平台发布的RTMP/RTSP直播播放SDK。此SDK支持arm64架构,基于X协议输出视频,采用PulseAudio和Alsa Lib处理音频,具备实时静音、快照、缓冲时间设定等功能,并支持H.265编码格式。此外,提供了示例代码展示如何实现多实例播放器的创建与管理,包括窗口布局调整、事件监听、视频分辨率变化和实时快照回调等关键功能。这一技术实现有助于提高直播服务的稳定性和响应速度,适应国产操作系统在各行业中的应用需求。
108 3
|
4月前
|
Web App开发 缓存 Linux
FFmpeg开发笔记(三十六)Linux环境安装SRS实现视频直播推流
《FFmpeg开发实战》书中第10章提及轻量级流媒体服务器MediaMTX,适合测试RTSP/RTMP协议,但不适合生产环境。推荐使用SRS或ZLMediaKit,其中SRS是国产开源实时视频服务器,支持多种流媒体协议。本文简述在华为欧拉系统上编译安装SRS和FFmpeg的步骤,包括安装依赖、下载源码、配置、编译以及启动SRS服务。此外,还展示了如何通过FFmpeg进行RTMP推流,并使用VLC播放器测试拉流。更多FFmpeg开发内容可参考相关书籍。
103 2
FFmpeg开发笔记(三十六)Linux环境安装SRS实现视频直播推流
|
4月前
|
Linux
FFmpeg开发笔记(三十四)Linux环境给FFmpeg集成libsrt和librist
《FFmpeg开发实战》书中介绍了直播的RTSP和RTMP协议,以及新协议SRT和RIST。SRT是安全可靠传输协议,RIST是可靠的互联网流传输协议,两者于2017年发布。腾讯视频云采用SRT改善推流卡顿。以下是Linux环境下为FFmpeg集成libsrt和librist的步骤:下载安装源码,配置、编译和安装。要启用这些库,需重新配置FFmpeg,添加相关选项,然后编译和安装。成功后,通过`ffmpeg -version`检查版本信息以确认启用SRT和RIST支持。详细过程可参考书中相应章节。
88 1
FFmpeg开发笔记(三十四)Linux环境给FFmpeg集成libsrt和librist