- Роберт love system progamming
- understanding linux cernel daniel bovet |(смотреть на рутрекере) - самое фудаментальное
- linux cernel module programming guide (github) burain salzman pomerantz
- про модули ядра https://habr.com/ru/companies/ruvds/articles/681880/
- elixir - документация по исходникам ядра
- lor.linux.org - форум для помощи
user-program
kernel-program
В том что UP только пользуется syscall, а kP может их создавать.
У кода модулей свой linux kernel coding style
.
Пользовательские программы взаимодейстив через IPC
- управление процессом
- управление памятью
- управление файловыми системами (есть иллюзорные (виртуальные) файловые системы)
- управление устройстввми как реальными и виртуальными /dev/null /dev/random
- управление сетью
Микроядро 1, 2 (быстро загружается , мало весит) Windows CE Моноядро 1,2,3,4,5
Темы:
- платформо-зависимый код
- динамическая память
- типы файловых систем
- Обращения к устройствам
Warning
Сбоку модулей ядра стоит выполнять в отдельном сборочном окружении, например, виртуалке или
docker-контейнере
Установка необходимых пакетов
sudo apt install -y gcc \
build-essential \
libncurses6 \
libncurses-dev \
flex \
bison
Проверяем имя системы:
uname -r
Установка исходников
sodo apt install linux-source-6.8.0
Переходим в директорию для исходников ядра и распаковываем исходники:
cd /usr/src
sudo tar -xvf ./linux-source-6.8.0.tar.bz2
cd /usr/src/linux-source-6.8.0
При написании кода ядра можно использовать только C lang. Стандартные include
- инструкции не работают. Нужно использовать специальные заголовочные библиотеки:
#include <linux/...h>
В директории /usr/src
есть папка с исходниками (заголовочными файлами)
Можно использовать только реинтерабельные функции (т.е. функции вызов которых при обращении к одному и тому же ресурсу не приводт к проблемам в системе). Код должен быть рассчитан на многопоточность. В ядре поток и процесс одно и тоже.
Сделать принт модуля ядра с лог:
printk()
Посмотртеть принты от модуля ядра можно следующим образом:
journalxtl
#or
tail -f /var/log/kern.log
Модули ядра имеют расщирение *.ko
и загружаются в систему слудующим образом:
sudo insmod ./hello.ko
Выгрузка модуля:
sudo rmmod ./hello.ko
Просмотр объектника
nm
Проверка символов ядра:
cat /proc/kallsyms | grep prink
Выделяют следующие памяти
- register
- static
- extern
Также необходимо пользоваться функциями volatile
.
Для исключения проблем с компиляцией лучше пользоваться лицензией GPL:
MODULE_LICENSE("GPL");
Вывести сообщение ядра в терминал:
printk()
- errno constats - константы если возникла какая нить ошибка в модуле ядра ( в случае успеха 0). В пользовательском коде наоборот
return -EIO
Макросы __init
и __exit
обозначают функции , которые вызываются при создании модуля и при выходе из него
printk(KERN_ALLERT "......");
Прочитать про макросы объявления констант
module_param()
Функции обратного вызова
//callback
module_param_cd()
Сгенерировать в модуле ядра случайное число , вывести его , диапазон для генерации передать в качестве параметра
При изменении параметров нужно перегенерировать число
Передать в модуль массив чисел целого значения заданного размера. Вычислить сумму элементов массива. Печатаем в лог и выгружаем модуль Проверять выход за диапозон не нужно
Найти функцию, которая позволяет писать из пространства ядра в пространство пользователя
Посмотреть символы ядры
cat /proc/kallsyms
Именовать модули и сомволы стоит делать как можно сложнее (прибавлять имена модулей и т.д)
hello1 hello1_print()
hello2 hello2_print()
- как сделать функцию модулем в ядре?
EXPORT_SYMBOL(myalert);
Модули ядра выполняются ядром в цикле последовательно раз за разом:
-
обязательно писать
const
при объявлении функций и переменных чтобы сразу отлавливать конфликты -
иснпетировние сивола
cat /proc/kallsyms | grep mysymbol
Команды по работе с модулями
insmod
rmmod
modinfo
lsmod
depmod # построение дерева зависимостей моделей
modprobe
В линуксе имеются три типа устройств
- символьные - работают с потоками данных. Удобно передавать как структуры, так и тексты
- radio
- блочные устройства
/dev
. Ссылки на описание работы с устройвами. Позволяет передавать данные блоками - сетевые интерфейсы
Большая часть задача может быть решена через символьные устройства. Большая часть сетевых соединений -это потокозависимые процессы. Драйвер сетевого устройства нужен для обработки сетевых пакетов.
Связь между устройствами осуществляется через директорию dev
.
ls /dev
- l - символическое устройство
- c- блочное устрово
-
- Старший номер устройства- это способ связать файл с реализаций доступа к этому устройству. Для хранения номеров устройство в линуксе имееся специальный
dev_t
.major
привязывает реализацию логики.minor
используется для точного определения устройства в системе. В современной системе довольно много старших номеров. Младших номеров может быть 32к. - В системе много виртуальных терминалов
tty
под разные задачи. - Под конкретную задачу выбирается старший номер устройства.
- младшие номера можно генерировать
- компьютер берет случайные числа из энтропии
/dev/urandom
и/dev/random
- Основные операции по доступу к устройству
open()
close()
read()
write()
struct file
описывает что происходит на уровне ядра и нельзя использовать у пользователя . Это тип данных , которые описывает что такое тип файлов на уровне операционной системыstruct inode*
дает гораздо больше информации чемstruct file*
- ononblock ?
try_module_get()
module_put()
? не дает просто так выгружать модули из системы- mknod - создает новый узел файловой стсемы
- TODO: elixir THIS_MODULE
Превратить генератор случайных чисел в символ ядра
Заполнить массив случайными числами, посчитать сумму, вывести в лог.
Создать новое устройство с новым мажорным номером.
- Модули могу писать в /proc некоторую информацию
-
- редактировать proc из пользоватльеского пространсва редактировать нельзя
Устройство radio должно иметь 3 файла. Устройства 0 и 1 должны только передавать. Записывать в устройство 1 нельзя- должно выдаваться собщение о запрете. В устройство 2 можно только записывать. Других устройств быть не должно (выдавать einval).
Добавить длинный текст в модуль, убедиться что оно принимается через cat. Нужна пользовательская программа , которая читает данные из модуля по 64 кб (желательно с помощью fopen, fclose). (radio текст подлинее )
Сообщение переданное в radio_2 должны получить через radio_0 или radio_1.
Какие типы адресов бывают ?
- виртуальные адреса приложений (используемые пользовательскими программами ).
- свое адресное пространство (статическая или динамическая )
- чужая память
- физические адреса , которые используются процессором при выделении память
- адреса шин (периферийных шин )
- логические адреса ядра (логические адрес равен физическому ). Соответсвуют страницам памяти (+/- страница). Как правило они 4кБ.
- виртуальные адреса ядра
kmaloc и vmalloc (выделение логических и виртуальных адресов в ядре). vmalloc- плохая практика, т.к. выделяет память динамически. Адреса будут раскиданы по физической и долго выделяются. kmalloc имеет ограничение на объем памяти
Пространство ядра - память закрепленное за ядром . Новый модуль увеличивает объем используемой ядром память. Пространство ядра должно быть непрерывно физически (или логически)
Пользовательские приложения могут использовать любые области памать. Логическая память ядра бывает только в нижней памяти. Виртуальная память ядра может находится как в верхней памяти , так и в нижней.
Модуль ядра и ядро должно быть размещено в нижней памяти
Есть еще swap, который находится над hi memory.
todo INSERT IMAGE
Как выглядит память? TODO: вставить картинку Ошибка сегментаци. Пример: программа обратилась к области другой программы. Позже появилось понятие селектора - автоматики , которая сама определяет , в какой сегмент памяти мы попали при смещении памяти . Селектор распределят страницы по приложениям.
Есть макрос PAGE_SIZE
~4кБ, который определяет размер страниц.
virt_to_page()
page_address()
kmap() kunmap() - позволяют получить виртуальный адрес для отображения страницы в системе (аналог mmap).
глобально есть два режима работы:
GFP_KERNEL
- используется по умолчанию в большинстве выделений. Выделяет модуль в нижней части памяти. В случае недостатка памяти, процесс можно поместить в стан. Нельзя использовать в модулях, которые не могут спать.GFP_ATOMIC
- для приложений , которые не могу спать. Если памяти нет, то будет выдана ошибка. ЧтобыGFP_ATOMIC
не крашил модуль, в ядре зарезервирована память для таких выделений.
__GFP_DMA
?
__GFP_HIGHUSER
GFP_NOIO
-запрещает во время выделения памяти любые операции
GFP_NOFS
- запрет на любые операции с файловой системы во время выделения. Используется для работы с дисками .
kfree()
- высвобождает память ,выделенную kmalloc().
kzalloc()
- заполняет память нулями.
kcalloc()
- заполняет память определенными символами
vzalloc()
- заполняет динамическую память нулями.
- На DPDK в теории можно писать real time приложения
- Можно ли получить доступ в чужую память? нет. Только через shared memory
Классификация потоков по соотношениям
- 1:N
- M:N
- M:1 POSIX pthread позволяют организовать легковесные процессы, которые делят адресное пространство с породившим его процессом. При этом linux даст каждому из потоков свое ID. С точки зрения ядра это все будут процессы, только с урезанным функционалом. TODO: Лидер сессии ?
spinlock
- взаимные исключения устройства (некий mutex
). Это битовый флаг в свойствах потока или процесса. Если блокировка доступна, то идет попытка установить блокирующий бит и заблокировать ресурс. Если он уже заблокирован, то в цикле проверяем что блокировка снята. Т.к. они постоянно работают , то они являются предпочтительным механизмом управления потоками.
Программист, использующий
spinlock
, должен гарантировать, что потоки спать не будут, иначе из состояния сна уже не выйти.
При работе spinlock обработчики прерываний отключаются, т.к. можно повесить систему на совсем.
- spilock_t
- spin_lock_init()
- spin_lock()
- spin_unlock()
Определить минимальный и максимальный объем памяти , выделяемый при kmaloc и vmalloc
Сделать пользовательский клиент для примера mapdemo из примера 11
Необходимо запрограммировать модуль, представляющий из себя устройство: Раз в заданный интервал времени, устройство должно генерировать заданное количество случайных чисел. Надо реализовать работу с эти модулем таким образом , чтобы пользователь могу работать с этими данными . Должна быть пользовательская программа.
Новый набор должен генерироваться раз в секунду.
В модуле создается два потока с разными потоками. Один поток печатает в лог tic, второй tak. Необходимо обеспечить синхронизацию.
blk-mq
- multi queue block device
В ядре 5 был полостью убраны интерфейсы работы со старыми блочными устройствами.
Раньше работа с блочными устройствами осуществлялась через очереди обращений к устройствам. Ее убрали потому что она долго работала по принципу запрос/ответ.
С блочными устройствами работа осуществляется по блокам памяти . Диск разбивается на блоки , а файлы являются списками блоков.
Сделать блочную файловую систему
Просмотр девайсов в linux^
cat /proc/devices
За круглым столом сидят 5 китайских философов . Между китайцами лежат палочики для риса . на столе стоят тарелки с рисом . Каждый философ может либо придаваться философии, а может есть . Есть рис можно только с двумя палками.
это задача 5.2, но рис представлен стеком в тарелке. За один раз можно съесть только одну единицу стека, а потом высвобождать палку. Между подходами к еде философ может ожидать.
- как мониторить потребляемые ресурсы от модуля
- разные типы ошибок типа -EIO, -EAGAIN в питоне
- как оформить устройство в /dev и нацепить модуль?
- valgrind для отладки
- символы функций в файлах
- состояния процессов в pthread
- планировщик kthread