Переключение контекста выполняет один и тот же оператор дважды

Я пытаюсь узнать, как работает переключение контекста и как заставить ваш процесс переключать контекст после получения определенного сигнала. Вот мой код

#include<stdio.h>
#include<stdlib.h>
#include<ucontext.h>
#include<signal.h>
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>

#define STACK_SIZE 4096

static ucontext_t thread1, thread2;

void thread1_fun() {
    static int a = 1;
    while (1) {
        printf("calling thread1 for %d time\n", a);
        sleep(5);
        a > 20 ? a = 0 : a++;
    }
}

void thread2_fun() {
    static int a = 1;
    while (1) {
        printf("calling thread2 for %d time\n", a);
        sleep(5);
        a > 20 ? a = 0 : a++;
    }
}

void sig_handler(int signal) {
    static int curr_thread = 0;
    printf("received signal %d\n", signal);
    if (curr_thread == 1) {
        curr_thread = 0;
        printf("switching from thread1 to thread2\n");
        setcontext(&thread1);
    } else {
        curr_thread = 1;
        printf("switching from thread2 to thread1\n");    
        setcontext(&thread2);
    }
}

int main() {
    int i = 0;
    struct sigaction act;
    act.sa_handler = sig_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGUSR1, &act, NULL);
    /* sigaction(SIGTERM, &act, NULL); */

    getcontext(&thread1);
    thread1.uc_stack.ss_sp = malloc (STACK_SIZE);
    thread1.uc_stack.ss_size = STACK_SIZE;
    thread1.uc_stack.ss_flags = 0;
    makecontext(&thread1, thread1_fun, 0);

    getcontext(&thread2);
    thread2.uc_stack.ss_sp = malloc (STACK_SIZE);
    thread2.uc_stack.ss_size = STACK_SIZE;
    thread2.uc_stack.ss_flags = 0;
    makecontext(&thread2, thread2_fun, 0);

    printf("%d\n", getpid());
    while (1);
}

Теперь я даю команду 'kill -s SIGUSR1 ' из терминала. Процесс переключает контекст после получения этого сигнала, но проблема в том, что он печатает "вызывающий поток в течение% d времени" дважды.

Например, если thread1 печатает "вызывающий thread1 для 3-го раза" и переходит в спящий режим, а thread1 находится в спящем режиме, если я посылаю сигнал для переключения контекста, thread2 начинает выполняться, теперь, если я снова отправляю сигнал для переключения контекста, то thread1 снова печатает "вызывающий thread1" в третий раз ". В идеале он должен выйти из сна и увеличить значение a, правильно? Почему он печатает одно и то же значение дважды?

Вот вывод, напечатанный кодом:

принятый сигнал 10

переключение с потока 2 на поток 1

вызов thread2 в течение 1 раза

принятый сигнал 10

переключение с нити 1 на нить 2

вызов thread1 в течение 1 раза

принятый сигнал 10

переключение с потока 2 на поток 1

вызов thread2 в течение 1 раза

принятый сигнал 10

переключение с нити 1 на нить 2

вызов thread1 в течение 1 раза

Пожалуйста, помогите мне с этим.

1 ответ

Несмотря на незначительность, члену uc_link элемента ucontext_t должно быть присвоено значение перед передачей в makecontext.

В противном случае из справочной страницы Linux setcontext вызовет функцию, которая была передана в makecontext. Это означает, что каждый раз, когда вызывается setcontext, не сохраняется никакой информации о предыдущем выполнении ни thread1_fun, ни thread2_fun. Вот почему одно и то же сообщение печатается несколько раз. При предыдущем вызове функция находилась в спящем режиме до того, как счетчик мог быть увеличен. При следующем вызове функция печатает неизмененное значение. С другой стороны, swapcontext сохранит текущий контекст в первом аргументе и активирует контекст во втором аргументе.

Также следует отметить, что обработчик сигнала работает в новом контексте. Это затрудняет вызов таких функций, как setcontext или swapcontext. Смотрите обсуждение здесь о swapcontext и сигналах. Как предлагается в этой ссылке, флаг типа sig_atomic_t может использоваться для сигнализации текущего состояния выполнения в thread1_fun и thread2_fun для вызова swapcontext.

Вот новая переменная сигнала, объявленная прямо под include:

static sig_atmoic_t switch_context = 0;

Вот обновленный thread1_fun (thread2_fun является аналогом с переключенными thread1 и thread2):

void thread1_fun() {
    static int a = 1;
    while (1) {
        printf("calling thread1 for %d time\n", a);
        sleep(5);
        a > 20 ? a = 0 : a++;
        if(switch_context) {
            switch_context = 0;
            swapcontext(&thread1, &thread2);
        }
    }
}

Вот новый sig_handler:

void sig_handler(int signal) {
    static int curr_thread = 1;
    printf("received signal %d\n", signal);
    if (curr_thread == 1) {
        curr_thread = 0;
        printf("switching from thread1 to thread2\n");
    } else {
        curr_thread = 1;
        printf("switching from thread2 to thread1\n");
    }
    switch_context = 1;
}

Дополнительно я заменил while (1); в конце основной функции для запуска контекста в thread1.

ucontext_t main_thread;
if (swapcontext(&main_thread, &thread1) == -1) {
    perror("swapcontext");
    exit(EXIT_FAILURE);
}
return 0;
Другие вопросы по тегам