Linux Device Drivers 3 / Драйверы устройств Линукс (третье издание): 2.3. Модули ядра в сравнении с приложениями

Оглавление
Английский оригинал Перевод на русский

Сожалеем, но текст оригинала доступен только зарегистрированным пользователям.

Модули ядра в сравнении с приложениями

hardorsoft 23.10.10 в 07:58

1

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

hardorsoft 23.10.10 в 08:50

1

В то время как большинство приложений выполняют одну задачу от начала до конца, каждый модуль просто регистрирует себя в порядке выполнения будущего запроса и инициируют функции которые непосредственно завершают его (запрос). Другими словами, задачу функции инициализации модулей — которая подготовляет более поздний вызов функций модулей, можно объяснить так: модуль говорит «Привет, я здесь, вот что я могу делать.» Функция выхода модуля (hello_exit в примере), вызывается только для того чтобы выгрузить модуль. Она говорит ядру: «Меня здесь больше нет, не больше не просите меня ни о чем.» Этот вид подхода в программировании похож на программирование «случайного управления», но пока не все приложения «случайного управления», как все и каждый модуль ядра. Другое важное отличие между приложениями «случайного управления» и кодом ядра это функция выхода: пока приложение, которое завершает свою работу лениво высвобождает ресурсы или избегает отключать все сразу, функция выхода (exit function) модуля должна внимательно отменить все что создала функция инициализации (init fuction) иначе куски кода будут оставаться в ядре до тех пор пока система не перегрузится.

hardorsoft 23.10.10 в 08:28

1

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

ximen 22.10.10 в 13:34

1

Как программист вы знаете что приложения могут вызывать функции не определяя их: связанная стадия вызова внешних ссылок использует соответствующую библиотеку функций. Prinf одна из таких вызываемых функций и определена в libc. Модуль, с другой стороны, связан только с ядром, и только функции могут вызвать его (модуль) из ядра; здесь нет библиотек к которым нужно обращается(?!!). Функция printk, которую мы использовали недавно в файле hello.c , для примера, версия определенной printf без ядра и экспортирована в модуль. Она ведет себя подобно настоящей функции, с некоторыми маловажными различиями, основой её недостаток, то что она не поддерживает числа с плавающей точкой.

hardorsoft 23.10.10 в 10:12

1

Как программист вы знаете, что приложения могут вызывать функции, не определяя их: связанная стадия вызова внешних ссылок использует соответствующую библиотеку функций. printf -- одна из таких вызываемых функций, она определена в libc. Модуль, с другой стороны, связан только с ядром, и может вызывать только те функции, которые экспортируются ядром. Функция printk, которую мы использовали ранее в файле hello.c., для примера, является версией printf, определенной внутри ядра и экспортируемой в модули. Она ведет себя подобно оригинальной функции с некоторыми маловажными различиями. Основной её недостаток заключается в том, что она не поддерживает числа с плавающей точкой.

galleyworm 12.04.12 в 06:12

0

Рисунок 2-1 показывает как вызовы функции и указатели используются в модулях и добавляют новую функциональность работающему ядру.

hardorsoft 23.10.10 в 10:42

1

Рисунок 2-1. Связь модулей в ядре

hardorsoft 23.10.10 в 10:42

0

Так как нет библиотек связанных с модулями, исходные файлы как правило ни когда не включают заголовки файлов, и в очень редких случаях в качестве исключения. Только функции которые в настоящее время являются частью ядра могут быть использованы в модулях ядра. Все что вы брали и сконфигурировали связанно с ядром и определено в заголовке который можно найти в исходном дереве ядра, большинство относящихся к делу заголовков находятся в директориях include/linux и include/asm, но другие поддиректории директории include добавлены в содержании материала связанны с особенными подсистемами ядра.

hardorsoft 23.10.10 в 10:42

0

Роль каждого отдельного заголовка ядра которые объясняются на протяжении всей книги тогда когда это необходимо.

hardorsoft 23.10.10 в 11:05

0

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

hardorsoft 23.10.10 в 11:05

0

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

ximen 25.10.10 в 13:08

2

2.3.1. Пространство пользователя и пространство ядра.

hardorsoft 23.10.10 в 11:05

0

Модуль работает в пространстве ядра, в то время как приложение в пространстве пользователя.

hardorsoft 23.10.10 в 11:18

0

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

victor8632 25.10.10 в 06:55

2

Роль операционной системы на практике — предоставить программам согласующийся вид аппаратного обеспечения компьютера. В дополнение, операционная система должна считать независимые операции программ и защищать от несанкционированного доступа к ресурсам. Эта не очевидная задача возможна только если ЦрУ навязывает защиту программного обеспечения системы от приложений.

