Розробка модулів ядра Linux Частина 1

Серія контенту:

Цей контент є частиною # із серії # статей: Розробка модулів ядра Linux

Цей контент є частиною серії: Розробка модулів ядра Linux

Слідкуйте за виходом нових статей цієї серії.

  • деталі внутрішнього пристрою і функціонування ядра Linux, а також проблеми, що виявляються в ньому. Ці питання належать до сфери компетенції команди розробників ядра, яку очолює Лінус Торвальдс.
  • утиліти та бібліотеки, що постачаються у складі Linux. Розробнику достатньо вміти використовувати ці інструменти при побудові модулів ядра, але глибші знання — це вже прерогатива спільнот GNU, FSF та розробників незалежних проектів.
  • питання інтеграції створюваного модуля в дерево вихідних кодів Linux чи будь-якого його дистрибутива. Ці питання повинні вирішуватися системотехніками або впровадженцями, які беруть на себе відповідальність за подальшу долю проекту, що розробляється. Тому приклади модулів, що демонструються, будуть створюватися не в дереві вихідних кодів системи, а в окремих каталогах цільових проектів.

Розробника модулів ядра повинні цікавити виключно питання створення власного модуля (драйвера) для використання в рамках певного цільового прикладного програмного проекту. Тому всі питання, що виходять за рамки цього практичного завдання, не розглядатимуться в рамках цього циклу статей.

Це коротке, але необхідне, введення пояснює, що можна очікувати від цієї та наступних статей, а на які питання вони не зможуть відповісти.

Створення першого модуля ядра

Лістинг 1. Модуль ядра Linux, що викликається
Лістинг 2. Викликаючий модуль ядра Linux

Також усі модулі включають заголовний файл (тількищо забезпечує їх синтаксичну зв'язок), наведений нижче:

Як і для складання будь-якого проекту, в даному випадку знадобиться файл сценарію складання з ім'ямMakefile, наведений у лістингу 3. Досвідчені користувачі легко зрозуміють, що в ньому збираються 3 незалежні модулі.

Лістинг 3. Типовий сценарій збирання

Примітка. Згідно з синтаксичними правилами утиліти make кожен рядок, що має відступ у лістингу 3 і не починається з першого символу рядка, повинна починатися з одного або декількох символів TAB, але в жодному разі не з пробілів.

Більш детально різноманітні варіанти вмістуMakefile для різних випадків збирання модулів будуть розглянуті пізніше, але й представленої інформації вже достатньо, щоб успішно виконати команду збирання.

Лістинг 4. Результат успішного складання модуля

У лістингу 4 наведено приклад успішного складання модуля (попередження щодо рядка 14 файлуmd1.c не суттєво), результати складання інших модулів повинні виглядати аналогічно. На цьому вся робота з програмування та збирання завершена, і можна переходити до подальших експериментів та спостережень. Всі ключові поняття і терміни, що виникають у ході цих експериментів, що мають істотне значення для розуміння природи і техніки модулів, виділятиметься таким шрифтом.

В результаті збірки було створено три файли модуля ядра:

Це файли об'єктного формату компілятора gcc, розширені додатковими символами (у термінології об'єктних модулів):

Спробуємо інсталювати (завантажити) один із нових модулів у системі:

Виникла помилка, тому що модульмістить посилання на невідомі ядру імена (хоча відомо, що ці відсутні імена визначені в іншому модуліmd1). У системному журналі про цю помилку буде виведено таку інформацію:

Слід виконати завантаження модулів в іншому, цього разу правильному порядку:

Все пройшло нормально, але в розроблених модулях були виклики функції printk() , що досі не розглядалася, але, за аналогією з printf() вона повинна була виводити текстові повідомлення під час завантаження модулів. Все це так, але впросторі користувача, при запуску програми та виклику printf() висновок здійснюється накеруючий термінал, а таким терміналом є текстова консоль або додаток графічного терміналу, якщо виконання відбувається в середовищі X Window System. Завантаження модулів у свою чергу виконувалася в просторі ядра, де немає і не може бути ніякого керуючого терміналу, тому виведення printk() направляється демону системного журналування, який поміщає його, зокрема,системний журнал (/var/log/messages ). Це питання ще обговорюватиметься далі, а поки що досить просто скористатися командою читання системного журналу (dmesg), як показано нижче:

