Модификация ядра Linux: добавляем новые системные вызовы
В этой статье мы научимся изменять ядро Linux, добавим собственные уникальные системные вызовы и в завершении соберем ядро с новой функциональностью.
Прежде чем перейти к модификации ядра, его нужно скачать.
Я буду описывать все свои шаги и рекомендую следовать им в точности, чтобы гарантированно получить такой же результат.
- Скачать ПО для запуска виртуальной машины, например Vimware или VirtualBox.
- Скачать образ Ubuntu 18.04 (http://releases.ubuntu.com/18.04/).
- Настроить виртуальную ОС, используя скачанный образ.
Установка необходимых компонентов:
>> 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:
Изменение разрешений и переименование каталога:
>> 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
Установка модулей ядра и образа:
>> sudo make modules_install >> sudo make install
>> sudo geany /etc/default/grub
После открытия файла сделайте следующее:
- Установите GRUB_DEFAULT как Ubuntu, with Linux4.15.18-custom ;
- Установите GRUB_TIMEOUT_STYLE как menu ;
- Установите GRUB_TIMEOUT как 5 ;
- В конце добавьте строку: GRUB_DISABLE_SUBMENUE=y .
>> sudo update-grub >> sudo reboot
После запуска системы убедитесь, что загрузили кастомное ядро:
Вывод должен быть 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 и добавьте в атрибут веса инициализацию.
В последнем блоке мы сообщили каждому процессу, что у него теперь есть новый атрибут веса, и что при каждом создании нового процесса этот атрибут нужно инициализировать как 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)
Вот и все. Вам осталось только собрать ядро, перезапустить систему и начинать пользоваться новой функциональностью.
Для сборки и перезагрузки выполните следующие команды:
make -j $(nproc) sudo cp -f arch/x86/boot/bzImage /boot/vmlinuz-4.15.18-custom sudo reboot
Автор оригинала статьи выражает признательность за ваше внимание и приглашает посетить его блог CodingKaiser, где вы найдете много интересных материалов из мира технологий и разработки.
Linux. Системное программирование.
Данная книга рассказывает о системном программировании в Linux. Системное программирование — это практика написания системного ПО, низкоуровневый код которого взаимодействует непосредственно с ядром и основными системными библиотеками. Иными словами, речь далее пойдет в основном о системных вызовах Linux и низкоуровневых функциях, в частности тех, которые определены в библиотеке C. Есть немало пособий, посвященных системному программированию для UNIX-систем, но вы почти не найдете таких, которые рассматривают данную тему достаточно подробно и фокусируются именно на Linux. Еще меньше подобных книгучитывают новейшие релизы Linux и продвинутые интерфейсы, ориентированные исключительно на Linux. Эта книга не только лишена всех перечисленных недостатков, но и обладает важным достоинством: дело в том, что я написал массу кода для Linux, как для ядра, так и для системных программ, расположенных непосредственно «над ядром». На самом деле я реализовал на практике ряд системных вызовов и других функций, описанных далее. Соответственно книга содержит богатый материал, рассказывая не только о том, как должны работать системные интерфейсы, но и о том, как они действительно работают и как вы сможете использовать их с максимальной эффективностью. Таким образом, данная книга одновременно является и руководством по системному программированию для Linux, и справочным пособием, описывающим системные вызовы Linux, и подробным повествованием о том, как создавать более интеллектуальный и быстрый код. Текст написан простым, доступным языком. Независимо от того, является ли создание системного кода вашей основной работой, эта книга научит полезным приемам, которые помогут вам стать по-настоящему высокопрофессиональным программистом.