(操作系统)如何在 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.00double 可以准确表示的非小数范围内。 因此我说 cmets 和周围的对话而不是答案可能很有趣。 ^^' 【参考方案1】:

EnterRegion()LeaveRegion() 函数正在使用一种称为“彼得森算法”的东西来实现临界区。

现在,Peterson 算法的关键在于,当线程读取turn 时,它必须 获取any 线程写入的最新(最新)值。也就是说,turn 上的操作必须是顺序一致的。此外,在EnterRegion() 中对interested[] 的写入必须在写入turn 之前(或同时)对所有线程可见。

因此,放置mfence 的位置在turn = process ; 之后——这样线程在对turn 的写入对所有其他线程可见之前不会继续。

说服编译器每次读取turninterested[]时从内存中读取也很重要,所以你应该设置它们volatile

如果您是为 x86 或 x86_64 编写此代码,那就足够了——因为它们通常“表现良好”,因此:

turninterested[process] 的所有写入都将按程序顺序进行

turninterested[other] 的所有读取也将按程序顺序进行

并设置那些volatile 确保编译器也不会摆弄顺序。

在这种情况下,在 x86 和 x86_64 上使用 mfence 的原因是在继续读取 turn 值之前将写入队列刷新到内存。所以,所有的内存写入都会进入一个队列,并且在未来的某个时间,每次写入都会到达实际内存,并且写入的效果将对其他线程变得可见——写入已经“完成”。以程序执行它们的相同顺序写入“完成”,但延迟。如果线程读取它最近写入的内容,处理器将从写入队列中选择(最近的)值。这意味着线程不需要等到写入“完成”,这通常是一件好事。但是,这确实意味着线程读取的值与任何其他线程将读取的值相同,至少在写入“完成”之前是这样。 mfence 所做的是停止处理器,直到所有未完成的写入都“完成”——因此任何后续读取都将读取任何其他线程将读取的相同内容。

LeaveRegion() 中写入interested[] 不需要(在x86/x86_64 上)mfence,这很好,因为mfence 是一项昂贵的操作。每个线程只写入自己的interested[] 标志,并且只读取另一个线程。此写入的唯一限制是它必须“完成”EnterRegion() (!) 中的写入。令人高兴的是,x86/x86_64 会按顺序完成所有写入操作。 [当然,在LeaveRegion() 中写入之后,EnterRegion() 中的写入可能会在其他线程读取标志之前“完成”。]

对于其他设备,您可能希望其他栅栏强制执行turninterested[] 的读/写顺序。但我不会假装自己知道的足够多,无法就 ARM 或 POWERPC 或其他任何东西提出建议。

【讨论】:

非常感谢克里斯!!!现在效果很好,你给了我一些很好的建议!

以上是关于(操作系统)如何在 c 中使用 __asm mfence的主要内容,如果未能解决你的问题,请参考以下文章

clang __asm__在case statment中使用标签,得到错误:指令操作数无效。

如何在 C 中使用 asm 添加两个 64 位数字时访问进位标志

x64 asm如何将函数指针设置为_cdecl C函数并调用它?

ARM嵌入式开发中的GCC内联汇编__asm__

C系列asm关键字和__asm关键字的区别

C系列asm关键字和__asm关键字的区别