Інший спосіб знайти виведені повідомлення, це вивчити файл системного журналу, але в деяких дистрибутивах для цього можуть знадобитися праваroot :

Після успішного створення та завантаження модулів можна їх вивантажити за допомогою команди rmmod:

Однак, на цьому кроці знову виникає помилка. Розглянемо лістинг виконання команди lsmod, розташований кількома абзацами вище:

  • на модульmd1 посилається деякі інші модулі або об'єкти ядра: цифра1 — це число таких модулів, що називаєтьсялічильником посилань ;
  • далі за лічильником посилань вказуєтьсясписок тих модулів, звідки виходять такіпосилання, у разі, це один модульmd2 ;
  • до тих пір, поки кількість посилань на будь-який модуль у системіне стане нульовим, модульне може бути вивантажений ;
  • іншими словами, модуль може бути вивантажений тільки після того, як будуть вивантажені всі інші модулі, що посилаються на нього (завантажені після нього — тут не може виникнути циклічності або перехресності посилань);
  • бувають випадки (як буде показано далі), коли модуль взагалі не може бути вивантажений, зокрема, коли лічильник посилань для модуля не може бути зроблений нульовим через якісь причини.

Наступна спроба вивантажити модулі вже враховує ці правила, спочатку вивантажуючи модульmd2, і лише потімmd1 :

Існує й інший спосіб відібрати повідомлення, що цікавлять, із системного журналу. Якщо знати зовнішній вигляд повідомлень аудиту, можна навпаки:відбракувати повідомлення, що засмічують листинг, за відповідним регулярним виразом. Досить часто журнал може бути засмічений повідомленнями виду, що повторюються:

У цьому випадку використовується фільтр, який надалі іноді фігуруватиме в прикладах, що демонструються:

У лістингу 5 наведено вихідний код останнього з модулів, представлених в архіві проекту (файлmd3.c ):

Лістинг 5. Модифікована версія модуля md2

  • функція ініціалізації цього модуля, виконавши всі запропоновані їй дії,навмисно повертає ненульове значення, що означає помилку ініціалізації модуля;
  • тоді такий модульне буде завантажений до ядра, але станеться це вжепісля виконання коду ініціалізуючої функції модуляу просторі ядра ;
  • а якщо такий модуль за задумом не завантажується, він, в принципі,може не мати функції вивантаження.

Такий модуль дуже нагадує звичні додатки простору користувача (процеси), але тільки код цей виконується в просторі ядра з усіма привілеями. Нижче наведено висновки після запуску цього модуля:

Висновок

У першій статті цього циклу було представлено короткий огляд техніки модульного програмування, який можна підсумувати такими твердженнями:

  • код модуля оголошує дві функції: ініціалізації та фіналізації (завершення);
  • імена функцій, що виконують ці завдання, оголошуються в макросах module_init() та module_exit() ;
  • ці функції повинні точно відповідати прототипу, показаному в лістингах прикладів;
  • функція ініціалізації виконується при завантаженні модуля в ядро, якщо ця функція повертає нульове значення (успіх), код модуля залишається резидентним і виконує подальшу роботу;
  • функція завершення викликається при розвантаженні модуля командою rmmod;
  • функція завершення за своїм прототипомне має значення, що повертається, тому, розпочавшись, вона вже не має механізмів повідомити про своє невдале виконання;
  • більшість операцій над модулями (insmod, rmmod, modprobe) вимагають правroot, але деякі індикативні команди (lsmod) можуть виконуватися і від імені ординарного користувача.

При такому записі необхідність використання макросів module_init() і module_exit() відпадає, а використовувати модифікатор static з цими функціями не можна (саме ці імена і використовуються при завантаженні та видаленні модуля). Звичайно, такий запис не сприяє покращенню читання коду, але він може істотно скоротити кількість рутинного коду, особливо в коротких ілюстративних кодах.прикладах, так що вона буде використовуватися в прикладах, що демонструються.