Infrastructure engineer
0,0
рейтинг
18 апреля 2011 в 22:45

Администрирование → Работаем с модулями ядра в Linux

*nix*

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

В современных ядрах при подключении оборудования модули подключаются автоматически, а это событие обрабатывается демоном udev, который создает соответствующий файл устройства в каталоге "/dev". Все это выполняется в том случае, если соответствующий модуль корректно установлен в дерево модулей. В случае с файловыми системами ситуация та же: при попытке монтирования файловой системы ядро подгружает необходимый модуль автоматически, и выполняет монтирование.
Если необходимость в модуле не на столько очевидна, ядро его не загружает самостоятельно. Например, для поддержки функции шифрования на loop устройстве нужно вручную подгрузить модуль «cryptoloop», а для непосредственного шифрования — модуль алгоритма шифрования, например «blowfish».

Поиск необходимого модуля

Модули хранятся в каталоге "/lib/modules/<версия ядра>" в виде файлов с расширением «ko». Для получения списка всех модулей из дерева можно выполнить команду поиска всех файлов с расширением «ko» в каталоге с модулями текущего ядра:

find /lib/modules/`uname -r` -name ‘*.ko’

Полученный список даст некоторое представление о доступных модулях, их назначении и именах. Например, путь «kernel/drivers/net/wireless/rt2x00/rt73usb.ko» явно указывает на то, что этот модуль — драйвер устройства беспроводной связи на базе чипа rt73. Более детальную информацию о модуле можно получить при помощи команды modinfo:

# modinfo rt73usb

filename: /lib/modules/2.6.38-gentoo-r1/kernel/drivers/net/wireless/rt2x00/rt73usb.ko
license: GPL
firmware: rt73.bin
description: Ralink RT73 USB Wireless LAN driver.
version: 2.3.0
author: rt2x00.serialmonkey.com
depends: rt2x00lib,rt2x00usb,crc-itu-t
vermagic: 2.6.38-gentoo-r1 SMP preempt mod_unload modversions CORE2
parm: nohwcrypt:Disable hardware encryption. (bool)

Поле «firmware» указывает на то, что этот модуль сам по себе не работает, ему нужна бинарная микропрограмма устройства в специальном файле «rt73.bin». Необходимость в файле микропрограммы появилась в связи с тем, что интерфейс взаимодействия с устройством закрыт, и эти функции возложены на файл прошивки (firmware). Взять firmware можно с сайта разработчика, установочного диска, поставляемого вместе с устройством, или где-нибудь в репозиториях дистрибутива, затем нужно его скопировать в каталог "/lib/firmware", при чем имя файла должно совпадать с тем, что указано в модуле.
Следующее поле, на которое нужно обратить внимание — это поле «depends». Здесь перечислены модули, от которых зависит данный. Логично предположить, что модули друг от друга зависят, например модуль поддержки USB накопителей зависит от модуля поддержки USB контроллера. Эти зависимости просчитываются автоматически, и будут описаны ниже.
Последнее важное поле — «param». Здесь описаны все параметры, которые может принимать модуль при загрузке, и их описания. В данном случае возможен только один: «nohwcrypt», который, судя по описанию, отключает аппаратное шифрование. В скобках указан тип значения параметра.
Более подробную информацию о модуле можно прочитать в документации к исходным кодам ядра (каталог Documentation) в дереве исходных кодов. Например, найти код нужного видеорежима драйвера «vesafb» можно в файле документации «Documentation/fb/vesafb.txt» относительно корня дерева исходных кодов.

Загрузка и выгрузка модулей

Загрузить модуль в ядро можно при помощи двух команд: «insmod» и «modprobe», отличающихся друг от друга возможностью просчета и удовлетворения зависимостей. Команда «insmod» загружает конкретный файл с расширением «ko», при этом, если модуль зависит от других модулей, еще не загруженных в ядро, команда выдаст ошибку, и не загрузит модуль. Команда «modprobe» работает только с деревом модулей, и возможна загрузка только оттуда по имени модуля, а не по имени файла. Отсюда следует область применения этих команд: при помощи «insmod» подгружается файл модуля из произвольного места файловой системы (например, пользователь скомпилировал модули и перед переносом в дерево ядра решил проверить его работоспособность), а «modprobe» — для подгрузки уже готовых модулей, включенных в дерево модулей текущей версии ядра. Например, для загрузки модуля ядра «rt73usb» из дерева ядра, включая все зависимости, и отключив аппаратное шифрование, нужно выполнить команду:

# modprobe rt73usb nohwcrypt=0

Загрузка этого модуля командой «insmod» произойдет следующим образом:

# insmod /lib/modules/2.6.38-gentoo-r1/kernel/drivers/net/wireless/rt2x00/rt73usb.ko nohwcrypt=0

Но нужно помнить, что при использовании «insmod» все зависимости придется подгружать вручную. Поэтому эта команда постепенно вытесняется командой «modprobe».

После загрузки модуля можно проверить его наличие в списке загруженных в ядро модулей при помощи команды «lsmod»:

# lsmod | grep rt73usb

Module Size Used by
rt73usb 17305 0
crc_itu_t 999 1 rt73usb
rt2x00usb 5749 1 rt73usb
rt2x00lib 19484 2 rt73usb,rt2x00usb

Из вывода команды ясно, что модуль подгружен, а так же в своей работе использует другие модули.
Чтобы его выгрузить, можно воспользоваться командой «rmmod» или той же командой «modprobe» с ключем "-r". В качестве параметра обоим командам нужно передать только имя модуля. Если модуль не используется, то он будет выгружен, а если используется — будет выдана ошибка, и придется выгружать все модули, которые от него зависят:

# rmmod rt2x00usb
ERROR: Module rt2x00usb is in use by rt73usb

# rmmod rt73usb
# rmmod rt2x00usb

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

Для автоматической загрузки модулей в разных дистрибутивах предусмотрены разные механизмы. Я не буду вдаваться здесь в подробности, они для каждого дистрибутива свои, но один метод загрузки всегда действенен и удобен: при помощи стартовых скриптов. В тех же RedHat системах можно записать команды загрузки модуля прямо в "/etc/rc.d/rc.local" со всеми опциями.
Файлы конфигурация модулей находится в каталоге "/etc/modprobe.d/" и имеют расширение «conf». В этих файлах преимущественно перечисляются альтернативные имена модулей, их параметры, применяемые при их загрузке, а так же черные списки, запрещенные для загрузки. Например, чтобы вышеупомянутый модуль сразу загружался с опцией «nohwcrypt=1» нужно создать файл, в котором записать строку:

options rt73usb nohwcrypt=1

Черный список модулей хранится преимущественно в файле "/etc/modules.d/blacklist.conf" в формате «blacklist <имя модуля>». Используется эта функция для запрета загрузки глючных или конфликтных модулей.

Сборка модуля и добавление его в дерево

Иногда нужного драйвера в ядре нет, поэтому приходится его компилировать вручную. Это так же тот случай, если дополнительное ПО требует добавление своего модуля в ядро, типа vmware, virtualbox или пакет поддержки карт Nvidia. Сам процесс компиляции не отличается от процесса сборки программы, но определенные требования все же есть.
Во первых, нужен компилятор. Обычно установка «gcc» устанавливает все, что нужно для сборки модуля. Если чего-то не хватает — программа сборки об этом скажет, и нужно будет доустановить недостающие пакеты.
Во вторых, нужны заголовочные файлы ядра. Дело в том, что модули ядра всегда собираются вместе с ядром, используя его заголовочные файлы, т.к. любое отклонение и несоответствие версий модуля и загруженного ядра ведет к невозможности загрузить этот модуль в ядро.
Если система работает на базе ядра дистрибутива, то нужно установить пакеты с заголовочными файлами ядра. В большинстве дистрибутивов это пакеты «kernel-headers» и/или «kernel-devel». Для сборки модулей этого будет достаточно. Если ядро собиралось вручную, то эти пакеты не нужны: достаточно сделать символическую ссылку "/usr/src/linux", ссылающуюся на дерево сконфигурированных исходных кодов текущего ядра.
После компиляции модуля на выходе будет получен один или несколько файлов с расширением «ko». Можно попробовать их загрузить при помощи команды «insmod» и протестировать их работу.
Если модули загрузились и работают (или лень вручную подгружать зависимости), нужно их скопировать в дерево модулей текущего ядра, после чего обязательно обновить зависимости модулей командой «depmod». Она пройдется рекурсивно по дереву модулей и запишет все зависимости в файл «modules.dep», который, в последствие, будет анализироваться командой «modprobe». Теперь модули готовы к загрузке командой modprobe и могут загружаться по имени со всеми зависимостями.
Стоит отметить, что при обновлении ядра этот модуль работать не будет. Нужны будут новые заголовочные файлы и потребуется заново пересобрать модуль.

«Слушаем» что говорит ядро

