《深入解析Android 5.0系统》——第6章,第6.1节原子操作

简介:

本节书摘来自异步社区《深入解析Android 5.0系统》一书中的第6章,第6.1节原子操作,作者 刘超,更多章节内容可以访问云栖社区“异步社区”公众号查看

6.1 原子操作
深入解析Android 5.0系统
对简单类型的全局变量进行操作时,即使是一些简单的操作,如加法、减法等,在汇编级别上也需要多条指令才能完成。整个操作的完成需要先读取内存中的值,在CPU中计算,然后再写回内存中。如果中间发生了线程切换并改变了内存中的值,这样最后执行的结果就会发生错误。避免这种问题发生的最好办法就是使用原子操作。

原子操作中没有使用锁,从效率上看要比使用锁来保护全局变量划算。但是,原子操作也不是没有一点性能上的代价,因此还是要尽量避免使用。

Android中用汇编语言实现了一套原子操作函数,这些函数在同步机制的实现中被广泛使用。

6.1.1 Android的原子操作函数
1.原子变量的加法操作

int32_t android_atomic_add(int32_t value, volatile int32_t* addr);

原子变量的减法操作可以通过传递负值给加法操作函数来完成。

2.原子变量的自增和自减操作

int32_t android_atomic_inc(volatile int32_t* addr);
int32_t android_atomic_dec(volatile int32_t* addr);
3.原子变量的与操作

int32_t android_atomic_and(int32_t value, volatile int32_t* addr);
4.原子变量的或操作

int32_t android_atomic_or(int32_t value, volatile int32_t* addr);
5.原子变量的设置

void android_atomic_acquire_store(int32_t value, volatile int32_t* addr);
void android_atomic_release_store(int32_t value, volatile int32_t* addr);

6.原子变量的读取

int32_t android_atomic_acquire_load(volatile const int32_t* addr);
int32_t android_atomic_release_load(volatile const int32_t* addr);
图像说明文字注意 上面这两个函数从功能上看是一样的,区别只是内存屏障位于读取前还是读取后,下面的两组函数也类似。 7.原子变量的比较并交换
int android_atomic_acquire_cas(int32_t oldvalue, int32_t newvalue, volatile int32_t* addr);
int android_atomic_release_cas(int32_t oldvalue, int32_t newvalue, volatile int32_t* addr);

8.还有两个原子变量的宏定义

#define android_atomic_write android_atomic_release_store
#define android_atomic_cmpxchg android_atomic_release_cas

6.1.2 原子操作的实现原理
Android原子操作的实现方式和CPU的架构有密切关系,现在的原子操作一般都是在CPU指令级别实现的。这种实现方式不但简单,而且效率非常高。

虽然原子操作的接口函数有10多个,但是,只有两个函数通过汇编代码真正实现了原子操作,它们是函数android_atomic_add()和android_atomic_cas(),其他函数都只是在内部调用它们而已。这两个函数的原理差不多。

ARM平台上的实现更复杂一点,下面以ARM平台的加法函数为例来分析原子变量的实现原理:

extern ANDROID_ATOMIC_INLINE
int32_t android_atomic_add(int32_t increment, volatile int32_t *ptr)
{
    int32_t prev, tmp, status;
    android_memory_barrier();
    do {
        __asm__ __volatile__ ("ldrex %0, [%4]\n"
                              "add %1, %0, %5\n"
                              "strex %2, %1, [%4]"
                              : "=&r" (prev), "=&r" (tmp),
                                "=&r" (status), "+m" (*ptr)
                              : "r" (ptr), "Ir" (increment)
                              : "cc");
    } while (__builtin_expect(status != 0, 0));
    return prev;
}

上面的代码中第一行使用的宏ANDROID_ATOMIC_INLINE的定义如下:

#define ANDROID_ATOMIC_INLINE inline __attribute__((always_inline))

这个宏的作用是把函数定义成了inline函数。

代码中第二行调用android_memory_barrier()函数的作用表示这里需要内存屏障(下节会介绍内存屏障)。

接下来是一段“内嵌汇编”(如果对“内嵌汇编”不了解,可以参考笔者的博客),“内嵌汇编”比较难懂,但是可以用下面这段展开的伪代码来表示它:

do {
    ldrex  prev,[ptr]
    add  tmp,  prev,  increment
    strex  status,  tmp, [ptr]
} whiile(status != 0)

