(Операционная система) Как я могу использовать __asm ​​mfence в c - PullRequest
0 голосов
/ 08 апреля 2020

Я беру класс операционной системы, и мой профессор дал нам это домашнее задание.

"Поместите __asm ​​mfence в правильное положение."

Эта проблема связана с использованием нескольких потоков и побочный эффект.

Основной поток увеличивает shared_var, но thread_1 делает это в то же время.

Таким образом, shared_var становится 199048359.000, когда код увеличивается в 2000000 раз.

Профессор сказал, что __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;
}

1 Ответ

2 голосов
/ 08 апреля 2020

Функции EnterRegion() и LeaveRegion() реализуют критическую область, используя вещь, называемую «алгоритм Петерсона».

Теперь ключ к алгоритму Петерсона заключается в том, что когда поток читает turn it должен получить последнее (самое последнее) значение, записанное любым потоком. То есть операции на turn должны быть последовательными. Кроме того, запись в interested[] в EnterRegion() должна стать видимой для всех потоков до (или в то же время), что и запись в turn.

Таким образом, место для размещения mfence после turn = process ; - чтобы поток не продолжался до тех пор, пока его запись в turn не будет видна всем другим потокам.

Также важно убедить компилятор читать из памяти каждый раз, когда он читает turn и interested[], поэтому вы должны установить их volatile.

Если вы пишете это для x86 или x86_64, этого достаточно - потому что они обычно "хорошо себя ведут", так что:

  • все записи в turn и interested[process] будут выполняться в программном порядке

  • все чтения turn и interested[other] также будет происходить в программном порядке

, и установка этих volatile также гарантирует, что компилятор не возится с порядком.

Причина использования mfence на x86 и x86_64 в этом случае означает sh очередь записи в память, прежде чем перейти к прочитайте значение turn. Таким образом, вся память записывает go в очередь, и через некоторое время в будущем каждая запись достигнет фактической памяти, и эффект записи станет видимым для других потоков - запись «завершена». Пишет «завершено» в том же порядке, в каком программа их делала, но задерживается. Если поток читает что-то, что он написал недавно, процессор выберет (самое последнее) значение из очереди записи. Это означает, что потоку не нужно ждать, пока запись завершится, что, как правило, хорошо. Однако это означает, что поток не читает то же значение, что любой другой поток будет читать, по крайней мере, пока запись не завершится. * * * * * * * * * * * * * * * * * * * * mfence * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *1049* делает остановку процессора до тех пор, пока все ожидающие записи не будут "завершены", поэтому любые последующие чтения будут считывать то же самое, что и любой другой поток.

Запись в interested[] в LeaveRegion() не требуется (на x86 / x86_64) mfence, что хорошо, потому что mfence является дорогостоящей операцией. Каждый поток только когда-либо пишет в свой собственный флаг interested[] и только когда-либо читает другой. Единственное ограничение на эту запись состоит в том, что она должна , а не"завершить" после записи в EnterRegion() (!). К счастью, x86 / x86_64 делает все записи по порядку. [Хотя, конечно, после записи в LeaveRegion() запись в EnterRegion() может «завершиться» до того, как другой поток прочитает флаг.]

Для других устройств вы можете захотеть, чтобы другие заборы принудительно применяли порядок чтения / записи turn и interested[]. Но я не претендую на то, что знаю достаточно, чтобы посоветовать ARM или POWERP C или что-нибудь еще.

...