Writing drivers for KolibriOS/ru: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
(Fixes for <asm> tags - replaced with <syntaxhighlight>) |
||
Line 15: | Line 15: | ||
Специально для желающих написать свой драйвер предоставляется каркас драйвера. Он находится в svn-репозитории вместе с ядром, точнее, в папке {{#svn:/kernel/trunk/drivers|svn://kolibrios.org/kernel/trunk/drivers}}. В исходниках дистрибутива [[Version:0.6.5.0|0.6.5.0]] этот путь соответствует папке {{#svn:/kernel/trunk/drivers|kernel/drivers}}. Ну что же, давайте посмотрим ({{#svn:/kernel/trunk/drivers/sceletone.asm|sceletone.asm из #450|450}}): | Специально для желающих написать свой драйвер предоставляется каркас драйвера. Он находится в svn-репозитории вместе с ядром, точнее, в папке {{#svn:/kernel/trunk/drivers|svn://kolibrios.org/kernel/trunk/drivers}}. В исходниках дистрибутива [[Version:0.6.5.0|0.6.5.0]] этот путь соответствует папке {{#svn:/kernel/trunk/drivers|kernel/drivers}}. Ну что же, давайте посмотрим ({{#svn:/kernel/trunk/drivers/sceletone.asm|sceletone.asm из #450|450}}): | ||
<asm> | <syntaxhighlight lang="asm"> | ||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
;; ;; | ;; ;; | ||
Line 29: | Line 29: | ||
include 'proc32.inc' | include 'proc32.inc' | ||
include 'imports.inc' | include 'imports.inc' | ||
</ | </syntaxhighlight> | ||
Ну, "[http://ru.wikipedia.org/wiki/Copyright Copyright]" - он и есть копирайт, комментарий в следующей строке извещает нас, куда же мы собственно попали, это неинтересно. Дальше мы должны известить компилятор, какой формат мы хотим получить. Драйвера должны иметь формат объектных файлов [http://en.wikipedia.org/wiki/COFF COFF]. Вот это и сказано. Матерное (для кого-то) слово посередине всего лишь означает (не подумайте ничего плохого!), что используется расширение формата [http://en.wikipedia.org/wiki/COFF COFF], введённое [http://ru.wikipedia.org/wiki/Microsoft Microsoft] и позволяющее указывать у секций атрибуты типа "writeable". В общем, так должно быть для всех драйверов. Дальше идёт включение вспомогательных файлов. {{#svn:/kernel/trunk/proc32.inc|proc32.inc}} содержит макросы для определения и вызова стандартных процедур (<tt>proc/endp</tt>, <tt>stdcall/ccall/invoke/cinvoke</tt>, <tt>local</tt> для создания локальных переменных) и находится в той же папке, что и {{#svn:/kernel/trunk/drivers/sceletone.asm|sceletone.asm|450}}. Его можно включать или не включать, макросы оттуда можно использовать или не использовать, в этой статье они используются, чтобы не усложнять восприятие (вообще говоря, при стремлении к максимальной эффективности использование макросов может повредить, но это тема отдельных жарких споров). {{#svn:/kernel/trunk/imports.inc|imports.inc}} содержит объявления для всех экспортируемых функций ядра. Загляните туда, ничего сложного там нет, просто куча стереотипных конструкций. На самом деле (в смысле того, как разрешается импорт при загрузке драйвера) список всех экспортируемых функций и данных ядра находится в файле {{#svn:/kernel/trunk/core/exports.inc|core/exports.inc}} (метка <tt>kernel_export</tt>), так что если вам как программисту ядра вдруг понадобиться что-нибудь своё экспортировать, лезьте туда (ну и {{#svn:/kernel/trunk/imports.inc|imports.inc}} отредактируйте из вежливости к другим). | Ну, "[http://ru.wikipedia.org/wiki/Copyright Copyright]" - он и есть копирайт, комментарий в следующей строке извещает нас, куда же мы собственно попали, это неинтересно. Дальше мы должны известить компилятор, какой формат мы хотим получить. Драйвера должны иметь формат объектных файлов [http://en.wikipedia.org/wiki/COFF COFF]. Вот это и сказано. Матерное (для кого-то) слово посередине всего лишь означает (не подумайте ничего плохого!), что используется расширение формата [http://en.wikipedia.org/wiki/COFF COFF], введённое [http://ru.wikipedia.org/wiki/Microsoft Microsoft] и позволяющее указывать у секций атрибуты типа "writeable". В общем, так должно быть для всех драйверов. Дальше идёт включение вспомогательных файлов. {{#svn:/kernel/trunk/proc32.inc|proc32.inc}} содержит макросы для определения и вызова стандартных процедур (<tt>proc/endp</tt>, <tt>stdcall/ccall/invoke/cinvoke</tt>, <tt>local</tt> для создания локальных переменных) и находится в той же папке, что и {{#svn:/kernel/trunk/drivers/sceletone.asm|sceletone.asm|450}}. Его можно включать или не включать, макросы оттуда можно использовать или не использовать, в этой статье они используются, чтобы не усложнять восприятие (вообще говоря, при стремлении к максимальной эффективности использование макросов может повредить, но это тема отдельных жарких споров). {{#svn:/kernel/trunk/imports.inc|imports.inc}} содержит объявления для всех экспортируемых функций ядра. Загляните туда, ничего сложного там нет, просто куча стереотипных конструкций. На самом деле (в смысле того, как разрешается импорт при загрузке драйвера) список всех экспортируемых функций и данных ядра находится в файле {{#svn:/kernel/trunk/core/exports.inc|core/exports.inc}} (метка <tt>kernel_export</tt>), так что если вам как программисту ядра вдруг понадобиться что-нибудь своё экспортировать, лезьте туда (ну и {{#svn:/kernel/trunk/imports.inc|imports.inc}} отредактируйте из вежливости к другим). | ||
Line 37: | Line 37: | ||
Ну что же, идём дальше: | Ну что же, идём дальше: | ||
<asm> | <syntaxhighlight lang="asm"> | ||
OS_BASE equ 0; | OS_BASE equ 0; | ||
new_app_base equ 0x60400000 | new_app_base equ 0x60400000 | ||
PROC_BASE equ OS_BASE+0x0080000 | PROC_BASE equ OS_BASE+0x0080000 | ||
</ | </syntaxhighlight> | ||
Константа <tt>OS_BASE</tt> означает адрес загрузки ядра. Для "официального" ядра (в частности, в [[Version:0.6.5.0|0.6.5.0]]) это 0, для "плоского" ядра это 0x80000000 - вот вам ещё одна несовместимость. Имейте в виду, что в будущем (возможно даже, что в скором) "плоское" ядро станет (хотя, может быть, и не станет - мало ли что?) "официальным", так что не рассчитывайте, что <tt>OS_BASE</tt> всегда будет нулём. Константа <tt>new_app_base</tt> означает линейный адрес, по которому загружаются приложения: все приложения загружаются по одному и тому же адресу, наложения не происходит, поскольку каждый процесс имеет свою таблицу страниц, при этом каждое приложение искренне уверено, что загружено по нулевому адресу - это достигается за счёт сегментной адресации - в 3-кольце селекторы <tt>cs/ds/es/ss</tt> имеют базу <tt>new_app_base</tt>, а в 0-кольце (в ядре и драйверах) - нулевую базу. Таким образом, для перевода адреса в приложении в указатель ядра нужно прибавить к нему <tt>new_app_base</tt> (если непонятно, почему, примите это как факт). С <tt>new_app_base</tt> несовместимость ещё хуже: в [[Version:0.6.5.0|0.6.5.0]] она равна 0x60400000, в {{#svn_rev:450}} - уже 0x80000000, в "плоском" ядре просто 0 (собственно, потому оно и "плоское", что использует плоскую модель памяти). Как узнать конкретные значения <tt>OS_BASE</tt> и <tt>new_app_base</tt> для данного ядра? Очень просто - они прописаны именно под такими именами в <tt>const.inc</tt> (из исходников ядра), так что достаточно найти их там. Третья из определяемых констант нужна для отвода глаз, в данном случае она не используется. Кстати, карта памяти Колибри располагается в {{#svn:/kernel/trunk/memmap.inc|memmap.inc}} из исходников ядра. | Константа <tt>OS_BASE</tt> означает адрес загрузки ядра. Для "официального" ядра (в частности, в [[Version:0.6.5.0|0.6.5.0]]) это 0, для "плоского" ядра это 0x80000000 - вот вам ещё одна несовместимость. Имейте в виду, что в будущем (возможно даже, что в скором) "плоское" ядро станет (хотя, может быть, и не станет - мало ли что?) "официальным", так что не рассчитывайте, что <tt>OS_BASE</tt> всегда будет нулём. Константа <tt>new_app_base</tt> означает линейный адрес, по которому загружаются приложения: все приложения загружаются по одному и тому же адресу, наложения не происходит, поскольку каждый процесс имеет свою таблицу страниц, при этом каждое приложение искренне уверено, что загружено по нулевому адресу - это достигается за счёт сегментной адресации - в 3-кольце селекторы <tt>cs/ds/es/ss</tt> имеют базу <tt>new_app_base</tt>, а в 0-кольце (в ядре и драйверах) - нулевую базу. Таким образом, для перевода адреса в приложении в указатель ядра нужно прибавить к нему <tt>new_app_base</tt> (если непонятно, почему, примите это как факт). С <tt>new_app_base</tt> несовместимость ещё хуже: в [[Version:0.6.5.0|0.6.5.0]] она равна 0x60400000, в {{#svn_rev:450}} - уже 0x80000000, в "плоском" ядре просто 0 (собственно, потому оно и "плоское", что использует плоскую модель памяти). Как узнать конкретные значения <tt>OS_BASE</tt> и <tt>new_app_base</tt> для данного ядра? Очень просто - они прописаны именно под такими именами в <tt>const.inc</tt> (из исходников ядра), так что достаточно найти их там. Третья из определяемых констант нужна для отвода глаз, в данном случае она не используется. Кстати, карта памяти Колибри располагается в {{#svn:/kernel/trunk/memmap.inc|memmap.inc}} из исходников ядра. | ||
Line 47: | Line 47: | ||
Едем дальше: | Едем дальше: | ||
<asm> | <syntaxhighlight lang="asm"> | ||
struc IOCTL | struc IOCTL | ||
{ .handle dd ? | { .handle dd ? | ||
Line 60: | Line 60: | ||
IOCTL IOCTL | IOCTL IOCTL | ||
end virtual | end virtual | ||
</ | </syntaxhighlight> | ||
Это просто объявление структуры (махинации с <tt>virtual</tt> - стандарт для FASM). | Это просто объявление структуры (махинации с <tt>virtual</tt> - стандарт для FASM). | ||
<asm> | <syntaxhighlight lang="asm"> | ||
public START | public START | ||
public service_proc | public service_proc | ||
public version | public version | ||
</ | </syntaxhighlight> | ||
Выше мы импортировали из ядра нужные нам функции ({{#svn:/kernel/trunk/imports.inc|imports.inc}}). А теперь мы даём ядру знать о себе. Начнём с конца. Переменная <tt>version</tt>, объявленная гораздо ниже в тексте, - это... нет, не версия драйвера, как можно было бы подумать! Это версия драйверного интерфейса, которую этот драйвер понимает. Ещё точнее, в одном <tt>dword</tt> закодированы два кода версии. Младшее слово в текущей реализации ядра не проверяется никак, но туда следует помещать номер версии интерфейса, "родной" для драйвера. Старшее слово означает минимальную версию, с которой драйвер ещё может работать. Это слово должно лежать на отрезке от <tt>DRV_COMPAT</tt> до <tt>DRV_CURRENT</tt>, константы определены в исходниках ядра в {{#svn:/kernel/trunk/core/dll.inc|core/dll.inc}}, в [[Version:0.6.5.0|0.6.5.0]] обе эти константы равны 3, в {{#svn_rev:450}} интерфейс уже изменился и теперь обе константы равны 4. Для чего нужны все эти сложности? Дело в следующем. Изменения в драйверной подсистеме могут быть следующих типов: полная или частичная переделка одной из базовых концепций; удаление одной из экспортируемых функций ядра; модификация функции (вчера функция принимала аргумент в стеке, а сегодня для эффективности аргумент передаётся в регистре; или добавился ещё какой-то аргумент; или изменился смысл аргументов и т.п.); добавление функции. В первом и третьем случае, собственно, ничего не поделаешь, драйверы переписывать надо. Второй тоже приводит к несовместимости. Но обидно перекомпилировать все драйвера только из-за того, что появилась новая функция, без которой эти драйвера прекрасно обходились. Вот и поддерживается загрузка "устаревших, но не слишком" драйверов. | Выше мы импортировали из ядра нужные нам функции ({{#svn:/kernel/trunk/imports.inc|imports.inc}}). А теперь мы даём ядру знать о себе. Начнём с конца. Переменная <tt>version</tt>, объявленная гораздо ниже в тексте, - это... нет, не версия драйвера, как можно было бы подумать! Это версия драйверного интерфейса, которую этот драйвер понимает. Ещё точнее, в одном <tt>dword</tt> закодированы два кода версии. Младшее слово в текущей реализации ядра не проверяется никак, но туда следует помещать номер версии интерфейса, "родной" для драйвера. Старшее слово означает минимальную версию, с которой драйвер ещё может работать. Это слово должно лежать на отрезке от <tt>DRV_COMPAT</tt> до <tt>DRV_CURRENT</tt>, константы определены в исходниках ядра в {{#svn:/kernel/trunk/core/dll.inc|core/dll.inc}}, в [[Version:0.6.5.0|0.6.5.0]] обе эти константы равны 3, в {{#svn_rev:450}} интерфейс уже изменился и теперь обе константы равны 4. Для чего нужны все эти сложности? Дело в следующем. Изменения в драйверной подсистеме могут быть следующих типов: полная или частичная переделка одной из базовых концепций; удаление одной из экспортируемых функций ядра; модификация функции (вчера функция принимала аргумент в стеке, а сегодня для эффективности аргумент передаётся в регистре; или добавился ещё какой-то аргумент; или изменился смысл аргументов и т.п.); добавление функции. В первом и третьем случае, собственно, ничего не поделаешь, драйверы переписывать надо. Второй тоже приводит к несовместимости. Но обидно перекомпилировать все драйвера только из-за того, что появилась новая функция, без которой эти драйвера прекрасно обходились. Вот и поддерживается загрузка "устаревших, но не слишком" драйверов. | ||
<asm> | <syntaxhighlight lang="asm"> | ||
version dd 0x00030003 | version dd 0x00030003 | ||
</ | </syntaxhighlight> | ||
Каркас драйвера рассчитан на... версию 3, т.е. с текущим ядром он не пойдёт! Дело в том, что этот каркас в общем-то не обновлялся (если не считать копирайта) с [[Version:0.6.5.0|0.6.5.0]], так что <tt>new_app_base</tt> и <tt>version</tt> остались старые. Попутно отмечу, что старшее слово - это первая тройка, а младшее - вторая в силу обратного расположения байт в слове и слов в двойном слове (вообще-то я уверен, что вы и так это знаете, но для очистки совести...) | Каркас драйвера рассчитан на... версию 3, т.е. с текущим ядром он не пойдёт! Дело в том, что этот каркас в общем-то не обновлялся (если не считать копирайта) с [[Version:0.6.5.0|0.6.5.0]], так что <tt>new_app_base</tt> и <tt>version</tt> остались старые. Попутно отмечу, что старшее слово - это первая тройка, а младшее - вторая в силу обратного расположения байт в слове и слов в двойном слове (вообще-то я уверен, что вы и так это знаете, но для очистки совести...) | ||
Line 84: | Line 84: | ||
Последняя порция констант | Последняя порция констант | ||
<asm> | <syntaxhighlight lang="asm"> | ||
DEBUG equ 1 | DEBUG equ 1 | ||
Line 90: | Line 90: | ||
DRV_EXIT equ -1 | DRV_EXIT equ -1 | ||
STRIDE equ 4 ;size of row in devices table | STRIDE equ 4 ;size of row in devices table | ||
</ | </syntaxhighlight> | ||
(из которых первая включает код отладочного вывода в блоках <tt>if DEBUG/end if</tt>, две следующие характеризуют возможные значения аргумента у процедуры <tt>START</tt>, последняя нужна для красоты и ни для чего больше) и мы наконец-то переходим к изучению кода: | (из которых первая включает код отладочного вывода в блоках <tt>if DEBUG/end if</tt>, две следующие характеризуют возможные значения аргумента у процедуры <tt>START</tt>, последняя нужна для красоты и ни для чего больше) и мы наконец-то переходим к изучению кода: | ||
<asm> | <syntaxhighlight lang="asm"> | ||
section '.flat' code readable align 16 | section '.flat' code readable align 16 | ||
</ | </syntaxhighlight> | ||
означает ровно-таки то, что написано; | означает ровно-таки то, что написано; | ||
<asm> | <syntaxhighlight lang="asm"> | ||
proc START stdcall, state:dword | proc START stdcall, state:dword | ||
Line 119: | Line 119: | ||
ret | ret | ||
endp | endp | ||
</ | </syntaxhighlight> | ||
Это код процедуры инициализации/финализации. При загрузке драйвера она вызывается с аргументом <tt>DRV_ENTRY</tt> = 1 и должна вернуть ненулевое значение при успехе. При завершении системы она вызывается с аргументом <tt>DRV_EXIT</tt> = -1. В нашем случае драйвер не работает ни с каким железом, так что ни инициализации никакого железа, ни вообще никакой финализации нет, а есть только минимально необходимые действия, чтобы драйвер считался загруженным, а именно, регистрация. Функция <tt>RegService</tt> экспортируется ядром и принимает два аргумента: имя драйвера (до 16 символов, включая завершающий 0) и указатель на процедуру обработки I/O, а возвращает 0 при неудаче или (ненулевой) зарегистрированный хэндл при успехе. Кстати, как узнать, что делает та или иная экспортируемая функция? Допустим, нам позарез нужно выделить пару страниц памяти ядра. Лезем в исходники ядра, файл {{#svn:/kernel/trunk/core/exports.inc|core/exports.inc}}, просматриваем экспортируемые имена (они осмысленны) и видим <tt>szKernelAlloc</tt>. Пролистываем вниз до метки <tt>kernel_export</tt> и ищем <tt>szKernelAlloc</tt> - обнаруживаем, что ему соответствует процедура <tt>kernel_alloc</tt>. Теперь ищем реализацию <tt>kernel_alloc</tt>, она обнаруживается в {{#svn:/kernel/trunk/core/heap.inc|core/heap.inc}}. Комментариев около функции нет, но есть объявление <tt>proc</tt>, из которого следует, что функция принимает один аргумент <tt>size</tt> типа <tt>dword</tt>. Теперь по названию ясно, что <tt>kernel_alloc</tt> выделяет память ядра в размере, равном единственному аргументу. Причём первые же три строчки кода функции показывают, что размер выравнивается вверх на границу 4096 (т.е. размер одной страницы), следовательно, функция выделяет некоторое целое количество страниц, а размер задаётся в байтах. | Это код процедуры инициализации/финализации. При загрузке драйвера она вызывается с аргументом <tt>DRV_ENTRY</tt> = 1 и должна вернуть ненулевое значение при успехе. При завершении системы она вызывается с аргументом <tt>DRV_EXIT</tt> = -1. В нашем случае драйвер не работает ни с каким железом, так что ни инициализации никакого железа, ни вообще никакой финализации нет, а есть только минимально необходимые действия, чтобы драйвер считался загруженным, а именно, регистрация. Функция <tt>RegService</tt> экспортируется ядром и принимает два аргумента: имя драйвера (до 16 символов, включая завершающий 0) и указатель на процедуру обработки I/O, а возвращает 0 при неудаче или (ненулевой) зарегистрированный хэндл при успехе. Кстати, как узнать, что делает та или иная экспортируемая функция? Допустим, нам позарез нужно выделить пару страниц памяти ядра. Лезем в исходники ядра, файл {{#svn:/kernel/trunk/core/exports.inc|core/exports.inc}}, просматриваем экспортируемые имена (они осмысленны) и видим <tt>szKernelAlloc</tt>. Пролистываем вниз до метки <tt>kernel_export</tt> и ищем <tt>szKernelAlloc</tt> - обнаруживаем, что ему соответствует процедура <tt>kernel_alloc</tt>. Теперь ищем реализацию <tt>kernel_alloc</tt>, она обнаруживается в {{#svn:/kernel/trunk/core/heap.inc|core/heap.inc}}. Комментариев около функции нет, но есть объявление <tt>proc</tt>, из которого следует, что функция принимает один аргумент <tt>size</tt> типа <tt>dword</tt>. Теперь по названию ясно, что <tt>kernel_alloc</tt> выделяет память ядра в размере, равном единственному аргументу. Причём первые же три строчки кода функции показывают, что размер выравнивается вверх на границу 4096 (т.е. размер одной страницы), следовательно, функция выделяет некоторое целое количество страниц, а размер задаётся в байтах. | ||
Line 125: | Line 125: | ||
Дальше идёт процедура обработки запросов <tt>service_proc</tt>: | Дальше идёт процедура обработки запросов <tt>service_proc</tt>: | ||
<asm> | <syntaxhighlight lang="asm"> | ||
handle equ IOCTL.handle | handle equ IOCTL.handle | ||
io_code equ IOCTL.io_code | io_code equ IOCTL.io_code | ||
Line 149: | Line 149: | ||
restore output | restore output | ||
restore out_size | restore out_size | ||
</ | </syntaxhighlight> | ||
Процедура обработки запросов вызывается, когда какой-то внешний код возжаждал общения именно с нашим драйвером. Это может быть как другой драйвер (формально драйвер может вызывать сам себя через механизм I/O, но смысла в этом нет), надыбавший где-то наш хэндл и вызвавший ServiceHandler, или даже само ядро (srv_handler, srv_handlerEx из {{#svn:/kernel/trunk/core/dll.inc|core/dll.inc}}), так и приложение функцией 68.17 (хэндл приложение может добыть при загрузке драйвера функцией 68.16). Нулевое возвращаемое значение означает успех, ненулевое соответствует ошибке. | Процедура обработки запросов вызывается, когда какой-то внешний код возжаждал общения именно с нашим драйвером. Это может быть как другой драйвер (формально драйвер может вызывать сам себя через механизм I/O, но смысла в этом нет), надыбавший где-то наш хэндл и вызвавший ServiceHandler, или даже само ядро (srv_handler, srv_handlerEx из {{#svn:/kernel/trunk/core/dll.inc|core/dll.inc}}), так и приложение функцией 68.17 (хэндл приложение может добыть при загрузке драйвера функцией 68.16). Нулевое возвращаемое значение означает успех, ненулевое соответствует ошибке. | ||
Line 159: | Line 159: | ||
Начало стандартное: | Начало стандартное: | ||
<asm> | <syntaxhighlight lang="asm"> | ||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
;; ;; | ;; ;; | ||
Line 173: | Line 173: | ||
include 'proc32.inc' | include 'proc32.inc' | ||
include 'imports.inc' | include 'imports.inc' | ||
</ | </syntaxhighlight> | ||
Ориентируемся на {{#svn_rev:450}} (для [[Version:0.6.5.0|0.6.5.0]] нужно было бы писать "new_app_base equ 0x60400000"): | Ориентируемся на {{#svn_rev:450}} (для [[Version:0.6.5.0|0.6.5.0]] нужно было бы писать "new_app_base equ 0x60400000"): | ||
<asm> | <syntaxhighlight lang="asm"> | ||
OS_BASE equ 0; | OS_BASE equ 0; | ||
new_app_base equ 0x80000000 | new_app_base equ 0x80000000 | ||
</ | </syntaxhighlight> | ||
Небольшая порция объявлений: | Небольшая порция объявлений: | ||
<asm> | <syntaxhighlight lang="asm"> | ||
struc IOCTL | struc IOCTL | ||
{ .handle dd ? | { .handle dd ? | ||
Line 205: | Line 205: | ||
section '.flat' code readable align 16 | section '.flat' code readable align 16 | ||
</ | </syntaxhighlight> | ||
Пока что всё стереотипно. Но пр |