- Драйверы устройств в Linux
- Часть 15: Диск в оперативной памяти — экспериментируем с драйверами блочных устройств
- Исходный код диска в оперативной памяти
- Теперь давайте изучим правила
- Основы драйверов блочных устройств
- Очередь запросов и функция запросов
- Запросы и их обработка
- Подведем итог
- Block device driver
Драйверы устройств в 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 формируется вертикальный (блочный) слой драйвера устройства. Итак, давайте разберемся в деталях.
Основы драйверов блочных устройств
Концептуально, блочные драйверы очень похожи на драйверы символьных устройств, в частности в отношении следующего:
- Используются файлы устройств
- Есть старшие и младшие номера
- Используются операции с файлами устройств
- Применяется концепция регистрации устройств
Итак, если вы уже знаете, как реализован символьный драйвер, вам будет проще понять реализацию блочных драйверов.
Тем не менее, они, безусловно, не идентичны. Основные различия заключаются в следующем:
- Абстракция блочно-ориентированных устройств отличается от абстракции байт-ориентированных устройств
- Чтобы обеспечить оптимальные производительность, блочные драйверы должны вызываться планировщиками ввода/вывода. Сравните это с символьными драйверами, которые должны использоваться внутри виртуальной файловой системы 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 предоставляются различные макросы:
Макрос 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_
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, конечно.
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-й век, в конце концов.
из уважения к твоему интеллекту, не буду тыкать тебя носом, как маленького котенка в его гавно, но спасибо, что напомнил одно старое выражение, «не мале, а всралося» 🙂
Источник