Билет 15 - honeycarbs/bmstu-os-6sem GitHub Wiki

Загружаемые модули ядра. Структура загружаемых модулей. Информация о процессах, доступная в ядре. Пример вывода информации о запущенных процессах, символ current (лаб. раб.). Взаимодействие загружаемых модулей в ядре. Экспорт данных. Пример взаимодействия модулей (лаб. раб.). Функция printk() – назначение и особенности. Регистрация функций работы с файлами. Пример заполненной структуры. Передача данных из пространства ядра в пространство пользователя и из пространства пользователя в пространство ядра. Примеры из лабораторный работ.

Загружаемые модули ядра

Загружаемые модули ядра - механизм, позволяющий включать дополнительные механизмы в ядро без его перекомпиляции. Структура модулей ядра имеет следующий вид:

static int __init md_init( void )
{
	printk( "Module md loaded\n" );
	return 0;
}

static void __exit md_exit( void )
{
	printk( "Module unloaded\n" );
}

module_init(md_init)
module_exit(md_exit)

После того, как такой модуль загружен в ядро (insmod), он становится частью кода ядра. Соответственно, в таком модуле могут использоваться библиотеки ядра. Для удаления используется команда rmmod.

В таком модуле надо использовать библиотеки ядра и функции ядра. Стандартные библиотеки Си использовать в ядре нельзя.

Структура загружаемых модулей

Загружаемые модули ядра имеют как минимум две точки входа - init и exit. Имена точек входа передаются с помощью макорса:

module_init(<точка входа init>)
module_exit(<точка входа exit>)

Макрос module_init служит для регистрации функции инициализации модуля. Макрос принимает имя функции в качестве фактического параметра. В результате эта функция будет вызываться, при загрузке модуля в ядро. Передаваемая функция должна соответствовать следующему прототипу:

int func_init(void);

Функция возвращает значение типа int. Если функция инициализации завершилась успешно, то возвращается значение нуль. В случае ошибки возвращается ненулевое значение.

Как правило, функция инициализации предназначена для запроса ресурсов, выделения памяти под структуры данных и т. п. Так как функция инициализации редко вызывается за пределами модуля, ее обычно не нужно экспортировать и можно объявить с ключевым словом static.

Макрос module_exit служит для регистрации функции, которая вызывается при удалении модуля из ядра. Обычно эта функция выполняет задачу освобождения занятых ресурсов. После завершения функции модуль выгружается.

Функция завершения должна соответствовать прототипу:

Функция завершения должна соответствовать прототипу:

void func_exit(void);

Пример вывода информации о запущенных процессах, символ current (лаб. раб.).

static int __init mod_init(void) {
    struct task_struct *task = &init_task; // структура - дескриптор процесса
    printk(KERN_INFO " + module is loaded.\n");
    do {
      printk(KERN_INFO " + %s - %d (%ld - state, %d - prio, %u - rt_prio, %d - static_prio, %d - normal_prio), parent %s - %d",
             task->comm, task->pid, task->state, task->prio, task->rt_priority, task->static_prio, task->normal_prio, task->parent->comm, task->parent->pid);
    } while ((task = next_task(task)) != &init_task);

    printk(KERN_INFO " + %s - %d, parent %s - %d, THREAD PID %c",
           current->comm, current->pid, current->parent->comm, current->parent->pid, current->thread_pid);
    return 0;
}

Кроме использования указателя task в системе есть так называемый символ current. Он определяет текущий процесс. Для его использования не требуется писать никаких указателей.

Иными словами символ current — это указатель на дескриптор текущего процесса, т. е. на экземпляр текущего task_struct.

Взаимодействие загружаемых модулей в ядре.

md1.c:

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

#include "md.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tatiana Kazaeva");

char* md1_data = "Привет мир!";

extern char* md1_proc(void) { return md1_data; }

static char* md1_local(void) { return md1_data; }

extern char* md1_noexport(void) { return md1_data; }

EXPORT_SYMBOL(md1_data);
EXPORT_SYMBOL(md1_proc);

static int __init md_init(void) {
  printk("+ module md1 start!\n");
  printk("+ module md1 use local from md1: %s\n", md1_local());
  printk("+ module md1 use noexport from md1: %s\n", md1_noexport());
  return 0;
}