hardorsoft 23.10.10 в 11:17

0

Каждый современный процессор позволяет навязывать такой режим работы. Избранный подход осуществляет различные операционные модальности (или уровни) внутри ЦрУ. Уровни играют разную роль и некоторые операции не разрешены более низким уровням, код программы может переключаться с одного уровня на другой только через ограниченное число входов. Юникс системы разработаны таким образом, чтобы дать расширение этих возможностей аппаратного обеспечения, используя два таких уровня. В настоящее время все процессоры поддерживают два уровня защиты а некоторые такие как семейство х86, имеют в распоряжении ещё больше уровней, когда отдельные уровни отсутствуют, тогда используются более высокие и более низкие уровни. В Юниксах ядро выполняется на самом высоком уровне (оно называется ещё режим суперпользователя), на этом уровне разрешено все, пока приложения выполняются на самом низком уровне ( также называется режим пользователя), процессор регулирует прямой доступ к аппаратному обеспечению и несанкционированный доступ к памяти.

hardorsoft 23.10.10 в 11:59

0

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

hardorsoft 23.10.10 в 22:44

0

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

hardorsoft 23.10.10 в 22:44

0

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

victor8632 25.10.10 в 07:10

1

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

hardorsoft 23.10.10 в 22:44

0

Роль модулей - расширение функциональности ядра; модульный код выполняется в пространстве ядра. Обычно драйвер выполняет обе задачи, описанные ранее: некоторые функции в модуле выполняются как часть системных вызовов, а некоторые из них отвечают за обработку прерываний.

victor8632 25.10.10 в 07:17

2

2.3.2 Параллелизм ядра

hardorsoft 23.10.10 в 23:07

0

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

hardorsoft 23.10.10 в 23:07

0

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

victor8632 26.10.10 в 09:48

1

Вот несколько источников параллелизма в программировании ядра. Конечно, система Линукс работает с многочисленными процессами, более чем одним которые могут попытаться использовать драйвер один момент времени. Большинство устройств допускают прерывание процессора, управление прерываниями запускается асинхронно и могут быть вызваны в тот момент когда драйвер пытается сделать что-то ещё. Несколько программных абстракций (такие как таймер ядра, о них мы поговорим в главе 7) также запускаются асинхронно. Кроме того, Линукс может запускаться на симметричных многопроцессорных системах (SMP), в результате драйвер может выполняться параллельно более чем на одном процессоре. И наконец, в код ядра версии 2.6 сделан вытесняемым, это изменение причина того что даже однапроцессорные системы имеют много таких же подходов параллелизма как и многопроцессорные системы.

hardorsoft 24.10.10 в 00:13

0

Вот несколько источников параллелизма в программировании ядра. Естественно, Линукс работает с многочисленными процессами, более одного из которых могут попытаться одновременно использовать ваш драйвер. Большинство устройств поддерживают прерывание процессора; обработчики прерываний запускаются асинхронно и могут быть вызваны в тот момент, когда ваш драйвер уже что-то делает. Некоторые программные абстракции (такие как таймеры ядра, описанные в главе 7) также выполняются асинхронно. Кроме того, конечно, Линукс может работать на симметричных многопроцессорных (SMP) системах, в результате чего ваш драйвер может быть запущен параллельно более чем на одном процессоре. И наконец, код ядра версии 2.6 сделан вытесняемым, что делает даже однопроцессорные системы сравнимыми с многопроцессорными в вопросах параллелизма.

victor8632 26.10.10 в 10:50

0

В результате, код ядра Линукс, включает код драйвера, должен быть входящим должен быть запустить больше чем один контекст в один момент. Структура данных должна быть внимательно спроектирована содержать в себе многочисленные самостоятельно отделяемые нити и код должен брать попечение о доступе к распределяемым данным в направлении котором предотвращается порча (искажение) данных. Написанный код который управляет параллелизмом и избегает условий гонки (??? race conditions) (ситуация в которой неудачный порядок выполнения причина неудобный режим работы) требующие размышлений и могут быть сложными. Правильное управление параллелизма требует писать корректный код ядра, по этой причине, каждый пример драйвера в этой книге был написан с учетом параллелизма. Используемые техники объясняются по мере приближения к ним; Глава 5 посвящена этому подходу и примитивы ядра доступны для управления параллелизма.

hardorsoft 24.10.10 в 00:14

0