在add指令的前后有两条看上去比较陌生的指令:ldrex和strex,这两条是AMRV6新引入的同步指令。ldrex指令的作用是将指针ptr指向的内容放到prev变量中,同时给执行处理器做一个标记(tag),标记上指针ptr的地址,表示这个内存地址已经有一个CPU正在访问。当执行到strex指令时,它会检查是否存在ptr的地址标记,如果标记存在,strex指令会把add指令执行的结果写入指针ptr指向的地址,并且返回0,然后清除该标记。返回的结果0将保存在status变量中,这样循环结束,函数返回结果。

如果在strex指令执行前发生了线程的上下文切换,在切换回来后,ldrx指令设置的标志将会被清除。这时再执行strex指令时,由于没有了这个标志,strex指令将不会完成对ptr指针的存储操作,而且status变量中的返回结果将是1。因此,循环将重新开始执行,直到成功为止。

builtin_expect()是gcc的内建函数,有两个参数,第一个参数是一个表达式,第二个参数是一个值。表达式的计算结果也是函数的结果。builtin_expect()用来告诉gcc预测表达式更可能的值是什么,这样gcc会根据预测值来优化代码。代码中表达的含义是预测“status!=0”这个表达式的值为“0”,预测while循环将结束。

图像说明文字提示 原子操作并没有禁止中断的发生或上下文切换,而是让它们不影响操作的结果。

6.1.3 内存屏障和编译屏障
现代 CPU中指令的执行次序不一定按顺序执行,没有相关性的指令可以打乱次序执行,以充分利用 CPU的指令流水线,提高执行速度。同时,编译器也会对指令进行优化,例如,调整指令顺序来利用CPU的指令流水线。这些优化方式,大部分时候都工作良好,但是在一些比较复杂的情况可能会出现错误,例如,执行同步代码时就有可能因为优化导致同步原语之后的指令在同步原语前执行。

内存屏障和编译屏障就是用来告诉CPU和编译器停止优化的手段。编译屏障是指使用伪指令“memory”告诉编译器不能把“memory”执行前后的代码混淆在一起,这时“memory”起到了一种优化屏障的作用。内存屏障是在代码中使用一些特殊指令,如ARM中的dmb、dsb和isb指令,x86中的sfence、lfence和mfence指令。CPU遇到这些特殊指令后,要等待前面的指令执行完成才执行后面的指令。这些指令的作用就好像一道屏障把前后指令隔离开了,防止CPU把前后两段指令颠倒执行。

(1)ARM平台的内存屏障指令。

dsb:数据同步屏障指令。它的作用是等待所有前面的指令完成后再执行后面的指令。

dmb:数据内存屏障指令。它的作用是等待前面访问内存的指令完成后再执行后面访问内存的指令。

isb:指令同步屏障。它的作用是等待流水线中所有指令执行完成后再执行后面的指令。

(2)x86平台上的内存屏障指令。

sfence:存储屏障指令。它的作用是等待前面写内存的指令完成后再执行后面写内存的指令。

lfence:读取屏障指令。它的作用是等待前面读取内存的指令完成后再执行后面读取内存的指令。

mfence:混合屏障指令。它的作用是等待前面读写内存的指令完成后再执行后面读写内存的指令。

要精确地理解这些指令的含义,需要去查阅处理器的说明。这里只是对它们做了一点简单的介绍。下面看看Android是如何利用这些指令来实现内存屏障和编译屏障的:

1.ARM平台的函数代码

(1)编译屏障:

void android_compiler_barrier()
{
    asm_volatile_("" : : : "memory");
}
编译屏障的实现只是使用了伪指令memory。

(2)内存屏障:

void android_memory_barrier()
{
#if ANDROID_SMP == 0
    android_compiler_barrier();
#else
    __asm__volatile_("dmb" : : : "memory");
#endif
}
void android_memory_store_barrier()
{
#if ANDROID_SMP == 0
    android_compiler_barrier();
#else
    __asm_volatile_("dmb st" : : : "memory");
#endif
}

内存屏障的函数中使用了宏ANDROID_SMP。它的值为0时表示是单CPU,这种情况下只使用编译屏障就可以了。在多CPU情况下,同时使用了内存屏障指令“dmb”和编译屏障的伪指令“memory”。函数android_memory_store_barrier()中的dmb指令还使用了选项st,它表示要等待前面所有存储内存的指令执行完后再执行后面的存储内存的指令。

2.x86平台下的函数代码

(1)编译屏障:

