Продолжая использовать наш сайт, вы даете согласие на обработку файлов cookie, которые обеспечивают правильную работу сайта. Благодаря им мы улучшаем сайт!
Принять и закрыть

Читать, слущать книги онлайн бесплатно!

Электронная Литература.

Бесплатная онлайн библиотека.

Читать: Разработка ядра Linux - Роберт Лав на бесплатной онлайн библиотеке Э-Лит


Помоги проекту - поделись книгой:

• Ядро Linux является полностью открытым во всех смыслах этого слова. Набор функций, реализованных в ядре Linux, — это результат свободной и открытой модели разработки операционной системы Linux. Если какая-либо функция ядра считается маловажной или некачественной, то разработчики ядра не обязаны ее реализовать. В противоположность этому, внесение изменений при разработке ядра Linux занимает "элитарную" позицию: изменения должны решать определенную практическую задачу, должны быть логичными и иметь понятную четкую реализацию. Следовательно, функции некоторых современных вариантов ОС Unix, такие как память ядра со страничной реализацией, не были реализованы. Несмотря на имеющиеся различия, Linux является операционной системой со строгим наследованием традиций ОС Unix.

Версии ядра Linux

Ядро Linux поставляется в двух вариантах: стабильном (stable) и разрабатываемом (development). Версии стабильного ядра - это выпуски продукции промышленного уровня, которая готова для широкого использования. Новые стабильные версии ядра обычно выпускаются для исправления ошибок и для предоставления новых драйверов устройств. Разрабатываемые версии ядра, наоборот, подвержены быстрым изменениям. По мере того как разработчики экспериментируют с новыми решениями, часто вносятся радикальные изменения в ядро.

Ядра Linux стабильных и разрабатываемых версий можно отличить друг от друга с помощью простой схемы присваивания имен (рис. 1.2.). Три числа, которые разделяются точкой, определяют версию ядра. Первое число — значение старшей (major) версии, второе — значение младшей (minor), третье число — значение редакции (выпуска, revision). Значение младшей версии также определяет, является ли ядро стабильным или разрабатываемым; если это значение четное, то ядро стабильное, а если нечетное, то разрабатываемое. Так, например, версия 2.6.0 определяет стабильное ядро. Ядро имеет старшую версию 2, младшую версию 6 и редакцию 0. Первые два числа также определяют "серию ядер", в данном случае серия ядер — 2.6.


Рис. 1.2. Соглашение о присваивании имен ядрам

Разработка ядра соответствует различным фазам. Вначале разработчики ядра работают над новыми функциями, что напоминает хаос. Через определенное время ядро оказывается сформировавшимся, и в конце концов объявляется замораживание функций.

Начиная с этого момента никакие новые функции не могут быть добавлены в ядро. Однако работа над существующими функциями может быть продолжена. После того как ядро становится почти стабильным, осуществляется замораживание кода. В этом случае допускаются только исправления ошибок. Вскоре после этого (можно надеяться) ядро выпускается в виде первой, новой, стабильной версии. Например, при стабилизации серии ядер 2.5 получается серия 2.6.

Все это неправда

По крайней мере — не совсем. Приведенное только что описание процесса разработки ядра технически правильное. Раньше процесс происходил именно так, как описано. Тем не менее летом 2004 года на ежегодном саммите для приглашенных разработчиков ядра Linux было принято решение продолжить разработку серии 2.6 ядра Linux и в ближайшем будущем не переходить на серию разрабатываемого ядра 2.7. Такое решение было принято потому, что ядро 2.6 получилось хорошим; оно, в основном, стабильно и на горизонте нет никаких новых функций, которые требуют серьезного вторжения в ядро.

Кроме того, и, возможно, это главное — существующая система поддержки, которая обеспечивается Линусом Торвальдсом и Эндрю Мортоном, работает чрезвычайно хорошо. Разработчики ядра уверены, что процесс разработки может продолжаться таким образом, что серия ядер 2.6 будет оставаться стабильной и в ней будут появляться новые возможности. Время рассудит, но уже сейчас результаты выглядят хорошо.

Эта книга базируется на ядрах стабильной серии 2.6.

Сообщество разработчиков ядра Linux

