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

image-loader.svg


В этой статье мы научимся изменять ядро 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 
#include 
#include 
#include 


Саму же реализацию начнем с 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

Автор оригинала статьи выражает признательность за ваше внимание и приглашает посетить его блог CodingKaiser, где вы найдете много интересных материалов из мира технологий и разработки.

image-loader.svg

© Habrahabr.ru