Mar 06, 2010 00:05
Предыстория
Однажды для некоторых целей мной был написан небольшой сетевой демон, в задачи которого, помимо какой-то вялой сетевой активности в качестве сервера, входило пару раз в секунду предпринимать некоторые важные действия.
Архитектура была достаточно тривиальной: бесконечный цикл вокруг select, который спал до тех пор, покуда нет сетевых задач, и не пришло время выполнять важные действия (таймаут до наступления которых высчитывался перед собственно вызовом мультиплексора).
И все работало нормально, пока в один прекрасный день не обнаружилось, что реакция у демона стала какая-то не та. Сетевая активность типа в порядке, а важные действия выполняются несколько не вовремя. Как будто постарел, бедолага.
Акт первый
Особо размышлять мне было не с руки, поэтому я тупо отрефакторил код в сторону уменьшения количества различных вычислений на моей стороне. Расчет был таков, что, даст Бог -- проблема после этого починится как-нибудь сама. Вызов select у меня стал блокирующим, а важные действия я зашедулил через setitimer с необходимым интервалом (с перешедуливанием по необходимости). Ну а собственно действия переехали, разумеется, в обработчик SIGALRM. Собрал, запустил, проверил -- все ок, отдал хозяевам и успокоился.
Но не тут-то было. Конечно же, нихера не починилось, все симптомы те же самые.
Акт второй
Я (уже в некотором раздражении) начал траблшутить. Выясняется, что демон, запущенный руками работает как надо, а тот же самый демон, стартанувший автоматически при запуске системы, тупит и тормозит.
В чем разница?? Процесс и там и там от рута. Может, в стандартных дескрипторах, текущей директории, способе запуска? В чем вообще отличие, между процессом, запущенном из ash и стартовавшем из inittab? Ну хорошо, я причесал демон под классический порядок вещей (раньше он запускался по-колхозному, типа "daemon &"): оснастил fork-ом, chdir-ом, setsid-ом, переоткрыл stdin/stdout/stderr из /dev/null...
Стало, конечно, "породистей", но по результативности из десяти баллов мои действия смогли бы набрать ноль.
Акт третий
Я уже в некотором расстройстве. Повелел демону на SIGUSR1 в специальный файл сбрасывать дамп полного своего состояния, предварительно снабдив программу разного рода счетчиками. Начал следить: % kill -USR1 `pidof my_daemon` && sleep 1 && cat my_daemon.dump.
Само собой, никаких проблем не выявилось.
Добавил в дампе счетчик вызовов обработчика SIGALRM и расчет ожидаемого его количества: ууу блядь, разошлось раза в три!!
Итого имеем: если запускать руками демон, то события SIGALRM наступают вовремя (как было задано setitimer-ом), а если он стартовал сам, то события наступают на 66% позже.
Акт четвертый
Ну, собственно, с этой точки уже стало более-менее понятно, в какую сторону смотреть.
Из консоли демон стартует с nice равным 0, а из inittab-а с 10. Луниксовый шедулер, походу, процесс с таким приоритетом за процесс не считает вообще: потипу грязного тупого бомжа-ниггера среди успешных белых людей. Даже сигналы ему кидает через силу, и то, только если процессор простаивает уже достаточно долго, и делать ну совсем нечего :)
Конечно же, setpriority( PRIO_PROCESS, getpid(), -4 ); проблему вылечил, и злой шедулер был посрамлен.
Эпилог
Добро в моем лице снова восторжествовало, пойду выпью кофейку по этому поводу.
setitimer,
daemon,
linux,
priority,
daily,
nice