Block device drivers linux

Драйверы устройств в Linux

Часть 15: Диск в оперативной памяти — экспериментируем с драйверами блочных устройств

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

После вкусного обеда изучение теории тянет слушателей в сон. Поэтому давайте сразу начнем с кода.

Исходный код диска в оперативной памяти

Давайте создадим директорий DiskOnRAM, в котором будут находиться следующие шесть файлов: три с исходными кодами на языке С, два с заголовочными файлами для С и один файл Makefile.

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

Как и обычно, с помощью команды make соберем драйвер «диска в оперативной памяти»( dor.ko ), объединив вместе три файла на С. Чтобы увидеть, как это делается, смотрите файл Makefile.

Чтобы привести в исходное состояние файлы, используемые при сборке, выполните, как и обычно, команду make clean .

Как только сборка будет завершена, выполните следующие три эксперимента (смотрите рис.1 — 3).

Рис.1: Экспериментируем с драйвером «диска в оперативной памяти»

Рис.2: xxd показывает первоначальные данные, находящиеся в первом разделе (/dev/rb1)

Рис.3: Форматирование третьего раздела (/dev/rb3)

Пожалуйста, обратите внимание, что все эти действия нужно выполнять с привилегиями пользователя root:

  • Загрузите драйвер dor.ko с помощью команды insmod . В результате будут созданы файлы блочного устройства, соответствующие диску размером в 512 Кбайтов, расположенному в оперативной памяти.
  • Проверьте файлы автоматически созданного блочного устройства ( /dev/rb* ). /dev/rb — это весь диск, размер которого равен 512 Кбайтов. rb1 , rb2 и rb3 являются первичными разделами, причем rb2 является расширенным разделом, в котором расположены логические разделы rb5 , rb6 и rb7 .
  • Прочитайте весь диск с помощью утилиты дампа диска dd .
  • Опять с помощью утилиты dd заполните нулями первый сектор первого раздела ( /dev/rb1 )
  • С помощью команды cat запишите некоторый текст в первый раздел диска ( /dev/rb1 ).
  • С помощью утилиты xxd отобразите начальное содержимое первого раздела ( /dev/rb1 ).
  • С помощью команды fdis k отобразите информацию о разделах диска. Результат работы команды fdisk показан на рис.3.
  • С помощью mkfs.vfat (рис.3) выполните быстрое форматирование третьего первичного раздела ( /dev/rb3 ) — создайте файловую систему vfat (точно такую, как у нашего флеш устройства).
  • Смонтируйте вновь отформатированный раздел с помощью команды mount , например, в точке монтирования /mnt (рис.3).
  • Утилита df , отображающая использования дисков, теперь должна показать, что этот раздел смонтирован в точке /mnt (рис.3). Вы можете пойти дальше и сохранить на диске данные, но не забудьте, что этот диск находится в оперативной памяти, т. е. после выключения компьютера файлы на нем не сохраняются.
  • После того, как вы размонтируете раздел с помощью команды umount /mnt , выгрузите драйвер с помощью команды rmmod dor .

Теперь давайте изучим правила

Мы всего лишь попробовали воспользоваться диском, созданным в оперативной памяти (disk on RAM — DOR), но при этом фактически не знаем правил, как это все происходит. Так что давайте попытаемся разобраться во всех подробностях этого процесса. В каждом из трех файлов .c представлена определенная часть драйвера; в ram_device.c и ram_device.h абстрагированы основные операции с памятью, такие vmalloc/vfree , memcpy и т. п., с помощью которых реализованы API таких операции, как инициализация/очистка, чтение/запись и т.д.

В partition.c и partition.h реализованы функции, эмулирующие в DOR работу с таблицами различных разделов. Чтобы разобраться с особенностями организации разделов, вспомните о том, что рассказывалось на утреннем занятии (т.е. в предыдущей статье).

С помощью этого кода предоставляется информация о разделах, например, номер раздела, его тип, размер и т. п., которая отображается с помощью функции fdisk . Файл ram_block.c является основой реализации блочного драйвера, позволяющей отображать DOR в пользовательском пространстве в виде файлов блочного устройства ( /dev/rb* ). Другими словами, с помощью четырех из пяти файлов ram_device.* и partition.* формируется горизонтальный слой драйвер устройства, а с помощью файла ram_block.c формируется вертикальный (блочный) слой драйвера устройства. Итак, давайте разберемся в деталях.