При появлении малейших неполадок с модулем, нужно смотреть сообщения ядра. Они выводятся по команде «dmesg» и, в зависимости от настроек syslog, в файл "/var/log/messages". Сообщения ядра могут быть информативными или отладочными, что поможет определить проблему в процессе работы модуля, а могут сообщать об ошибке работы с модулем, например недостаточности символов и зависимостей, некорректных переданных параметрах. Например, выше рассмотренный модуль «rt73usb» требует параметр типа bool, что говорит о том, что параметр может принимать либо «0», либо «1». Если попробовать передать «2», то система выдаст ошибку:

# modprobe rt73usb nohwcrypt=2
FATAL: Error inserting rt73usb (/lib/modules/2.6.38-gentoo-r1/kernel/drivers/net/wireless/rt2x00/rt73usb.ko): Invalid argument


Ошибка «Invalid argument» может говорить о чем угодно, саму ошибку ядро на консоль написать не может, только при помощи функции «printk» записать в системный лог. Посмотрев логи можно уже узнать в чем ошибка:

# dmesg | tail -n1
rt73usb: `2' invalid for parameter `nohwcrypt'


В этом примере выведена только последняя строка с ошибкой, чтобы не загромаждать статью. Модуль может написать и несколько строк, поэтому лучше выводить полный лог, или хотя бы последние строк десять.
Ошибку уже легко найти: значение «2» неприемлемо для параметра «nohwcrypt». После исправления, модуль корректно загрузится в ядро.

Из всего сказанного можно сделать один вывод: ядро Linux играет по своим правилам и занимается серьезными вещами. Тем не менее — это всего лишь программа, оно, по сути, не сильно отличается от других обычных программ. Понимание того, что ядро не так уж страшно, как кажется, может стать первым шагом к пониманию внутреннего устройства системы и, как результат, поможет быстро и эффективно решать задачи, с которыми сталкивается любой администратор Linux в повседневной работе.
Дмитрий Щербаков @Vorb
карма
132,2
рейтинг 0,0
Infrastructure engineer
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Администрирование