Когда вы начинаете разрабатывать код ядра Linux, вы становитесь частью глобального сообщества разработчиков ядра Linux. Главный форум этого сообщества — список рассылки разработчиков ядра Linux (linux-kernel mailing list). Информация по поводу подписки на этот форум доступна по адресу http://vger.kernel.org. Следует заметить, что это достаточно перегруженный сообщения список рассылки (количество сообщений порядка 300 в день) и что другие читатели этого списка (разработчики ядра, включая Линуса) не очень склонны заниматься ерундой. Однако этот список рассылки может оказать неоценимую помощь в процессе разработки; здесь вы сможете найти тестологов, получить экспертную оценку и задать вопросы.

В последних главах приведен обзор процесса разработки ядра и более полное описание того, как успешно принимать участие в деятельности сообщества разработчиков ядра.

Перед тем как начать

Эта книга посвящена ядру Linux: как оно работает, почему оно работает и чему следует уделить внимание. Далее будут описаны принципы работы и реализация основных подсистем ядра, а также интерфейсы и программная семантика. Эта книга касается практических вопросов, и в ней используется подход на основании золотой серединки указанных выше направлений. Такой интересный подход в сочетании с анекдотами из личной практики автора и советами по хакерским приемам позволяет быть уверенным в том, что книга станет хорошим стартом.

Я надеюсь, что у читателей есть доступ к системе Linux и дереву исходного кода ядра. В идеале предполагается, что читатель— это пользователь операционной системы Linux, который уже "копался" в исходном программном коде, но все же нуждается в некоторой помощи для того, чтобы все связать воедино. В принципе, читатель может и не быть пользователем Linux, но хочет разобраться в устройстве ядра из чистого любопытства. Тем не менее, для того чтобы самому научиться писать программы — исходный код незаменим. Исходный программный код свободно доступен — пользуйтесь им!

Удачи!

Глава 2

Начальные сведения о ядре Linux

В этой главе будут рассмотрены основные вопросы, связанные с ядром Linux: где получить исходный код, как его компилировать и как инсталлировать новое ядро. После этого рассмотрим некоторые допущения, связанные с ядром Linux, отличия между ядром и пользовательскими программам, а также общие методы, которые используются в ядре.

Ядро имеет интересные особенности, которые отличают его от других программ, но нет таких вещей, в которых нельзя разобраться. Давайте этим займемся.

Получение исходного кода ядра

Исходный программный код последней версии ядра всегда доступен как в виде полного архива в формате tar (tarball), так и виде инкрементной заплаты по адресу http://www.kernel.org.

Если нет необходимости по той или другой причине работать со старыми версиями ядра, то всегда нужно использовать самую последнюю версию. Архив kernel.org — это то место, где можно найти как само ядро, так и заплаты к нему от ведущих разработчиков.

Инсталляция исходного кода ядра

Архив исходного кода ядра в формате tar распространяется в сжатых форматах GNU zip (gzip) и bzip2. Формат bzip2 наиболее предпочтителен, так как обеспечивает больший коэффициент сжатия по сравнению с форматом gzip. Архив ядра в формате bzip2 имеет имя linux-x.y.z.tar.bz2, где x, y, z — это номер соответствующей версии исходного кода ядра. После загрузки исходного кода его можно декомпрессировать очень просто. Если tar-архив сжат с помощью GNU zip, то необходимо выполнить следующую команду.

$ tar xvzf linux-x.y.z.tar.gz

Если сжатие выполнено с помощью bzip2, то команда должна иметь следующий вид.

$ tar xvjf linux-x.y.z.tar.bz2

Обе эти команды позволяют декомпрессировать и развернуть дерево исходных кодов ядра в каталог с именем linux-x.y.z.

Где лучше инсталлировать и изменять исходный код

Исходный код ядра обычно инсталлируется в каталог /usr/src/linux. Заметим, что это дерево исходного кода нельзя использовать для разработок. Версия ядра, с которой была скомпилирована ваша библиотека С, часто связывается с этим деревом каталогов. Кроме того, чтобы вносить изменения в ядро, не обязательно иметь права пользователя root, вместо этого лучше работать в вашем домашнем каталоге и использовать права пользователя root только для инсталляции ядра. Даже при инсталляции нового ядра каталог /usr/src/linux лучше оставлять без изменений.

Использование заплат

