QEMU如何模拟目标机上的原子指令?

2024-05-05 11:52

本文主要是介绍QEMU如何模拟目标机上的原子指令?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Qemu行为级emulator而非simulator,在模拟目标架构时,会利用HOST架构的支持特性实现目标架构的行为,下面分析一下QEMU对目标架构原子指令的模拟方式。

HOST架构自然是X86,为了搭建环境的方便,选择RISCV架构作为目标架构,参考这篇文章搭建RISCV QEMU运行环境:

https://blog.csdn.net/tugouxp/article/details/137614570?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22137614570%22%2C%22source%22%3A%22tugouxp%22%7D

验证方案

1.首先,确保初始目标架构程序没有调用任何原子指令,验证方法是在QEMU中模拟TARGE架构流程的必经之路do_atomic_op_i32/do_atomic_op_i64打断点,确保初始程序没有调用任何目标架构上的原子操作指令。以本测试为例,riscv_programming_practice/chapter_2/benos下的初始测试程序就没有调用任何原子指令,先在do_atomic_op_i32/do_atomic_op_i64打上断点,之后运行程序,不会进入断点。

2.之后加入断点程序:

#include "uart.h"                                                                                                                                                                                           static void arch_atomic_add(int i, unsigned long *v) 
{__asm__ __volatile__ ( "amoadd.d  zero, %1, %0": "+A" (*v) :"r" (i) ¦: "memory");
}unsigned long abc;
void kernel_main(void)
{arch_atomic_add(1, &abc);uart_init();uart_send_string("Welcome RISC-V!\r\n");while (1) {;}   
}

再次打上断点,发现这次拦截到了对原子操作指令的调用:

上述调用栈完成了对原子指令的翻译之后,之后开始执行GEN BUFFER中的指令,ATOMIC ADD指令具体由helper_atomic_fetch_addq_le函数实现:

helper_atomic_fetch_addq_le函数最终调用了X86 HOST主机的 "lock xadd"指令完成了对RISCV AMOADD指令的模拟操作。

所以,这一种模拟方式是使用HOST架构CPU上的原子指令来模拟目标主机架构上的原子指令。

RISCV amoswap.d指令的模拟

测试代码:

#include "uart.h"unsigned long xchg(volatile void *ptr, unsigned long new)
{          unsigned long result;asm volatile (" amoswap.d %[result], %[new], (%[ptr])\n": [result]"=r"(result), [ptr]"+r"(ptr): [new]"r"(new): "memory");return result;                                                                                                                                                                                      
}          void kernel_main(void)
{          unsigned long abc = 0;xchg(&abc, 2); uart_init();uart_send_string("Welcome RISC-V!\r\n");while (1) {;}  
}

运行时抓到的调用堆栈如下,可以看到,最终是通过helper_atomic_xchgq_le helper函数实现的,顾名思义,那一定是调用了X86 HOST主机上的xchgq指令了:

不知为何,这里没有在HOST指令xchg前加LOCK,可能是个问题吧,不管怎样,TARGET机到HOST机上的原子指令映射关系找到了。

总结:

用HOST主机的原子操作指令模拟目标机的原子指令的方式可以描述为,在指令翻译阶段,调用do_atomic_op_i64将目标机上的原子指令翻译成调用HOST机上的原子操作函数的HELPER指令。当指令BUFFER执行到原子指令操作时,进入HELPER函数,用HOST机上的原子指令替代目标机上的原子操作,实现原子操作的语义。

第二种方式 stop-the-world handling :

观察do_atomic_op_i64的实现发现,对应HOST机上的原子操作helper函数只有在CONFIG_ATOMIC64被定义时才会生成,而CONFIG_ATOMIC64是configure中根据动态检测HOST主机是否支持原子指令的结果设置的:

这实际上对应了QEMU的另一种原子操作实现方式:stop-the-world handling,停止世界。什么情况下需要这种原子操作机制来模拟TARGET机的原子指令呢?首先,虽然基本上所有处理器都支持原子操作指令,但是各处理器对原子操作指令的支持程度是不同的,并不是所有的目标架构上的原子指令在HOST机上都能得到支持。其次,即便HOST主机能够支持大部分目标机上的原子操作,但是有些特殊情况,比如8位,16位等等非字对齐的原子操作,基本上很难得到支持。所以,QEMU实现了这种被称为stop-the-world的原子指令实现方式。

