В этой статье мы научимся изменять ядро 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
Автор оригинала статьи выражает признательность за ваше внимание и приглашает посетить его блог CodingKaiser, где вы найдете много интересных материалов из мира технологий и разработки.