2017-02-13 16 views
0

У меня есть функция, которая проходит через массив указателей функций и вызывает каждую из этих функций по порядку.Вызов функции указателем, находящимся в массиве при изменении содержимого массива

Что произойдет, если содержимое массива указателей функций будет изменено во время этого процесса? Можно ли считать вызовы функций достаточно атомными, чтобы быть уверенными в том, что ничего неожиданного не происходит или нужно не менять указатели на функции, например, когда происходит нажатие на стек?

Следующий пример (псевдошум). init() запускается один раз при запуске, а функция callFunctions() запускается периодически, скажем, раз в секунду. Затем изменяется changeFunctions() и изменяет содержимое функцииPtrArray []. Это может произойти в любой момент времени, поскольку код запускается в разных процессах как ОС, подобный среде.

void (*functionPtrArray[3]) (void); 

void init(void) 
{ 
    functionPtrArray[0] = function1; 
    functionPtrArray[1] = function2; 
    functionPtrArray[2] = function3; 
} 

void callFunctions(void) 
{ 
    for (i = 0; i < 3; i++) 
    { 
     *functionPtrArray[i](); 
    } 
} 

void changeFunctions(void) 
{ 
    functionPtrArray[0] = newFunction1; 
    functionPtrArray[1] = newFunction2; 
    functionPtrArray[2] = newFunction3; 
} 
+0

Ключевой вопрос заключается в том, важно ли, чтобы после выполнения f [0] f [1] и f [2] были выполнены с тем же набором, что и f [0]. В противном случае ваш код в порядке (при условии, что извлечение и сохранение указателя функции являются атомарными, в основном это означает, что они являются родным размером процессора и размера шины, поэтому циклы чтения/записи извлекают/сохраняют целые собственные машинные слова за один цикл). –

+0

@PaulOgilvie Что заставляет вас думать, что какой-либо данный общий процессор может читать слово (x байтов) за один цикл? Даже если это предположение было истинным, а это не так, многие архитектуры имеют расширенные адреса за пределами размера машинного слова, предназначенные для хранения как кода, так и данных. – Lundin

ответ

2

редактировать: Я был слишком длинным и упреждающий Лундин: https://stackoverflow.com/a/42201493/5592711

Важный момент: вызов функции в C сводится к прыжку, поэтому, когда программа выполняет функцию, изменение гипотетического указателя, используемого для вызова функции, ничего не изменит для потока программы, поскольку указатель инструкции уже находится в пределах функции.

Например, в этом фрагменте some_function() будет выполняться штраф и не зависит от модификации ptr.

void(*ptr)(void) = &some_function; 
(*ptr)(); 
// ... in another thread, while 'some_function()' is executing 
ptr = &other_function; 

Однако, вы приподняв важный момент, так как операции с памятью не являются атомарными, некоторые потоки могут изменить functionPtrArray[0] элемент, а другой поток читает его, что приведет его, чтобы перейти к некоторому адресу и мусора приведет к сбою вашей программы, как в этом гипотетическом примере.

Чтобы обеспечить синхронизацию между потоками, вы можете использовать mutexes, например, используя библиотеку pthread (вы можете сами Google, вы найдете много информации).

В вашем примере, с помощью такой синхронизации может выглядеть следующим образом:

// You can use static mutex initialization 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 

void callFunctions(void) 
{ 
    pthread_mutex_lock(&mutex); 
    for (i = 0; i < 3; i++) 
    { 
     *functionPtrArray[i](); 
    } 
    pthread_mutex_unlock(&mutex); 
} 

void changeFunctions(void) 
{ 
    pthread_mutex_lock(&mutex); 
    functionPtrArray[0] = newFunction1; 
    functionPtrArray[1] = newFunction2; 
    functionPtrArray[2] = newFunction3; 
    pthread_mutex_unlock(&mutex); 
} 

Таким образом, модифицируя массив указателей функций и выполнения функции являются взаимоисключающими задачами. Вы должны знать, что этот пример имеет несколько ограничений:

  1. Мьютексы блокируется в течение всего выполнения функций (что не требуется, вы можете просто заблокировать его читать указатели на функции, разблокировать, а затем выполнить их)
  2. Функции должны быть не самим вызовом changeFunctions, иначе вы окажетесь в тупике.
+0

«изменить ... пока другой поток читал его»: я считаю, что выбор адреса функции из массива является атомарным действием в ассемблере, предполагая, что адреса функций - это собственный размер машинного слова. –

+0

Да и нет, это зависит от платформы: http://stackoverflow.com/questions/1350994/is-it-safe-to-read-an-integer-variable-thats-being-concurrently-modified-withou –

+0

Monti, спасибо за указатель. Однажды я прочитал: «У вас есть способность учиться на ошибках. Сегодня вы узнаете много» («вы», что означает «я»). –

2

callFunctions() выполняется периодически, скажем, один раз в секунду. Затем изменяется changeFunctions() и изменяет содержимое функцииPtrArray []. Это может произойти в любой момент времени, поскольку код запускается в разных процессах как ОС, подобный среде.

Из вашего описания, это очевидно, что functionPtrArray массив изменяется, и доступ к 4by более чем один нити/процесса (ов) в unsychronized способом, который является data race. Итак, вам нужно будет обеспечить синхронизацию так или иначе.

2

Это тот же случай, что и для любой переменной в многопоточном сценарии. Нет, они не могут считаться атомными, если вы не используете квалификатор _Atomic от C11.

Существует много ситуаций, когда чтение указателя функции не будет атомарным. Один пример - 8-битные ЦП с 16-разрядными адресами. Некоторые из этих архитектур имеют инструкцию для обеспечения безопасной, бесперебойной обработки 16-разрядного индексного регистра, а другие нет. Другим примером является любая архитектура, которая поддерживает расширенную память за пределами ширины адресной шины по умолчанию (банковские, «дальние указатели»).

Поскольку код стоит, массив указателей функций не должен изменяться другим потоком/процессом/ISR/обратным вызовом, или могут возникнуть ошибки состояния гонки. Вы должны защитить доступ к массиву с помощью раздела semaphore/mutex/critical.

Поскольку вызовы функций могут занимать некоторое время выполнения, вы не хотите блокировать все остальные потоки во время выполнения. Это, вероятно, лучше, чтобы скопировать вниз указатели на функции локально, так как в этом псевдокоде:

void callFunctions(void) 
{ 
    for (i = 0; i < 3; i++) 
    { 
    void(*local)(void); 

    grab_mutex(); 
     local = functionPtrArray[i]; 
    release_mutex(); 

    local(); // call is completely thread-safe 
    } 
} 
+0

Поскольку вызов не имеет параметров, вызов будет атомарным (символически «mov ax, & f», за которым следует 'call ax'). Поэтому я не думаю, что это решение добавляет ничего (если бы весь массив не был скопирован). –

+0

@PaulOgilvie Не была указана конкретная система. Поверить, что атомный доступ гарантирован на всех системах, портативно, был бы очень наивным. Для начала рассмотрим очень распространенный случай 8-битных MCU с 16-разрядными адресами - они не могут иметь атомарный доступ к указателям на функции по определению. Но, конечно, предположим, что каждый компьютер в мире - это компьютер и голосуйте за меня, потому что вы не знаете других компьютеров ... – Lundin

+0

Lundin. Возможно, я немного наивен. Невозможно отобразить до тех пор, пока не будет отредактировано решение. Извиняюсь. –