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

Комментарии (1)


  1. nagayev
    25.08.2025 14:12

    Ура, Хабр снова торт!