Недавно поступили такие задачи: собрать ядро Linux, написать для него модуль и с его помощью перехватывать системные вызовы. И если первые две я выполнил без проблем, то в процессе выполнения третьей у меня возникло впечатление, что работа с системными вызовами вышла из моды лет 10 назад.
Периодически я находил в интернете статьи, которые были близкими к тому, что я искал, некоторые были даже очень хорошо написаны, но у всех был существенный недостаток — они устарели.
Начальные условия
- 4 ядра процессора Intel Core i7
- 4 Гб оперативной памяти + 4 Гб swap
- Ubuntu 16.10 x64 на виртуальной машине VirtualBox 5.1.10
- Ядро Linux 4.9.0
- gcc 6.2.0
Для редактирования конфигурации ядра в псевдографическом режиме нужен ncurses:
sudo apt-get update
sudo apt-get install libncurses5-dev
Сборка чистого ядра
Я рекомендую собрать чистое ядро прежде, чем начать разработку модулей. На это есть 2 причины:
- Первая сборка ядра — довольно продолжительный процесс. Чаще всего он длится от 20 минут до 3 часов. Если же провести сборку заранее, вы получите большую часть бинарников ядра, которые не будут нуждаться в перекомпиляции. Это позволит полностью сосредоточиться на разработке модуля, не мучаясь в ожидании ответа на вопрос “Запустится ли мой первый Hello World?”
- Успешно собрав чистое ядро, вы убедитесь, что на этом этапе нет проблем и что можно приступать к следующему. Иногда загрузка со свежесобранным ядром может быть неуспешной, и, если вы собирали его вместе с модулем, сложно будет понять, что именно положило систему.
Итак, сборка ядра:
- Скачиваем архив с исходниками:
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-x.x.x.tar.xz
где x.x.x — версия ядра.
Либо можно скачать архив руками с kernel.org
- Извлекаем данные из архива:
tar -xpJf linux-x.x.x.tar.xz
- Переходим в только что распакованную папку:
cd linux-x.x.x
- Генерируем конфигурацию ядра по умолчанию:
make defconfig
Для продвинутых:make menuconfig
Откроется псевдографический интерфейс конфигурации ядра. В большинстве опций разобраться не сложно, но без понимания каждого изменяемого параметра очень легко всё поломать. Для первой сборки я рекомендую использовать всё же параметры по умолчанию.
- Запускаем непосредственно сборку ядра и модулей:
make && make modules
Сборка будет длиться от 20 минут до 3х часов.
Лайфхак:make -j x && make modules -j x
Где x — количество ядер процессора + 1. То есть в моём случае x = 5.
Такое значение рекомендуют установить во всех руководствах, но на самом деле значение можно установить любое. Я решил “увеличить количество ядер вдвое”, то есть запустить сборку c параметром -j 9. Это не ускоряет сборку в 2 раза, но увеличивает конкурентоспособность процессов сборки по отношения ко всем другим процессам в системе.
Кроме того, в системном мониторе(gnome-system-monitor) всем make-процессам я установил максимальный приоритет. Система после этого буквально зависла, но сборка прошла за 6 минут. Используйте этот метод на свой страх и риск.
После успешной сборки нужно установить всё то, что мы собрали. Это требует root-прав.
- Установка заголовков:
sudo make headers_install
- Установка модулей:
sudo make modules_install
- Установка непосредственно ядра:
sudo make install
- Команды установки должны сгенерировать начальный RAM-диск и обновить grub. Если вдруг начальный RAM-диск не сгенерировался — система с новым ядром не запустится.
Проверить это можно по наличию файла "/boot/initrd.img-x.x.x" (x.x.x — версия ядра)
Если файла не обнаружилось — генерируем его руками:
sudo update-initramfs –c –k x.x.x
- Обновляем загрузчик grub:
sudo update-grub
Готово! После перезапуска система запустится с новым ядром. Проверить текущую версию ядра:
uname -r
Если вдруг что-то пошло не так, и система не загружается с новым ядром, перезагрузите компьютер, в меню grub перейдите в advanced options и выберите другую версию ядра(ту, под которой вы загружались раньше, обычно у версий по умолчанию добавляют суффикс -general)
Создание модуля
Создание модуля ядра во многом напоминает написание обычной пользовательской программы на C, кроме некоторых отличий связанных с ядром:
- Ядро не имеет доступа к стандартным библиотекам языка C. Причина этого – скорость выполнения и объем кода. Часть функций, однако, можно найти в исходниках ядра. Например, обычные функции работы со строками описаны в файле lib/string.c
- Отсутствие защиты памяти. Если обычная программа предпринимает попытку некорректного обращения к памяти, ядро может аварийно завершить процесс. Если ядро предпримет попытку некорректного обращения к памяти, результаты будут менее контролируемыми. К тому же ядро не использует замещение страниц: каждый байт, используемый в ядре, – это один байт физической памяти.
- В ядре нельзя использовать вычисления с плавающей точкой. Активизация режима вычислений с плавающей точкой требует сохранения и проставления регистров устройства поддержки вычислений с плавающей точкой, помимо других рутинных операций.
- Фиксированный стек(причём довольно небольшой). Именно поэтому не рекомендуется использовать рекурсию в ядре.
Hello world!
Давайте на конкретном примере рассмотрим “Hello world” в виде модуля ядра. Создадим файл hello.c в любой удобной для вас папке:
// hello.c
#include <linux/module.h>
#include <linux/kernel.h>
static int __init myinit(void)
{
printk("%s\n","<my_tag> hello world");
return 0;
}
static void __exit myexit(void) {}
module_init(myinit);
module_exit(myexit);
MODULE_LICENSE("GPL");
Обычная пользовательская программа начинается с вызова функции main() и работает, пока не возвратит системе какое-то значение.
Модуль же по сути является частью кода самого ядра, в котором содержатся функции-обработчики некоторых событий. Простейший пример такого события — загрузка или выгрузка самого модуля.
Функции, которые обрабатывают эти события — соответственно
static int __init myinit(void)
static void __exit myexit(void)
Они отмечены макросами __init, __exit и зарегистрированы с помощью module_init и module_exit как обработчики событий. Название этих функций может быть любым, но не должно конфликтовать с другими функциями в ядре.
Поскольку ядро не использует стандартную библиотеку C, мы не можем использовать stdio.h. Вместо этого мы подключаем файл kernel.h, в котором реализована функция printk. Эта функция аналогична printf с тем лишь отличием, что выводит сообщения не в окно терминала, а в системный лог (/var/log/syslog).
В этот лог пишется очень много сообщений со всей системы, поэтому наши нужно пометить каким-то оригинальным тегом, чтобы потом с помощью утилиты grep можно было выделить только сообщения нашего модуля.
Ещё одна непонятная строчка — MODULE_LICENSE(«GPL»);
Она указывает, что наш модуль соответствует лицензии GPL. Без этого часть возможностей внутри ядра будет недоступна.
Сборка
Для того чтобы собрать этот модуль в той же папке, где лежит исходный код модуля, создадим Makefile:
# указываем путь к недавно собранному ядру
KERNEL_PATH = /path-to-your-kernel/linux-x.x.x
# перечисляем файлы, которые будут собираться в файлы модулей
obj-m += hello.o
all:
# запуск make с параметром -C указывает, что мы запускаем сборку в окружении
# KERNEL_PATH. Это нужно для того, чтобы компилятор подтянул зависимости
# из исходников ядра
# SUBDIRS - это место, в котором будут лежать результаты сборки,
# в данном случае - текущая папка
make -C $(KERNEL_PATH) SUBDIRS=$(PWD) modules
# Удаление ненужных файлов, которые создались в процессе сборки
make clean
# описание файлов, которые можно удалить после сборки
clean:
rm -f *.o *.mod* Module.symvers modules.order
После создания Makefile переходим непосредственно к сборке:
make
Через пару секунд в нашей папке появится файл hello.ko — готовый скомпилированный модуль.
Загрузка и выгрузка
Существует 2 способа загрузки модуля в ядро:
- Сборка модуля вместе с ядром. В таком случае загрузка модуля происходит как часть запуска системы, а сам модуль становится частью кода ядра.
- Динамическая загрузка в уже запущенной системе. Вышеописанный способ создания модуля предполагает именно такой способ загрузки. В этом случае загрузка модуля больше похожа на запуск обычной пользовательской программы.
Загрузить модуль:
sudo insmod hello.ko
Команда insmod загружает модуль в пространство ядра, тем самым вызывая функцию инициализации.
После этого модуль попадает в список загруженных. Проверить это можно командой lsmod:
В функции инициализации мы добавили вызов printk, который выводит в системный лог наше сообщение.
Для просмотра системного лога существует утилита dmesg:
dmesg | grep '<my_tag>'
Вышеуказанная команда выведет
<my_tag> hello world
После того, как мы загрузили модуль, он так и останется висеть в ядре до тех пор, пока его не выгрузят. Чтобы сделать это:
sudo rmmod hello.ko
Эта команда вызовет обработчик события __exit, но поскольку у нас там пустая функция, кроме выгрузки модуля из ядра ничего не произойдёт.
static int __init myinit(void)
{
printk("%s\n","<my_tag> hello world");
return -1;
}
Далее мы рассмотрим первый способ загрузки модуля, а также то, ради чего изначально писалась эта статья.
Перехват системных вызовов
Небезопасный способ
Когда-то давно, ещё до ядра версии 2.6, для того, чтобы перехватить системный вызов, писали функцию-хук, которая её заменяла: выполняла другой код + вызывала непосредственно сам syscall(чтобы не нарушить работоспособность системы).
Так как каждый системный вызов подобно функции имеет свой адрес, а в Linux есть специальная таблица где эти адреса хранятся, задача сводилась к тому, чтобы в этой самой таблице заменить адрес системного вызова на адрес нашей функции.
Позже, разработчики Linux пытались устранить возможность такого способа, но до сих пор существуют хаки, которые позволяют реализовать этот метод.
Тем не менее, он очень небезопасный, и я не буду его описывать. Тем более, что для решения задачи в то же время придумали более изящное и безопасное решение.
LSM
LSM — это фреймворк для разработки модулей безопасности ядра. Он был создан для того, чтобы расширить стандартную модель безопасности DAC, сделать её более гибкой. Этот фреймворк использует известный модуль безопасности SELinux, а также ещё несколько других, встроенных в ядро.
Самое ценное для нас в данном фреймворке то, что он реализован через набор заранее предустановленных в ядро хуков(по сути, тот способ, который я описывал выше, но безопасный, потому что ядро заранее рассчитано на наличие таких хуков).
LSM позволяет вставлять в код своих хуков вызов пользовательских, что позволяет безопасно работать с системными вызовами без изменения таблицы символов.
Всё предельно просто. Рассмотрим пример создания модуля безопасности foobar, который перехватывает системный вызов mk_dir.
Написание кода
- Находим в исходниках ядра папку security, создаём в ней папку для нашего модуля, а в ней — его исходный код foobar.c:
// /security/foobar/foobar.c //---INCLUDES #include <linux/module.h> #include <linux/lsm_hooks.h> //---HOOKS //mkdir hook static int foobar_inode_mkdir(struct inode *dir, struct dentry *dentry, umode_t mask) { printk("%s\n","<my_tag> mkdir hook"); return 0; } //---HOOKS REGISTERING static struct security_hook_list foobar_hooks[] = { LSM_HOOK_INIT(inode_mkdir, foobar_inode_mkdir), }; //---INIT void __init foobar_add_hooks(void) { security_add_hooks(foobar_hooks, ARRAY_SIZE(foobar_hooks)); }
Файл lsm_hooks.h содержит заголовки тех самых предустановленных хуков, LSM_HOOK_INIT регистрирует соответствие foobar_inode_mkdir() хуку inode_mkdir(), а security_add_hooks() добавляет нашу функцию в общий список пользовательских хуков LSM.
Таким образом, при каждом вызове mkdir будет вызываться наша функция foobar_inode_mkdir().
- Добавляем заголовок нашей функции в файл “/include/linux/lsm_hooks.h”:
#ifdef CONFIG_SECURITY_FOOBAR extern void __init foobar_add_hooks(void); #else static inline void __init foobar_add_hooks(void) { } #endif
Все вызовы происходят в исходном файле security.c (далее), этим шагом мы оповещаем его о существовании нашей функции.
- В файле “/security/security.c” находим функцию “int __init security_init(void)” и добавляем в её тело следующий вызов:
foobar_add_hooks();
Всё, зависимости в коде настроены корректно. Осталось только оповестить конфигурационные файлы ядра о том, что мы хотим собрать его вместе с нашим модулем.
Конфигурация сборки
- В папке с нашим модулем(/security/foobar/) создадим файл Kconfig:
config SECURITY_FOOBAR bool "FooBar security module" default y help Any help text here
Это создаст пункт меню с нашим модулем.
- Откроем файл /security/Kconfig и добавим следующий текст сразу за строчкой “menu «Security options»":
source security/foobar/Kconfig
Это добавит наш пункт меню в глобальное меню настроек ядра.
- Создадим Makefile в папке с нашим модулем:
obj-$(CONFIG_SECURITY_FOOBAR) += foobar.o
- Откроем Makefile всего раздела безопасности(/security/Makefile) и добавим в него следующие строчки(по аналогии с такими же строчками для других модулей):
subdir-$(CONFIG_SECURITY_FOOBAR) += foobar obj-$(CONFIG_SECURITY_FOOBAR) += foobar/
- Запустим конфигурирование в псевдографическом режиме:
make menuconfig
Если перейти в подменю “Security options”, первым пунктом мы увидим наш модуль, отмеченный символом “y” (мы установили это значение по умолчанию, когда создавали файл Kconfig), что означает, что мы интегрируем наш модуль непосредственно в код ядра.
Сборка
На этом этапе проводим самую обыкновенную сборку ядра, как это было описано в начале статьи. Но поскольку мы уже предварительно собрали чистое ядро, процесс немного упростился:
make && make modules
make не требует параметра -j, поскольку пересоберёт ядро с нашим модулем за несколько секунд.
sudo make install
Установка заголовков и модулей не требуется, это было произведено ранее.
Всё!
Осталось перезагрузить систему, после чего в ядре будет висеть наш модуль с перехватом mkdir. Как и говорил ранее, проверяем так:
dmesg | grep '<my_tag>'
Учтите, что в системе, скрываясь от ваших глаз, происходит очень много процессов, так что не удивляйтесь, когда увидите там много перехватов.
Надеюсь, кому-то это руководство будет полезным(если бы кто-то написал его вместо меня до того, как я начал копаться в ядре — он сэкономил бы мне 2-3 недели жизни).
Любая критика приветствуется.
Спасибо за внимание.
Комментарии (40)
nckma
21.12.2016 13:35А как перехватывает системные вызовы strace?
https://habrahabr.ru/post/215577/aaefimov
21.12.2016 14:17+2Вот мне стало интересно и я скачал исходники strace. Он работает через ptrace (это набор функций под каждую архитектуру в ядре) далее он обращается к ptrace_traceme, в тех же самых security_hook_heads
kmu1990
21.12.2016 14:36Он использует ptrace, т. е. это в userspace — мы можем оставновить процесс перед входом в системный вызов и после завершения, посмотреть на состояние и даже изменить его, но свой код в привелигированном режиме ptrace нам выполнить не даст.
selenite
21.12.2016 15:03самая большая проблема с LSM, а также подсистемами трассировки/профилирования — это то, что количество вещей, которые можно сделать исключительно на них, ограничено фантазией разработчиков ядра :)
в некоторых случаях приходится править уже код ядра, и только потом пытаться отправить изменения в апстрим.
nitso
21.12.2016 15:07+3В крайней мере заинтересовал макрос MODULE_LICENCE. В большинстве источников указывается «напишите GPL или свою лицензию». В гораздо меньшем упоминается «ограничение возможностей взаимодействия с ядром». Еще в одном месте упоминается «загрузка такого модуля опозорит ядро» (loading xxx.ko will taint the kernel) в логах.
А как на самом деле работает этот механизм и на что влияет? Или все ограничивается ворнингом?melon
21.12.2016 16:04+1если найдёте какую-то информацию на этот счёт, отпишитесь пожалуйста, тоже интересно.
Tujh
21.12.2016 16:08Если не ошибаюсь, то в Fedora не даёт репортить баги ядра если в нём загружены модули с другими лицензиями. В частности, наличие модулей vbox* в отчёте об ошибке блокирует их отправку в bagzilla-у.
jok40
21.12.2016 16:23Если мне не изменяет память, то если модуль вызывает методы из других GPL-модулей (например, platform_driver_unregister или irq_of_parse_and_map) и в нём нет макроса MODULE_LICENSE(«GPL»), то Linux отругается и не даст загрузить этот модуль.
dimorphus
21.12.2016 18:16+6Модуль ядра представляет собой объектный файл. При загрузке его в ядро командой insmod или modprobe происходит системный вызов. Ядро загружает объектный код модуля и само производит линковку. Линковка производится для вызываемых функций в модуле, которые в ядре помечаются как экспортируемые. Помечаются следующей строчкой: EXPORT_SYMBOL(<function_name>). Так вот, есть ещё директива EXPORT_SYMBOL_GPL. Делает она тоже самое за одним нюансом: эту функцию могут вызывать только модули под лицензией GPL. Если у вас модуль под другой лицензией и при этом в его коде вызывается функция ядра, экспортируемая через EXPORT_SYMBOL_GPL, то ядро просто не загрузит модуль, не произведёт линковку.
dimorphus
21.12.2016 18:40+4Уточнение. Сейчас проверил, модуль даже не соберётся имея лицензию отличную от GPL и попытавшись вызвать функцию EXPORT_SYMBOL_GPL. Вот пример:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/reboot.h> MODULE_LICENSE("I hate GPL!"); int init(void) { int *i = kmalloc(sizeof(int), GFP_KERNEL); //exported with EXPORT_SYMBOL kernel_restart(NULL); // exported with EXPORT_SYMBOL_GPL return *i; } module_init(init);
И результат попытки сборки:
make -C /lib/modules/4.8.14-300.fc25.x86_64/build M=/home/work/ make[1]: Entering directory '/usr/src/kernels/4.8.14-300.fc25.x86_64' CC [M] /home/work/workspace/work/module/gpl_test.o Building modules, stage 2. MODPOST 1 modules FATAL: modpost: GPL-incompatible module gpl_test.ko uses GPL-only symbol 'kernel_restart' scripts/Makefile.modpost:91: recipe for target '__modpost' failed
Посмотреть на объявление kernel_restart можно здесь.
melon
21.12.2016 16:03-1а такой способ позволяет перехватывать вызовы fork & mmap? Какие ограничения у данного подхода? Потому что я пробовал метод с заменой таблицы модулей, там в случае перехвата fork & mmap у меня был Kernel Panic, но потом мне объяснили, почему это плохо и я стал смотреть в сторону systemtap, но он большой и разбираться было лень, поэтому пока забил.
BiosUefi
21.12.2016 17:44+1Очень интересно.
Хук будет вызываться перед системным вызовом я так понял. А возможен ли вызов хука после системного вызова, а не до?win32asm
27.12.2016 13:50В таком виде — нет.
Если я правильно понимаю код ядра, то этот хук он на самом деле «паразитирует» на задаче SELinux`а проверить права процесса на действие _перед самим действием_.
Есть интересное следствие — если из хука вернуть не 0 а ошибку, то действие не будет выполнено.
anybkarnak
21.12.2016 17:44Спасибо за статью. Можно ли так же хукнуть системные вызовы без пересборки самого ядра, а используя insmod?
dimorphus
21.12.2016 17:44+4Используя LSM вы не перехватываете системные вызовы по нескольким причинам. Во-первых, прежде чем вызвать ваш LSM обработчик сначала производится стандартная проверка прав доступа. Так, если у пользователя не хватает прав на создание директории, то системный вызов сразу вернёт код ошибки пользовательскому процессу. Ваш обработчик при этом не вызовется. Во-вторых, не для всех системных вызовов есть соответствующий LSM hook.
Предназначение LSM несколько в другом — в возможности реализации дополнительных проверок прав доступа поверх стандартных. Более того, c некоторых пор LSM стал стековым интерфейсом, то есть приобрёл возможность подключать сразу несколько модулей безопасности (например SELinux, Yama). Это означает, что если ваш модуль работает после SELinux, а он решает не давать доступа на mkdir, то ваш обработчик не будет вызываться.
На мой взгляд, если нужен именно перехват всех системных вызовов, то можно было бы использовать Systemtap. Вот, кстати стандартный примерчик https://sourceware.org/systemtap/examples/process/syscalls_by_proc.stp
#! /usr/bin/env stap global syscalls probe nd_syscall.* { syscalls[execname()]++ } probe end { printf ("%-10s %-s\n", "#SysCalls", "Process Name") foreach (proc in syscalls-) printf("%-10d %-s\n", syscalls[proc], proc) }
dimorphus
21.12.2016 18:53+1Приведённый скрипт на systemtap просто подсчитывал количество системных вызовов, сделанных каждым процессом. Вот ещё один простенький пример. Он отслеживает начало обработки системного вызова ядром и завершение обработки. Выводит информацию о процессе, который его инициировал, аргументы системного вызова и код результата его обработки. if можно расскомментировать, если нужно отслеживать только какой-то конкретный процесс.
syscall_test.stp:
#!/usr/bin/stp probe nd_syscall.* { #if (pid() == target()) { printf("<syscall %s: %s(%s)\n", execname(), name, argstr) #} } probe nd_syscall.*.return { #if (pid() == target()) { printf(">syscall ret %s: %s result: %s\n", execname(), name, retstr) #} }
Запускается так:
$ sudo stap ./syscall_test.stp
Вот пример вывода (ядро скомпилировано с поддержкой многоядерности, поэтому системные вызовы обрабатываются не последовательно):
<syscall konsole: write(95, "\0", 1) <syscall ksmserver: ioctl(28, 21531, 0x7fffab1c9ec4) >syscall ret konsole: write result: 1 >syscall ret ksmserver: ioctl result: 0 <syscall ksmserver: read(28, 0x5626d6785938, 40) >syscall ret ksmserver: read result: 40 <syscall konsole: lseek(94, -2147482516, SEEK_SET) <syscall ksmserver: poll(0x5626d66df110, 64, -1) >syscall ret konsole: lseek result: -22 (EINVAL) <syscall konsole: write(2, "HistoryFile::add.seek: Invalid argument\n", 40) >syscall ret konsole: write result: 40 <syscall konsole: lseek(93, 22447548, SEEK_SET) >syscall ret konsole: lseek result: 22447548 <syscall konsole: write(93, "l\004\0\200", 4) >syscall ret konsole: write result: 4
denis_obrezkov
21.12.2016 17:45+1Вместе с ядром поставляются на выбор несколько систем, основанных на LSM: AppArmor, SELinux, Smack, TOMOYO, Yama. Мне кажется, что документация по LSM и фреймворк Yama представляют сами по себе немалый объем информации — как раз тот случай, когда код говорит сам за себя.
Было бы интересно прочитать про хуки на исполнение файлов с минимальной реализацией какой-либо системы доступа, мандатной, к примеру.
loskamo
21.12.2016 18:48Ребят, много таких, кто не просто прочитал статью, но и прошёлся по мануалу?
Интересно, у всех ли всё получилось, и если не получилось, то почему?
Если что-то не так — будем разбираться, править.pavel_pimenov
22.12.2016 10:42А почему максимальный приоритет выставлялся для make?
всегда думал, что максимум CPU в подобных случаях поедает компилятор,
или измененный приоритет make из гнома наследуется и на него как дочернего?
A1EF
23.12.2016 19:32+1Я вообще ни разу не системный программист, но статья понравилась своей простотой и ясностью. Проделал все тоже самое на Debian 8.6. Взял ядро 4.9 и все прекрасно получилось:)
Godless
22.12.2016 00:05спасибо за статью. прямо как-будто читаю Шрайбера с недокументированными возможностями вин2к и волшебным int 2e…
jcmvbkbc
22.12.2016 11:37После успешной сборки нужно установить всё то, что мы собрали. Это требует root-прав.
Установка заголовков:
sudo make headers_install
Это нужно только для сборки libc, при обычной сборке ядра/модулей этот шаг не нужен.loskamo
22.12.2016 14:12Согласен, но заголовки также нужны и для модулей virtual-box, я решил что туториал это не усложнит, но пользу принесёт
KosmiK2001
28.12.2016 14:17Ну и пример в дефолте. дичь.
>Makefile:13: *** missing separator.
Ну ладно, выровнял через пробел и;
>arch/x86/Makefile:173: CONFIG_X86_X32 enabled but no binutils support
>Makefile:670: Cannot use CONFIG_CC_STACKPROTECTOR_REGULAR: -fstack-protector not supported by compiler
>make[1]: *** Нет правила для сборки цели «стол/test». Останов.
Класс, чё тут сказать.shlemisto
28.12.2016 15:44лечится вот этим https://bugs.launchpad.net/ubuntu/+source/gcc-defaults/+bug/1574982/comments/15
afiskon
В высшей мере интересно, спасибо! Реквестирую больше статей о ядерной разработке в Linux.
ph4n70m
Интересно, есть ли у linux сообщества разработчиков общая база знаний и актуальной информации? Или всё надо находить по статьям в интернете? Что-то помимо /usr/src/linux/Documentation/
afiskon
Последний раз когда я узнавал, книги были сильно устаревшими (была какая-то одна акутальная, но на немецком языке), а статьи быстро устаревали, потому что разработчики ядра не сильно парились по обратной совместимости внутри ядра. То есть чтобы быть в теме нужно постоянно чем-то около ядерным заниматься.