Атрибути пристроїв, або ioctl must die

У процесі роботи над ОС Фантом, яка взагалі не Юнікс ніяким місцем, мені, тим не менш, захотілося зробити в ньому Unix-compatible підсистему. Не те, щоб прямо POSIX, але щось досить близьке. Почасти з цікавості, частково зручності, частково як ще один migration path. (Ну і взагалі було цікаво, наскільки важко написати простенький Юнікс «з голови».) В якості цілі номер 1 було поставлено завдання запустити quake 1 for Unix, яка і була досягнута.

У процесі, природно, з'явилися open/close/r/w/ioctl, і з'явилося відчуття, що останній непристойно, ганебно застарів. В якості вправи для размятия мозку я реалізував (в додаток до звичайного ioctl) деякий альтернативний API, який би дозволив керувати властивостями пристроїв більш гнучким і зручним з точки зору користувача способом. Цей API, звичайно, має свої очевидні мінуси, і, в цілому, ця стаття — RFC, aka request For Comments.

Отже, API на рівні користувача:

// returns name of property with sequential number nProperty, error or
errno_t listproperties( int fd, int nProperty, char *buf, int buflen );

errno_t getproperty( int fd, const char *pName, char *buf, int buflen );
errno_t setproperty( int fd, const char *pName, const char *pValue );


Правила:

  1. Ніяких дефайнов з номерами, тільки імена.
  2. Ніяких бінарних даних, тільки рядка

Це не дуже ефективно, але хочеться припустити, що установка/читання властивостей — процес рідкісний, і тому впиратися в його швидкість сенсу небагато, та й саме перемикання контексту при виклику коштує чимало.

Можна кілька оптимізувати інтерфейс, наприклад, так:

// returns name of property with sequential number nProperty, error or
errno_t listproperties( int fd, int nProperty, char *buf, int buflen );

// returns property id by name
errno_t property_by_name( int fd, int *nProperty, const char *name );


errno_t getproperty( int fd, int nProperty, char *buf, int buflen );
errno_t setproperty( int fd, int nProperty, const char *pValue );

// fast lane

errno_t getproperty_i32( int fd, int nProperty, int32_t *data );
errno_t setproperty_i32( int fd, int nProperty, int32_t data );


Ця схема для одиничного властивості не повільніше, ніж ioctl.

Чим вона хороша: можна зробити загальну команду (напр mode), яка управляє параметрами будь-якого драйвера, не знаючи про нього нічого — mode /dev/myCrazyDriver видасть список властивостей, а mode /dev/myCrazyDriver name val встановить властивість name значення val.

Реалізація всередині драйвера (для якої, звичайно, в ядрі є відповідна нехитра інфраструктура) теж нескладна:

static property_t proplist[] =
{
{ pt_int32, "leds", 0, &leds, 0, (void *)set_leds, 0, 0 },
};


Ця рядок описує властивість, що має тип int32, лежить у змінній leds, і якщо воно змінилося, то треба викликати функцію set_leds.

В реальності крім pt_int32 народилися тільки pt_mstring — malloc'ed strings, що теж досить зручно.

Взагалі, треба сказати, темпи розвитку API класичного Юникса мене трохи дивують — є відчуття, що ніхто їм всерйоз не займається, хоча, здається, певна систематизація йому явно не зашкодить.

У мене є ще кілька доповнень до традиційного POSIX-у, які мені, Юниксоиду з 30-річним стажем, здаються просто очевидними. Буде час — опублікую.

Посилання на реалізацію (шкіра та кістки, але все ж):



Здається очевидним, але згадаю, що механізм, в принципі, можна застосовувати для будь-яких сутностей, зовсім не тільки для пристроїв.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.