HAL

Материал из MediaWiki
Перейти к навигации Перейти к поиску

В этой статье мы рассмотрим способы общения с системным демоном HAL.

Введение

HAL (Hardware Abstraction Layer) — это демон, хранящий информацию о системных устройствах, их типах, возможностях и другую информацию, которую он в состоянии получить через интерфейсы ядра. Демон предоставляет более высокоуровневый доступ к информации об устройствах, нежели прямое общение с sysfs и др. Используя HAL API мы может просканировать список системных устройств, найти в нём дисковые накопители, Audio CD, USB флеш карты, WiFi карты, клавиатуры, мыши и т. д. HAL также содержит несколько полезных сигналов, основываясь на которых мы можем отследить момент появления новых устройств в системе (например, когда был вставлен новый USB флеш накопитель, DVD диск, поднят WiFi интерфейс и т. д.). HAL предоставляет также и некоторые полезные методы, которые можно вызывать удалённо через интерфейс D-Bus.

Структура данных, в которой HAL хранит всю информацию, можно представить как ассоциативный массив. Ключом в этом массиве будет являться т. н. уникальный идентификатор устройства, или UDI. Значением этого ключа будет ещё один ассоциативный массив, в котором в свою очередь ключом будет имя свойства данного устройства, а значением — значение этого свойства для данного устройства. Имена UDI зависят от системы, не стоит полагаться на них постоянство. Чтобы было понятней, представим такую упрощённую структуру для некоего устройства:

UDI1
    |---block.device      /dev/sda1
    |---block.is_volume   1
    |---info.capabilities volume,block
    |---volume.label      WORK
    `---volume.uuid       0717-AD77
UDI2
    …
UDI3
    …

Здесь у некоего устройства с UDI, равным абстрактному UDI1, имеется 5 свойств. Имя каждого свойства строго определено. Значение свойства зависит от имени. Для устройства UDI1 мы может сказать следущее:

  • на основании block.device — что этому устройству сопоставлен файл /dev/sda1 в файловой системе /dev;
  • на основании block.is_volume — что это некий раздел;
  • на основании info.capabilities — что это раздел на блочном устройстве хранения данных;
  • на основании volume.label — что этот раздел имеет метку WORK;
  • на основании volume.uuid — что этот раздел имеет UUID 0717-AD77.

Мы можем проверить эту информацию с помощью системной утилиты blkid (запускается от пользователя root):


# blkid | grep WORK
/dev/sda1: LABEL="WORK" UUID="0717-AD77" TYPE="vfat"

Общение с HAL демоном происходит через D-Bus. Для D-Bus имеется множество биндингов для различных языков. Это значит, что для общения с HAL у нас не должно возникнуть трудностей. В этой статье мы постараемся рассказать о нескольких способах взаимодействия с HAL.

Также отметим, что на шине D-Bus демон предоставляет интерфейсы и методы для прямого вызова любым D-Bus клиентом. Например, с помощью qdbusviewer или любым консольным клиентом (qdbus, dbus-send).

Каждое устройство в иерархии может иметь дочерние устройства (например, как шина USB и подключённые к ней устройства).

Подготовка

Во всех современных дистрибутивах демон HAL уже должен быть установлен, настроен и запущен. Для программирования нам понадобятся только заголовки HAL и D-Bus. Для справки по HAL понадобится пакет hal-doc. В Debian-подобных системах их можно установить так:


# aptitude install libhal-dev libhal-storage-dev hal-doc

Теперь мы готовы для общения с HAL.

Для просмотра всех устройств и их свойств в графическом виде можно использовать утилиту hal-device-manager. Она присутствует в Debian Etch, но в более поздних дистрибутивах (в т.ч. и последних Ubuntu) следует использовать gnome-device-manager.

Свойства

В качестве примера рассмотрим некоторые имена свойств.

  • info.capabilities — список строк, описание того, что из себя представляет это устройство. Это важное свойство для отделения устройств друг от друга по типу;
  • info.parent — содержит UDI родительского элемента в иерархии HAL;
  • block.device — содержит файл устройства в системной иерархии (например, /dev/sda1);
  • block.is_volume — содержит значение true, если данное устройство — раздел и может быть смонтировано;
  • volume.fstype — тип файловой системы раздела (если это раздел);
  • volume.label — метка тома;
  • volume.fsusage — как используется файловая система (обычная файловая система, RAID, неформатированная область и т.д.);
  • linux.sysfs_path — путь к файлу устройства в иерархии sysfs.

За полным списком возможных имён свойств обратитесь к документации HAL.

Консоль

Мы можем общаться с HAL с помощью любого клиента D-Bus. Например, с помощью консольного dbus-send. Для каждого устройства на шине D-Bus предусмотрен интерфейс org.freedesktop.Hal.Device, который предоставляет методы для получения и установки свойств данного конкретного устройства.

Например, получим список облуживаемых устройств (похожий результат даст команда lshal):

$ dbus-send --system --print-reply --dest=org.freedesktop.Hal \
            /org/freedesktop/Hal/Manager                      \
            org.freedesktop.Hal.Manager.GetAllDevices
method return sender=:1.1 -> dest=:1.28 reply_serial=2
   array [
      string "/org/freedesktop/Hal/devices/acpi_P001"
      string "/org/freedesktop/Hal/devices/acpi_P002"
      ...
      string "/org/freedesktop/Hal/devices/pci_10de_368"
      string "/org/freedesktop/Hal/devices/pci_10de_362"
      string "/org/freedesktop/Hal/devices/pci_10de_369"
   ]

Каждая строка в ответе — это UDI обслуживаемого устройства.

Теперь получим свойство info.capabilities для одного из дисковых разделов, основываясь на его UDI:

$ dbus-send --system --print-reply --dest=org.freedesktop.Hal  \
            /org/freedesktop/Hal/devices/volume_uuid_0717_AD77 \
            org.freedesktop.Hal.Device.GetProperty             \
            string:info.capabilities
method return sender=:1.1 -> dest=:1.31 reply_serial=2
   array [
      string "volume"
      string "block"
   ]

Теперь получим свойство volume.label (метка DOS) для этого же раздела:

$ dbus-send --system --print-reply --dest=org.freedesktop.Hal  \
            /org/freedesktop/Hal/devices/volume_uuid_0717_AD77 \
            org.freedesktop.Hal.Device.GetProperty             \
            string:volume.label
method return sender=:1.1 -> dest=:1.33 reply_serial=2
   string "WORK"

Получим свойство volume.size (размер раздела в байтах) для этого же раздела:

$ dbus-send --system --print-reply --dest=org.freedesktop.Hal  \
            /org/freedesktop/Hal/devices/volume_uuid_0717_AD77 \
            org.freedesktop.Hal.Device.GetProperty             \
            string:volume.size
method return sender=:1.1 -> dest=:1.36 reply_serial=2
   uint64 15002878464

C/C++

В этом разделе мы напишем HAL клиента на оригинальной библиотеке libhal (на языке C, стандарта C99). Это необходимо прежде всего для тулкитов, где нет высокоуровнего D-Bus клиента, либо для чисто консольной программы, которая не использует тулкиты вообще.

Итак, всё что нам понадобится — это один исходный файл и элементарный скрипт компиляции. Makefile можете написать по своему усмотрению.

Скрипт компиляции (назовём его mk):

<syntaxhighlight lang="sh">

  1. !/bin/sh
  1. компилируем исходник main.c. Не забываем о флагах и библиотеках HAL и D-Bus

gcc -o hal -O2 -std=c99 \

       `pkg-config --cflags hal` `pkg-config --cflags dbus-1` \
       main.c                                                 \
       `pkg-config --libs hal` `pkg-config --libs dbus-1`

</syntaxhighlight>

Стратегия написания простейшего клиента HAL сводится к следующему:

  • создать новый HAL контекст, используемый в HAL API;
  • создать соединение с D-Bus сессией;
  • ассоциировать HAL контекст с D-Bus соединением;
  • инициализировать HAL;
  • использовать функции из HAL API для общения с демоном (например, получим список всех устройств);
  • деинициализировать клиента HAL.

<syntaxhighlight lang="c">

  1. include <stdio.h>
  2. include <stdlib.h>
  1. include <libhal.h>
  1. include <dbus/dbus.h>

// функция обработки UDI static void handleDevice(LibHalContext *ctx, const char *udi, DBusError *error) {

   if(!udi)
       return;
   printf("%s\n", udi);
   // нас интересуют только устройства, имеющие свойство info.capabilities
   if(!libhal_device_property_exists(ctx, udi, "info.capabilities", NULL))
       return;
   // получим значение свойства (массив строк)
   int i = 0;
   char **capabilities = libhal_device_get_property_strlist(ctx, udi, "info.capabilities", NULL);
   if(!capabilities)
       return;
   // сейчас 'capabilities' представляет собой массив строк
   // пройдёмся по всем строкам, выведем на экран значения
   while(*(capabilities+i))
   {
       printf("\t%s\n", *(capabilities+i));
       ++i;
   }
   printf("\n");
   // освободим память!
   libhal_free_string_array(capabilities);

}

static void die(const char *s) {

   if(s)
       fprintf(stderr, "%s\n", s);
   exit(1);

}

int main(int argc, char **argv) {

   DBusConnection *dbus;
   LibHalContext *ctx;
   DBusError error;
   // создаём новый контекст
   ctx = libhal_ctx_new();
   if(!ctx)
       die("Cannot initialize HAL context");
   // инициализируем объект error, он нам понадобится в функциях HAL API
   dbus_error_init(&error);
   // создаём соединение с D-Bus
   dbus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
   // ассоциируем D-Bus соединение с HAL контекстом
   libhal_ctx_set_dbus_connection(ctx, dbus);
   // инициализируем HAL клиента с нашей стороны
   if(!libhal_ctx_init(ctx, &error))
       die("Cannot initialize HAL");
   // с помошью HAL API получаем список устройств, обслуживаемых демоном,
   // каждое устройство представляет собой строку с UDI
   int numDevices;
   char **halDevices = libhal_get_all_devices(ctx, &numDevices, NULL);
   // нет устройств??
   if(!halDevices)
       die("Cannot get device list");
   printf("Number of devices: %d\n", numDevices);
   // для каждого UDI вызываем функцию обработки
   for(int i = 0;i < numDevices;i++)
       handleDevice(ctx, halDevices[i], &error);
   // освобождаем память!
   libhal_free_string_array(halDevices);
   if(dbus_error_is_set(&error))
       dbus_error_free(&error);
   // завершаем D-Bus соединение
   dbus_connection_close(dbus);
   dbus_connection_unref(dbus);
   // завершаем клиента
   libhal_ctx_free(ctx);
   return 0;

} </syntaxhighlight>

Эта программа выдаст на консоль список всех обслуживаемых устройств, и свойство info.capabilities каждого устройства.

Qt

С помощью Qt общаться с HAL можно без особых трудностей, т.к. в 4-ю версию этого тулкита уже встроен D-Bus клиент (необходимы установленные пакеты libqt4-dbus и libqt4-dev).

Стратегия написания простейшего клиента HAL на Qt очень проста: с помощью D-Bus-клиента Qt подключиться к системной шине (именно там запущен демон HAL), и вызывать необходимые методы D-Bus. В примере с Qt используются два полезных сигнала, предоставляемых HAL — DeviceAdded и DeviceRemoved. Первый срабатывает когда добавляется какое-либо устройство, второй — когда удаляется. Наглядными примерами могут послужить USB-флешки или DVD-диски. Когда такой сигнал возникает, вызываются все подписанные на него удалённые методы. Подписаться на эти сигналы можно средствами того же Qt.

<syntaxhighlight lang="c++">

  1. include <QDBusConnection>
  2. include <QDBusInterface>
  3. include <QDBusReply>
  1. include <QStringList>
  2. include <QDebug>
  1. include "hal.h"

namespace {

// функция вызова D-Bus методов, которые принимают один параметр и возвращают значение template<typename T> T dbusRequest(QDBusInterface &i, const QString &method, const QString &param) {

   QDBusReply<T> reply = i.call(method, param);
   
   return reply.value();

}

}

HAL::HAL() : QTextEdit() {

   resize(640, 480);
   /*
    *  Используем сигналы DeviceAdded и DeviceRemoved за слежением
    *  за добавлением/удалением устройств. Следует помнить, что при добавлении,
    *  скажем, USB флешки, в системе возникает несколько устройств.
    *  Отделить нужные устройства от вспомогательных можно с помощью анализа
    *  их свойств.
    */
   QDBusConnection sys = QDBusConnection::systemBus();
   sys.connect("org.freedesktop.Hal",
               "/org/freedesktop/Hal/Manager",
               "org.freedesktop.Hal.Manager",
               "DeviceAdded",
               this,
               SLOT(slotAdded(const QString &)));
   sys.connect("org.freedesktop.Hal",
               "/org/freedesktop/Hal/Manager",
               "org.freedesktop.Hal.Manager",
               "DeviceRemoved",
               this,
               SLOT(slotRemoved(const QString &)));
   // прочитаем список всех UDI устройств, и занесём этот список
   // в текстовое поле
   readDevices();

}

void HAL::readDevices() {

   // с помощью QDBusInterface устанавливаем соединение с D-Bus шиной
   QDBusInterface i("org.freedesktop.Hal",
                    "/org/freedesktop/Hal/Manager",
                    "org.freedesktop.Hal.Manager",
                    QDBusConnection::systemBus());
   // вызываем стандартный HAL метод - GetAllDevices. Этот
   // метод возвращает список всех обслуживаемых UDI в виде массива строк
   QDBusReply<QStringList> reply = i.call("GetAllDevices");
   // ответ битый?
   if(reply.isValid())
   {
       // получаем список строк из ответа
       QStringList l = reply.value();
       QStringList::iterator itEnd = l.end();
       // проходим по списку строк (UDI) и заносим каждую строку
       // в текстовое поле
       for(QStringList::iterator it = l.begin();it != itEnd;++it)
           append(*it);
   }
   else
       append("Failed to get device list");

}

void HAL::slotAdded(const QString &udi) {

   /* 
    *  при добавлении устройства попробуем определить, что оно из себя
    *  представляет (попробуем определить устройства с разделами, например USB
    *  флеш накопители или DVD диски)
    */
   QDBusInterface i("org.freedesktop.Hal",
                    udi,
                    "org.freedesktop.Hal.Device",
                    QDBusConnection::systemBus());
   QStringList caps = dbusRequest<QStringList>(i, "GetProperty", "info.capabilities");
   qDebug() << "Added device with UDI" << udi << caps;
   // устройство не имеет файловой системы, оно нас не интересует
   if(dbusRequest<QString>(i, "GetProperty", "volume.fsusage") != "filesystem" &&
           !dbusRequest<bool>(i, "GetProperty", "volume.disc.has_audio"))
       return;
   QDBusMessage reply = i.call("GetProperty", "volume.size");
   // получаем другие данные по устройству
   QString product = dbusRequest<QString>(i, "GetProperty", "info.product");
   QString size = reply.arguments().first().toString();
   QString parent = dbusRequest<QString>(i, "GetProperty", "block.storage_device");
   // родитель содержит параметры устройства, как модель и шину,
   // к которой оно подключено
   QDBusInterface iParent("org.freedesktop.Hal",
               parent,
               "org.freedesktop.Hal.Device",
               QDBusConnection::systemBus());
   QString model = dbusRequest<QString>(iParent, "GetProperty", "storage.model");
   QString vendor = dbusRequest<QString>(iParent, "GetProperty", "storage.vendor");
   QString bus = dbusRequest<QString>(iParent, "GetProperty", "storage.bus");
   append(QString("Added device, model (%1), vendor (%2), bus (%3), product (%4), size (%5)")
           .arg(model).arg(vendor).arg(bus).arg(product).arg(size));

}

void HAL::slotRemoved(const QString &udi) {

   append(QString("Removed device with UDI %1").arg(udi));

} </syntaxhighlight>

При вставке DVD-диска в текстовом поле должно появится сообщение типа:

Added device, model (_NEC DVD_RW ND-3540A), vendor (), bus (ide), product (Мой диск), size (4644896768)

Tcl

Не менее просто чем из Qt, с HAL можно работать и из Tcl. Пример можно посмотреть в разделе D-Bus.

Ссылки