本期主题:
linux下操作寄存器
往期链接:
- linux设备驱动中的并发
- linux设备驱动中的编译乱序和执行乱序
- linux设备驱动之内核模块
- linux字符驱动
- linux字符驱动之ioctl部分
- linux字符驱动之read、write部分
- linux驱动调试之Debugfs
文章目录
- 1.为什么有这个问题
- 1.内存空间与I/O空间
- 2.内存管理单元
- 3.Linux内存管理
- 2.用户空间下怎么访问寄存器
- 1.mmap原理
- 2.mmap函数原型
- 3.mmap使用例子
- 3.内核空间下怎么访问寄存器
- 1.ioremap
- 2.实际例子
1.为什么有这个问题
Linux开发与裸机开发不同,在裸机开发中直接使用指针方式就能访问到对应地址,但是在Linux中,由于
内存管理
的存在,并不能这么粗暴的直接用指针进行访问。
那么Linux下的内存管理是怎样的呢?linux内存管理相当复杂,本篇文章只能简单介绍一部分:
1.内存空间与I/O空间
- I/O空间: 在x86处理器中存在着I/O空间的概念,I/O空间是相对于内存空间而言的,通过特定的指令 in、out来访问,端口号标识了外设的寄存器地址;
//intel的in指令格式如下: IN 累加器, { 端口号 | DX}
- 内存空间: 大多数的
嵌入式微控制器(如ARM、PowerPC等)并不提供I/O空间,仅存在内存空间
,可以直接通过地址、指针来进行访问;
下图给出了内存空间和I/O空间的对比:
从上图可以看出,内存空间是必须的,而I/O空间是可选的。
2.内存管理单元
高性能处理器一般会提供一个内存管理单元MMU,该单元辅助操作系统进行内存管理。
提供虚拟地址和物理地址映射、内存访问权限保护和cache缓存控制等硬件支持。
3.Linux内存管理
对于包含MMU的处理器,linux提供了复杂的存储管理系统,(以32位处理器为例),进程所能够访问的空间达到4GB。
在Linux系统中,进程的4GB内存空间被分为两个部分——用户空间和内核空间,具体的描述可以看这个链接:操作系统系列(二)——进程
2.用户空间下怎么访问寄存器
用户空间下我们对于寄存器的访问可以使用 mmap接口
1.mmap原理
mmap是一种
内存映射文件的方法
,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。
2.mmap函数原型
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
参数:
start:映射区的开始地址
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:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1
offset:被映射对象内容的起点
3.mmap使用例子
以操作一个地址为0xA4030000的寄存器为例
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#deinfe REG_BASE 0xA4030000
int main(void)
{
int fd;
void *map_reg_base;
// map to register base and memory
fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd) {
DEBUG("Success to open /dev/mem fd=%08x\n", fd);
} else {
DEBUG("Fail to open /dev/mem fd=%08x\n", fd);
}
//这里的map_reg_base就是被mmap过后的指针,可以直接操作了
map_reg_base = mmap(0, 0x400, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, REG_BASE);
}
3.内核空间下怎么访问寄存器
1.ioremap
在内核中访问I/O内存(通常是一些控制器的寄存器)之前,需要使用ioremap()函数将设备所处的物理地址映射到虚拟地址上。
函数原型:
void __iomem *ioremap(resource_size_t phys_addr, unsigned long size)
/**
* ioremap - map bus memory into CPU space
* @phys_addr: bus address of the memory
* @size: size of the resource to map
*
* ioremap performs a platform specific sequence of operations to
* make bus memory CPU accessible via the readb/readw/readl/writeb/
* writew/writel functions and the other mmio helpers. The returned
* address is not guaranteed to be usable directly as a virtual
* address.
*
* This version of ioremap ensures that the memory is marked uncachable
* on the CPU as well as honouring existing caching rules from things like
* the PCI bus. Note that there are other caches and buffers on many
* busses. In particular driver authors should read up on PCI writes
*
* It's useful if some control registers are in such an area and
* write combining or read caching is not desirable:
*
* Must be freed with iounmap.
*/
2.实际例子
使用ioremap和不使用ioremap进行对比:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <asm/io.h>
#define USE_IOREMAP
#define H3_GPIO_BASE (0x01C20800)
static volatile unsigned long *gpio_regs = NULL;
static int __init ioremap_mod_init(void)
{
int i = 0;
printk(KERN_INFO "ioremap_mod init\n");
#ifdef USE_IOREMAP
gpio_regs = (volatile unsigned long *)ioremap(H3_GPIO_BASE, 1024);
#else
gpio_regs = (volatile unsigned long *)H3_GPIO_BASE;
#endif
for (i=0; i<3; i++)
printk(KERN_INFO "reg[%d] = %lx\n", i, gpio_regs[i]);
return 0;
}
module_init(ioremap_mod_init);
static void __exit ioremap_mod_exit(void)
{
printk(KERN_INFO "ioremap_mod exit\n ");
#ifdef USE_IOREMAP
iounmap(gpio_regs);
#endif
}
module_exit(ioremap_mod_exit);
MODULE_AUTHOR("es-hacker");
MODULE_LICENSE("GPL v2");
实验结果:
1.使用ioremap
$ insmod ioremap
ioremap_mod init
reg[0] = 71227722
reg[1] = 33322177
reg[2] = 773373
2.不使用ioremap
$ insmod ioremap_mod.ko
Unable to handle kernel paging request at virtual address 01c20800
pgd = c9ece7c0
[01c20800] *pgd=6ddd7003, *pmd=00000000
Internal error: Oops: 206 [#1] SMP ARM
CPU: 1 PID: 1253 Comm: insmod Tainted: G O 4.14.111 #116
Hardware name: sun8i
task: ef15d140 task.stack: edc50000
PC is at ioremap_mod_init+0x3c/0x1000 [ioremap_mod]
LR is at ioremap_mod_init+0x14/0x1000 [ioremap_mod]
pc : [<bf5d903c>] lr : [<bf5d9014>] psr: 600e0013
sp : edc51df8 ip : 00000007 fp : 118fa95c
r10: 00000001 r9 : ee7056c0 r8 : bf5d6048
r7 : bf5d6000 r6 : 00000000 r5 : bf5d6200 r4 : 00000000
r3 : 01c20800 r2 : 01c20800 r1 : 00000000 r0 : bf5d5048
Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user
Control: 30c5387d Table: 49ece7c0 DAC: b106c794
Process insmod (pid: 1253, stack limit = 0xedc50210)
[<bf5d903c>] (ioremap_mod_init [ioremap_mod]) from [<c0201a70>] (do_one_initcall+0x40/0x16c)
[<c0201a70>] (do_one_initcall) from [<c02b20c8>] (do_init_module+0x60/0x1f0)
[<c02b20c8>] (do_init_module) from [<c02b1214>] (load_module+0x1b48/0x2250)
[<c02b1214>] (load_module) from [<c02b1ad8>] (SyS_finit_module+0x8c/0x9c)
[<c02b1ad8>] (SyS_finit_module) from [<c0221f80>] (ret_fast_syscall+0x0/0x4c)
Code: e5953000 e3050048 e1a01004 e34b0f5d (e7932104)
---[ end trace 928c64a33a054308 ]---
Segmentation fault