В сообществе разработчиков ядра Linux заплаты (patch) — это основной язык общения. Вы будете распространять ваши изменения исходного кода ядра в виде заплат и получать изменения кода от других разработчиков тоже в виде заплат. При данном рассмотрении наиболее важными являются инкрементные заплаты (incremental patch), которые позволяют перейти от одной версии ядра к другой. Вместо того чтобы загружать большой архив ядра, можно просто применить инкрементную заплату и перейти от имеющейся версии к следующей. Это позволяет сэкономить время и пропускную способность каналов связи. Для того чтобы применить инкрементную заплату, находясь в каталоге дерева исходных кодов ядра, нужно просто выполнить следующую команду.

$ patch -p1 < ../patch-x.y.z

Обычно заплата для перехода на некоторую версию ядра должна применяться к предыдущей версии ядра.

В следующих главах использование заплат рассматривается более подробно.

Дерево исходных кодов ядра

Дерево исходных кодов ядра содержит ряд каталогов, большинство из которых также содержит подкаталоги. Каталоги, которые находятся в корне дерева исходных кодов, и их описание приведены в табл. 2.1.

Таблица 2.1. Каталоги в корне дерева исходных кодов ядра

Каталог Описание
arch Специфичный для аппаратной платформы исходный код
crypto Криптографический API
Documentation Документация исходного кода ядра
drivers Драйверы устройств
fs Подсистема VFS и отдельные файловые системы
include Заголовочные файлы ядра
init Загрузка и инициализация ядра
ipc Код межпроцессного взаимодействия
kernel Основные подсистемы, такие как планировщик
lib Вспомогательные подпрограммы
mm Подсистема управления памятью и поддержка виртуальной памяти
net Сетевая подсистема
scripts Сценарии компиляции ядра
security Модуль безопасности Linux
sound Звуковая подсистема
usr Начальный код пространства пользователя (initramfs)

Некоторые файлы, которые находятся в корне дерева исходных кодов, также заслуживают внимания. Файл COPYING — это лицензия ядра (GNU GPL v2). Файл CREDITS — это список разработчиков, которые внесли большой вклад в разработку ядра. Файл MAINTAINERS — список людей, которые занимаются поддержкой подсистем и драйверов ядра. И наконец, Makefile — это основной сборочный файл ядра.

Сборка ядра

Сборка ядра достаточно проста. Это может показаться удивительным, но она даже более проста, чем компиляция и инсталляция других системных компонентов, как, например библиотеки glibc. В ядрах серии 2.6 встроена новая система конфигурации и компиляции, которая позволяет сделать эту задачу еще проще и является долгожданным улучшением по сравнению с серией ядер 2.4.

Так как доступен исходный код ядра Linux, то, это означает, что есть возможность сконфигурировать ядро перед компиляцией. Есть возможность скомпилировать поддержку только необходимых драйверов и функций. Конфигурация ядра— необходимый этап перед тем, как его компилировать. Поскольку в ядре бесчисленное количество функций и вариантов поддерживаемого аппаратного обеспечения, возможностей по конфигурации, мягко говоря, много. Конфигурация управляется с помощью опций конфигурации в виде CONFIG_FEATURE. Например, поддержка симметричной многопроцессорной обработки (Symmetric multiprocessing, SMP) устанавливается с помощью опции CONFIG_SMP. Если этот параметр установлен, то поддержка функций SMP включена. Если этот параметр не установлен, то функции поддержки SMP отключены. Все конфигурационные параметры хранятся в файле .config в корневом каталоге дерева исходного кода ядра и устанавливаются одной из конфигурационных программ, например, с помощью команды make xconfig. Конфигурационные параметры используются как для определения того, какие файлы должны быть скомпилированы во время сборки ядра, так и для управления процессом компиляции через директивы препроцессора.

Конфигурационные переменные бывают двух видов: логические (boolean) и переменные с тремя состояниями (tristate). Логические переменные могут принимать значения yes и no. Такие переменные конфигурации ядра, как CONFIG_PREEMPT, обычно являются логическими. Конфигурационная переменная с тремя состояниями может принимать значения yes, no и module. Значение module отвечает конфигурационному параметру, который установлен, но соответствующий код должен компилироваться как модуль (т.е. как отдельный объект, который загружается динамически). Драйверы устройств обычно представляются конфигурационными переменными с тремя состояниями.

Конфигурационные параметры могут иметь целочисленный, или строковый, тип. Эти параметры не контролируют процесс сборки, а позволяют указать значения, которые встраиваются в исходный код ядра с помощью препроцессора. Например, с помощью конфигурационного параметра можно указать размер статически выделенного массива.

