LINUX.ORG.RU

wakeup_process() и многопоточие


0

2

есть модуль ядра с character device. ф-ия read работает так:
-усыпляет процесс (current-state = TASK_INTERRUPTIBLE)
-запускает дма трансфер и ждёт (shedule())
-получает данные от устройства(interrupt)
-копирует данные процессу и делает wakeup_process().
Однопоточная программа просыпается. Многопоточная - нет. Виснет на read.
В чём может быть проблемма?

Вставляй отладочный printk в read, тогда поймешь, на чем висит.

ttnl ★★★★★
()
Ответ на: комментарий от fang

кусок обработчика прерывания:

intr_done = 1;
if(sp605.task){//если кто-то эти данные ждёт - будим
    wake_up_process(sp605.task);
}

кусок read:

set_current_state(TASK_INTERRUPTIBLE);
set_and_run_dma_transfer();//запускаем dma и ждём
while(!intr_done){
    schedule();
}
intr_done = 0;
set_current_state(TASK_RUNNING);

повставлял printk. прерывание приходит, но потом но wake_up_process не будится почему-то. вроде как schedule() не срабатывает. С однопоточным приложением проблем нет.

nanoo_linux
() автор топика
Ответ на: комментарий от nanoo_linux

> if(sp605.task){//если кто-то эти данные ждёт - будим

wake_up_process(sp605.task);

}



А что произойдёт, если два треда полезут к этому девайсу? - Один заменит другого в «sp605.task» и этот другой никогда не проснётся. Для этих целей используют wait queues. В твоей ситуации, вероятнее всего, нужно эксклюзивное ожидание.

while(!intr_done){

schedule();


}



Проблема с синхронизацией. Запросто возможна ситуация, когда установленный intr_done будет сигнализировать об окончании _предыдущего_ dma, и тред пройдёт этот код на ура, не дождавшись окончания передачи данных. Этот флаг вообще не нужен. При использовании wait queue тебе перед вызовом schedule() достаточно будет проверить, присутствует ли до сих пор этот тред в очереди и, если нет, то радостно выходить отсюда.

fang
()
Ответ на: комментарий от fang

В твоей ситуации, вероятнее всего, нужно эксклюзивное ожидание.

Вы абсолюнтно правы. Именно эксклюзивное ожидание и используется. После того, как какой-либо процес открыл устройство все остальные посылаются в -EBUSY.

nanoo_linux
() автор топика
Ответ на: комментарий от nanoo_linux

тут ещё такой моментик. если процессу sigstop а потом sigcont послать - срабатывает. read завершается. Чувсвую, что я всё таки что-то напортачил. Буду рад любым идеям. Спасибо.

nanoo_linux
() автор топика
Ответ на: комментарий от nanoo_linux

> Буду рад любым идеям

Боюсь, что этих двух приведённых маленьких кусочков мне будет недостаточно для того, чтобы новых идей нагенерить. Хотелось бы увидеть код read() и обработчика целиком. Ну, или можно ещё проще: выложи куда-нибудь модуль целиком и дай ссылку.

И хотелось бы сразу уточнить: читать лезет всегда один поток?

fang
()
Ответ на: комментарий от fang
...
static irqreturn_t sp605_interrupt_handler(int irq, void *dev_id)
{
        intr_done = read_reg(DCR2_OFFSET) & 0x8 >> 3;
        if(!intr_done){
                printk("irq called but dma_done is 0!\n");
                return IRQ_HANDLED;
        }
        pr_info(">>> intr 1\n");
        if(sp605.task){
                wake_up_process(sp605.task);
        }
        pr_info(">>> intr 2\n");
        return IRQ_HANDLED;
}
...
static ssize_t sp605_read
(struct file *fils, char __user *user, size_t len, loff_t *offset)
{
        unsigned long user_out;
        pr_info(">>> 1\n");
        set_current_state(TASK_INTERRUPTIBLE);
                set_and_run_dma_transfer();
                while(!intr_done){
                        pr_info(">>> 2\n");
                        schedule();
                        pr_info(">>> 3\n");
                }
        pr_info(">>> 4\n");
        intr_done = 0;
        set_current_state(TASK_RUNNING);

        pci_dma_sync_single_for_cpu(
                sp605.sp605,
                sp605.d_addr,
                sp605.d_size,
                PCI_DMA_FROMDEVICE
        );
        if(len > sp605.d_size){
                len = sp605.d_size;
        }
        pr_info(">>> 5\n");
        user_out = copy_to_user(user, (void*)sp605.buff, sp605.d_size);
        pci_dma_sync_single_for_device(
                sp605.sp605,
                sp605.d_addr,
                sp605.d_size,
                PCI_DMA_FROMDEVICE
        );
        pr_info(">>> 6\n");
        user_out = sp605.d_size - user_out;
        return user_out;
}

читать лезет всегда один поток

так точно. в open что-то вроде защиты стоит. тоже с флагами. Знаю, что это быдлокод, что надо в private_data указатели со структурами писать, но пока сделано так. Там ещё много косяков. Боюсь даже представить, что будет, когда два устройства в комп вставят.

nanoo_linux
() автор топика
Ответ на: комментарий от nanoo_linux

> > читать лезет всегда один поток

так точно.


тогда мне не очень понятно, почему виснет только
в mt случае.

но даже в этом коде много ошибок. «if(sp605.task)»
выглядит racy. скорее всего, так оно и есть, но
я не вижу где/как этот .task меняется.