void android_compiler_barrier(void)
{
    __asm__ __volatile__ ("" : : : "memory");
}
和ARM平台下一样,编译屏障的实现只是使用了伪指令memory。

(2)内存屏障:

#if ANDROID_SMP == 0
void android_memory_barrier(void)
{
    android_compiler_barrier();
}
void android_memory_store_barrier(void)
{
    android_compiler_barrier();
}
#else
void android_memory_barrier(void)
{
    asm__volatile_("mfence" : : : "memory");
}
void android_memory_store_barrier(void)
{
    android_compiler_barrier();
}
#endif

x86平台也一样,如果是单CPU,内存屏障的实现只使用了编译屏障。在多CPU情况下,函数android_memory_barrier()使用了CPU指令“mfence”,对读写内存的情况都进行了屏障。但是android_memory_store_barrier()函数只使用了编译屏障,这是因为Intel的CPU不对写内存的指令重新排序。所以不需要内存屏蔽指令。

相关文章
|
5月前
|
数据采集 监控 API
告别手动埋点!Android 无侵入式数据采集方案深度解析
传统的Android应用监控方案需要开发者在代码中手动添加埋点,不仅侵入性强、工作量大,还难以维护。本文深入探讨了基于字节码插桩技术的无侵入式数据采集方案,通过Gradle插件 + AGP API + ASM的技术组合,实现对应用性能、用户行为、网络请求等全方位监控,真正做到零侵入、易集成、高稳定。
678 60
|
11月前
|
机器学习/深度学习 文字识别 监控
安全监控系统:技术架构与应用解析
该系统采用模块化设计,集成了行为识别、视频监控、人脸识别、危险区域检测、异常事件检测、日志追溯及消息推送等功能,并可选配OCR识别模块。基于深度学习与开源技术栈(如TensorFlow、OpenCV),系统具备高精度、低延迟特点,支持实时分析儿童行为、监测危险区域、识别异常事件,并将结果推送给教师或家长。同时兼容主流硬件,支持本地化推理与分布式处理,确保可靠性与扩展性,为幼儿园安全管理提供全面解决方案。
501 3
|
6月前
|
Linux 测试技术 语音技术
【车载Android】模拟Android系统的高负载环境
本文介绍如何将Linux压力测试工具Stress移植到Android系统,用于模拟高负载环境下的CPU、内存、IO和磁盘压力,帮助开发者优化车载Android应用在多任务并发时的性能问题,提升系统稳定性与用户体验。
448 6
|
6月前
|
Java 数据库 Android开发
基于Android的电子记账本系统
本项目研究开发一款基于Java与Android平台的开源电子记账系统,采用SQLite数据库和Gradle工具,实现高效、安全、便捷的个人财务管理,顺应数字化转型趋势。
|
9月前
|
网络协议 安全 区块链
DNS+:互联网的下一个十年,为什么域名系统正在重新定义数字生态? ——解读《“DNS+”发展白皮书(2023)》
DNS+标志着域名系统从基础寻址工具向融合技术、业态与治理的数字生态中枢转变。通过与IPv6、AI和区块链结合,DNS实现了智能调度、加密传输等新功能,支持工业互联网、Web3及万物互联场景。当前,中国IPv6用户达7.6亿,全球DNSSEC支持率三年增长80%,展现了其快速发展态势。然而,DNS+仍面临安全威胁、技术普惠瓶颈及生态协同挑战。未来,需推动零信任DNS模型、加强威胁情报共享,并加速标准制定,以筑牢数字时代网络根基,实现更安全、高效的数字生态建设。
583 4
|
9月前
|
安全 Java Android开发
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
393 0
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
|
11月前
|
安全 搜索推荐 Android开发
Android系统SELinux安全机制详解
如此看来,SELinux对于大家来说,就像那位不眠不休,严阵以待的港口管理员,守护我们安卓系统的平安,维护这片海港的和谐生态。SELinux就这样,默默无闻,却卫士如山,给予Android系统一份厚重的安全保障。
360 18
|
12月前
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
448 15
|
12月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
12月前
|
传感器 人工智能 监控
反向寻车系统怎么做?基本原理与系统组成解析
本文通过反向寻车系统的核心组成部分与技术分析,阐述反向寻车系统的工作原理,适用于适用于商场停车场、医院停车场及火车站停车场等。如需获取智慧停车场反向寻车技术方案前往文章最下方获取,如有项目合作及技术交流欢迎私信作者。
923 2

推荐镜像

更多