Выявление ошибки в модуле ядра Linux

Я отмечаю Майкла, как он был первым. Спасибо osgx и сотруднику месяца за дополнительную информацию и помощь.

Я пытаюсь определить ошибку в модуле ядра потребителя / производителя. Это проблема, которую мне дают на курсе в университете. Мой ассистент преподавателя не смог понять это, и мой профессор сказал, что все будет в порядке, если я загрузлю онлайн (он не думает, что Стек может понять это!).

  • Я включил модуль, make-файл и Kbuild.
  • Запуск программы не гарантирует появления ошибки.
  • Я думал, что проблема была в строке 30, так как поток может поспешить к строке 36 и истощить другие потоки. Мой профессор сказал, что это не то, что он ищет.
  • Несвязанный вопрос: какова цель строки 40? Это кажется неуместным для меня, но мой профессор сказал, что это оправдывает себя.
  • Мой профессор сказал, что ошибка очень тонкая. Баг не тупиковый.
  • Мой подход заключался в определении критических разделов и общих переменных, но я в тупике. Я не знаком с трассировкой (как метод отладки), и мне сказали, что, хотя это может помочь, нет необходимости определять проблему.

Файл: final.c

#include <linux/completion.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/module.h>

static int actor_kthread(void *);
static int writer_kthread(void *);

static DECLARE_COMPLETION(episode_cv);
static DEFINE_SPINLOCK(lock);
static int episodes_written;
static const int MAX_EPISODES = 21;
static bool show_over;
static struct task_info {
    struct task_struct *task;
    const char *name;
    int (*threadfn) (void *);
} task_info[] = {
    {.name = "Liz", .threadfn = writer_kthread},
    {.name = "Tracy", .threadfn = actor_kthread},
    {.name = "Jenna", .threadfn = actor_kthread},
    {.name = "Josh", .threadfn = actor_kthread},
};

static int actor_kthread(void *data) {
    struct task_info *actor_info = (struct task_info *)data;
    spin_lock(&lock);
    while (!show_over) {
        spin_unlock(&lock);
        wait_for_completion_interruptible(&episode_cv); //Line 30
        spin_lock(&lock);
        while (episodes_written) {
            pr_info("%s is in a skit\n", actor_info->name);
            episodes_written--;
        }
        reinit_completion(&episode_cv); // Line 36
    }

    pr_info("%s is done for the season\n", actor_info->name);
    complete(&episode_cv); //Why do we need this line?
    actor_info->task = NULL;
    spin_unlock(&lock);
    return 0;
}

static int writer_kthread(void *data) {
    struct task_info *writer_info = (struct task_info *)data;
    size_t ep_num;

    spin_lock(&lock);
    for (ep_num = 0; ep_num < MAX_EPISODES && !show_over; ep_num++) {
        spin_unlock(&lock);

        /* spend some time writing the next episode */
        schedule_timeout_interruptible(2 * HZ);

        spin_lock(&lock);
        episodes_written++;
        complete_all(&episode_cv);
    }

    pr_info("%s wrote the last episode for the season\n", writer_info->name);
    show_over = true;
    complete_all(&episode_cv);
    writer_info->task = NULL;
    spin_unlock(&lock);
    return 0;
}

static int __init tgs_init(void) {
    size_t i;
    for (i = 0; i < ARRAY_SIZE(task_info); i++) {
        struct task_info *info = &task_info[i];
        info->task = kthread_run(info->threadfn, info, info->name);
    }
    return 0;
}

static void __exit tgs_exit(void) {
    size_t i;
    spin_lock(&lock);
    show_over = true;
    spin_unlock(&lock);
    for (i = 0; i < ARRAY_SIZE(task_info); i++)
        if (task_info[i].task)
            kthread_stop(task_info[i].task);
}

module_init(tgs_init);
module_exit(tgs_exit);
MODULE_DESCRIPTION("CS421 Final");
MODULE_LICENSE("GPL");

Файл: kbuild

Kobj-m := final.o

Файл: Makefile

# Basic Makefile to pull in kernel's KBuild to build an out-of-tree
# kernel module

KDIR ?= /lib/modules/$(shell uname -r)/build

all: modules

clean modules:

2 ответа

Решение

При уборке в tgs_exit() функция выполняет следующее, не удерживая спин-блокировку:

    if (task_info[i].task)
        kthread_stop(task_info[i].task);

Это возможно для потока, который заканчивается, чтобы установить его task_info[i].task NULL между проверкой и вызовом kthread_stop(),

Я здесь совсем запутался.

Вы утверждаете, что это вопрос предстоящего экзамена, и он был выпущен лицом, проводящим курс. Почему они это сделали? Тогда вы говорите, что ТА не удалось решить проблему. Если ТА не может этого сделать, кто может ожидать, что студенты сдают экзамен?

(профессор) не думает, что Стек может понять это

Если утверждают, что уровень на этом сайте плохой, я определенно согласен. Но, тем не менее, заявлять, что он ниже уровня, ожидаемого от случайного университета, - это натяжение. Если нет никаких претензий такого рода, я еще раз спрашиваю, как студенты должны это делать. Что если проблема решится?

Сам код непригоден для обучения, поскольку он слишком сильно отличается от распространенных идиом.

Другой ответ здесь отметил один побочный эффект актуальной проблемы. А именно, было заявлено, что цикл в tgs_exit может состязаться с потоками, выходящими самостоятельно, и проверить указатель задачи -> на ненулевое, а сразу после этого он становится равным NULL. Обсуждение, может ли это привести к вызову kthread_stop(NULL), на самом деле не актуально.

Либо поток ядра, выходящий сам по себе, все очистит, либо для этого необходим kthread_stop (и, возможно, что-то еще).

Если первое верно, код страдает от возможного использования после освобождения. После того, как tgs_exit проверит, что указатель, целевой поток мог завершиться. Может быть, до вызова kthread_stop или, может быть, так же, как он был выполнен. В любом случае, возможно, что переданный указатель устарел, поскольку область уже была освобождена выходящим потоком.

Если последнее верно, код страдает от утечек ресурсов из-за недостаточной очистки - нет вызовов kthread_stop, если tgs_exit выполняется после завершения всех потоков.

Kthread_* api позволяет потокам просто выходить, поэтому эффекты такие же, как описано в первом варианте.

В качестве аргумента, скажем, код вкомпилирован в ядро ​​(в отличие от загрузки в виде модуля). Скажем, функция выхода вызывается при выключении.

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

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

actor_info->task = NULL;

Таким образом, поток пропускается в обработчике выхода, который теперь может завершиться и позволить ядру выгружать сам модуль...

spin_unlock(&lock);
return 0;

... но этот код (расположенный в модуле!), возможно, еще не был выполнен.

Этого бы не произошло, если бы код следовал идиоме, если всегда использовал kthread_stop.

Другая проблема заключается в том, что писатели разбудили всех (так называемая "проблема громового стада"), а не одного актера.

Возможно, ошибка, которую нужно найти, состоит в том, что в каждом эпизоде ​​есть не более одного актера? Может быть, модуль может выйти, когда есть эпизоды, написанные, но еще не разыгранные?

Код очень странный, и если вам показали разумную реализацию поточно-ориентированной очереди в пользовательском пространстве, вы должны увидеть, что то, что здесь представлено, не подходит. Например, почему он блокируется мгновенно, не проверяя эпизоды?

Также забавный факт, что блокировка записи в show_over не играет никакой роли в правильности.

Есть больше проблем, и вполне вероятно, что я пропустил некоторые. На самом деле, я думаю, что вопрос низкого качества. Это не похоже ни на что в реальном мире.

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