Атрибуты устройств, или 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, or error
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 );
Правила:
- Никаких дефайнов с номерами, только имена.
- Никаких бинарных данных, только строки
Это не очень эффективно, но хочется предположить, что установка/чтение свойств — процесс редкий, и потому упираться в его скорость смысла немного, да и само переключение контекста при вызове стоит немало.
Можно несколько оптимизировать интерфейс, например, так:
// returns name of property with sequential number nProperty, or error
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-летним стажем, кажутся просто очевидными. Будет время — опубликую.
Ссылки на реализацию (кожа и кости, но всё же):
Кажется очевидным, но упомяну, что механизм, в принципе, можно применять для любых сущностей, вовсе не только для устройств.