Переключение контекстов потоков с помощью SIGALRM
У меня проблема. Мне нужно реализовать программу, которая переключает потоки ucontext, используя таймер и SIGALRM, но я получаю ошибку сегментации, когда я переключаю потоки, используя мою функцию evict_thread. Я считаю, что это является результатом состояния гонки, которое возникает в разное время во время выполнения программ. Вот мой evict_thread
void evict_thread(int signal)
{
// Check that there is more than one thread in the queue
if ((int)list_length(runqueue) > 1)
{
// Remove the currently executing thread from the runqueue and store its id
int evict_thread_id = list_shift_int(runqueue);
// Place the thread at the back of the run queue
list_append_int(runqueue, evict_thread_id);
// Get the id of the thread that is now at the head of the run queue
int exec_thread_id = list_item_int(runqueue, 0);
// Set the start time for new thread to the current time
clock_gettime(CLOCK_REALTIME, &thread_table[exec_thread_id]->start);
printf("Switching context from %s to %s\n",
thread_table[evict_thread_id]->thread_name,
thread_table[exec_thread_id]->thread_name);
// Execute the thread at the head of the run queue
if (swapcontext(&thread_table[evict_thread_id]->context, &thread_table[exec_thread_id]->context) == -1)
{
perror("swapcontext failed\n");
printf("errno: %d.\n", errno);
return;
}
}
return;
}
Вышеуказанная функция вызывается следующим образом
// Set the SIGALRM
if (sigset(SIGALRM, evict_thread) == -1)
{
perror("sigset failed\n");
printf("errno: %d.\n", errno);
return;
}
// Initialize timer
thread_switcher.it_interval.tv_sec = 0;
thread_switcher.it_interval.tv_usec = quantum_size;
thread_switcher.it_value.tv_sec = 0;
thread_switcher.it_value.tv_usec = quantum_size;
setitimer(ITIMER_REAL, &thread_switcher, 0);
Очередь выполнения - это просто глобальный список целых чисел, которые являются индексами в глобальной таблице указателей на потоки ucontext. Список реализован с использованием структуры данных списка из библиотеки общих утилит C, доступной на libslack.org.
Когда я отключаю таймер и позволяю каждому потоку завершиться до переключения контекстов, программа работает правильно, но когда потоки переключаются во время выполнения, я получаю ошибку сегментации примерно в 80% случаев.
Также, когда я пытаюсь использовать gdb для отслеживания ошибки сегментации, это говорит о том, что это происходит в системном вызове.
3 ответа
Я не могу дать вам совет о том, как заставить это работать, но вот несколько моментов о том, что не работает:
Обработчики сигналов работают асинхронно относительно вашего другого кода. например, сигнал может сработать, когда какой-то код обновляет ваш runqueue
и когда работает обработчик сигнала list_append_int(runqueue, evict_thread_id);
у вас довольно серьезное состояние гонки.
printf()
не должен вызываться в обработчике сигнала, это может привести к тупику или хуже. Вот список функций, которые безопасно вызывать в обработчике сигналов. setcontext / swapcontext не упоминается как безопасный для вызова в обработчике сигналов, хотя на его man-странице linux сказано, что вы можете вызывать setcontext() в обработчике сигналов - я не уверен, что авторитетно в этом.
Также обратите внимание, что говорит man-страница для setcontext():
Когда происходит сигнал, текущий пользовательский контекст сохраняется, и ядро создает новый контекст для обработчика сигнала.
Поэтому, когда вы запускаете swapcontext(), вы можете сохранить контекст обработчика сигнала вместо текущего контекста, который работал до того, как сигнал включился.
Помните, что обработчики сигналов работают асинхронно с вашим основным кодом. man 7 signal
страница стоит внимательно прочитать, чтобы убедиться, что вы придерживаетесь руководящих принципов. Например, в разделе Async-signal-safe-functions
нет упоминания о printf
или другие функции, такие как swapcontext
, Это означает, что вы не можете надежно вызывать эти функции из обработчика сигнала.
В общем, старайтесь выполнять как можно меньше работы с обработчиком сигналов. Обычно это просто означает установку флага типа sig_atomic_t
в обработчике сигнала, затем проверяя состояние этого флага в вашем основном цикле.
Возможно, измените код так, чтобы переключение контекста происходило в основном цикле, а не из обработчика сигнала. Вы могли бы использовать sigwait
в основном цикле, чтобы ждать сигнала таймера.
Как предположение: вы передаете ядру что-то, что не видно оттуда, потому что вы переключаете контекст. Вы спрашиваете о segfault, но ваш код делает интересные вещи.
Возможно, если бы вы рассматривали более стандартную модель для планирования потоков, вы могли бы избежать проблем. Вместо того, чтобы планировать потоки с помощью переключателей контекста, существуют другие способы сделать это. И вы можете вызывать их из вашего потока выселения, используя ту же самую текущую модель программы.
Некоторые из этих предложений немного специфичны для системы. Если вы можете сказать нам, какая у вас ОС, мы найдем что-то подходящее для вашей ситуации. Или вы можете проверить это сами.
Читайте о планировании потоков POSIX. Обратите особое внимание на SCHED_FIFO, который будет работать с вашей моделью.
https://computing.llnl.gov/tutorials/pthreads/man/sched_setscheduler.txt
Обычно это относится к использованию библиотеки потоков POSIX для планирования потоков, вместо того, чтобы пытаться делать это сложным образом.