Вам когда-нибудь хотелось создать собственный системный вызов? Может быть, вы получали такое домашнее задание, пытались сделать это из интереса или просто для того, чтобы узнать что-то новое о ядре. В любом случае, системные вызовы – крутая штука, чтобы подробнее разобраться в Linux.
❯ Чем может быть интересно это руководство?
Есть и другие руководства на ту же тему, что и этот пост, но проблема заключается в том, что они не успевают за разработкой ядра — слишком быстро она идёт. Многие руководства не только успели устареть, но и содержат немало ошибок. Именно поэтому я и решил написать этот пост, заодно проследив такие ошибки и исправив их.
❯ Подготовка системы для компиляции ядра
В дистрибутивах на основе Red Hat / Fedora / Open Suse можно попросту сделать так:
Sudo dnf builddep kernel
Sudo dnf install kernel-devel
В дистрибутивах на основе Debian / Ubuntu:
sudo apt-get install build-essential vim git cscope libncurses-dev libssl-dev bison flex
❯ Получаем ядро
Клонируем ветку исходников ядра, причём, нас интересует именно версия 6.8. Но описанные здесь инструкции должны работать и на более новых версиях ядра (если, конечно, разработчики ядра вновь не поменяют весь процесс).
git clone --depth=1 --branch v6.8 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
В идеале требуется, чтобы склонированная версия была не старше или новее той версии ядра, что установлена у вас сейчас.
Проверить текущую версию ядра можно следующей командой:
uname -r
❯ Создаём новый системный вызов
Выполним следующий код:
cd linux
make mrproper
mkdir hello
cd hello
touch hello.c
touch Makefile
Так мы создадим каталог под названием «hello
» прямо в загруженном коде ядра, а в этом каталоге положим два файла — hello.c
с кодом системного вызова и Makefile, в котором в виде правил изложено, как составить такой же файл.
Откройте hello.c
в вашем любимом текстовом редакторе и запишите в него следующий код:
#include <linux/kernel.h>
#include <linux/syscalls.h>
SYSCALL_DEFINE0(hello) {
pr_info("Hello World\n");
return 0;
}
В логе ядра будет выведено «Hello World
».
В https://www.kernel.org/doc/html/next/process/adding-syscalls.html сказано:
Смысл макроса
"SYSCALL_DEFINEn()
вполне понятен. Под ‘n’ понимается количество аргументов у системного вызова, а далее макрос принимает имя системного вызова, за которым следуют пары (тип, имя) для параметров, сообщаемых в качестве аргументов.”
Поскольку мы хотим просто вывести информацию на экран, воспользуемся n=0
Далее добавим в Makefile следующий текст:
obj-y := hello.o
Теперь:
cd ..
cd include/linux/
Откроем в этом каталоге файл “syscalls.h
” и добавим:
asmlinkage long sys_hello(void)

Это прототип той функции системного вызова, которую мы создали выше.
Откройте файл “Kbuild
” в корне ядра (cd ../..) и в самом низу его добавьте:
obj-y += hello/

Таким образом мы сообщаем системе сборки ядра, что нужно скомпилировать и только что включённый каталог.
Справившись с этим, мы также должны указать системный вызов как новую запись в таблице, описывающей архитектуру.
В каждой архитектуре ЦП могут быть свои специфичные системные вызовы, поэтому в программе нужно сообщить, на какой архитектуре мы работаем.
Файл для x86_64
выглядит так:
arch/x86/entry/syscalls/syscall_64.tbl
Именно сюда добавьте запись о системном вызове. Не забывайте, что ему можно присвоить только такой номер, который пока не занят, и ни в коем случае не используйте номера, запрещённые в комментариях к таблице.
В моем случае оказался свободен номер 462, и я добавил новую запись вот так:
462 common hello sys_hello

Здесь 462 отображается на наш системный вызов, такая практика обычна для обеих архитектур. Наш системный вызов называется hello, а его входная функция — sys_hello
.
❯ Компилируем и устанавливаем новое ядро
Выполним команды, перечисленные в этом разделе.
ЗАМЕЧАНИЕ: я никоим образом и ни в какой форме не могу гарантировать безопасность, надёжность, целостность и стабильность вашей системы после того, как вы проделаете всё описанное в данном руководстве. Все перечисленные здесь инструкции я собрал по собственному опыту и не знаю, как они отразятся на работе ваших систем. Поэтому дальнейшую работу продолжайте внимательно и осторожно.
Теперь, когда мы сделали все разрешённое, перейдём к запрещённому ;)
cp /boot/config-$(uname -r) .config
make olddefconfig
make -j $(nproc)
sudo make -j $(nproc) modules_install
sudo make install
Здесь мы копируем конфигурационный файл того ядра, что загружено в настоящий момент, приказываем системе сборки использовать те же значения, что выставлены там, а для всех остальных параметров оставляем значения по умолчанию. Затем собираем ядро с возможностью параллельной обработки, исходя из того, сколько ядер у нас в распоряжении — это указано в nproc. После этого на свой страх и риск устанавливаем собранное нами ядро.
На компиляцию ядра уходит много времени, так что заварите себе кофе и полюбуйтесь рядами строк в терминале.
Весь процесс может занять до нескольких часов, в зависимости от скорости работы системы. Кулер вашего компьютера может реветь, только чтобы не допустить перегрева (да, был такой опыт).
❯ Самое интересное: используем новый системный вызов
Теперь, когда наш системный вызов запечён в ядре, перезагрузим систему и убедимся, что новое (модифицированное нами) ядро выбирается из grub при загрузке.

Загрузившись, давайте напишем на C программу, которая будет использовать этот системный вызов.
Создадим файл, который будет называться, например, «test.c
», в котором будет следующее содержимое:
#include <stdio.h>
#include <sys/syscall.h>
#include <unistd.h>
int main(void) {
printf("%ld\n", syscall(462));
return 0;
}
Здесь замените 462
тем номером, которым вы обозначили новый системный вызов в вашей таблице.
Скомпилируйте программу и запустите её.
make test
chmod +x test
./test
Если всё пройдёт нормально, то в командной строке появится «0
», и вывод системного вызова будет виден во всех логах ядра.
К логам обращаемся через dmesg
sudo dmesg | tail
Вуаля — здесь вы увидите сообщение, выведенное вашим системным вызовом.
Поздравляю, вы справились! Но ещё раз напомню следующие моменты:
На компиляцию ядра уходит много времени;
Свежескомпилированное ядро занимает довольно много места, поэтому убедитесь, что это место у вас имеется;
Код ядра Linux быстро меняется.
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩
nagayev
Ура, Хабр снова торт!