2. Программирование ядра linux
Ядро́ (kernel) — центральная часть операционной системы (ОС), обеспечивающая приложениям координированный доступ к ресурсам компьютера, таким как процессорное время, память, внешнее аппаратное обеспечение, внешнее устройство ввода и вывода информации. Также обычно ядро предоставляет сервисы файловой системы и сетевых протоколов.
Различают понятия монолитного ядра, модульного ядра и микроядра.
Монолитное ядро предоставляет богатый набор абстракций оборудования. Все части монолитного ядра работают в одном адресном пространстве. Это такая схема операционной системы, при которой все компоненты её ядра являются составными частями одной программы, используют общие структуры данных и взаимодействуют друг с другом путём непосредственного вызова процедур.
Модульное ядро — современная, усовершенствованная модификация архитектуры монолитных ядер операционных систем. В отличие от «классических» монолитных ядер, модульные ядра, как правило, не требуют полной перекомпиляции ядра при изменении состава аппаратного обеспечения компьютера. Вместо этого модульные ядра предоставляют тот или иной механизм подгрузки модулей ядра, поддерживающих то или иное аппаратное обеспечение (например, драйверов).
Микроядро предоставляет только элементарные функции управления процессами и минимальный набор абстракций для работы с оборудованием. Бо́льшая часть работы осуществляется с помощью специальных пользовательских процессов, называемых сервисами. Решающим критерием «микроядерности» является размещение всех или почти всех драйверов и модулей в сервисных процессах, иногда с явной невозможностью загрузки любых модулей расширения в собственно микроядро, а также разработки таких расширений.
Из презентации:
• Ядро обеспечивает инфраструктуру для работы приложений и использования различных аппаратных средств • Это код низкого уровня, который взаимодействует с интерфейсами аппаратных средств, планирует и распределяет память и т.д.
• Ядро Linux – 1991 год Линус Торвальдс (Minix-подобная операционная система). Первый официальный выпуск Linux 1.0 в марте 1994. Одной из многих сильных сторон ОС, основанных на Linux - является то, что они поддерживают широкий диапазон аппаратных средств и платформ. Каждая версия, начиная с 1.2, включала поддержку новых типов процессоров и особенностей. Поддержка Hyperthreading (один физический процессор – несколько логических) появилась еще в ядре 2.4.17.
МОДУЛИ ЯДРА.
Ядро, способное поддерживать большое разнообразие аппаратных средств, будет слишком большим. Модули ядра позволяют при необходимости загрузить обеспечивающее поддержку программное обеспечение, такое как драйверы для аппаратных средств или файловые системы. Это позволяет запускать систему с небольшим ядром и затем подгружать модули по мере необходимости. Часто эта подгрузка происходит автоматически, например, при подключении устройств USB.
КОНФИГУРИРОВАНИЕ МОДУЛЕЙ
• Команды для выполнения задач загрузки – полномочия суперпользователя • Команды, выдающие информацию о модулях, – обычным пользователем.
• Воспользуйтесь командой lsmod, чтобы узнать, какие модули загружены на вашей системе в настоящее время
• Большинство из них поставляются вместе с ядром • Однако некоторые, такие как nvnet, nvidia и sata_nv от корпорации NVIDIA, содержат проприетарный код и не являются частью стандартного ядра • Таким образом, модульный подход позволяет подключать проприетарный код к ядру с открытым кодом.
• Помимо имени модуля, команда lsmod показывает также размер и число пользователей модуля. • Команда modinfo выдает информацию об одном или нескольких модулях • Эта информация содержит полный путь до файла, имя автора, лицензию, другие параметры, которые можно передать модулю, версию, зависимости и другую инфо. • Если "use count" модуля равен 0, вы можете без опасений удалить его: rmmod • Удаленный вами модуль может понадобиться вновь • Чтобы загрузить модуль, введите команду insmod, полный путь для модуля, который необходимо загрузить, и все необходимые опции. • Команда modprobe предоставляет высокоуровневый интерфейс, оперирующий именами модулей, а не именами путей файлов • Она также управляет загрузкой дополнительных модулей, от которых зависит загружаемый модуль, и позволяет не только загружать, но и удалять модули.
ЗАВИСИМОСТИ
• Если один модуль зависит от другого, при помощи команды modprobe можно управлять автоматической загрузкой нескольких модулей • Зависимости прописаны в файле modules.dep, хранящемся в подкаталоге /lib/modules соответствующего ядра (uname -r) • Этот файл вместе с несколькими map-файлами генерируется при помощи команды depmod • Команда depmod просматривает модули в подкаталоге /lib/modules текущего ядра и обновляет информацию о зависимостях
• Вы можете изменить поведение команд modprobe и depmod, скорректировав файл /etc/modules.conf • Обычно это делается для создания псевдонимов (alias) для имен модулей и определения команд, которые должны быть запущены после загрузки модуля или перед его отключением
• Вы должны также знать, что некоторые системы используют другой конфигурационный файл, modprobe.conf, и есть системы, которые хранят информацию о конфигурации модулей в каталоге /etc/modules.d • В некоторых системах вы можете встретить файлы с названием /etc/modules • Эти файлы содержат имена модулей ядра, которые должны быть загружены в ходе загрузки системы
КОНФИГУРАЦИЯ
• Цель config использует интерфейс командной строки для получения ответов многие на вопросы, касающиеся создания или обновления файла .config • После появления конфигурационных целей, использующих меню, интерфейс командной строки используется редко
• Цель menuconfig использует программу с меню-интерфейсом, построенную на базе ncurses, для создания или обновления файла .config • Вы должны только ответить на вопросы для элементов, которые хотите изменить
• Цель xconfig использует систему графического меню, основанную на QT, используемом в KDE
• Цель gconfig использует систему графического меню, основанную на QT, используемом в GNOME
• Цель oldconfig позволяет создать конфигурацию с использованием существующего файла .config
СБОРКА
• Чтобы начать сборку ядра 2.6, выполните make • Чтобы начать сборку ядра 2.4, выполните эти три команды: make dep make bzImage make modules • Первая создает файлы необходимых зависимостей. Вторая собирает ядро. И последняя собирает модули
УСТАНОВКА
• Сначала вам необходимо выполнить make modules_install для установки модулей ядра в новый подкаталог /lib/modules
• И в заключение, вам необходимо выполнить make install для установки нового ядра и стартового RAM-диска (initial RAM disk) в каталог /boot и обновления конфигурации загрузчика
• Обратите внимание, что в процессе сборки автоматически создается необходимый стартовый RAM-диск (initial RAM disk или initrd) • Если у вас возникнет необходимость создать его вручную, это можно сделать при помощи команды mkinitrd
УСТАНОВКА ИЗ ИСХОДНИКОВ (ИЗ ОПЫТА)
-докачать необходимые пакеты: kernel-package, libncurses5-dev, fakeroot.
-скачать исходники ядра, распаковать архивы (tar)
-создать символическую ссылку на директорию с исходниками (ln -s path name)
-скопировать старый конфиг (или создать новый) => make menuconfig
-fakeroot make-kpkg –initrd -jN kernel_image kernel_headers
-dpkg -i linux-headers***.deb linux-image***.deb
ОПТИМИЗАЦИЯ ЯДРА
Для оптимизации ядра надо исключить из него все лишнее, а для этого надо точно знать, какие устройства используются в системе, и какие будут использоваться в будущем. То есть нужна информация об оборудовании: процессор(ах), оперативной памяти, чипсете материнской платы, видеокарте и т.д.
Хотелось бы добавить, что для ускорения компиляции на многоядерных процессорах можно изменить переменную CONCURRENCY_LEVEL, это позволяет запускать несколько процессов компиляции.
CONCURRENCY_LEVEL=5 make-kpkg --initrd kernel_image …
Цифра 5 означает запуск 5-ти одновременных компиляций. ОЧЕНЬ МАЛО. ДОБАВИТЬ!!!
ПРОГРАММИРОВАНИЕ НА УРОВНЕ ЯДРА И МОДУЛИ
#include <linux/module.h> #include <linux/kernel.h> int init_module(void) { printk("<1>Hello world 1.\n"); return 0; } void cleanup_module(void) { printk(KERN_ALERT "Goodbye world 1.\n"); } |
Makefile: obj-m += hello-1.o make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules Если несколько файлов: obj-m += startstop.o startstop-objs := start.o stop.o |
• Модуль – код, который может быть загружена или выгружена ядром по мере необходимости или вручную. Модули расширяют функциональные возможности ядра без необходимости перезагрузки системы.
• Драйверы устройств, позволяют ядру взаимодействовать с аппаратурой компьютера. Без поддержки модулей пришлось бы писать монолитные ядра и добавлять новые возможности прямо в ядро. При этом, после добавления в ядро новых возможностей, пришлось бы перезагружать систему
ЗАГРУЗКА МОДУЛЕЙ
• Возникает необходимость загрузки новых возможностей – модулей. Демон kmod вызывает утилиту modprobe, передавая: Название модуля или Универсальный идентификатор. Если передается универсальный идентификатор, то modprobe сначала пытается отыскать имя соответствующего модуля в файле /etc/modules.conf. Данному универсальному идентификатору соответствует файл модуля *.ko. Затем modprobe отыскивает файл /lib/modules/version/modules.dep и вызывает insmod, чтобы сначала загрузить необходимые для удовлетворения зависимостей модули, а затем и запрошенный модуль.
ИСПОЛНЕНИЕ МОДУЛЯ
Работа программы – main(). Модули – init_module() (или функция, определённая через вызов module_init). Функция запуска модуля, подготавливает его для последующих вызовов. Вторая точка входа в модуль – cleanup_module производит "откат" изменений. Любой модуль обязательно должен иметь функцию инициализации и функцию завершения.
ФУНКЦИИ, ДОСТУПНЫЕ ИЗ МОДУЛЯ
• Приложения: – Вызов функций, которые не определены в самой программе – На стадии компоновки разрешаются все внешние ссылки
• Модули: – проходят стадию компоновки, но только с ядром, и могут вызывать только те функции, которые экспортируются ядром • Разрешение ссылок на внешние символы производится утилитой insmod
ПРОСТРАНСТВО ПОЛЬЗОВАТЕЛЯ И ПРОСТРАНСТВО ЯДРА
• За доступ к ресурсам системы отвечает ядро, будь то видеоплата, жесткий диск или даже память • Программы часто конкурируют между собой за доступ к тем или иным ресурсам • Ядро должно обслужить конкурирующие запросы, и "выстроить" их в порядке очередности • К тому же сам центральный процессор может работать в различных режимах • Каждый из режимов имеет свою степень "свободы" действий • Процессор Intel 80386 имеет четыре таких режима, которые часто называют «кольцами» • Unix использует только два из них: наивысший (привилегированный режим) и низший (пользовательский режим).
• Как правило, программа обращается к библиотечным функциям, находясь в пользовательском режиме • Затем библиотечные функции обращаются к системным вызовам • Системные вызовы происходят в привилегированном режиме (printf => write) • Системный вызов завершает работу – возвращает управление.
• Ядро тоже имеет свое собственное адресное пространство • Поскольку модуль по сути является частью ядра, то он так же работает в адресном пространстве ядра. • Есть операционные системы, в основе которых лежит микроядро • В таких ОС каждый модуль получает свое адресное пространство.
Каждый процесс в многозадачной ОС выполняется в собственном виртуальном адресном пространстве. Соответствие между виртуальным пространством и физической памятью описывается с помощью таблицы страниц (page table). Ядро создает и заполняет таблицы, а процессор обращается к ним при необходимости осуществить трансляцию адреса. Каждый процесс работает со своим набором таблиц. Есть один важный момент — концепция виртуальной адресации распространяется на все выполняемое ПО, включая и само ядро. По этой причине для ядра резервируется часть виртуального адресного пространства (т.н. kernel space).
ПРОСТРАНСТВА ИМЕН???
УПРАВЛЕНИЕ УСТРОЙСТВАМИ
В Linux каждое аппаратное устройство представлено псевдофайлом (файлом устройства) в каталоге /dev. Этот файл обеспечивает средства взаимодействия с аппаратурой.
Работу по подключению и удалению устройств выполняет ядро системы. Изменения состояния устройств (подключение нового или удаление существующего) должны быть при этом видимы в пользовательском пространстве. При подключении новых устройств они должны тут же корректно настраиваться и (при необходимости) опознаваться пользовательскими приложениями. Файлы устройств (device nodes) в каталоге /dev обеспечивают доступ к соответствующим устройствам. В данном каталоге находятся файлы только тех устройств, которые в настоящий момент подключены к системе. Каждое устройство имеет свой соответствующий файл. Если устройство отключается от системы — данный файл удаляется.
Содержимое каталога /dev хранится на виртуальной файловой системе и все файлы, находящиеся в нем, создаются при каждом запуске системы. Модифицированные или созданные вручную файлы не сохраняются после перезагрузки. Файлы и каталоги, которые необходимо сохранить или которые всегда должны присутствовать в каталоге /dev, независимо от состояния соответствующего устройства, необходимо помещать в каталог /lib/udev/devices. При запуске системы содержимое данного каталога копируется в /dev.
ПОСЛЕДОВАТЕЛЬНЫЕ И ПАРАЛЛЕЛЬНЫЕ ПОРТЫ.
После́довательный порт. Порт называется «последовательным», так как информация через него передаётся по одному биту, последовательно бит за битом. Некоторые интерфейсы компьютера (например, Ethernet, FireWire и USB) используют последовательный способ обмена информацией.
Паралле́льный порт — тип интерфейса, разработанный для компьютеров (персональных и других) для подключения различных периферийных устройств. В вычислительной технике параллельный порт является физической реализацией принципа параллельного соединения. До появления USB параллельный интерфейс был адаптирован помимо принтеров к большому числу периферийных устройств.
USB (Universal Serial Bus — «универсальная последовательная шина») — последовательный интерфейс передачи данных для периферийных устройств в вычислительной технике. ХАРАКТЕРИСТИКИ???
• Устройства подразделяются на две большие группы -- блочные и символьные
• Основное различие – обмен данными с блочным устройством производится порциями байт – блоками
• Они имеют внутренний буфер, благодаря чему повышается скорость обмена
• Символьные же устройства – это лишь каналы передачи информации, по которым данные следуют последовательно, байт за байтом
• Большинство устройств относятся к классу символьных, поскольку они не ограничены размером блока и не нуждаются в буферизации Если первый символ в списке, полученном командой ls -l /dev, 'b', тогда это блочное устройство, если 'c', тогда – символьное
ДРАЙВЕРЫ УСТРОЙСТВ
Драйверы устройств являются одной из разновидностей модулей ядра. Например, драйвер звуковой платы связывает файл устройства /dev/sound со звуковой платой. Пользовательское приложение может использовать для своей работы /dev/sound, ничего не подозревая о типе установленной звуковой платы.
Одному устройству могут соответствовать несколько файлов устройств (узлов) - 1, нескольким устройствам — один файл устройства - 2, ни одному устройству — файл устройства - 3, одному устройству — ни один файл устройства - 4.
1) Если на одном жестком диске несколько разделов (каждому разделу – файл)
2) Если устройства имеют одинаковый контроллер и соответственно одинаковый драйвер
3) /dev/null (вся информация, попавшая туда, пропадает) /dev/random (для устройств генерирующих рандомные данные) /dev/urandom (для устройств генерирующих псевдорандомные данные) /dev/zero (устройство генерирует нули)
4) Если устройство не опознано или вообще не является устройством.
МЛАДШИЙ И СТАРШИЙ НОМЕРА
• Все файлы устройств создаются в процессе установки системы с помощью утилиты mknod
• Чтобы создать новое устройство, например, с именем "coffee", со старшим номером 12 и младшим номером 2, нужно выполнить команду mknod /dev/coffee c 12 2
• Когда происходит обращение к файлу устройства, ядро использует старший номер файла, для определения драйвера, который должен обработать это обращение • Это означает, что ядро в действительности не использует и даже ничего не знает о младшем номере • Единственный, кто обеспокоен этим – это сам драйвер • Он использует младший номер, чтобы отличить разные физические устройства.
НАПИСАНИЕ ДРАЙВЕРА
-лицензия GPL - GNU Public License
-структура file_operations
• Определена в файле linux/fs.h • Содержит указатели на функции драйвера, которые отвечают за выполнение различных операций с устройством • Например, практически любой драйвер символьного устройства реализует функцию чтения данных из устройства, и адрес этой функции, среди всего прочего, хранится в этой структуре. • Компилятор gcc предоставляет программисту довольно удобный способ заполнения полей структуры в исходном тексте • Ниже приводится пример подобного заполнения:
struct file_operations fops = {.read=device_read, .write=device_write, .open=device_open, .release=device_release};
• Добавление драйвера в систему подразумевает его регистрацию в ядре • Это означает - получение старшего номера в момент инициализации модуля • Получить его можно вызовом функции register_chrdev(), определенной в файле linux/fs.h
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
• где unsigned int major -- это запрашиваемый старший номер устройства, • const char *name -- название устройства, которое будет отображаться в /proc/devices • struct file_operations *fops -- указатель на таблицу file_operations драйвера
IOCTL
#include <linux/kernel.h>
// ДРАЙВЕР ДЛЯ КЛАВИАТУРЫ. ЗАМЕНА
ОБРАБОТЧИКА ПРЕРЫВАНИЙ #include
<linux/module.h> #include
<linux/interrupt.h> #include
<asm/io.h>
MODULE_LICENSE("GPL");
static
char scancode;
static
irqreturn_t irq_handler(int irq, void *data){ char
* sc = (char *)data; //сколько данных можно
передать? sc[0]
= inb(0x60); sc[1]
= inb(0x60); return
IRQ_WAKE_THREAD;
} static
irqreturn_t irq_thread(int irq, void *data){ char
* sc = (char *)data; printk(KERN_ALERT
"### Scan Code1 %#x %s\n", sc[0] & 0x7f, sc[0] &
0x80 ? "Released" : "Pressed"); printk(KERN_ALERT
"### Scan Code1 %#x %s\n", sc[1] & 0x7f, sc[1] &
0x80 ? "Released" : "Pressed"); return
IRQ_HANDLED;
} static
int __init modu_init(void){ return
request_threaded_irq(1, irq_handler, irq_thread, IRQF_SHARED,
“KeyLogger", &scancode); }
static
void __exit modu_clean(void){ free_irq(1,
&scancode); } module_exit(modu_clean); module_init(modu_init);
