(操作系统)如何在 c 中使用 __asm mfence
Posted
技术标签:
【中文标题】(操作系统)如何在 c 中使用 __asm mfence【英文标题】:(Operating System) How can I use __asm mfence in c 【发布时间】:2020-07-20 10:39:29 【问题描述】:我正在上操作系统课,我的教授给了我们这个作业。
“将 __asm mfence 放置在适当的位置。”
这个问题是关于使用多线程及其副作用。
主线程正在增加 shared_var,但 thread_1 正在同时进行。
因此,当代码增加 2000000 次时,shared_var 变为 199048359.000。
教授说__asm mfence 会解决这个问题。但是,我不知道该放在哪里。
我正在尝试在 google、github 和此处搜索问题,但找不到来源。
我不知道这是一个愚蠢的问题,因为我的专业不是计算机科学。
另外,我想知道为什么这段代码显示 199948358.0000 而不是 2000000.00
任何帮助将不胜感激。
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <conio.h>
int turn;
int interested[2];
void EnterRegion(int process);
void LeaveRegion(int process);
DWORD WINAPI thread_func_1(LPVOID lpParam);
volatile double shared_var = 0.0;
volatile int job_complete[2] = 0, 0;
int main(void)
DWORD dwThreadId_1, dwThrdParam_1 = 1;
HANDLE hThread_1;
int i, j;
// Create Thread 1
hThread_1 = CreateThread(
NULL, // default security attributes
0, // use default stack size
thread_func_1, // thread function
&dwThrdParam_1, // argument to thread function
0, // use default creation flags
&dwThreadId_1
); // returns the thread identifier
// Check the return value for success.
if (hThread_1 == NULL)
printf("Thread 1 creation error\n");
exit(0);
else
CloseHandle( hThread_1 );
/* I am main thread */
/* Now Main Thread and Thread 1 runs concurrently */
for (i = 0; i < 10000; i++)
for (j = 0; j < 10000; j++)
EnterRegion(0);
shared_var++;
LeaveRegion(0);
printf("Main Thread completed\n");
job_complete[0] = 1;
while (job_complete[1] == 0) ;
printf("%f\n", shared_var);
_getch();
ExitProcess(0);
DWORD WINAPI thread_func_1(LPVOID lpParam)
int i, j;
for (i = 0; i < 10000; i++)
for (j = 0; j < 10000; j++)
EnterRegion(1);
shared_var++;
LeaveRegion(1);
printf("Thread_1 completed\n");
job_complete[1] = 1;
ExitThread(0);
void EnterRegion(int process)
_asm mfence;
int other;
other = 1 - process;
interested[process] = TRUE;
turn = process;
while (turn == process && interested[other] == TRUE)
_asm mfence;
void LeaveRegion(int process)
_asm mfence;
interested[process] = FALSE;
_asm mfence;
【问题讨论】:
你可以提到 win32 标签而不是更广泛的操作系统标签。您可能还想看看***.com/a/8561459/8155816,似乎没有答案,但围绕它进行了很多讨论。 @alc:这个答案在这里不适用。shared_var
被初始化为 0.0
并且总是以 1.0
递增。这将总是产生一个没有小数部分的浮点值。浮点值可以准确地表示任何非小数(在其范围内)。 2000000.00
在double
可以准确表示的非小数范围内。
因此我说 cmets 和周围的对话而不是答案可能很有趣。 ^^'
【参考方案1】:
EnterRegion()
和 LeaveRegion()
函数正在使用一种称为“彼得森算法”的东西来实现临界区。
现在,Peterson 算法的关键在于,当线程读取turn
时,它必须 获取any 线程写入的最新(最新)值。也就是说,turn
上的操作必须是顺序一致的。此外,在EnterRegion()
中对interested[]
的写入必须在写入turn
之前(或同时)对所有线程可见。
因此,放置mfence
的位置在turn = process ;
之后——这样线程在对turn
的写入对所有其他线程可见之前不会继续。
说服编译器每次读取turn
和interested[]
时从内存中读取也很重要,所以你应该设置它们volatile
。
如果您是为 x86 或 x86_64 编写此代码,那就足够了——因为它们通常“表现良好”,因此:
对turn
和interested[process]
的所有写入都将按程序顺序进行
turn
和 interested[other]
的所有读取也将按程序顺序进行
并设置那些volatile
确保编译器也不会摆弄顺序。
在这种情况下,在 x86 和 x86_64 上使用 mfence
的原因是在继续读取 turn
值之前将写入队列刷新到内存。所以,所有的内存写入都会进入一个队列,并且在未来的某个时间,每次写入都会到达实际内存,并且写入的效果将对其他线程变得可见——写入已经“完成”。以程序执行它们的相同顺序写入“完成”,但延迟。如果线程读取它最近写入的内容,处理器将从写入队列中选择(最近的)值。这意味着线程不需要等到写入“完成”,这通常是一件好事。但是,这确实意味着线程不读取的值与任何其他线程将读取的值相同,至少在写入“完成”之前是这样。 mfence
所做的是停止处理器,直到所有未完成的写入都“完成”——因此任何后续读取都将读取任何其他线程将读取的相同内容。
在LeaveRegion()
中写入interested[]
不需要(在x86/x86_64 上)mfence
,这很好,因为mfence
是一项昂贵的操作。每个线程只写入自己的interested[]
标志,并且只读取另一个线程。此写入的唯一限制是它必须不“完成”EnterRegion()
(!) 中的写入。令人高兴的是,x86/x86_64 会按顺序完成所有写入操作。 [当然,在LeaveRegion()
中写入之后,EnterRegion()
中的写入可能会在其他线程读取标志之前“完成”。]
对于其他设备,您可能希望其他栅栏强制执行turn
和interested[]
的读/写顺序。但我不会假装自己知道的足够多,无法就 ARM 或 POWERPC 或其他任何东西提出建议。
【讨论】:
非常感谢克里斯!!!现在效果很好,你给了我一些很好的建议!以上是关于(操作系统)如何在 c 中使用 __asm mfence的主要内容,如果未能解决你的问题,请参考以下文章
clang __asm__在case statment中使用标签,得到错误:指令操作数无效。
如何在 C 中使用 asm 添加两个 64 位数字时访问进位标志