Билет 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, использующий экспортируемое имя, связывается с этим именем по абсолютному адресу.
- Пробуем загрузить 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)
- Теперь в правильном порядке:
$ 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 — это число таких ссылающихся модулей, которое называется счётчиком ссылок.
- Пытаемся выгрузить:
$ 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;
}