Ядра, которые включаются в поставки ОС Linux такими производителями, как Novell и Redhat, компилируются как часть дистрибутива. В таких ядрах обычно имеется большой набор различных функций и практически полный набор всех драйверов устройств в виде загружаемых модулей. Это позволяет получить хорошее базовое ядро и поддержку широкого диапазона оборудования. К сожалению, как разработчикам ядра, вам потребуется компилировать свои ядра и самим разбираться, какие модули включать, а какие нет.

В ядре поддерживается несколько инструментов, которые позволяют выполнять конфигурацию. Наиболее простой инструмент — это текстовая утилита командной строки:

make config

Эта утилита просматривает все параметры один за другим и интерактивно запрашивает у пользователя, какое значение соответствующего параметра установить — yes, no или module (для переменной с тремя состояниями). Эта операция требует длительного времени, и если у вас не почасовая оплата, то лучше использовать утилиту на основе интерфейса ncurses:

make menuconfig

или графическую утилиту на основе системы X11:

make xconfig

или еще более удобную графическую утилиту, основанную на библиотеке gtk+:

make gconfig

Эти утилиты позволяют разделить все параметры по категориям, таким как Processor Features (Свойства процессора) и Network Devices (Сетевые устройства). Пользователи могут перемещаться по категориям и, конечно, изменять значения конфигурационных параметров. Команда

$ make defconfig

позволяет создать конфигурационный файл, который будет содержать параметры, используемые по умолчанию для текущей аппаратной платформы. Хотя эти параметры и достаточно общие (ходят слухи, что для аппаратной платформы i386 используется конфигурация Линуса), они являются хорошей стартовой точкой, если вы никогда перед этим не занимались конфигурацией ядра. Чтобы все сделать быстро, необходимо выполнить эту команду, а потом проверить, включена ли поддержка всех нужных аппаратных устройств.

Конфигурационные параметры содержатся в корне дерева каталогов исходного кода ядра в файле с именем .config. Для вас может показаться более простым, так же как и для большинства разработчиков, непосредственно редактировать этот конфигурационный файл. Достаточно легко проводить поиск в этом файле и изменять значение конфигурационных параметров. После внесения изменений в конфигурационный файл или при использовании существующего конфигурационного файла для нового дерева каталогов исходного кода ядра, необходимо активизировать и обновить конфигурацию с помощью команды:

make oldconfig

Кстати, перед сборкой ядра эту команду также необходимо выполнить. После того как конфигурация ядра выполнена, можно выполнить сборку с помощью команды:

make

В отличие от предыдущих серий ядер, в версии 2.6 больше нет необходимости выполнять команду make dep перед сборкой ядра, так как создание дерева зависимостей выполняется автоматически. Также не нужно указывать цель сборки, например bzImage, как это было необходимо для более ранних версий. Правило, записанное в файле с именем Makefile, которое используется по умолчанию, в состоянии обработать все!

Уменьшение количества выводимых сообщений

Для того чтобы уменьшить шум, связанный с сообщениями, которые выдаются во время сборки, но в то же время видеть предупреждения и сообщения об ошибках, можно использовать такую хитрость, как перенаправление стандартного вывода команды make(1):

make > "имя_некоторого_файла"

Если вдруг окажется необходимым просмотреть выводимые сообщения, можно воспользоваться соответствующим файлом. Но обычно, если предупреждения или сообщения об ошибках выводятся на экран, в этом нет необходимости.

На самом деле я выполняю следующую команду

make > /dev/null

что позволяет совсем избавиться от ненужных сообщений.

Параллельная сборка

Программа make(1) предоставляет возможность разбить процесс сборки на несколько заданий. Каждое из этих заданий выполняется отдельно от остальных и параллельно с остальными, существенно ускоряя процесс сборки на многопроцессорных системах. Это также позволяет более оптимально использовать процессор, Поскольку время компиляции большого дерева исходного кода также включает время ожидания завершения ввода-вывода (время, в течение которого процесс ждет завершения операций ввода-вывода).

По умолчанию утилита make(1) запускает только одну задачу, поскольку часто файлы сборки содержат некорректную информацию о зависимостях. При неправильной информации о зависимостях несколько заданий могут начать "наступать друг другу на ноги", что приведет к ошибкам компиляции. Конечно же, в файле сборки ядра таких ошибок нет. Для компиляции ядра с использованием параллельной сборки необходимо выполнить следующую команду.