Основы драйверов блочных устройств

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

  • Используются файлы устройств
  • Есть старшие и младшие номера
  • Используются операции с файлами устройств
  • Применяется концепция регистрации устройств
Читайте также:  Спящий режим linux debian

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

Тем не менее, они, безусловно, не идентичны. Основные различия заключаются в следующем:

  • Абстракция блочно-ориентированных устройств отличается от абстракции байт-ориентированных устройств
  • Чтобы обеспечить оптимальные производительность, блочные драйверы должны вызываться планировщиками ввода/вывода. Сравните это с символьными драйверами, которые должны использоваться внутри виртуальной файловой системы VFS.
  • Чтобы обеспечить эффективный доступ к данным, блочные драйверы проектируются так, что их можно интегрировать с механизмом кэш-буфера Linux. Символьные драйверы являются драйверами непосредственного доступа, которые напрямую получают доступ к аппаратному обеспечению.

И все это является причиной различий в реализации. Давайте проанализируем ключевые фрагменты кода из файла ram_block.c , и начнем с конструктора драйвера rb_init() .

Первым шагом будет регистрация 8-битного (блочного) старшего номера (что неявно означает регистрацию всех 256 8-битных младших номеров, связанных с ним). Функция для этого выглядит следующим образом:

Здесь major является старшим номером, который должен регистроваться, а name является регистрационной меткой, отображаемой в директории /proc/devices . Интересно, что если в качестве первого параметра major передается 0, то функция register_blkdev() пытается выделить и зарегистрировать произвольный свободный старший номер; в случае успеха происходит возврат выделенного старшего номера. Соответствующая функция отмены регистрации выглядит следующим образом:

Прототипы обеих этих функций находятся в
.

На втором шаге в структуру block_device_operations (прототип в
) заносятся операции для работы с файлами устройств с зарегистрированными старшими номерами.

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

  • Очередь запросов для запросов на чтение/запись
  • Механизм блокировки запросов для защиты от одновременного доступа
  • Функция запросов для обработки запросов из очереди запросов

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

  • Префикс имени файла устройства, который, как правило, называется именем диска disk_name ( rb для драйвера dor )
  • Начальный младший номер для файлов устройств, который, как правило, называется первым младшим номером first_minor .

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

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

Все эти операции регистрируются в структуре struct gendisk с помощью следующей функции:

Соответствующая функция удаления delete выглядит следующим образом:

Прежде, чем использовать функцию добавления диска add_disk() , нужно либо непосредственно, либо с помощью различных макросов/функций, таких как set_capacity() , инициальзировать различные поля структуры struct gendisk . Как минимум, нужно непосредственно инициализировать следующие поля — major , first_minor , fops , queue , disk_name . И даже перед тем, как эти поля будут инициализированы, нужно будет с помощью следующей функции выделить память под структуру struct gendisk :

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

Прототипы всех этих функций имеются в
.

Очередь запросов и функция запросов

Перед тем, как использовать функцию add_disk() , нужно также инициализировать очередь запросов и и занести ее в структуру struct gendisk . Инициализация очереди запросов осуществляется с помощью следующей функции:

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

Функция запроса (обработки) должна быть определена с помощью следующего прототипа:

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

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

Ниже приведен типичный пример обработки запроса, демонстрируемый на примере функции rb_request() из файла ram_block.c :

Запросы и их обработка

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

Читайте также:  Samsung clp 360 драйвер windows 10

Макрос rq_for_each_segment() является специализированным, в котором с помощью команды iter происходит обращение к струтуре struct request (req) и при каждой итерации выполняется извлечение конкретных данных из буфера в структуру struct bio_vec ( bv: basic input/output vector ). А затем, когда на каждой итерации передача данных будет завершена, для выполнения соответствующей передачи данных будет использован, в зависимости от типа операции, один из следующих интерфейсов API из файла ram_device.c :

Код функции rb_transfer() смотрите полностью в файле ram_block.c .

Подведем итог

