(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 365 Apps for business (corporate)
Microsoft Office 365 Профессиональный Плюс. Подписка на 1 рабочее место на 1 год
Microsoft 365 Business Basic (corporate)
Microsoft 365 Business Standard (corporate)
Microsoft Windows Professional 10, Электронный ключ
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
Краткие описания программ и ссылки на них
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100