在分析stop-the-word之前,首先思考一下,如果把这个问题交给你,该如何实现,QEMU有多个后端实现,包括KVM和TCG,TCG还包括NATIVE和TCI两种方式实现的后端,KVM只能应用在目标机和HOST机为相同架构的情况,不存在指令翻译,目标系统原子操作指令直接在HOST机上执行。不考虑这种情况。stop-the-world只适用于TCG模式,也就是下图左边的分支。

我们继续分析。为了关闭ATOMIC64,修改configure文件,强制关闭对atomic64的支持,之后重新编译QEMU:

由于CONFIG_ATOMIC64影响了do_atomic_op_i64函数的实现,而后者是TCGM模式下的一段公共代码,所以继续调试do_atomic_op_i64函数,此时,do_atomic_op_i64函数调用了gen_helper_exit_atomic,说明负责BUILD的HELPER函数变成了类似“helper_exit_atomic"之类的名字:

下一步,虽然并不确定是否存在一个名字为helper_exit_atomic的函数,不过可以先以helper_exit_atomic为符号打断点,如果能够打成功,说明存在这样的函数,经过测试,发现确实可以断在helper_exit_atomic函数上,函数定义在qemu-4.2.1/accel/tcg/tcg-runtime.c中,利用了宏技巧在编译阶段生成,所以直接在源码中搜索符号是搜不到的。

helper函数中调用了cpu_loop_exit_atomic函数,step 进入,发现除了将EXCP_ATOMIC赋值给cpu->exception_index,没有做其他事就RESTORE虚拟机状态了。

在QEMU中,exception_index一般用来记录VCPU的异常类型,这里将EXCP_ATOMIC赋值给exception_index,说明stop-the-world方式是通过触发QEMU自定义的EXCP_ATOMIC来处理原子指令的。

进一步分析,看QEMU是如何处理EXCP_ATOMIC类型的异常的,书接上回,当HELPER函数返回,QEMU 继续在TCG的CODE BUFFER中执行,由于CPU一般在每条指令的结束后检测异常,在结束当前CODE BUFFER中ATOMIC指令的执行后,异常将迫使虚拟机VCPU从tcg_cpu_exec退出,此时,tcg_cpu_exec返回值为EXCP_ATOMIC异常码,针对此类型的异常,QEMU继续执行cpu_exec_step_atomic处理:

cpu_exec_step_atomic函数继续处理,调用start_exclusive为当前CPU建立一个独占的执行环境,所谓保持原子性,从CPU的角度来看,无非保证同一时刻,某个内存变量只能被一个CPU处理,stop-the-world机制亦是如此,只不过它操作的对象不是物理CPU,而是QEMU的虚拟CPU VCPU,在QEMU中,每个VCPU对应一个线程,所以如何建立一个VCPU 独占的环境就变成了如何建立一个某个时刻只运行一个线程进入的临界区,建立临界区的方法很多,用户态MUTEX就可以,所以,如果不出意外,stop-the-world最终使用的是线程级同步机制实现的exclusive临界区。

分析start_exclusive的实现,不必分析细节,仅仅从代码流程上看,它的目的应该是想起他VCPU发送信号,让其他VCPU停下来,只运行当前CPU执行,其同步函数使用的是qemu_cond_wait,底层通过pthread_cond_wait/pthread_cond_signal实现。

当从start_exclusive函数返回后,VCPU独占执行的环境已经具备,接着就可以再次进入VCPU 虚拟机,接着从ATOMIC原子指令的下一条指令开始执行了,由于此时只有一个VCPU在执行,原子性的语义自然得到了保证。

当原子指令执行结束后,QEMU会执行end_exclusive,唤醒其他VCPU,结束一个VCPU独占的状态:

start_exclulsive/end_exclusive调用堆栈:

stop-the-world流程可以用下图简单表示

总结

关于QEMU对原子操作的指令,社区提供的回复总结:

All hosts that can run QEMU support at least some atomic instructions. Where possible we use the host atomic operations to provide the necessary atomicity guarantees that a guest instruction must have. For cases where we can't do that (eg where the guest needs an atomic 16-byte store but the host doesn't have one), we arrange to pause execution of all the other guest vCPU threads, do the thing that must be atomic, and then let everything resume.
I would suggest starting by translating some guest code with the atomic operation you're interested in, and using the '-d' suboptions in_asm, op and out_asm to look at the generated TCG operations and the generated host code for it.The stop-the-world handling happens when something calls cpu_loop_exit_atomic(), which then raises an EXCP_ATOMIC internal-to-QEMU exception, which is handled by some top-level-loop code that calls cpu_exec_step_atomic(), which (a) uses start_exclusive() and end_exclusive() to ensure that it is the only vcpu running and (b) generates new host code with the CF_PARALLEL flag clear to tell the translator that it can assume it's the only thing running (which in turn means "you don't need to actuallydo this operation atomically").

基本说明了通过HOST Native atomic 指令替代实现和stop-the-world两种方法实现,和分析是一致的。


结束

这篇关于QEMU如何模拟目标机上的原子指令?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/961658

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

usaco 1.2 Transformations(模拟)

我的做法就是一个一个情况枚举出来 注意计算公式: ( 变换后的矩阵记为C) 顺时针旋转90°:C[i] [j]=A[n-j-1] [i] (旋转180°和270° 可以多转几个九十度来推) 对称:C[i] [n-j-1]=A[i] [j] 代码有点长 。。。 /*ID: who jayLANG: C++TASK: transform*/#include<

烟火目标检测数据集 7800张 烟火检测 带标注 voc yolo

一个包含7800张带标注图像的数据集,专门用于烟火目标检测,是一个非常有价值的资源,尤其对于那些致力于公共安全、事件管理和烟花表演监控等领域的人士而言。下面是对此数据集的一个详细介绍: 数据集名称:烟火目标检测数据集 数据集规模: 图片数量:7800张类别:主要包含烟火类目标,可能还包括其他相关类别,如烟火发射装置、背景等。格式:图像文件通常为JPEG或PNG格式;标注文件可能为X

hdu4431麻将模拟

给13张牌。问增加哪些牌可以胡牌。 胡牌有以下几种情况: 1、一个对子 + 4组 3个相同的牌或者顺子。 2、7个不同的对子。 3、13幺 贪心的思想: 对于某张牌>=3个,先减去3个相同,再组合顺子。 import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOExcepti

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟) 题目描述 给定一个链表,链表中的每个节点代表一个整数。链表中的整数由 0 分隔开,表示不同的区间。链表的开始和结束节点的值都为 0。任务是将每两个相邻的 0 之间的所有节点合并成一个节点,新节点的值为原区间内所有节点值的和。合并后,需要移除所有的 0,并返回修改后的链表头节点。 思路分析 初始化:创建一个虚拟头节点

工作常用指令与快捷键

Git提交代码 git fetch  git add .  git commit -m “desc”  git pull  git push Git查看当前分支 git symbolic-ref --short -q HEAD Git创建新的分支并切换 git checkout -b XXXXXXXXXXXXXX git push origin XXXXXXXXXXXXXX

每日一题|牛客竞赛|四舍五入|字符串+贪心+模拟

每日一题|四舍五入 四舍五入 心有猛虎,细嗅蔷薇。你好朋友,这里是锅巴的C\C++学习笔记,常言道,不积跬步无以至千里,希望有朝一日我们积累的滴水可以击穿顽石。 四舍五入 题目: 牛牛发明了一种新的四舍五入应用于整数,对个位四舍五入,规则如下 12345->12350 12399->12400 输入描述: 输入一个整数n(0<=n<=109 ) 输出描述: 输出一个整数

【算法专场】模拟(下)

目录 前言 38. 外观数列 算法分析 算法思路 算法代码 1419. 数青蛙 算法分析 算法思路 算法代码  2671. 频率跟踪器 算法分析 算法思路 算法代码 前言 在前面我们已经讲解了什么是模拟算法,这篇主要是讲解在leetcode上遇到的一些模拟题目~ 38. 外观数列 算法分析 这道题其实就是要将连续且相同的字符替换成字符重复的次数+

Lua 脚本在 Redis 中执行时的原子性以及与redis的事务的区别

在 Redis 中,Lua 脚本具有原子性是因为 Redis 保证在执行脚本时,脚本中的所有操作都会被当作一个不可分割的整体。具体来说,Redis 使用单线程的执行模型来处理命令,因此当 Lua 脚本在 Redis 中执行时,不会有其他命令打断脚本的执行过程。脚本中的所有操作都将连续执行,直到脚本执行完成后,Redis 才会继续处理其他客户端的请求。 Lua 脚本在 Redis 中原子性的原因