Почему этот код тупик?

Я создал 2 потока ядра Linux в своем загружаемом модуле и привязал их к отдельным ядрам процессора, работающим на двухъядерном устройстве Android. После того, как я запустил это несколько раз, я заметил, что устройство перезагружается с сбросом сторожевого таймера HW. Я ударил вопрос последовательно. Что может быть причиной тупика?

По сути, мне нужно сделать так, чтобы оба потока одновременно выполняли do_something() на разных ядрах, чтобы никто не крал циклы процессора (т. Е. Прерывания отключены). Я использую спинлок и переменную переменную для этого. У меня также есть семафор для родительского потока для ожидания дочернего потока.

#define CPU_COUNT 2

/* Globals */
spinlock_t lock;
struct semaphore sem;
volatile unsigned long count;

/* Thread util function for binding the thread to CPU*/
struct task_struct* thread_init(kthread_fn fn, void* data, int cpu)
{
    struct task_struct *ts;

    ts=kthread_create(fn, data, "per_cpu_thread");
    kthread_bind(ts, cpu);
    if (!IS_ERR(ts)) {
        wake_up_process(ts);
    }
    else {
        ERR("Failed to bind thread to CPU %d\n", cpu);
    }
    return ts;
}

/* Sync both threads */
void thread_sync()
{   
    spin_lock(&lock);
    ++count;
    spin_unlock(&lock); 

    while (count != CPU_COUNT);
}

void do_something()
{
}

/* Child thread */
int per_cpu_thread_fn(void* data)
{
    int i = 0;
    unsigned long flags = 0;
    int cpu = smp_processor_id();

    DBG("per_cpu_thread entering (cpu:%d)...\n", cpu);

    /* Disable local interrupts */
    local_irq_save(flags);

    /* sync threads */
    thread_sync();

    /* Do something */
    do_something();

    /* Enable interrupts */
    local_irq_restore(flags);

    /* Notify parent about exit */
    up(&sem);
    DBG("per_cpu_thread exiting (cpu:%d)...\n", cpu);
    return value;
}

/* Main thread */
int main_thread()
{
    int cpuB;
    int cpu = smp_processor_id();
    unsigned long flags = 0;

    DBG("main thread running (cpu:%d)...\n", cpu);

    /* Init globals*/
    sema_init(&sem, 0);
    spin_lock_init(&lock);
    count = 0;

    /* Launch child thread and bind to the other CPU core */
    if (cpu == 0) cpuB = 1; else cpuB = 0;        
    thread_init(per_cpu_thread_fn, NULL, cpuB);

    /* Disable local interrupts */
    local_irq_save(flags);

    /* thread sync */
    thread_sync();

    /* Do something here */
    do_something();

    /* Enable interrupts */
    local_irq_restore(flags);

    /* Wait for child to join */
    DBG("main thread waiting for all child threads to finish ...\n");
    down_interruptible(&sem);
}

1 ответ

Решение

Я не уверен, это реальная причина, но ваш код содержит некоторые серьезные ошибки.

Сначала в while (count != CPU_COUNT);, Вы не должны читать переменную общего доступа без удержания блокировки, если только чтение не является атомарным. С count это не гарантировано.

Вы должны защищать от чтения count с замком. Вы можете заменить цикл while следующим:

unsigned long local_count;
do {
    spin_lock(&lock);
    local_count = count;
    spin_unlock(&lock);
} while (local_count != CPU_COUNT);

В качестве альтернативы вы можете использовать атомарные типы. Обратите внимание на отсутствие блокировки

atomic_t count = ATOMIC_INIT(0);

...

void thread_sync() {
    atomic_inc(&count);
    while (atomic_read(&count) != CPU_COUNT);
}

Вторая проблема с прерываниями. Я думаю, вы не понимаете, что делаете.

local_irq_save() сохраняет и отключает прерывания. Затем вы отключаете прерывания снова с local_irq_disable(), После некоторой работы вы восстанавливаете предыдущее состояние с помощью local_irq_restore()и разрешить прерывания с local_irq_enable(), Это включение совершенно неправильно. Вы разрешаете прерывания независимо от их предыдущего состояния.

Третья проблема Если основной поток не связан с процессором, вы не должны использовать smp_processor_id() если вы не уверены, что ядро ​​не будет перепланировано сразу после того, как вы получите номер процессора. Лучше использовать get_cpu(), который отключает выгрузку ядра, а затем возвращает идентификатор процессора. Когда закончите, позвоните put_cpu(),

Но когда вы звоните get_cpu(), это ошибка для создания и запуска других потоков. Вот почему вы должны установить сродство основного потока.

В-четвертыхlocal_irq_save() а также local_irq_restore() макросы, которые принимают переменную, а не указатель на unsigned long, (У меня есть ошибка и некоторые предупреждения, передающие указатели. Интересно, как вы скомпилировали свой код). Удалить ссылку

Окончательный код доступен здесь: http://pastebin.com/Ven6wqWf

Другие вопросы по тегам