static void __exit md_exit(void) { printk("+ module md1 unloaded!\n"); }

module_init(md_init);
module_exit(md_exit);

md2.c:

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

#include "md.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tatiana Kazaeva");

static int __init md_init(void) {
  printk("+ module md2 start!\n");
  printk("+ data string exported from md1 : %s\n", md1_data);
  printk("+ string returned md1_proc() is : %s\n", md1_proc());

  return 0;
}

static void __exit md_exit(void) {
  printk("+ module md2 unloaded!\n");
}

module_init(md_init);
module_exit(md_exit);

md.h:

extern char* md1_data;
extern char* md1_proc(void);
extern char* md1_noexport(void);

Один модуль может использовать данные и функции, используемые в другом модуле. Модуль md2, использующий экспортируемое имя, связывается с этим именем по абсолютному адресу.

  1. Пробуем загрузить cначала только md2:
$ sudo insmod md2.ko
insmod: ERROR: could not insert module md2.ko: Unknown symbol in module

В журнале:

$ dmesg
[ 6987.204306] md2: Unknown symbol md1_data (err -2)
[ 6987.204337] md2: Unknown symbol md1_proc (err -2)
  1. Теперь в правильном порядке:
$ sudo insmod md1.ko
$ sudo insmod md2.ko
$ lsmod | head -1 && lsmod | grep md
Module                  Size  Used by
md2                    16384  0
md1                    16384  1 md2

на модуль md1 ссылается некоторые другие модули или объекты ядра: цифра 1 — это число таких ссылающихся модулей, которое называется счётчиком ссылок.

  1. Пытаемся выгрузить:
$ sudo rmmod md1
rmmod: ERROR: Module md1 is in use by: md2

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

$ sudo rmmod md2
$ sudo rmmod md1
$ lsmod | head -1 && lsmod | grep md
Module                  Size  Used by

Функция printk() – назначение и особенности.

printk() - функция ядра, по аналогии с printf эта функция должна выводить текстовое сообщение, но в отличиии от printf printk выводит информацию в системный журнал. Для чтения журнала нужно использовать команду dmesg.
Система поддерживает 8 уровней протоколирования, например KERN_INFO или KERN_ERR. Каждая строка (макроподстановка) представляет собой целое число в угловых скобках. Чем больше величина, тем больше приоритет.

Регистрация функций работы с файлами.

С некоторой версии ядра file_operations не поддерживается. Пример из лаб. раб. регистрации функций работы с файлами:

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,6,0)
#define HAVE_PROC_OPS
#endif

#ifdef HAVE_PROC_OPS
static struct proc_ops fileops = {
    .proc_read = fortune_read,
    .proc_write = fortune_write,
    .proc_open = fortune_open,
    .proc_release = fortune_release,
};
#else
static struct file_operations fileops = {
    .owner = THIS_MODULE,
    .read = fortune_read,
    .write = fortune_write,
    .open = fortune_open,
    .release = fortune_release,
};
#endif

Передача данных из пространства ядра в пространство пользователя и из пространства пользователя в пространство ядра.

// const char __user *buf - адрес источника в пространстве пользователя
ssize_t fortune_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) {
  int space_left = (_BUF_SIZE - write_inx) + 1;

  printk(KERN_INFO "== fortine write is called");

  if (space_left < count) {
    printk(KERN_INFO "== ERROR: buffer is full. Terminating...");
    return -ENOSPC;
    }
    if (copy_from_user(&_cookie_pot[write_inx], buf, count)) {
      return -EFAULT; // no space left on device
    }

    write_inx += count;
    _cookie_pot[write_inx - 1] = '\0';

    printk(KERN_INFO "== writing successful");
    return count;
}

static ssize_t fortune_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {
    int len;
    if (*ppos > 0) {
        return 0;
    }
    if (read_inx >= write_inx) {
        read_inx = 0;
    }
    len = 0;

    if (write_inx > 0) {
        len = sprintf(tmp, "%s\n", &_cookie_pot[read_inx]);

        copy_to_user(buf, tmp, len);
        buf += len;
        read_inx += len;
    }

    *ppos += len;

    printk(KERN_INFO "== reading successful");
    return len;
}