$ make -jn

где n — количество заданий, которые необходимо запустить.

Обычно запускается один или два процесса на процессор. Например, на двухпроцессорной машине можно использовать следующий запуск.

$ make -j4

Используя такие отличные утилиты, как distcc(1) и ccache(1), можно еще более существенно уменьшить время компиляции ядра.

Инсталляция ядра

После того как ядро собрано, его необходимо инсталлировать. Процесс инсталляции существенно зависит от платформы и типа системного загрузчика. Для того чтобы узнать, в какой каталог должен быть скопирован образ ядра и как установить его для загрузки, необходимо обратиться к руководству по используемому системному загрузчику. На случай если новое ядро будет иметь проблемы с работоспособностью, всегда следует сохранить одну или две копии старых ядер, которые гарантированно работоспособны!

Например, для платформы x86, при использовании системного загрузчика grub можно скопировать загружаемый образ ядра из файла arch/i386/boot/bzImage в каталог /boot и отредактировать файл /etc/grub/grub.conf для указания записи, которая соответствует новому ядру. В системах, где для загрузки используется загрузчик LILO, необходимо соответственно отредактировать файл /etc/lilo.conf и запустить утилиту lilo(8).

Инсталляция модулей ядра автоматизирована и не зависит от аппаратной платформы. Просто нужно запустить следующую команду с правами пользователя root.

$ make modules_install

В процессе компиляции в корневом каталоге дерева исходного кода ядра также создается файл System.map. В этом файле содержится таблица соответствия символов ядра их начальным адресам в памяти. Эта таблица используется при отладке для перевода адресов памяти в имена функций и переменных.

"Зверек другого рода"

Ядро имеет некоторые отличия в сравнении с обычными пользовательскими приложениями, эти отличия хотя и не обязательно приводят к серьезным усложнениям при программировании, но все же создают специфические проблемы при разработке ядра.

Эти отличия делают ядро зверьком другого рода. Некоторые из старых правил при этом остаются в силе, а некоторые правила являются полностью новыми. Хотя часть различий очевидна (все знают, что ядро может делать все, что пожелает), другие различия не так очевидны. Наиболее важные отличия описаны ниже.

• Ядро не имеет доступа к библиотеке функций языка С.

• Ядро программируется с использованием компилятора GNU С.

• В ядре нет такой защиты памяти, как в режиме пользователя.

• В ядре нельзя легко использовать вычисления с плавающей точкой.

• Ядро использует стек небольшого фиксированного размера.

• Поскольку в ядре используются асинхронные прерывания, ядро является преемптивным и в ядре имеется поддержка SMP, то в ядре необходимо учитывать наличие параллелизма и использовать синхронизацию.

• Переносимость очень важна.

Давайте рассмотрим более детально все эти проблемы, так как все разработчики ядра должны постоянно помнить о них.

Отсутствие библиотеки libc

В отличие от обычных пользовательских приложений, ядро не компонуется со стандартной библиотекой функций языка С (и ни с какой другой библиотекой такого же типа). Для этого есть несколько причин, включая некоторые ситуации с дилеммой о курице и яйце, однако первопричина — скорость выполнения и объем кода. Полная библиотека функций языка С, и даже только самая необходимая ее часть, очень большая и неэффективная для ядра.

При этом не нужно расстраиваться, так как многие из функций библиотеки языка С реализованы в ядре. Например, обычные функции работы со строками описаны в файле lib/string.с. Необходимо лишь подключить заголовочный файл <linux/string.h> и пользоваться этими функциями.

Заголовочные файлы

Заметим, что упомянутые заголовочные файлы и заголовочные файлы, которые будут упоминаться далее в этой книге, принадлежат дереву исходного кода ядра. В файлах исходного кода ядра нельзя подключать заголовочные файлы извне этого дерева каталогов, так же как и нельзя использовать внешние библиотеки,

Отсутствует наиболее известная функция printf(). Ядро не имеет доступа к функции printf(), однако ему доступна функция printk(). Функция printk() копирует форматированную строку в буфер системных сообщений ядра (kernel log buffer), который обычно читается с помощью программы syslog. Использование этой функции аналогично использованию printf():



Поделиться книгой:

На главную
Назад