(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Перенос приложений управления устройствами с Windows на Linux

Источник: ibm
Сунь Лин, программист-стажер, IBM Ян И, программист-стажер, IBM

Если вы разрабатываете приложения управления устройствами для различных платформ, вы уже знаете, что 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).

 

 

Сравнение имен файлов устройств и имен путей к устройству

С точки зрения разработчика, для управления устройством необходимым условием является получение дескриптора (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 и среды, в которой выполняется компилирование.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 19.01.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Microsoft Office для дома и учебы 2019 (лицензия ESD)
Microsoft Windows Professional 10, Электронный ключ
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год.
Microsoft Office 365 Профессиональный Плюс. Подписка на 1 рабочее место на 1 год
Microsoft 365 Apps for business (corporate)
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
CASE-технологии
Программирование на Microsoft Access
Новые материалы
Все о PHP и даже больше
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100