IBM®
Перейти к тексту
    в России и странах СНГ [изменить]    Условия использования
 
 
   
    Главная страница    Продукты    Услуги и решения    Поддержка и загрузка    Мой профиль    
Перейти к тексту

developerWorks Россия  >  Linux  >

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

Процесс переноса приложений значительно облегчается, если понять различия между Windows и Linux в подходе к управлению устройствами

developerWorks
Опции документа
PDF format - Fits A4 and Letter

PDF - Fits A4 and Letter
66KB (12 страница)

Загрузить Adobe® Reader®

Опции документа, требующие включения JavaScript, не отображаются

Обсудить


Выскажите мнение об этой странице

Помогите нам улучшить содержание


Уровень сложности: средний

Сунь Лин, программист-стажер, 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
The Windows device control architecture

При управлении устройствами операции ввода/вывода осуществляются при помощи IRP (I/O Request Packet - Пакет запроса ввода/вывода). Менеджер ввода/вывода создает IRP и отправляет его на вершину стека. После этого драйверы устройств получают местоположение в стеке пакета IRP, содержащего параметры для данного запроса ввода/вывода. В соответствии с требованиями, указанными в IRP (такими как create, read, write, devioctl, cleanup, или close), каждый драйвер выполняет свое задание при помощи аппаратных интерфейсов.

Архитектура управления устройствами в Linux

В Linux архитектура управления устройствами немного другая, и основное отличие заключается в том, что обычные файлы, директории, устройства и сокеты являются файлами —в Linux все является файлом. Чтобы обратиться к устройству, ядро Linux отображает вызов операции с устройством на драйвер устройства при помощи файловой системы. В Linux не существует менеджера ввода/вывода: все запросы ввода/вывода в начале поступают в файловую систему (см. рисунок 2).


Рисунок 2. Архитектура управления устройствами в Linux
The Linux device control architecture


В начало


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

С точки зрения разработчика, для управления устройством необходимым условием является получение дескриптора (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/ROMCdRomx
Накопитель на магнитной ленте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. Соответствие между функциями управления устройствами
WindowsLinux
CreateFileopen
CloseHandleclose
DeviceIoControlioctl
ReadFileread
WriteFilewrite

Давайте более подробно рассмотрим три наиболее часто используемые функции: 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 для передачи управляющей информации определенному устройству используется вызов ioctlint 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
WindowsLinux
#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 и среды, в которой выполняется компилирование.



Ресурсы

Научиться

Получить продукты и технологии
  • Закажите 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. В настоящий момент он готовится к получению магистерской степени по специальности "'электроника" в университете Цзяотун в Шанхае. Обладает опытом в области управления устройствами на различных платформах.




Выскажите мнение об этой странице


Пожалуйста, найдите минутку и заполните форму, чтобы повысить уровень сервиса.



 


 


 


Поделиться этой статьей:

забобрить забобрить memori сохранить в memori




В начало


IBM обладает всеми авторскими правами касательно информации, расположенной на developerWorks. Использование информации приведенной на этом ресурсе без явного письменного разрешения от IBM или первоначального автора запрещены. Если Вы желаете использовать информацию с developerWorks, пожалуйста воспользуйтесь регистрационной формой для того, чтобы связаться с нами запрос на использование материалов developerWorks Россия.
    IBM в России Конфиденциальность Контакты