 | Уровень сложности: средний Сунь Лин, программист-стажер, IBM Ян И, программист-стажер, IBM
16.01.2009 Процесс переноса приложений управления устройствами с
Microsoft® Windows® на Linux® значительно упрощается, если понять
различия в подходах к управлению устройствами в этих операционных системах. Авторы
рассматривают эти различия и приводят пример переноса приложения, созданного
на C/C++.
Если вы разрабатываете приложения управления устройствами для
различных платформ, вы уже знаете, что Windows и Linux используют
различные методы для управления аппаратными устройствами и перенос
приложений с одной платформы на другую может быть связан со значительными
сложностями. В этой статье мы рассматриваем подходы к работе с
устройствами в этих операционных системах, начиная с архитектуры и заканчивая
выполнением системных вызовов, уделяя особое внимание особенностям каждой
из платформ. Также мы приводим пример переноса приложения (на C/C++),
подробно иллюстрируя все возникающие особенности.
Предположения:
В данной статье термин "Windows" относится к Windows 2000
и более поздним версиям, также должна быть установлена среда разработки Microsoft Visual C++®
версии 6 или выше. Для Linux используется ядро версии 2.6, также
должен быть установлен GNU GCC.
Сравнение архитектур управления
аппаратными устройствами
Методы управления устройствами в Windows и Linux различны
Архитектура управления устройствами в
Windows
В Windows связь между пользовательским приложением и драйверами устройств
осуществляется при помощи подсистемы ввода/вывода, которая также предоставляет
инфраструктуру для поддержки драйверов устройств. Драйверы устройств обеспечивают
интерфейс ввода/вывода для конкретных аппаратных устройств (см. рисунок 1).
Рисунок 1. Архитектура управления
устройствами в Windows
При управлении устройствами операции ввода/вывода осуществляются при помощи
IRP (I/O Request Packet - Пакет запроса ввода/вывода). Менеджер ввода/вывода создает
IRP и отправляет его на вершину стека. После этого драйверы устройств получают
местоположение в стеке пакета IRP, содержащего параметры для данного запроса ввода/вывода.
В соответствии с требованиями, указанными в IRP (такими как create,
read, write,
devioctl, cleanup,
или close), каждый драйвер выполняет свое задание при помощи
аппаратных интерфейсов.
Архитектура управления устройствами
в Linux
В Linux архитектура управления устройствами немного другая, и основное отличие
заключается в том, что обычные файлы, директории, устройства и сокеты являются файлами
—в Linux все является файлом. Чтобы обратиться к устройству, ядро Linux отображает
вызов операции с устройством на драйвер устройства при помощи файловой системы.
В Linux не существует менеджера ввода/вывода: все запросы ввода/вывода в начале
поступают в файловую систему (см. рисунок 2).
Рисунок 2. Архитектура управления
устройствами в Linux
Сравнение имен файлов устройств и
имен путей к устройству
С точки зрения разработчика, для управления устройством необходимым
условием является получение дескриптора (handle) для этого устройства. Так как архитектуры
управления устройствами в Windows и Linux различны, то и получение
дескриптора устройства в этих системах осуществляется по-разному.
В общем случае дескриптор устройства определяется по имени
соответствующего драйвера устройства.
В Windows имя драйвера устройства отличается от имени обычного файла, вместо
этого используется так называемое device pathname – имя пути к устройству. Оно
имеет фиксированный формат наподобие \.DeviceName. При программировании на C/C++
строка символов будет иметь следующий вид:
\\.\DeviceName. В коде программы следует использовать строку
\\\\.\\DeviceName.
DeviceName должно соответствовать имени, определенному
в программе драйвера устройства.
Некоторые из имен устройств определены Microsoft и не могут быть изменены (см.
таблицу 1).
Таблица 1. Имена устройств в Windows (x =
0, 1, 2 и т.д.)
| Устройство | Имя пути к устройству (Pathname) |
|---|
| Флоппи-диск | A: B: |
|---|
| Логическая область жесткого диска | C: D: E: . . . |
|---|
| Физический жесткий диск | PhysicalDrivex |
|---|
| CD-ROM, DVD/ROM | CdRomx |
|---|
| Накопитель на магнитной ленте | Tapex |
|---|
| Последовательный порт | COMx |
|---|
Например, в программе на C/C++ для имен пути к устройству (pathname)
используются следующие обозначения:
\\\\.\\PhysicalDrive1,
\\\\.\\CdRom0 и
\\\\.\\Tape0. Более подробную информацию с указанием устройств,
которые не вошли в данный список, можно найти в разделе
Ресурсы в конце данной статьи.
Поскольку в Linux все устройства описываются как файлы, то всем
имеюшимся устройствам соответствуют файлы, находящиеся в директории ./dev.
К числу таких драйверов устройств относятся:
- жесткие диски с интерфейсом IDE (Integrated Drive Electronics), например, /dev/hda и
/dev/hdb
- дисководы CD-ROM, некоторые из них относятся к категории IDE-устройств, другие являются
дисководами CD-RW (CD read/write), которые эмулируются как устройства SCSI (Small
Computer Systems Interface), например, /dev/scd0
- последовательные порты, например, /dev/ttyS0 для последовательного порта COM1, /dev/ttyS1
для последовательного порта COM2 и так далее
- устройства управления, например, /dev/input/mice (мышь) и другие
- принтеры, например, /dev/lp0
Наиболее распространенные файлы устройств можно найти с помощью приведенного выше
описания. Чтобы получить имена других файлов устройств и подробную информацию по
по этим устройствам, воспользуйтесь командой dmesg.
Сравнение основных системных вызовов
Основные системные вызовы для управления устройствами включают следующие операции:
open (открыть), close (закрыть), I/O control (управление вводом/выводом), read/write (чтение/запись) и др.
Соответствие между этими операциями в Windows/Linux показано в Таблице 2.
Таблица 2. Соответствие между функциями управления устройствами
| Windows | Linux |
|---|
| CreateFile | open |
|---|
| CloseHandle | close |
|---|
| DeviceIoControl | ioctl |
|---|
| ReadFile | read |
|---|
| WriteFile | write |
|---|
Давайте более подробно рассмотрим три наиболее часто используемые функции:
create, close и
devioctl.
Открытие и закрытие устройства в
Windows
В Windows для открытия и закрытия устройства используются CreateFile и
CloseHandle. Для открытия устройства используется функция
CreateFile. Эта функция возвращает дескриптор,
который затем может использоваться для доступа к объекту (см. листинг
1).
Листинг 1. Функция CreateFile в Windows
HANDLE CreateFile (LPCTSTR lpFileName, //Имя файла устройства
(Device Pathname)
DWORD dwDesiredAccess, //Способ доступа к объекту (чтение, запись
или одновременно чтение и запись)
DWORD dwShareMode, //Способ совместного использования объекта
(чтение, запись, одновременно чтение и запись или совместное использование запрещено)
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
//Атрибут безопасности, который определяет,
может ли возвращенный дескриптор наследоваться дочерними процессами
DWORD dwCreationDisposition, //Действие, которое предпринимается в случае
существования такого файла или его отсутствия
DWORD dwFlagsAndAttributes, //Атрибуты и флаги, относящиеся к файлу
HANDLE hTemplateFile);
//Дескриптор файла, используемого в качестве шаблона
|
Параметр lpFileName представляет собой имя пути к устройству (device path name),
которое уже было описано ранее. В общем случае, для открытия устройства достаточно задать
dwDesiredAccess равным 0 или
GENERIC_READ|GENERIC_WRITE,
dwShareMode как
FILE_SHARE_READ|FILE_SHARE_WRITE,
dwCreationDisposition как
OPEN_EXISTING,
dwFlagsAndAttributes и
hTemplateFile равными 0 или NULL. Возвращаемый дескриптор
будет использоваться в последующих операциях по управлению устройством.
Чтобы закрыть устройство, используйте функцию
CloseHandle. Параметр
hObject должен быть установлен равным дескриптору, который был возвращен
при открытии устройства:
BOOL WINAPI CloseHandle (HANDLE hObject);.
Открытие и закрытие устройства в
Linux
В Linux для открытия и закрытия устройства используются командыopen и
close. Как уже говорилось ранее, открытие устройства ничем не отличается
от открытия обычного файла. В листинге 2 показан пример использования функции
open для получения дескриптора устройства.
Листинг 2. Функция open в Linux
int open (const char *pathname,
int flags,
mode_t mode);
|
При успешном вызове этой функции в качестве дескриптора файла возвращается
дескриптор файла с наименьшим существующим номером, который еще не открыт в этом процессе.
В случае неудачи возвращается значение -1. Дескриптор файла используется в качестве
дескриптора устройства.
Параметр flags должен включать одно из следующих значений:
O_RDONLY, O_WRONLY,
или O_RDWR. Другие флаги могут использоваться в качестве опций. Аргумент
mode определяет разрешение использования в том случае, когда создается новый файл.
Функция close используется для закрытия устройства в Linux
аналогично закрытию обычного файла: int close(int fd);.
Функция DeviceIoControl в Windows
Функция управления устройством (DeviceIoControl в Windows и
ioctl в Linux) является наиболее часто используемой функцией в задачах
управления устройствами, с ее помощью выполняется обращение к устройствам,
получение информации, отправка команд и обмен данными. Пример
использования функции DeviceIoControl приведен в Листинге 3:
Листинг 3. Использование функции DeviceIoControl в Windows
BOOL DeviceIoControl (HANDLE hDevice,
DWORD dwIoControlCode,
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize,
LPDWORD lpBytesReturned,
LPOVERLAPPED lpOverlapped);
|
Этот системный вызов отправляет указанному устройству код управления и прочие необходимые данные.
Соответствующий драйвер устройства будет затем работать в соответствии с кодом управления,
переданным при помощи параметра
dwIoControlCode. Например,
с помощью IOCTL_DISK_GET_DRIVE_GEOMETRY можно получить параметры,
характеризующие структуру жесткого диска (тип устройства, количество цилиндров,
количество дорожек для каждого цилиндра, количество секторов для каждой дорожки
и так далее). Определения всех кодов управления, заголовочные файлы и подробную информацию
по этой теме можно найти на Web-сайте MSDN (ссылка на этот сайт приводится в разделе
Ресурсы).
Будут ли использоваться буферы ввода/вывода и какова их структура и размер -
все это зависит от самого устройства и от операции
ioctl, которая выполняется процессом. Также они определяются
параметром dwIoControlCode, который указывается в вызове.
Если указатель на операцию overlapped устанавливается равным NULL, то операция
DeviceIoControl будет выполняться блокирующим
(синхронным) способом. В противном случае операция будет выполняться асинхронно.
Операция ioctl в Linux
В Linux для передачи управляющей информации определенному устройству
используется вызов ioctl
—
int ioctl(int fildes, int request, /* arg */ ...);
—. Первым параметром fildes в этом вызове является дескриптор открытого
файла, который был возвращен функцией open() и
который описывает данное устройство.
В отличие от соответствующего системного вызова
DeviceIOControl, в функции
ioctl список входных параметров не является фиксированным.
Он зависит от типа запроса, который выполняется с помощью ioctl, а также от того, что указано в параметре запроса -
аналогом может являться параметр
dwIoControlCode в используемой в Windows функции
DeviceIOControl. Однако при переносе приложений
необходимо уделить внимание выбору правильного параметра request, так как параметр
dwIoControlCode в функции
DeviceIOControl и параметр
request в функции ioctl принимают различные
значения и не существует какого-либо списка, который обеспечивает отображение между
dwIoControlCode/request.
Обычно значение для параметра request выбирается исходя из его определения,
которое содержится в заголовочном файле. Все определения кодов управления
содержатся в файлах /usr/include/{asm,linux}/*.h.
Параметр arg используется для передачи подробной информации,
относящейся к команде, которая необходима данному устройству для выполнения
заданной операции. Тип данных для arg зависит от выполняемого
запроса на управление. Мы можем использовать данный аргумент как для отправки
подробной информации, связанной с данной командой, так и для получения
возвращаемых данных.
Пример переноса приложения
Давайте рассмотрим процесс переноса приложения с Windows на Linux.
Данный пример занимается чтением журнала SMART с главного жесткого диска (IDE)
персонального компьютера.
Шаг 1. Определение типа устройства
Как мы уже знаем, в Linux любое устройство рассматривается как файл. На первом этапе
необходимо понять, какое имя файла в Linux соответствует данному устройству. Только
используя соответствующее имя файла мы сможем получить дескриптор устройства,
который необходим для дальнейшего управления устройством.
В нашем примере объектом управления является жесткий диск с интерфейсом IDE.
В Linux такие устройства описываются как /dev/hda, /dev/hdb и т.д. В первоначальном
приложении (для Windows) имя пути к устройству (device path name) для жесткого диска
было следующим: \\\\.\\PhysicalDrive0. В Linux этому устройству соответствует имя /dev/hda.
Шаг 2. Изменяем заголовочные файлы
Необходимо заменить включаемые при помощи директивы #include заголовочные
файлы их аналогамии в Linux (см. Таблицу 3):
Таблица 3. Заголовочные файлы, включаемые при помощи #include
| Windows | Linux |
|---|
| #include <windows.h> | #include <sys/types.h> #include
<sys/stat.h> #include <fcntl.h>
|
|---|
| #include <devioctl.h> | #include <sys/ioctl.h> |
|---|
| #include <ntddscsi.h> | #include <linux/hdreg.h> |
|---|
windows.h используется для функций, отвечающих
за открытие и закрытие устройства (CreateFile и
CloseHandle). Соответственно, в Linux необходимо
включить заголовочные файлы, описывающие функции open() и
close() - это файлы
sys/types.h, sys/stat.h, and fcntl.h.
devioctl.h в Windows используется для функции
DeviceIoControl, мы заменяем его файлом
sys/ioctl.h, чтобы получить возможность работать с функцией
ioctl .
В заголовочном файле ntddscsi.h (он относится к
DDK) определяется набор кодов управления, которые служат
для целей управления устройством. Так как в нашем примере мы
будем работать только с жестким диском (интерфейс IDE), то в программе
для Linux нам необходимо добавить файл linux/hdreg.h.
В других случаях необходимо убедиться, что включены заголовочные файлы
со всеми необходимыми кодами управления. Например, если вместо жесткого
диска необходимо обратиться к CD-ROM, то вместо указанного выше файла
следует включить файл linux/cdrom.h.
Шаг 3. Изменить функции и
параметры
Теперь давайте более подробно рассмотрим код. В листинге 4 приводится
подробная информация по командам.
Листинг 4. Подробная информация по командам
unsigned char cmdBuff[7];
cmdBuff[0] = SMART_READ_LOG; // используется для определения SMART-команд
cmdBuff[1] = 1; // регистр счетчика секторов IDE
cmdBuff[2] = 1; // регистр номера сектора IDE
cmdBuff[3] = SMART_CYL_LOW; // нижнее значение для номера цилиндра IDE
cmdBuff[4] = SMART_CYL_HI; // верхнее значение для номера цилиндра IDE
cmdBuff[5] = 0xA0 | (((Dev->Id-1) & 1) * 16); // регистр диска/головки IDE
cmdBuff[6] = SMART_CMD; // действительная команда IDE
|
Информация о командах взята из спецификации по командам ATA. Так как для переноса
данного кода на Linux не требуется какой-то дополнительной информации, то переходим
далее.
Код программы, который приводится в Листинге 5, открывает в Windows основной жесткий диск.
Листинг 5. Открытие основного жесткого диска в Windows
HANDLE devHandle = CreateFile("\\\\.\\PhysicalDrive0", //pathname (имя пути к устройству)
GENERIC_WRITE|GENERIC_READ, //Access Mode (режим доступа)
FILE_SHARE_READ|FILE_SHARE_WRITE, //Sharing Mode (режим совместного доступа)
NULL,OPEN_EXISTING,0,NULL);
|
Вновь обратившись к разделу с описанием открытия и закрытия устройства,
мы вспоминаем, что для открытия устройства в Linux нам необходимы два параметра
(имя пути к файлу и режим доступа к устройству). В соответствии с приведенным
выше исходным кодом, первый параметр принимает вид /dev/hda, а второй -
O_RDONLY|O_NONBLOCK.
После внесения изменений мы получаем:
HANDLE devHandle = open("/dev/hda", O_RDONLY | O_NONBLOCK);.
Аналогично изменяем CloseHandle(devHandle); на
close(devHandle);.
Основным вопросом является использование функции ioctl,
которая обеспечивает доступ к нужному устройству и позволяет получить
необходимую информацию. Оригинальный код программы для Windows показан
в листинге 6:
Листинг 6. Исходный код, показывающий
использование DeviceIoControl в Windows
typedef struct _Buffer{
UCHAR req[8]; // Подробная информация, не относящаяся к
коду управления
ULONG DataBufferSize; // размер буфера данных Data Buffer, здесь равен 512
UCHAR DataBuffer[512]; // буфер данных Data Buffer
} Buffer;
Buffer regBuffer;
memcpy(regBuffer.req, cmdBuff, 7); //req[7] зарезервирован для использования
//в будущем, должен быть равен 0.
regBuffer.DataBufferSize = 512;
unsigned int size = 512+12; // размер regBuffer
// 8 - для req, 4 - для DataBufferSize, 512 - для данных
DWORD bytesRet = 0; // количество возвращенных данных
int retval; // возвращенное значение
retval = DeviceIoControl(devHandle,
IOCTL_IDE_PASS_THROUGH, //код управления
regBuffer, // входной буфер, включает размер подробной команды,
regBuffer, // выходной буфер, используется входной буфер
size,
&bytesRet, NULL);
if (!retval)
cout<<"DeviceIoControl failed."<<endl;
else
memcpy(data, retBuffer.DataBuffer, 512);
|
DeviceIoControl имеет больше параметров по , чем
ioctl. В обеих платформах первым параметром является
дескриптор устройства, который возвращается функцией
CreateFile (в Windows)/open()
(в Linux). Однако коды управления в Windows и запросы в Linux
определяются настолько различными способами, что не существует
какого-то фиксированного правила, связывающего эти два параметра -
о чем мы уже говорили ранее.
IOCTL_IDE_PASS_THROUGH определяется в
заголовочном файле ntddscsi.h при помощи следующего выражения
CTL_CODE (IOCTL_SCSI_BASE, 0x040a, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS).
Рассматривая определения, которые содержатся в заголовочном файле
/usr/include/linux/hdreg.h, мы выбираем для Linux в качестве соответствия
код управления
HDIO_DRIVE_CMD.
В дополнение к этому, устройству для выполнения конкретной задачи
необходимо предоставить детальную информацию. Команда включается
в буфер, который передается в процессе выполнения операции, также в
этом буфере оставляется место для возвращаемых данных. Мы используем
один и тот же буфер как для передачи команды, так и для получения
необходимой информации из журнала. В Linux нет необходимости использовать
все восемь байт - можно удалить информацию, связанную с размером буфера данных.
В этом примере используются только 4 байта, относящихся к команде.
Итак, в Linux соответствующий код (листинг 7) выглядит намного проще, так как
по сравнению с Windows функции имеют более простую структуру и аргументы.
Листинг 7. Исходный код, показывающий
использование функции ioctl в Linux
int retval;
unsigned char req[4+512]; // Размер буфера позволяет сохранять
// возвращенные данные плюс 4 байта
// для хранения подробной информации о команде
req[0]= cmdBuff[6]; // В соответствии с требованиями данного
// примера используются только 4 байта
req[1]= cmdBuff[2];
req[2]= cmdBuff[0];
req[3]= cmdBuff[1];
retval = ioctl(devHandle, HDIO_DRIVE_CMD, &req);
if(ret)
cout<<"ioctl failed."<<endl;
else
memcpy(data, &req[4], 512);
|
Шаг 4. Тестирование в окружении
Linux
После внесения необходимых исправлений в заголовочные файлы, функции и
их параметры, программа может быть запущена в операционной системе
Linux. Теперь нашей задачей является скомпилировать программу на
платформе Linux и исправить остающиеся синтаксические ошибки. Могут также
понадобиться дополнительные доработки в зависимости от версии Linux и
среды, в которой выполняется компилирование.
Ресурсы Научиться
- Оригинал статьи:
Migrate device control applications from Windows to Linux (EN).
- В серии из 9 статей на сайте developerWorks
архитектор электронного бизнеса Крис Уолден (Chris Walden)
рассказывает о переносе ваших рабочих навыков с Windows на
Linux, рассматривая такие темы, как работа в сети, работа с командной
строкой и справочными системами, и даже компиляция пакетов из
имеющегося исходного кода. Особый интерес может представлять статья
"Windows-to-Linux roadmap: Part 6. Working with partitions and file systems"
(developerWorks, ноябрь 2003 г.), в которой содержится информация по работе
с устройствами в Linux.
- Ресурсы
Migration station
помогут при переносе приложений с Windows, Solaris и OS/2 и обеспечат нормальную работу приложений в среде Linux
на системах, основанных на x86, POWER и System z.
- Статья
Device File Names
на MSDN содержит подробную информацию по названиям устройств в Windows.
- Статья
CreateFile Function
на MSDN содержит подробную информацию о том, как функции Windows работают с устройствами.
- Статья
"UNIX Application Migration Guide"
содержит указания и примеры по переносу UNIX-приложений.
- В разделе
Linux сайта developerWorks вы найдете множество ресурсов для разработчиков Linux; также советуем просмотреть на
наши наиболее популярные статьи и
учебные материалы.
- Советуем обратиться к
советам по Linux
и
учебным пособяим по Linux
на сайте developerWorks.
- Оставайтесь в курсе новостей, посещая раздел
технических мероприятий и Web-трансляций developerWorks.
Получить продукты и технологии
-
Закажите SEK для Linux,
комплект из двух DVD с новейшими ознакомительными версиями программного обеспечения IBM для Linux:
DB2®, Lotus®, Rational®, Tivoli® и
WebSphere®.
- Используйте
ознакомительное ПО IBM,
которые можно загрузить непосредственно с сайта developerWorks, в своем следующем проекте
для Linux.
Обсудить
Об авторах  | |  | Сунь Лин (Sun Ling) работает программистом-стажером в IBM China
System Technology Lab и готовится к получению магистерской
степени по специальности "Проектирование информационной безопасности"
в университете Цзяотун в Шанхае. В настоящий момент она работает
в группе CIM Convergence team. Она имеет опыт разработки приложений
управления устройствами и переноса приложений на Linux. |
 | |  | Ян И (Yang Yi) работает программистом-стажером в IBM China
System and Technology Lab. В настоящий момент он готовится к получению
магистерской степени по специальности "'электроника" в
университете Цзяотун в Шанхае. Обладает опытом в области управления
устройствами на различных платформах. |
Выскажите мнение об этой странице
|  |