Модификация ядра Linux: добавляем новые системные вызовы

Kate

Administrator
Команда форума
В этой статье мы научимся изменять ядро Linux, добавим собственные уникальные системные вызовы и в завершении соберем ядро с новой функциональностью.

Прежде чем перейти к модификации ядра, его нужно скачать.

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

  • Скачать ПО для запуска виртуальной машины, например Vimware или VirtualBox.
  • Скачать образ Ubuntu 18.04 (http://releases.ubuntu.com/18.04/).
  • Настроить виртуальную ОС, используя скачанный образ.

После загрузки Ubuntu открыть терминал и следовать дальнейшим инструкциям:

Установка необходимых компонентов:

>> sudo sed -i "s/# deb-src/deb-src/g" /etc/apt/sources.list
>> sudo apt update -y
>> sudo apt install -y build-essential libncurses-dev bison flex
>> sudo apt install -y libssl-dev libelf-dev

Скачивание исходного кода Linux:

>> cd ~
>> apt source linux

Изменение разрешений и переименование каталога:

>> sudo chown -R student:student ~/linux-4.15.0/
>> mv ~/linux-4.15.0 ~/linux-4.15.18-custom

Настройка сборки ядра:

>> cd ~/linux-4.15.18-custom
>> cp -f /boot/config-$(uname -r) .config
>> geany .config
# Найти параметр CONFIG_LOCALVERSION и установить его как "-custom"
>> yes '' | make localmodconfig
>> yes '' | make oldconfig

Компиляция ядра:

>> make -j $(nproc)

Установка модулей ядра и образа:

>> sudo make modules_install
>> sudo make install

Настройка GRUB:

>> sudo geany /etc/default/grub

После открытия файла сделайте следующее:

  • Установите GRUB_DEFAULT как Ubuntu, with Linux4.15.18-custom;
  • Установите GRUB_TIMEOUT_STYLE как menu;
  • Установите GRUB_TIMEOUT как 5;
  • В конце добавьте строку: GRUB_DISABLE_SUBMENUE=y.

Для завершения нам понадобится сгенерировать файл конфигурации GRUB и выполнить перезагрузку:

>> sudo update-grub
>> sudo reboot

После запуска системы убедитесь, что загрузили кастомное ядро:

>> uname -r

Вывод должен быть 4.15.18-custom.

На этом с подготовительной частью мы закончили.

Код​


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

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

Нам нужно реализовать поддержку двух поведенческих паттернов:

  • При ответвлении дочерний процесс будет получать тот же вес, что и его родитель;
  • Процесс init будет иметь вес 0.

Системный вызовы, которые мы собираемся реализовать, смогут:

  • Устанавливать вес текущего процесса;
  • Получать общий вес текущего процесса рекурсивно.

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

Для этого откройте ~/linux-4.15.18-custom/include/linux/sched.h
и в структуре task_struct добавьте целочисленный атрибут веса.

struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
/*
* По причинам, описанным в заголовочном файле (смотрите current_thread_info()), это
* должен быть первый элемент в task_struct.
*/
struct thread_info thread_info;
#endif
/* -1 unrunnable, 0 runnable, >0 stopped: */
int weight; #line 569
volatile long state;
/*
* С этого начинается рандомизируемая часть task_struct. Выше можно
* добавлять только критически важные для планировщика элементы.
*/
randomized_struct_fields_start

Теперь нужно сообщить каждому процессу, каков его начальный вес. Для этого в том же каталоге, что и ранее, откройте init_task.h, в нем перейдите к макроопределению INIT_TASK и добавьте в атрибут веса инициализацию.

#define INIT_TASK(tsk) \
{ \
INIT_TASK_TI(tsk) \
.weight = 0, \ #line 228
.state = 0, \
.stack = init_stack, \
...

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

В текущем же мы настроим основу для новых системных вызовов.

Перейдите в ~/linux-4.15.18-custom/arch/x86/entry/syscalls/ и откройте syscall_64.tbl.

Промотайте вниз файла и зарезервируйте номера системных вызовов.

...
332 common statx sys_statx
333 common hello sys_hello
334 common set_weight sys_set_weight
335 common get_total_weight sys_get_total_weight
...

Далее мы создадим сигнатуру системного вызова. В том же каталоге откройте syscalls.h и промотайте вниз файла:

...
asmlinkage long sys_pkey_free(int pkey);
asmlinkage long sys_statx(int dfd, const char __user *path, unsigned flags, unsigned mask, struct statx __user *buffer);
asmlinkage long sys_hello(void);
asmlinkage long sys_set_weight(int weight); #line 944
asmlinkage long sys_get_total_weight(void);
#endif

Все настроено. Осталось только реализовать эти системные вызовы.

Перейдите в ~/linux-4.15.18-custom/kernel и создайте новый файл syscalls_weight.c.

Не забудьте в том же каталоге открыть Makefile и добавить ваш новый файл в процесс сборки:

# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the linux kernel.
#
obj-y = fork.o exec_domain.o panic.o \
cpu.o exit.o softirq.o resource.o \
sysctl.o sysctl_binary.o capability.o ptrace.o user.o \
signal.o sys.o umh.o workqueue.o pid.o task_work.o \
extable.o params.o \
kthread.o sys_ni.o nsproxy.o \
notifier.o ksysfs.o cred.o reboot.o \
async.o range.o smpboot.o ucount.o hello_syscall.o syscalls_weight.o
...

Откройте созданный syscalls_weight.c, и давайте переходить к реализации.

Сначала добавляем библиотеки:

#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/sched.h>

Саму же реализацию начнем с sys_set_weight.

asmlinkage long sys_get_weight(int weight){
if(weight < 0){
return -EINVAL;
}
current->weight = weight;
return 0;
}

Обратите внимание:

  • current – это указатель на текущую активную задачу;
  • При работе с системными вызовами принято возвращать 0 в случае успешного выполнения и отрицательное значение при возникновении ошибки, как мы и прописали (учитывая, что мы не хотим допускать отрицательный вес).

Переходя к реализации следующего системного вызова, мы сначала определяем вспомогательную функцию:

int traverse_children_sum_weight(struct task_struct *root_task){
struct task_struct *task;
struct list_head *list;
int sum = root_task->weight;

list_for_each(list, &root_task->children){
task = list_entry(list, struct task_struct, sibling);
sum += traverse_children_sum_weight(task, true);
}
return sum;

После чего пишем саму реализацию:

asmlinkage long sys_get_total_weight(void){
return traverse_children_sum_weight(current);
}

Вот и все. Вам осталось только собрать ядро, перезапустить систему и начинать пользоваться новой функциональностью.

Для сборки и перезагрузки выполните следующие команды:

make -j $(nproc)
sudo cp -f arch/x86/boot/bzImage /boot/vmlinuz-4.15.18-custom
sudo reboot


 
Сверху