__I、 __O 、__IO(从别那搬运过来的)

__I、 __O 、__IO是什么意思? 
这是ST库里面的宏定义,定义如下:

#define __I volatile const /*!< defines ‘read only’ permissions /
#define __O volatile /
!< defines ‘write only’ permissions /
#define __IO volatile /
!< defines ‘read / write’ permissions */
显然,这三个宏定义都是用来替换成 volatile 和 const 的,所以我们先要了解 这两个关键字的作用:

volatile 
简单的说,就是不让编译器进行优化,即每次读取或者修改值的时候,都必须重新从内存或者寄存器中读取或者修改。

一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到 volatile变量。不懂得volatile的内容将会带来灾难。假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。 
1)一个参数既可以是const还可以是volatile吗?解释为什么。 
2); 一个指针可以是volatile 吗?解释为什么。 
3); 下面的函数有什么错误:

int square(volatile int *ptr)
{
return ptr * ptr;
}
1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2); 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3) 这段代码有点变态。这段代码的目的是用来返指针
ptr指向值的平方,但是,由于
ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = ptr;
return a * b;
}
由于
ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
const 
只读变量,即变量保存在只读静态存储区。编译时,如何尝试修改只读变量,则编译器提示出错,就能防止误修改。 
const与define 
两者都可以用来定义常量,但是const定义时,定义了常量的类型,所以更精确一些(其实const定义的是只读变量,而不是常量)。#define只是简单的文本替换,除了可以定义常量外,还可以用来定义一些简单的函数,有点类似内置函数。const和define定义的常量可以放在头文件里面。(小注:可以多次声明,但只能定义一次)

const与指针
int me;
const int * p1=&me; //p1可变,*p1不可变 const 修饰的是 p1,即p1不可变
int * const p2=&me; //p2不可变,*p2可变 const 修饰的是 p2,即p2不可变
const int *const p3=&me; //p3不可变,*p3也不可变 前一个const 修饰的是 *p3,后一个const 修饰的是p3,两者都不可变
前面介绍了 volatile 和 const 的用法,不知道大家了解了没?了解了后,下面的讲解就更加容易了: 
__I :输入口。既然是输入,那么寄存器的值就随时会外部修改,那就不能进行优化,每次都要重新从寄存器中读取。也不能写,即只读,不然就不是输入而是输出了。 
__O :输出口,也不能进行优化,不然你连续两次输出相同值,编译器认为没改变,就忽略了后面那一次输出,假如外部在两次输出中间修改了值,那就影响输出 
__IO:输入输出口,同上

为什么加下划线?

原因是:避免命名冲突 
一般宏定义都是大写,但因为这里的字母比较少,所以再添加下划线来区分。这样一般都可以避免命名冲突问题,因为很少人这样命名,这样命名的人肯定知道这些是有什么用的。 
经常写大工程时,都会发现老是命名冲突,要不是全局变量冲突,要不就是宏定义冲突,所以我们要尽量避免这些问题,不然出问题了都不知道问题在哪里。
————————————————
版权声明:本文为优快云博主「凡旭国」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/sinat_20265495/article/details/79535551

### 关于 `memcpy_fromio` 的实现细节 在 Linux 内核中,函数 `memcpy_fromio` 并不是一个标准的公开 API 或者广泛使用的函数名称。通常情况下,在内核开发领域,类似的命名可能指的是从 I/O 地址空间复制数据到常规内存的操作。这种操作一般通过特定的架构支持或者辅助函数来完成。 #### 可能涉及的核心概念 1. **I/O 映射机制**: 在许多体系结构上,设备寄存器或其他硬件资源被映射到虚拟地址空间中的特殊区域。这些区域可以通过专门的访问方法读取或写入。 2. **`ioremap` 和 `iounmap`**: 这些函数用于将物理地址范围映射到内核虚地址空间以便后续访问[^3]。一旦完成了这样的映射,就可以像处理普通指针一样对待该地址。 3. **实际的数据传输方式**: - 对某些平台而言,直接利用 C 风格的标准库函数 (比如 memcpy)搬运字节可能是可行的选择; - 不过更常见的是采用专为这一目的设计的功能宏/inline 函数,它们能够更好地适应不同 CPU 架构下的需求并提供必要的屏障(barrier),从而保障多线程环境以及 DMA 操作的一致性和可靠性。 以下是基于假设场景的一个简单例子展示如何模仿此类行为: ```c void my_memcpy_fromio(void *dest, const volatile void __iomem *src, size_t count){ char *tmp_dest = dest; const volatile char __iomem *tmp_src = src; while(count--){ *(tmp_dest++) = readb(tmp_src++); } } ``` 上述代码片段定义了一个名为 `my_memcpy_fromio` 的自定义版本,它接受三个参数——目标缓冲区起点(`dest`)、源端口基址(`src`)还有要传送的数量(`count`)。这里采用了逐位拷贝的方式,并且每次调用都借助了另一个假定存在的工具函数 `readb()` 完成单个字符级别的提取工作;当然也可以替换成长度更大的单位(如双字)。注意这里的 `__iomem` 类型标记强调指出所指向的内容属于特殊的 IO 区域而非普通的 RAM 块。 另外值得注意的是,由于涉及到潜在的缓存一致性问题以及其他低层考量因素,因此真实世界里的解决方案往往要比上面给出的例子复杂得多。 ### 总结 虽然没有确切找到完全匹配叫做 “memcpy_fromio” 的具体接口描述文档资料链接[^4] ,但是可以根据以上分析推测其大致功能应该是围绕着高效安全地把来自外部设备或者其他非易失存储介质上的信息迁移到主机内部可用位置上来展开讨论 。如果确实存在这样一个同名实体的话,则很可能会遵循相似的设计原则和技术路线图来进行构建 。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值