11 November 2011

The UNIX way

Один наш проект, работающий на ARM/Linux, внезапно начал вылетать с доставляющей надписью "I/O possible" во время работы со звуковой картой. 

Я не сильно большой знаток *NIX систем и Linux, специализируюсь больше на разработке кросс-платформенных решений, а тут как раз представился удобный случай повнимательнее взглянуть на знаменитые сигналы UNIX. 


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

Итак, что было обнаружено при курении man-pages.

#1. Сам подход с асинхронными вызовами в непонятно каком контексте может и был прогрессивным решением лет тридцать назад. В XXI же веке, когда без использования threads даже "хело ворлд" не напишешь, это выглядит откровенным долбоебиз архаизмом. Заводишь себе отдельный поток и ставишь в нем wait на интересующие тебя события... Правда, в Linux, по-моему, так и не добавили человеческий wait для множества разнородных объектов, но это уже детали.

#2. Изначально примитивный и очень простой механизм -- асинхронный вызов для аварийных ситуаций с передачей одного целого числа как кода аварии (см. signal() в сишной библиотеке) превратили в уродливое монстроподобное гавно, которое разве что за рисование окон не отвечает. А стандартную сишную функцию теперь, понятное дело, категорически не рекомендуют использовать.
Так как на механизм сигналов нацепили кучу свистелок и перделок, ребром встал вопрос о конфликтах их использования в рамках одного приложения, т.к., к примеру, о завершении асинхронной I/O операции (привет, I/O possible!) может быть интересно узнать далеко не в одном месте программы. Сигналы, вроде как, надо обрабатывать в дружеском кооперативном режиме, соединяя вызовы обработчиков в цепочки. Для этих целей в sigaction добавили возможность при выставлении своего обработчика узнать предыдущий. Решение чертовски неудачное, потому что у вас нет гарантированной возможности свой обработчик корректно снять с сигнала, не порушив всю цепочку. 
Одни идиоты придумали такое решение, которое не решает проблему, для которой оно было придумано. 
А другие идиоты, таки да, не понимают, что sigaction придумали идиоты, и таки пытаются свой обработчик снять. В итоге получаем вылет программы и тот самый "I/O possible". 

#3. Решение проблемы цепочки обработчиков для сигналов тривиально и до него додуматься может даже школьник, прогулявший половину уроков по турбо паскалю. Пара функций SetSignalHook(), ClearSignalHook() и гарантированно корректное управление цепочкой обработчиков под капотом (обычный связанный список и обеспечение с ним thread safe работы). 

#4. Типичный *NIX подход -- насовывания кучи гавна в уже готовый API, вместо того, чтобы завести новый. Вместо того, чтобы четко различать такие вещи, как обработка сигнала завершения программы, реагирование на segfault, завершение асинхронной операции ввода-вывода и так далее, они пытаются объять необъятное, засунув никак не связанные вещи в одну единственную функцию. 
И конечно же, нас ждет убер структура, для которой есть десяток вариантов трактовки, в зависимости от того, что именно вы обрабатываете. 

siginfo_t {
               int      si_signo;    /* Signal number */
               int      si_errno;    /* An errno value */
               int      si_code;     /* Signal code */
               int      si_trapno;   /* Trap number that caused
                                        hardware-generated signal
                                        (unused on most architectures) */
               pid_t    si_pid;      /* Sending process ID */
               uid_t    si_uid;      /* Real user ID of sending process */
               int      si_status;   /* Exit value or signal */
               clock_t  si_utime;    /* User time consumed */
               clock_t  si_stime;    /* System time consumed */
               sigval_t si_value;    /* Signal value */
               int      si_int;      /* POSIX.1b signal */
               void    *si_ptr;      /* POSIX.1b signal */
               int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
               int      si_timerid;  /* Timer ID; POSIX.1b timers */
               void    *si_addr;     /* Memory location which caused fault */
               long     si_band;     /* Band event (was int in
                                        glibc 2.3.2 and earlier) */
               int      si_fd;       /* File descriptor */
               short    si_addr_lsb; /* Least significant bit of address
                                        (since kernel 2.6.32) */
           }

И конечно же, ваш ждет куча другого легаси гавна, вроде двух различных видов функций для обработки сигналов, или лежащего рядом указателя sa_restorer, который как бэ obsolete и не должен использоваться... Кста, если вы загляните в signal.h, то увидите там # ifdef __KERNEL__ и #else секцию, которая начинается словами /* Here we must cater to libcs that poke about in kernel headers. */. Очень мило!

Отдельный большой вопрос, почему при запросе асинхронной I/O операции я не могу конкретно указать callback, куда я желаю получить нотификацию, для меня остается загадкой. 

Кстати, другой хрестоматийный пример подобного "развития" API -- беркли сокеты, о которых я не могу говорить больше пяти секунд, не приходя в лютое бешенство от криворукости и скудоумия людей, их придумавших.

#5. Давайте о чем-то хорошем, а?...
Мне вот, к примеру, нравится, что при отсутствии обработчика завершения асинхронной I/O операции приложений падает. Я серьезно, без всякого сарказма. 
Нет, иногда конечно в unix-way практикуется, к примеру, падение в segfault при проблемах с парсингом своего конфигурационного файла (а чо? тебе в падлу взять gdb и посмотреть в чем проблема, бро?). Но в целом, стойкое игнорирование ошибок любой серьезности -- истинный путь джедая. Мы же серьезная ОС, на серверах работаем! Мы не может падать по любому пустяку, мы лучше молча продолжим работу, делая вид, что все в полном порядке!
Я понимаю, что такая фатальная реакция на I/O сигнал получилась случайно. Просто потому, что сигналы исторически несут в себе информацию о серьезных неполадках. Но все равно, как-то греет душу.

На этом все, любите Linux и друг друга.

No comments:

Post a Comment