Итак, благодаря тому, что мы рассмотрели методику создания жесткого диска и поэкспериментировали с его разделами, форматированием и другими низкоуровневыми операциями, выполняемыми на жестком диске, мы, на самом деле, изучили интересные драйверы блочных устройств. Спасибо за внимание. Теперь можно задавать вопросы — пожалуйста, не стесняйтесь, задавайте ваши вопросы и комментируйте.

Источник

Block device driver

Есть задача изготовления драйвера блочного устройства. В нём нужно делать I/O к другим дископодобным девайсам . Гуглил, читал LDD, LDD3 . но видимо что-то всё равно упускаю .

Есть тут кто имеет предметный опыт написания ? В качестве первого вопроса: после инсталляции модуля в dmesg появляется «cut -here» секция, что это означает ? Что-то пошло не так, или это информационный блок ?

Следующая проблема связана с I/O из модуля, похоже что нет возврата из vfs_read/vfs_write , сужу по dmesg, не выходим на «Checking read/write status».

Буду благодарен за любую помощь.

после инсталляции модуля в dmesg появляется «cut -here» секция, что это означает ? Что-то пошло не так, или это информационный блок ?

Там же написано:

Это предупреждение. Хочешь узнать больше — смотришь block/genhd.c:596 ядра, которое у тебя используется.

Следующая проблема связана с I/O из модуля, похоже что нет возврата из vfs_read/vfs_write

Там вообще всё плохо — коды возврата из copy_*_user не проверяются, set_fs/get_fs/vfs_. Модуль блочного устройства обращается к VFS?

genhd.c — ох, просмотрел в этой вермишели . Спсб.

По copy_to/from_usr — добавил проверки . Спсб за наколку. Ещё дополнительно обклал их set_fs() . и вообще всё заработало. 🙂

Ещё дополнительно обклал их set_fs() . и вообще всё заработало. 🙂

Из того, что я написал, самое полезное — вопрос «Модуль блочного устройства обращается к VFS?». Это намек на то, что так делать нельзя. То, что тебе приходится пользоваться get_fs/set_fs (я ими за 15 лет работы не пользовался ни разу) — это признак того, что ты всё делаешь не так.

Тогда , пожалуйста конкретнее. Использование vfs_read/write — единственно найденный подтверждённый. Есть другие ? Я весь внимания.

Разумеется я бы предпочёл миновать VFS и прочая, но напрямую работать с дривером целевого блочного устройства. Есть такой способ ?

Использование vfs_read/write — единственно найденный подтверждённый

Подтвержденный кем? Не слушай его больше.

Разумеется я бы предпочёл миновать VFS и прочая,

«Предпочел»? Ты _обязан_ это сделать.

но напрямую работать с дривером целевого блочного устройства.

«Целевого»? Я думал, ты его сам пишешь.

Конечно. У драйвера блочного устройства есть интерфейс. Описан в LDD3: https://static.lwn.net/images/pdf/LDD3/ch16.pdf (да, я знаю, что книга устарела, но для понимания концепции она годится). Как ты мог это упустить при чтении, я просто не понимаю.

Дружище, спасибо за диалог конечно, но . не скупись. Главу 16 я прочел. Книжка протухла спустя неделю после выпуска небось, и методы описанные там не работают. Шняга а-ля sbull без перепилки не заработает.

Что до того, что я пишу, я вроде написал что пишу, но могу ещё раз — делаю дривер , который переадресует преодобработанные запросы к блочному устройству.

Как получить доступ к интерфейсу? Вот каков был вопрос.

Шняга а-ля sbull без перепилки не заработает.

Если ты хочешь научиться писать блочные драйверы, перепиливание sbull под новое ядро — отличное упражнение.

Это слово пишется «драйвер».

«Если ты хочешь научиться писать блочные драйверы, перепиливание sbull под новое ядро — отличное упражнение.»

Ну это, то что можно пользовать заместо vfs_рутин.

Если ты хочешь научиться писать блочные драйверы, перепиливание sbull под новое ядро — отличное упражнение.

Ну это, то что можно пользовать заместо vfs_рутин.

Главы 12 и 13. И глава 16, конечно.

Читайте также:  Linux fluxbox gnome terminal

