netlib.narod.ru< Назад | Оглавление | Далее >

Сервисные функции

Одна из наиболее важных структур данных, относящихся к сигналам, sigset_t, обслуживается набором простых функций, определенных в файле include/linux/signal.h, начало которого находится в строке 17123. Те же функции для платформы х86 реализованы более оптимально, на ассемблере; их реализация начинается со строки 12204. (Единственная платформа, которая отличается специфической реализацией рассматриваемого кода, — это m68k.) Поскольку для понимания важны как платформенно-независимая, так и версия для х86, мы рассмотрим обе реализации.

Платформенно-независимая версия функций для sigset_t

Код упомянутых функций находится в файле include/linux/signal.h, который начинается в строке 17123. Эти функции, также называемые «bitops» (bit-level operations, т.е. поразрядные операции), описываются ниже.

sigaddset

Строка 17148, подобно и другому коду в этом файле, на первый взгляд вызывает легкое замешательство. Сущностью всего кода ядра является быстродействие. Таким образом, можно было бы рассчитывать, что вместо решения на этапе выполнения, подобного:

if (_NSIG_WORDS == 1)
    set->sig[0] |= 1UL << sig;
else
    set->sig[sig / _NSIG_BPW]
                |= 1UL << (sig % _NSIG_BPW);

должно присутствовать решение на этапе компиляции:

#if (_NSIG_WORDS == 1)
    set->sig[0] |= 1UL << sig;
#else
    set->sig[sig / _NSIG_BPW]
               |= 1UL << (sig % _NSIG_BPW);
#endif

Неужели последний код не будет работать быстрее? В конце концов, ведь все условия в операторах if поддаются расчету на этапе компиляции, поэтому препроцессор может убрать все, что связано с проверкой на этапе выполнения.

Туман рассеивается, стоит только подумать о том, что те же действия делает также и оптимизатор. Оптимизатор gcc интеллектуален настолько, что способен выяснить, что оператор if может работать только в одной своей части и удалить код, который никогда не будет выполняться. Результирующий объектный код оказывается одинаковым как для версии «этапа выполнения», так и для версии «этапа компиляции».

Но почему бы не счесть второй вариант кода, основанный на использовании препроцессора, в любом случае более удачным, вдобавок предположив наличие не очень хорошего оптимизатора? Нет необходимости. С одной стороны, такой код более труден в понимании. Разница в читабельности становится более ощутимой, когда речь идет о более сложном коде. Возьмите, например, фрагмент с оператором switch в sigemptyset, который начинается в строке 17264. Сейчас упомянутый фрагмент кода выглядит так:

switch (_NSIG_WORDS)
{
default:
        memset(set, 0, sizeof(sigset_t));
        break;
case 2: set->sig[1] = 0;
case 1: set->sig[0] = 0;
        break;
}

(Отметьте преднамеренную перестановку порядка случаев 2 и 1.) Если код переписать так, чтобы задействовать препроцессор, он может выглядеть так:

#if ((_NSIG_WORDS != 2) && \
     (_NSIG_WORDS != 1))
    memset(set, 0, sizeof(sigset_t));
#else /* (_NSIG_WORDS is 2 or 1. */
#if (_NSIG_WORDS == 2)
    set->sig[1] = 0;
#endif
    set->sig[0] = 0;
#endif /* _NSIG_WORDS test. */

Оптимизатор gcc сгенерирует один и тот же объектный код для обоих случаев. Какая из версий кода более удобочитаема?

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

sigdelset

sigismember

sigfindinword

sigmask

Платформенно-зависимая версия функций для sigset_t

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

Для платформы х86 (а также и m68k) компилятор не использует платворменно-независимые версии функций. В строке 17126 включается файл asm/signal.h, который в случае х86, благодаря символической ссылке, устанавливаемой makefile, разрешается файлом include/asm-i386/signal.h. Строка 12202 определяет препроцессорный символ __HAVE__ARCH_SIG_BITOPS, который обеспечивает замену определений платформенно-независимых функций (см. строку 17140).

sigaddset

sigdelset

sigismember

sigmask

sigfindinword

Функции работы с наборами

В дополнение к предшествующей группе, следующая группа макросов и функций выполняет операции с sigset_ts как над набором. Как и в предшествующей группе, функции предохраняются препроцессорным символом __HAVE_ARCH_SIG_SETOPS. На данный момент не существует ни одной платформенно-зависимой версии этой группы функций, а лишь только универсальная.

_SIG_SET_BINOP

_SIG_SET_OP

sigemptyset

sigaddsetmask

siginitset