В результате, код ядра Линукс, включая код драйвера, должен быть reentrant—it (?), должна быть возможность запускать его одновременно более, чем в одном контексте. Структуры данных должны быть тщательно спроектированы с таким расчетом, чтобы хранить множество потоков, запущенных отдельно, и код должен обеспечивать доступ к общим данным таким образом, чтобы предотвратить их повреждение. Написание кода, который обладал бы параллелизмом и позволял бы избежать состояний гонки (ситуаций, когда порядок выполнения частей кода влияет на его поведение) требует глубоких размышлений и может быть весьма сложным. Правильное управление параллелизмом требует написания корректного кода ядра; вот почему каждый пример драйвера в этой книге написан с расчетом на параллелизм. Используемые методы объясняются по мере их рассмотрения; этому вопросу также посвящена глава 5, в ней также описаны доступные примитивы ядра для управления параллелизмом.

victor8632 26.10.10 в 12:22

1

Распространенная ошибка программистов драйверов предпологать что параллелизм не проблема до тех пор пока отдельный сегмент кода не «заснет» (или блок кода). Даже в предыдущих ядрах (которые на примитивные), это предположение было имело силу на многопроцессорных системах. В коде ядра 2.6 может никогда предполагать что он может просверлить процессор позволив растягивать код. Если вы пишете код на помня о параллелизме, это будет причиной катастрофических ошибок которые очень сложно исправлять.

hardorsoft 24.10.10 в 00:36

0

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

victor8632 26.10.10 в 13:07

1

2.3.3. Текущий процесс

ximen 22.10.10 в 13:29

0

Хотя модули ядра не выполняются последовательно как приложения, большинство действий выполняемых ядром действуют от имени отдельного процесса. Код ядра может отправлять поток процесса допуская глобальный поток сообщений, определенный в файле current.h, который производит указатель на stuct (структуру) task_struct, определенную в файле sched.h. Указатель потока отправляет процессору что теперь идет выполнение. Во время выполнения системного вызова, такого как методы open и read, поток процесса то что инициировало вызов. Код ядра может использовать информация характерную для процесса используя поток, если ему это необходимо. Пример этой техники приводится в главе 6.

hardorsoft 24.10.10 в 05:47

0

Хотя модули ядра не выполняются последовательно как приложения, большинство действий ядра выполняются от имени отдельного процесса. Код ядра может ссылаться на текущий процесс через доступную глобальную переменную current, описанную в файле current.h>, которая является указателем на структуру struct task_struct, содержащуюся в файле sched.h>. Текущий указатель ссылается на процесс, который выполняется в настоящее время. Во время выполнения системного вызова, такого как open или read, текущим считается процесс, который инициировал системный вызов. Если в этом есть необходимость, код ядра может использовать информацию процесса при помощи переменной current. Пример использования этого метода представлен в главе 6.

victor8632 26.10.10 в 13:38

1

На самом деле current не является по-настоящему глобальной переменной. Необходимость поддержки SMP заставила разработчиков ядра создать механизм, находящий текущий процесс на соответствующем процессоре. Этому механизму было также необходимо быть быстрым, поскольку ссылки на current возникают часто. В результате появился архитекурно-зависимый механизм, который обычно скрывает указатель на структуру task_struct в стеке ядра. Детали этой реализации остаются скрытыми даже от других подсистем ядра и в драйверы устройств можно просто включить sched.h> и ссылаться на текущий процесс. Например, следующий оператор выводит идентификатор и имя команды текущего процесса, беря их из соответствующих полей структуры task_struct:

allez 24.10.10 в 06:50

1

<code>printk(KERN_INFO "The process is \"%s\" (pid %i)\n", current->comm, current->pid);</code>

ximen 22.10.10 в 13:34

0

Имя команды, хранящееся в <code>current-&gt;comm</code>, представляет собой базовое имя файла программы (при необходимости урезанное до 15 символов), которая выполняется текущим процессом.

ximen 22.10.10 в 13:36

1

2.3.4 Ещё несколько подробностей

ximen 22.10.10 в 13:29

0

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

ximen 25.10.10 в 05:52

1

Приложения полагаются на виртуальную память с очень большой областью для стека. Стек используется для хранения истории вызовов функций и всех автоматических переменных, созданных текущей функцией. У ядра же, наоборот, стек очень маленький и может иметь размер одной 4096-байтной страницы. Функции делят этот стек со всей цепочкой вызовов в пространстве ядра. Поэтому создание больших автоматических переменных является плохой идеей -- если вам нужны большие структуры, следует выделять для них память динамически во время вызова.

ximen 25.10.10 в 06:00

0

В API ядра часто встречаются имена функций, начинающиеся с двойного подчеркивания (__). Функции, отмеченные таким образом, обычно являются низкоуровневыми компонентами интерфейса и должны использоваться с большой осторожностью. По сути, двойное подчеркивание говорит программисту: "Если ты вызываешь эту функцию, будь уверен, что ты знаешь, что делаешь".

ximen 25.10.10 в 06:05

1

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

ximen 25.10.10 в 06:11

1
Минутку...