USB и PCI мне не нужны.

Интерфейсом дривера обычно обзываеся таки таблица рутин, ну там что-то типа open/read/write/ioctl .

Если я не могу добраться до интерфейса драйвера скази или сата, что бы прям туда сформировать запрос — то я пока не нашёл метода (не считая fs_read/write ). Скорей всего есть такой способ.

А что тебе нужно? Чем для «изготовления драйвера блочного устройства» не подходит просто запись в память, как в sbull?

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

Если тебе нужны SCSI или ATA, то интерфейсы к ним (специфичные для обоих случаев) описаны в документации к ядру.

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

sbull — демонстрирует интерфейс (комплект рутин/мэтодов) к блочному устройству , как он видиться из верхнего уровня, как учебная задача это проходиться за 2 академчаса с контрольной работой (пиво инклюдед). sbull — не демонстрирует логики, с помошью которой, не используя штатный I/O интерфейс а-ля sys_read/write (или vfs_read/write) взять req/bio и поставить его в очередь запросов например к девайсу /dev/sdb, используя же только интерфейс блочного устройства.

Что бы было понятнее, есть базовый интерфейс, он описывается такой шнягой: struct block_device_operations

Всё было бы замечательно, если бы в этой байде (block_device_ops) был явным образом вкорячен метод а-ля «qio» (Enqueue I/O — название выдуманное), тогда остаётся найти через какой-нибудь API способ добывать этот интерфейс для нужного блок-девайса и, используя, qio — перепуливать req/bio в нужный девайс, минуя такую хрень как копирование буферов юзер/кернел space и прочая.

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

PS:Ты речь ведёшь пр интерфейсы к контроллерам (а-ля SCSI, ATA, IDE, DSSI, VAXBI, FC и ещё хрен знает какие) — они мне не треба. 🙂

учебная задача это проходиться за 2 академчаса с контрольной работой

А. Ну тогда говнокод, который ты сделал, вполне подходит. Думаю, тот, кто выделил на это 2 академических часа, вовсе не предполагал нормальной реализации (через очереди запросов).

Перестань коверкать язык. Такой текст — неуважение к тому, кто пытается его читать.

Я готов посмотреть твой говнокод. Дальше, не учи уважению , коли тыкаешь не знакомуму человек.

У тебя есть что по теме ?

«вовсе не предполагал нормальной реализации (через очереди запросов).» — ты не понимаешь о чём говоришь.

Спасибо за участие. Я бы послушал кого-то поопытнее.

Дальше, не учи уважению , коли тыкаешь не знакомуму человек.

Это Интернет и я намного старше. Не вижу причин обращаться к тебе на «вы» и не ожидаю такого обращения к себе.

Если ты хотел спросить «ты писал драйверы для layered блочных устройств», то нет. Но если бы надо было — написал бы.

Да вроде бы не за что.

Намного старше кого? А даже если так, то чё ? Сходу ментора включаешь ? Рановато, ответы пока твои были не впопад.

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

«Да вроде бы не за что.» — я знаю, только из вежливости.

Я пишу блочные драйвера иногда. tailgunner всё правильно говорит. (и насчёт коверканья языка я с ним полностью согласен).

Ты вообще чего хочешь то? Какое ядро? Почитай какой-нибудь драйвер блочный если не хочешь устаревшие книжки читать. Почитай null_blk или scsi_debug напирмер.

Наваял говнокод, показал людям, почему теперь удивляешься, что тебя в него тыкают более опытные товарищи?

Если никогда раньше блочных драйверов не писал, то в ядре полно примеров в каталоге drivers/block. Там ничего сложного, в общем-то.

чувак, ну по всем же видно, что ты невероятно крут и рюхаст для этого форума, ну не проверяешь ты код возврата функций (прости, когда хотел обозвать их «рутинами» чуть не блеванул), дык то вообще какой-то отщепенец придумал в далеком 1981-ом, настоящие мужыки давно уже так не делают — на дворе-то XXI-й век, в конце концов.

из уважения к твоему интеллекту, не буду тыкать тебя носом, как маленького котенка в его гавно, но спасибо, что напомнил одно старое выражение, «не мале, а всралося» 🙂

Источник

Оцените статью