while/schedule ожидание просто неправильно. начиная
со второй итерации это busy wait.

если TASK_INTERRUPTIBLE, тогда надо проверять
signal_pending().

в любом случае, выкиньте это все. для этого есть
wait_event_interruptible().

idle ★★★★★
()
Ответ на: комментарий от nanoo_linux

set_current_state(TASK_INTERRUPTIBLE);

set_and_run_dma_transfer();

static ssize_t sp605_read
(struct file *fils, char __user *user, size_t len, loff_t *offset)
{
    ....
    set_and_run_dma_transfer();

    spin_lock_irqsave(...);
    if (!intr_done) {
        set_current_state(TASK_INTERRUPTIBLE);
    }
    spin_lock_irqrestore(...);
    
    while (!intr_done) {    
        schedule();
    }
    ...
}
static irqreturn_t sp605_interrupt_handler(int irq, void *dev_id)
{
    spin_lock_irqsave(...);
    intr_done = read_reg(DCR2_OFFSET) & 0x8 >> 3;
    spin_lock_irqrestore(...);

    if(!intr_done){
        printk("irq called but dma_done is 0!\n");
        return IRQ_HANDLED;
    }
    pr_info(">>> intr 1\n");
    if(sp605.task){
        wake_up_process(sp605.task);
    }
    pr_info(">>> intr 2\n");
    return IRQ_HANDLED;
}
fang
()
Ответ на: комментарий от idle

в многопоточном приложении только один поток читает, да и вообще с устройством работает.

ну в общем принцип, которыя я пытался реализовать такой:
1) read запускает dma и засыпает до тех пор пока dma не закончится.
2) когда dma закончилось, прерывание вызывает обработчик, который будит заснувший процесс.
3) процесс читает всё из dma буфера и возрващается в пользовательское пространство.

Из-за того, что один процесс всё синхронно по идее. Спинлок вроде как не нужен.

sp605.task ставится в open, а в close сбрасывается в NULL.

nanoo_linux
() автор топика
Ответ на: комментарий от nanoo_linux

> опишите хотя бы алгоритм

да какой тут алгоритм ;)

на этой статье


глянул одним глазом... в этой же статье и про
wait_event_interruptible() есть про который я
говорил.

только, еще раз, если interruptible - надо проверять
сигналы (те смотреть, что оно возвращает).

sp605.task ставится в open, а в close сбрасывается в NULL.


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

idle ★★★★★
()
Ответ на: комментарий от idle

> не думаю, что нам нужен spinlock вокруг intr_done

Не спорю - дрянь. Как я уже написал, я не думаю, что сам intr_done нужен. Я попытался просто с помощью минимальных изменений заставить код работать. Аргументация для сего простая: например, dma отработал до того, как поток выставил своё состояние в TASK_INTERRUPTIBLE, а сам поток отработал свой time slice до проверки intr_done. Не вижу, как в этом случае поток когда-нибудь получит снова управление (без постороннего вмешательства - с помощью, например, посылки сигнала).

fang
()
Ответ на: комментарий от fang

В общем, затеял я это всё по одной простой причине - потому что усмотрел, что в исходном варианте возможна ситуация, когда тред выставил своё состояние в TASK_INTERRUPTIBLE, но dma запустить не успел, потому что у него кончился тайм слайс. Но реализовал всё по-дурацки, поторопился. Можно гораздо проще:

static ssize_t sp605_read
(struct file *fils, char __user *user, size_t len, loff_t *offset)
{
    unsigned long user_out;
    pr_info(">>> 1\n");
    preempt_disable();
    set_current_state(TASK_INTERRUPTIBLE);
    set_and_run_dma_transfer();
    preempt_enable();
    ...
} 

Если после этого виснуть не перестанет, значит проблема, вероятно, в логике, open()/close(). Вообще, неплохо было бы удостовериться, что процесс, который работает с устройством, и процесс, про который драйвер думает, что он работает с устройством — один и тот же. Неплохо бы tid этого процесса распечатать и в read() и в interrupt_handler().

fang
()
Ответ на: комментарий от fang

> dma отработал до того, как поток выставил своё состояние

TASK_INTERRUPTIBLE, а сам поток отработал свой time slice

до проверки intr_done.



поэтому нам нужен барьер между изменением task->state и
проверкой intr_done.

и он у нас есть, именно поэтому set_task_state() делает mb().

spinlock не нужен.

idle ★★★★★
()
Ответ на: комментарий от fang

> тред выставил своё состояние в TASK_INTERRUPTIBLE,

но dma запустить не успел, потому что у него кончился

тайм слайс.



нет, у вас непрвавильное понимание. если бы дело обстояло
так, как вы думаете, любое изменение task->state было бы
ошибочным.

даже если thread вытесняется (в вашей терминологии
time slice закончился) сразу после set_task_state(),
он остается runnning. да, даже если у него ->state =
TASK_INTERRUPTIBLE.

см schedule(). перед тем, как сделать deactivate_task()
мы проверяем PREEMPT_ACTIVE.

idle ★★★★★
()
Ответ на: комментарий от idle

В общем, насколько я понял, вы предлагаете сделать wait_queue? Хорошо, попробую. Но доступ к машине будет на следующей неделе только. Так что отпишусь не скоро.
В любом случае спасибо за помощь!

nanoo_linux
() автор топика
Ответ на: комментарий от nanoo_linux

Сделал через waitqueue - работает. Так и не понял в чём траблы были.

nanoo_linux
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.