Комментарии (49)

  • +2
    Очень хорошо изложено. На мой взгляд ещё стоит добавить про modprobe.d/*.conf — автоматическое конфигурирование модулей при загрузке. Полезно в частности для видео устройств.
    • +1
      Спасибо, добавил!
  • +1
    Большое спасибо, очень доступный и информативный материал, однозначно в избранное.
    Некоторые подробности загрузки/выгрузки стали более ясны и прозрачны
  • 0
    Хорошо изложено, спасибо.
  • +8
    Пользуясь случаем хочу порекомендовать две просто великолепные книги по ядру Linux
    The Linux Programming Interface by Michael Kerrisk
    Linux Kernel Development by Robert Love
    • 0
      О да! По Роберту Лаву я учился писать модули ядра. Автор не только рассказывает как писать модули, но и передает некую философию, руководствуясь которой можно избежать многих ошибок проектирования и правильно программировать в условиях пространства ядра.
    • +2
      В этот же список можно добавить
      Understanding the Linux Kernel by Daniel P. Bovet (Author) & Marco Cesati (Author)
  • 0
    Интересно было бы узнать ппро конфигурирование udev — например, загрузка своего модуля при появлении устройства с особыми свойствами.
    • +1
      $ man udev
      • +2
        Ну извините конечно, но по этой статье можно с таким же успехом дать ссылку на man modprobe, man insmod, man lsmod, man rmmod, man modinfo и man modprobe.conf
  • 0
    > В Linux ядро монолитное, т.е. все его драйвера и подсистемы работают в своем адресном пространстве, отделенном от пользовательского.

    Монолитное — значит что ядро не поделено на части, а не то что вы тут написали. Монолитное ядро противопоставляется микроядерным — Minix и HURD, где каждый отдельный сервис — это отдельный процесс а «истинное» микроядро лишь занимается обменом между информации между ними.

    > rmmod
    rmmod -> modprobe -r

    > Эти зависимости просчитываются автоматически, и будут описаны ниже.

    Я не совсем понял, что здесь значил слово «автоматически»?
    • 0
      А не одно и то же? Монолитное ядро по сути один большой процесс, выполняющийся в рамках одного адресного пространства. Все службы ядра существуют и выполняются в одном большом адресном пространстве ядра.
      «Автоматически» — имелось ввиду, что не нужно все делать руками, есть специальные механизмы определения зависимостей и их разрешения.
      • 0
        Это не один процесс, это одно адресное пространство. Процессов много, но память общая и все могут друг-друга уронить или иметь общие данные без копирования.
  • 0
    Скрипты загрузки и выгрузки модулей, это, пока, единственный способ бороться с отваливанием вай-фая после суспенда или хибернейта. За статью спасибо, ее бы месяц назад.

    зы. Регулярка find /lib/modules/`uname -r` -name ‘*.ko’ почему-то не срабатывает на убунту 10.10.
    • 0
      Подозреваю, что не работает из-за кавычек (хабр их заменяет на свои). Попробуйте использовать символ ' вокруг *.ko
      • 0
        подставлял свои — не срабатывает
    • 0
      uname -r — это подстановка. Пишется в кавычках, которые находятся на русской букве «ё». А '*.ko' пишется в обычных одинарных кавычках. Подстановку можно, кстати, написать по другому:
      find /lib/modules/$(uname -r) -name '*.ko'
      • 0
        а еще бывает .ko.gz

        >filename: /lib/modules/2.6.38-ARCH/kernel/drivers/net/wireless/rt2x00/rt73usb.ko.gz
  • 0
    >Необходимость в файле микропрограммы появилась в связи с тем, что интерфейс взаимодействия с устройством закрыт, и эти функции возложены на файл прошивки (firmware).

    разве фирмварь не грузится в само устройство, а не драйвер?
    • 0
      В этом случае нет. Судя по документации, этот файл грузится непосредственно в память ядра.
      • 0
        Вот вообще тему фирмвари бы поподробнее раскрыть -_-
        • 0
          обычно фирмваря — бинарный блоб, который драйвер читает из /lib/firmware/ и заливает в сустройство.

          в этом конкретном — надо смотреть сорц. как-то странно было бы держать в памяти ядро непонятно что и не тэйнтить его, как в случае с проприетарными драйверами.
  • 0
    Большое спасибо за статью. Интересует такой вопрос — Есть ли смысл вкомпиливать модули в ядро, интересно узнать в каких случаях это может потребоваться и будет ли влиять на производительность или надежность?
    • 0
      Чем больше вкомпилено в ядро, тем ядро медленнее загружается (может и работает), но так безопаснее — вам не надо думать о том, что какой-то модуль мог не подгрузится, когда он нужен.
      А если выбрать модульную структуру, то все наоборот)
      • 0
        Если что-то не вкомпилено в ядро, но нужно (критерий нужности с точки зрения автоматики) то это время, выигранное на этапе загрзуки ядра, будет скушано обратно на следующем этапе. Плюс уйдет время на подгрузку динамического объекта и всякие релокации.
    • 0
      Бывают ситуации, когда все нужные модули вкомпилируются в ядро и функция подгрузки модулей отключается. Это делают на тех системах, на которых не будут менять ни конфигурацию оборудования, ни файловые системы. Создается что-то типа бастиона, невозможно ничего не загрузить, ни выгрузить. Согласно этой политике повышается безопасность, т.к. невозможно загрузить какой либо вредоносный код в ядро. Но я с таким встречался только один раз, когда знакомый админ таким образом настроил внутренний шлюз. Он работал отлично, маршрутизировал довольно много трафика и держал аптаймы до нескольких лет. Но когда на маме сдохли конденсаторы и пришлось ее заменить — пришлось пересобирать ядро, т.к. таких матерей уже в продаже давно нет.
      Смысл вкомпилировать только некоторые модули есть тогда, когда вы уверены, что это оборудование или файловая система будет у вас всегда, но опять же прироста в производительности это не даст. Я так поступаю чисто для удобства на домашней системе: вкомпилирую модули поддержки корневой файловой системы и контроллера жесткого диска в ядро, чтобы не использовать initrd. Других преимуществ я не вижу. Может быть кто нибудь еще просветит.
      • 0
        >Согласно этой политике повышается безопасность, т.к. невозможно загрузить какой либо вредоносный код в ядро

        Глупости и наивняк. Если рут уже получен, то в ядро можно насрать и без красивой подгрузки модуля.
        • 0
          Согласен, я тоже этот метод не приемлю. Просто если цель нагадить в системе — да, этого достаточно, а чтобы незамеченно в системе сидела какая-то резидентная гадость — довольно не плохая идея интегрироваться в ядро.
          • 0
            «незаметная» резидентная гадость, которую видно в списке модулей — это как-то глупо.

            надо бы смотреть, надо бы посмотреть, как настоящие руткиты в реальности грузятся.
            • 0
              Ну никто же не назовет модуль «resident evil»? ) Он заныкается и попробуй его найти.
              А по поводу руткитов да… надо почитать.
          • 0
            Я вообще о том, что загрузить код в пространство ядра можно и без механизма модулей.
      • 0
        Подсказали что плюс использовать модули невкомпиленными, в том что при обновлении модуля можно просто заменить модуль и нет необходимости пересобирать ядро. Сомневаюсь что такая ситуация может часто встречаться, тем более что модули часто зависят от версии ядра.
        • 0
          >Сомневаюсь что такая ситуация может часто встречаться, тем более что модули часто зависят от версии ядра.

          На девелоперской машине такое бывает по несколько раз на день.

          В дистрибутивах конечно после одного патча чего угодно будет выпущен новый пакет с новыми версиями.

          >модули часто зависят от версии ядра.

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

          Поле vermagic в том же modinfo.
    • 0
      Если вкомпилить в ядро модули, нужные для монтирования рутфс, то можно выкинуть initrd/initramfs и сэкономить несколько секунд на загрузке.

      Я так на нетбуке делаю.

      После загрузки уже неважно, что откуда взялось.
  • 0
    >Для автоматической загрузки модулей в разных дистрибутивах предусмотрены разные механизмы.

    Это какие еще? Механизм один — ядро пинает юзерспейсу id устройства, modprobe по табличке алиасов находит нужный модуль. Все.

    В том же выхлопе modinfo rt73usb табличка есть.
    • 0
      Я немного некорректно выразился, имел ввиду автозагрузку модуля при запуске системы. Например, чтобы грузится cryptoloop и blowfish на домашней генту я прописываю их в /etc/modules.autoload.d/kernel-2.6, а в тех же redhat системах механизм другой. Я подправлю вечером, спасибо.
  • 0
    А как ядро определяет, для какого устройства какой модуль грузить?
    Есть устройство которое работает через COM-USB переходник (он внутри устройства стоит). Случайным образом при загрузке системы для устройства может быть загружен модуль pl2303 и соответственно оно станет /dev/ttyUSB*, а может загрузиться cdc_acm и оно станет /dev/ttyACM*

    Внести один из этих модулей в blacklist я не могу, т.к. есть другие устройства, которые их используют. Как заставить ядро для USB устройства с конкретным ID грузить конкретный драйвер?
    • 0
      >как ядро определяет, для какого устройства какой модуль грузить?

      habrahabr.ru/blogs/linux/112527/

      >Случайным образом при загрузке системы

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

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

      еще можно разрулить на уровне имени устройства, написав хитрое правило udev.
  • 0
    lsmod покажет список загруженных модулей, modinfo — возможные опции загрузки конкретного модуля.

    А вот есть ли способ узнать, с какими опциями загружен модуль? Это, пожалуй, даже чаще востребовано, чем modinfo, особенно с ноутбуками с snd-hda-intel с их вечными проблемами с входом для наушников.

    А также интересно, можно ли «на лету» изменить параметры загрузки модуля без его выгрузки (иногда зависимости не дают выгрузить) и повторной загрузки.

    Если эти два вопроса имеют решение, хорошо было бы его увидеть в самой статье для полноты картины — к остальному содержимому претензий нет.
    • 0
      Довольно интересные вопросы… так чтобы списком получать я даже не знаю. Можно проверять значение параметра через файловую систему /sys: в каталоге "/sys/modules/<module_name>/parameters" сидят файлы, названные по имени параметра, данные в них — значения параметра.
      А по поводу изменения параметров на лету никогда не сталкивался с проблемой, но поищу информацию. Спасибо за интересные вопросы.
      • 0
        echo $VALUE >/sys/modules/<module_name>/parameters/$PARAMETER не проканает?
        • 0
          Не, уже попробовал) Permission Denied говорит…
  • 0
    Я всегда считал что некоторые вещи очень желательно делать модульными при сборке ядра, например — модуль wifi-карты и bluetooth. Все ради того, чтобы выгружать модуль ненужного в данный момент устройства и, типа, экономить электричество. Раскройте же глаза на правду, о линукс-гуру!)
  • 0
    Отличная статья. Хорошо структурирует знания и дает понимание четкое понимание того, как это все работает :)
    • 0
      Спасиб)
  • 0
    Спасибо, теперь я хоть что-то понимаю в ядре линукса =)
  • 0
    modprobe -l
    • 0
      Случайно отправилось :(

      >> find /lib/modules/`uname -r` -name ‘*.ko’
      modprobe -l не проще?
  • 0
    круто, спасибо за статью — после нее я наконец-то перестал бояться ядер/модулей)

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