I/O 寄存器和常规内存
不管硬件寄存器和内存之间的强相似性, 存取 I/O 寄存器的程序员必须小心避免被 CPU(或者编译器)优化所戏弄, 它可能修改希望的 I/O 行为.
I/O 寄存器和 RAM 的主要不同是 I/O 操作有边际效果, 而内存操作没有: 一个内存写的 唯一效果是存储一个值到一个位置, 并且一个内存读返回最近写到那里的值. 因为内存存 取速度对 CPU 性能是至关重要的, 这种无边际效果的情况已被多种方式优化: 值被缓存, 并且 读/写指令被重编排.
编译器能够缓存数据值到 CPU 寄存器而不写到内存, 并且即便它存储它们, 读和写操作 都能够在缓冲内存中进行而不接触物理 RAM. 重编排也可能在编译器级别和在硬件级别都 发生: 常常一个指令序列能够执行得更快, 如果它以不同于在程序文本中出现的顺序来执 行, 例如, 为避免在 RISC 流水线中的互锁. 在 CISC 处理器, 要花费相当数量时间的操 作能够和其他的并发执行, 更快的.
当应用于传统内存时(至少在单处理器系统)这些优化是透明和有益的, 但是它们可能对正 确的 I/O 操作是致命的, 因为它们干扰了那些"边际效果", 这是主要的原因为什么一个 驱动存取 I/O 寄存器. 处理器无法预见这种情形, 一些其他的操作(在一个独立处理器上 运行, 或者发生在一个 I/O 控制器的事情)依赖内存存取的顺序. 编译器或者 CPU 可能 只尽力胜过你并且重编排你请求的操作; 结果可能是奇怪的错误而非常难于调试. 因此, 一个驱动必须确保没有进行缓冲并且在存取寄存器时没有发生读或写的重编排.
硬件缓冲的问题是最易面对的:底层的硬件已经配置(或者自动地或者通过 Linux 初始化 代码)成禁止任何硬件缓冲, 当存取 I/O 区时(不管它们是内存还是端口区域).
对编译器优化和硬件重编排的解决方法是安放一个内存屏障在必须以一个特殊顺序对硬件 (或者另一个处理器)可见的操作之间. Linux 提供 4 个宏来应对可能的排序需要:
#include <linux/kernel.h> void barrier(void)
这个函数告知编译器插入一个内存屏障但是对硬件没有影响. 编译的代码将所有的 当前改变的并且驻留在 CPU 寄存器的值存储到内存, 并且后来重新读取它们当需 要时. 对屏障的调用阻止编译器跨越屏障的优化, 而留给硬件自由做它的重编排.
#include <asm/system.h> void rmb(void);
void read_barrier_depends(void);
void wmb(void); void mb(void);
这些函数插入硬件内存屏障在编译的指令流中; 它们的实际实例是平台相关的. 一 个 rmb ( read memory barrier) 保证任何出现于屏障前的读在执行任何后续读之 前完成. wmb 保证写操作中的顺序, 并且 mb 指令都保证. 每个这些指令是一个屏 障的超集.
read_barrier_depends 是读屏障的一个特殊的, 弱些的形式. 而 rmb 阻止所有跨 越屏障的读的重编排, read_barrier_depends 只阻止依赖来自其他读的数据的读 的重编排. 区别是微小的, 并且它不在所有体系中存在. 除非你确切地理解做什么, 并且你有理由相信, 一个完整的读屏障确实是一个过度地性能开销, 你可能应当坚 持使用 rmb.
void smp_rmb(void);
void smp_read_barrier_depends(void); void smp_wmb(void);
void smp_mb(void);
屏障的这些版本仅当内核为 SMP 系统编译时插入硬件屏障; 否则, 它们都扩展为 一个简单的屏障调用.
在一个设备驱动中一个典型的内存屏障的用法可能有这样的形式:
writel(dev->registers.addr, io_destination_address); writel(dev->registers.size, io_size);
writel(dev->registers.operation, DEV_READ); wmb();
writel(dev->registers.control, DEV_GO);
在这种情况, 是重要的, 确保所有的控制一个特殊操作的设备寄存器在告诉它开始前已被 正确设置. 内存屏障强制写以需要的顺序完成.
因为内存屏障影响性能, 它们应当只用在确实需要它们的地方. 屏障的不同类型也有不同 的性能特性, 因此值得使用最特定的可能类型. 例如, 在 x86 体系上, wmb() 目前什么 都不做, 因为写到处理器外不被重编排. 但是, 读被重编排, 因此 mb() 被 wmb() 慢.
值得注意大部分的其他的处理同步的内核原语, 例如自旋锁和原子的 _t 操作, 如同内存 屏障一样是函数. 还值得注意的是一些外设总线(例如 PCI 总线)有它们自己的缓冲问题; 我们在以后章节遇到时讨论它们.
一些体系允许一个赋值和一个内存屏障的有效组合. 内核提供了几个宏来完成这个组合; 在缺省情况下, 它们如下定义:
#define set_mb(var, value) do {var = value; mb();} while 0
#define set_wmb(var, value) do {var = value; wmb();} while 0
#define set_rmb(var, value) do {var = value; rmb();} while 0
在合适的地方, <asm/system.h> 定义这些宏来使用体系特定的指令来很快完成任务. 注 意 set_rmb 只在少量体系上定义. (一个 do...while 结构的使用是一个标准 C 用语, 来使被扩展的宏作为一个正常的 C 语句可在所有上下文中工作).
I/O 寄存器和常规内存的更多相关文章
- [汇编语言]-第九章 根据位移进行转移的jmp指令 段内短转移 段内近转移 段间转移(远转移) 转移的目的地址在指令中,在寄存器中,在内存中的jmp指令
1- jmp为无条件转移指令,可以只修改IP, 也可以同时修改CS和IP jmp指令要给出两种信息: (1) 转移的目的地址 (2) 转移的距离(段间转移, 段内转移, 段内近转移) 2- 依据位移进 ...
- DMA内存申请--dma_alloc_coherent 及 寄存器与内存【转】
转自:https://blog.csdn.net/ic_soc_arm_robin/article/details/8203933 在项目驱动过程中会经常用到dma传输数据,而dma需要的内存有自己的 ...
- 【转】Linux设备驱动之I/O端口与I/O内存
原文网址:http://www.cnblogs.com/geneil/archive/2011/12/08/2281367.html 一.统一编址与独立编址 该部分来自于:http://blog.ch ...
- Linux 设备驱动 Edition 3
原文网址:http://oss.org.cn/kernel-book/ldd3/index.html Linux 设备驱动 Edition 3 By Jonathan Corbet, Alessand ...
- LDD3 第9章 与硬件通信
一.I/O端口和I/O内存 每种外设都通过读写寄存器进行控制.大部分外设都有几个寄存器,不管是在内村地址空间还是在I/O地址空间,这些寄存器的访问地址都是连续的. 在硬件层,内存区域和I/O区域没有区 ...
- 汇编寄存器(内存访问)基础知识之三---mov指令
1 内存中字的存储 一个字型数据占2个内存单元,内存里面一个内存单元一个字节(8位),高地址单位放高8位,低地址单元放低8位. 注意:0号是地址单元,1是高地址单元(上是低地址,下面是高地址) (1 ...
- 计算机cpu、寄存器、内存区别
1.寄存器是中央处理器内的组成部份.它跟CPU有关.寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令.数据和位址.在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC). ...
- 内存,寄存器和cache的区别与联系
1. 寄存器是中央处理器内的组成部份.寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令.数据和位址.在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC).在中央处理器的 ...
- 内存管理内幕mallco及free函数实现
原文:https://www.ibm.com/developerworks/cn/linux/l-memory/ 为什么必须管理内存 内存管理是计算机编程最为基本的领域之一.在很多脚本语言中,您不必担 ...
随机推荐
- ImmutableMap不可使用null的问题
示例 在项目中有发现类似下方的代码, Map tmpParams = ImmutableMap.of( "extraInfos", ext.get("extraInfos ...
- JavaScript--时间日期格式化封装
这是一个正常的封装: 其他非正常的请按照以下语句自由搭配 <!DOCTYPE html> <html lang="en"> <head> < ...
- JAVA高级--java泛型
类型的参数化 泛型类可以同时设置多个参数 泛型类可以继承泛型类 泛型类可以实现泛型接口 示例--泛型类 package com.date; public class GenericDemo { pub ...
- framework7 上拉加载一些ajax问题
1.请求第一组数据后如果不能产生上拉进度条,则无法进行上拉加载. 解决办法:首次加载的数据量设置合理即可. 2.同一组数据请求多次,原因是异步刷新时间差,请求参数未更新,多次触发了上拉加载. 解决办法 ...
- oracle表复杂查询--创建数据库实例
n 创建数据库有两种方法: 1)通过oracle提供的向导工具 2)我们可以用手工步骤直接创建 但我们创建完一个新的数据库实例后,在服务中就会有两个新的服务创建,这时,你根据实际需要去启动相应的数据 ...
- axios细节之绑定到原型和axios的defaults的配置属性
把axios绑定到原型 vue开发者一套很好用的实践,一般来说,实践如果能够让大部分人都接受,会逐渐成为一个默认的标准. // 把axios配置到原型上 Vue.prototype.$axios = ...
- 关于编码的发展演变:ASCII、GB2312、GBK、gb18030、Unicode、UTF-8
[1]ASCII 每个字符占据1bytes(字节),第一次以规范标准发表是在1967年,最后一次修订是在1986年.用二进制表示的话最高位必须为0(扩展的ASCII不在考虑范围内),因此ASCII只能 ...
- QT 中如何实现一个简单的动画
QT可以实现一下简单的动画,比如 运动的时钟 闪烁的按钮. 动画的实现: (1)创建一个定时器 (2)调用QWidget::update()通知界面重绘 实现一个按钮闪烁的例子: circlewidg ...
- keep-alive vue组件缓存避免多次加载相应的组件
keep-alive vue组件缓存避免多次加载相应的组件
- 如何解决iOS内存错误
由于iOS5.0之前没有自动应用计数机制,也没有Java那样的垃圾回收功能.我们都需要自己管理和控制对象的回收,这是一件很麻烦的事情,也是做iOS项目中最容易出现的问题.如果不掌握这些方法,调试这些问 ...