Ключевые слова:lib, threads, proccess, faq, select, poll, fork, daemon, debug, time, gcc, (найти похожие документы)
From: "Valentin Nechayev" <netch@segfault.kiev.ua>
Newsgroups: fido7.ru.unix.prog
Subject: ru.unix.prog FAQ - ответы на вопросы по программированию под Unix
RU.UNIX.PROG FAQ
$Id: FAQ.p1,v 1.3 2003/02/06 18:26:52 netch Exp $
--------------------------------
>Q: А что бы почитать по эхотагу:
A:
1. В электронном виде:
- http://www.faqs.org/faqs/by-newsgroup/comp/comp.unix.programmer.html
- http://unixfaq.ru
- http://opennet.ru
- http://docs.freebsd.org/44doc/psd/20.ipctut/paper.html
- http://docs.freebsd.org/44doc/psd/21.ipc/paper.html
- http://www.ecst.csuchico.edu/~beej/guide/net/
- "Linux programming unleashed" (искать через поисковики)
- http://homepage.mac.com/~dbutenhof/Threads/source.html (примеры к книге)
- http://www.kegel.com
- "The C10K problem" http://www.kegel.com/c10k.html (few notes on how to
configure operating systems and write code to support thousands of clients)
- comp.prgramming.threads FAQ http://www.lambdacs.com/cpt/FAQ.html
- STL Programmer's Guide http://www.sgi.com/tech/stl/
- И естественно ;-) http://www.gnu.org/software/gcc/onlinedocs/
Есть и масса других источников, все не перечислить;)
Не забывайте про поисковики - они могут найти то что хорошо спрятано;)
Непосредственно в системе есть:
- многочисленные маны. можно читать все подряд;) на многих платформах в
каждой секции есть intro, имеет смысл начинать с него.
- при наличии GPL софта - документацию в texinfo. Скажи `info' и увидишь
общий каталог.
- /usr/share/doc
2. В бумажном виде:
- Richard W. Stevens, все книги;)
В частности, "Advanced programming in UNIX environment",
"Network programming" (в двух томах), есть русские переводы.
- David Butenhof, "Programming with POSIX threads"
- Робачевский (отнестись немного критически)
- Scott Meyers, Effective STL, Effective C++ & More Effective C++.
Первая есть в переводе на русский "Эффективное использование STL",
остальные две бегают в электронном виде где-то в РУHете.
--------------------------------
>Q: А какая еще есть литература?
A: Идите в RU.BOOKS.COMPUTING
--------------------------------
>Q: Где стандарты?
A:
1. http://www.unix-systems.org/.
Single Unix Specification версии 3 одновременно является Posix.1-2001.
Можно читать с сайта (зарегистрировавшись), можно скачать;)
2. http://www.ieee.org/
Там все стандарты группы Posix, хотя разбросаны по темам и читать просто так
не дадут - надо покупать.
Есть еще много групп стандартов: XPG, SVID, стандарты ISO C, ISO C++
и так далее. В большинстве они недоступны бесплатно.
--------------------------------
>Q: Какие есть IDE (integrated development environments) под Unix?
> Ну чтобы компилятор, среда редактирования, отладчик и прочее - были все
> вместе?
A: (Alexey Mahotkin)
UNIX сам по себе является Integrated Development Environment.
В "обычных" IDE есть бинарник-интегратор, который вызывает в лучшем случае
внешние утилиты, а в худшем случае -- свою реализацию каждой функцию из DLL
или прямо зашитую в бинарник.
В UNIX таким бинарником-интегратором является shell (Emacs считается
shell'ом в данном случае). Для выполнения каждой функции вызываются
специально написанные динамически выполняемые модули, такие как make, cc,
ld, и т. д.
Преимущество в этом такое же, как преимущество математических функций
высшего порядка перед "обычными" функциями.
Например, функция "отслеживать зависимости" чаще всего реализуется с
помощью make, но можно также легко использовать, скажем, cook, или же
переключаться между GNU Make и BSD Make по вкусу. Точно такая ситуация с
используемыми редактором, компилятором, etc. Более того, сам по себе shell
является "функцией высшего порядка", и легко может быть заменен.
Кроме того, так как пространство функций практически неограниченно, то IDE
"Unix" обеспечивает также заранее не предусмотренные функции высшего
порядка, например, различную автогенерацию кода, поддержку тестирования и
т. п.
A: (Valentin Nechayev, скомпилировав ответы разных участников)
В принципе, это не Unix way ;) На все варианты IDE все равно не наклепаешь,
поэтому есть более другие средства, из которых можно собрать себе аналог
специфического IDE. Надо понимать, что IDE как таковой не даст, например,
возможность собрать программу на другой платформе - для этого нужны make,
autotools и тому подобные средства, а продукция разных IDE обычно
несовместима с ними.
Краткое перечисление существующих IDE:
vim + ctags + скрипты с vim.sf.net :)
[x]emacs :)
KDevelop (разработка под иксы и Qt)
CodeWarrior
CodeForge
Eclipse
fte :)
Anjuta
Там, где стоят смайлики - это не IDE в привычном виндовом понимании,
а рабочие среды с функциями редактирования текстов, вызова компиляторов,
парсинга ошибок компиляции с выделением проблемных частей кода,
полезных вспомогательных средств (code browsers) и прочая и прочая.
vim и emacs являются первыми двумя широко используемыми и имеющими обширную
community.
--------------------------------
>Q: а у меня зомби плодятся, в списке процессов <defunct>
A: http://www.faqs.org/faqs/unix-faq/programmer/faq/, и не говорите, что Вас
не предупреждали.
Пример кода там не совсем правилен - см. следующий вопрос.
--------------------------------
>Q: Дочерние процессы становятся зомби, несмотря на то, что есть
> обработчик сигнала SIGCHLD, вызывающий wait/waitpid/wait3/wait4.
> Что не так?
A:
Сигнал SIGCHLD, как и все остальные сигналы основной (не realtime) группы,
может накапливаться так, что обработчик вызывается только один раз
независимо от того, сколько за это время сигнал был послан. В ядре в этом
случае просто ставится флажок, что надо вызвать обработчик; этот флажок
снимается при вызове обработчика. Поэтому на каждый вызов SIGCHLD надо
предполагать, что успело завершиться несколько процессов. Поэтому код
обработчика должен содержать цикл "пока еще есть завершившиеся процессы",
например:
for(;;) {
pid = waitpid( -1, &istatus, WNOHANG );
if( pid == -1 && errno == EINTR ) continue;
if( pid <= 0 ) break;
<<учесть этот pid>>
}
В примере в Unix Programming FAQ записан одиночный if, это неправильно.
Ну и проверьте, что обработчик SIGCHLD не заблокирован и действительно
вызывается, а то мало ли чего вы там накрутили в sigaction и sigprocmask ;-|
--------------------------------
>Q: Как переносимо обеспечить сборку разделяемой библиотеки?
A: Для большинства установок достаточно того, что умеет libtool.
Имеет смысл рассмотреть использование полного комплекта GNU autotools
(см. например "Building a Shared Library" в info automake),
хотя libtool может работать и самостоятельно.
--------------------------------
>Q: Чем плохо и чем хорошо использовать threads?
A:
Вопрос почти религиозный. У тредов есть и преимущества, и недостатки.
Стивенс рассматривает ряд моделей работы множества процессов и тредов,
рассмотрение можно начать с изучения сделанного ним анализа.
См. также приложение "как писать сервера"
--------------------------------
>Q: Как писать сервера?
Ответ в приложении.
--------------------------------
>Q: Пишу демона, компилирую, запускаю - на запускается с диагностикой
> bind: Can't assign requested address ...
A: (Snar) А вы структуру sockaddr_in перед заполнением нулями забили ? :)
A: (Netch) Вообще-то лучше все структуры заливать нулями перед их заполнением.
Иногда тако-о-ое вылазит, когда не ждешь...
Еще пример, кроме sockaddr_in, где обязательна заливка нулями - DBT
для Berkeley DB версий 2 и выше.
--------------------------------
>Q: Пишу демона, если демон перезапускается - не может сделать bind()
A: Смотреть в сторону setsockopt с опциями SO_REUSEADDR, SO_REUSEPORT.
SO_REUSEPORT есть не везде.
--------------------------------
>Q: Хочу ждать в треде одновременно mutex, event и поступление данных в пайп.
A:
Штатных средств нет. Лучше всего сделать свою прослойку, которая будет
для каждого треда держать сигнальный пайп и по, например, освобождению
mutex'а, кидать байтик в передающий конец пайпа той ветки, которой отдается
мьютекс. Есть несколько готовых прослоек такого рода.
Ну а несколько файловых объектов уже штатным образом ожидаются через
select/poll/kqueue.
--------------------------------
>Q: Чем ловить утечки памяти в программе на C:
A:
http://www.cs.colorado.edu/homes/zorn/public_html/MallocDebug.html (внести
поправку на завышенную оценку dmalloc)
valgrind:
очень мощное средство, только для Linux/x86.
dmalloc:
Ловит только ошибки записи только при проверке heap, обычно
каждая n-я операция malloc/free, но можно вызвать проверку явно.
При включении проверок на каждую операцию изрядно тормозит.
Имеет смысл использовать только для отлова memory-leaks.
Hеочевидно конфигурится. Использование без чтения доки (каждый раз заново:))
проблематично..
Работает на всём.
ccmalloc:
Выпадает при неверном обращении к памяти, ищет leak-и.
Функциональность почти как у ElectricFence + dmalloc.
Достаточно удобен. Чтение мануала практически не требуется.
Congif удобен, хорошо откомментирован.
Заявлено что работает в Linux и Solaris, я пробовал только в Linux. (Eugene
Exarevsky)
Появился относительно недавно.
LeakTracer
ElectricFence:
Hе умеен ловить утечки памяти.
Фактически всё что умеет - выпасть в кору при обращении к неверной странице.
Hе требует конфигурирования.
Достаточно быстро работает.
Практически на всём (я лично пробовал в Sinix, Irix, Linux - Eugene Exarevsky)
memprof
BoundsChecker
mprof
Insure (платный)
dbx (на Sparc)
YAMD:
- Он маленький и в случае чего нетрудно будет разобраться
где и что не так.
- Нет необходимости в перекмпиляции/линковки программ для вылавливания
багов при работе с памятью по крайней мере на платформах, где есть
LD_PRELOAD (ELF).
(Продолжение рассказа про YAMD отдельно)
njamd
довольно нормально работает с тредами. (Andrey Melnikov)
--------------------------------
>Q: Надо ли проверять код возврата close()? А если вернется -1 и EINTR?
A:
Надо. Проблемы возникают редко, но возникают.
Например, может прерваться происходящий по close() сброс буферов на NFS.
Для fclose() это еще более характерно.
--------------------------------
>Q: А как бы узнать, нажали ли кнопочку на клавиатуре?
A:
man curses; man curses_getch
Если curses неприменима - man termios
--------------------------------
>Q: Хочу таймеры. Man что?
A:
sleep
usleep
nanosleep
select
poll
(два последних - с пустыми наборами дескрипторов)
alarm
setitimer
eventlib
sleep, alarm - с точностью до секунды, остальные - точнее.
При необходимости точности более нескольких миллисекунд - бросать штатные
средства и искать realtime.
--------------------------------
>Q: Как писать оконные интерфейсы переносимо между Windows и Unix?
>Какие toolkits для этого можно применять?
A: (Victor Wagner, выдрано из письма на немного другую тему)
Информации о кроссплатформной переносимости тулкитов (возможности работы в
Windows и MacOS) я не привожу специально, так как считаю что писать
кроссплатформные GUI в большинстве случаев вредно. GUI должен быть удобен
пользователю. Пользователям Unix и оболочек дешевых удобно разное. Исключения
бывают, но редко (вертикальные рынки).
A: (с эхи по нитке)
fox-toolkit
FLTK
wxWindows
Qt
gtk (gtk+)
все - уродцы те еще...
Tk - не уродец ;)
--------------------------------
>Q: Включает ли -Wall в gcc ключ -W?
A: Не включает. В документации к 3.2 это хорошо видно, в 2.95 - вскользь.
BTW, начиная с gcc 3.4 -W будет называться -Wextra.
RU.UNIX.PROG FAQ - приложение 1 (Как писать сервера)
>Q: Как писать сервера?
A: (Lev Walkin, Dmitri Lenev, множественные дополнения, особенно от
Igor Sysoev и Igor Khasilev)
Возможны следующие варианты:
1. Сервер может использовать однопросессную FSM (Finite State Machine)
архитектуру, используя те или иные методы получения данных о состоянии
сокетов (select, poll, etc).
Плюсы:
+ Очень эффективный метод с точки зрения CPU.
Минусы:
- Не масштабируется с ростом числа процессоров.
- Серверные FSM, как правило, достаточно сложны и
требуют тщательного подхода при проектировании.
- в случае если обработка данных пришедших по соединению
требует долгой (в частности блокирующей) операции, то на
время выполнения этой операции невозможно обрабатывать
другие соединения. Соответственно возникают проблемы с
задержкой обработки запросов...
Проблема в том что:
а) Например в случае ввода вывода на диск, неблокирующий ввод-вывод
по select/poll не всегда поддерживается...
б) даже если мы пользуемся другим механизмом не обладающим данным
недостатком, например kqueue, или aio, то нам все равно может быть
не доступна напрямую работа с файлом. Ну например есть библиотека
для работы с СУБД и нет возможности залезть в ее внутренности
чтобы получить файловые дескрипторы соответствующие соединениям с
сервером СУБД.
в) даже если мы имеем полный контроль над вводом выводом то может
возникать потребность в долгих вычислениях (то есть затык в
занятости процессора)... Ну можно конечно вручную пытаться
квантовать работу но это не всегда удобно...
В принципе все три проблемы можно решить используя для выполнения
длительных или блокирующих операций slave процессы или нити
делая их тем самым не блокирующими. В принципе про данный подход
можно посмотреть здесь: http://www.cs.princeton.edu/~vivek/flash99/
(Dmitri Lenev)
+ По собственному опыту могу сказать что имея скажем проработанную
библиотеку классов писать сервера на FSM достаточно легко...
Примеры реализации:
- innd
- squid (с ufs хранилищем)
- named (с поправкой на протокол UDP для большинства передач)
Пример реализации со slave процессами для блокирующих операций:
- squid с diskd
Пример реализации со slave тредами для блокирующих операций:
- squid с aufs
2. Сервер может использовать несколько процессов, каждый из которых
обслуживает собственного клиента:
Плюсы:
+ Простая модель данных
+ Скалируется с ростом числа процессоров.
+ Ошибки в одном процессе не приводят к отказу в
обслуживании остальных клиентов.
Минусы:
- Процесс - это достаточно тяжелый объект OS, поэтому
метод неприменим при большом количестве одновременно
работающих клиентов (больше нескольких десятков или
сотен).
- Несмотря на масштабируемость, модель очень тяжела
и в среднем гораздо менее эффективна, чем предыдущая.
Примеры реализации:
- Большинство MTA (sendmail, postfix, exim, qmail)
- Традиционные попперы (qpopper, cucipop, popa3d)
- И другие традиционные unix'овые сервисы (telnetd, rlogind, nnrpd,...)
У всех перечисленных выше время жизни процесса - время обслуживания клиента.
- apache 1.*
У apache - процессы форкаются заранее, процесс может жить
неограниченное время.
3. Сервер может использовать небольшое число процессов, каждый из
которых имплементирует FSM (a la пункт 1).
Плюсы:
+ Если уже имеется система по типу #1, то перевод ее
на рельсы системы #3 как правило, достаточно простой.
Это дает возможность сделать систему скалируемой за
счет очень небольших усилий.
Минусы:
- Все равно придется делать полную FSM.
4. Сервер - процесс, использующий нити (threads) для каждого клиента
(сокета).
Плюсы:
+ Небольшая сложность разработки, похожа на #2.
Требуется проработка механизмов защиты общих данных.
+ В зависимости от OS, модель может быть и масштабируемой,
и эффективной (Solaris, HP-UX).
Минусы:
- В зависимости от OS, модель может быть как неэффективной (Linux,
так как нить "весит" почти столько же, сколько и процесс), так и
не масштабируемой с ростом числа процессоров (FreeBSD
с user-space threads).
- (Igor Khasilev) Если планируется обслуживать одновременно большое число
подключенных клиентов (от тысячи и выше в зависимости от ОС) эта модель
может оказаться нерабочей по причинам: расход адресного пространства
на стек для каждой нити, большая нагрузка на планировщик и
ограничение на общее число нитей в системе (особенно в случае
1:1 модели). Иногда может спасти экстенсивный путь - переход на
64-битные платформы.
- Существенно затрудняется отладка.
Примеры:
- Oops! 1.*
- apache 2.*
- CommunigatePro (исходный код недоступен, но в Usenet можно найти много
деталей устройства с авторским описанием)
5. Сервер - процесс, использующий небольшое количество нитей, каждая
из которых обслуживает некоторое количество сокетов одновременно.
Плюсы:
+ На архитектурах с kernel-threads (Linux, Solaris)
обладает масштабируемостью и очень эффективна.
Минусы:
- Требуется разработка FSM по типу #1, плюс разработка
разграничения доступа к общим данным (#4).
- Не приносит масштабируемости на некоторых имплементациях
потоков (FreeBSD), поэтому в целом несколько менее
эффективна, чем #1.
6. Несколько процессов, каждый из которых поддерживает нескольких
клиентов путем выделения по потоку на клиента или методом #5.
Плюсы:
+ Система защищена от неустранимых сбоев при
обработке одного клиента, так как остаются работать
остальные процессы.
+ Система масштабируется с ростом числа процессоров.
Минусы:
- Очевидно, складывается сложность всех перечисленных
выше методов.
- Не предоставляет преимуществ перед #3 на одном
процессоре.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Некоторые методы получения состояния (активности) сокета
(файлового дескриптора):
Плюсы select():
+ Широкая портабельность.
+ Очень эффективен при относительно небольшом числе одновременно
активных сокетов (передача в ядро и назад по три бита на сокет).
Минусы select():
- На многих платформах максимальное ограничение на 1024 (иногда
другое) файловых дескрипторах не обходится без
перекомпилирования приложения или даже ядра системы (для
FreeBSD не нужно перекомпилировать ядро, только приложение).
- При большом количестве неактивных клиентов передача в ядро и
назад пустого состояния сокета представляет собой сплошные
накладные расходы.
Плюсы poll():
+ Не содержит имманентного ограничения на максимальное
количество обслуживаемых сокетов.
+ Достаточно широкая портабельность.
Минусы poll():
- Менее эффективен, чем select() (так как передает в ядро
и назад по восемь байт на сокет) (Реализация в Linux
использует особенно тормозной алгоритм обхода данных в poll())
Плюсы /dev/poll (Последние Solaris, патчи для Linux):
+ Не имеет ограничения на максимальное количество обслуживаемых
сокетов.
+ Из-за модели передачи event'ов вместо модели передачи
состояний, очень эффективен при обслуживании большого количества
клиентов, только часть из которых очень активна (типичная
ситуация для web- и другого вида серверов). Состояния неактивных
сокетов не вызывают расхода ресурсов.
Минусы /dev/poll:
- Не портабелен.
- Неадекватно реагирует на закрытие дескриптора, занесённого в список
контроля и не вынесенного оттуда перед закрытием. Дескриптор остаётся
в списке (числясь своим номером). Поэтому перед закрытием надо
обязательно выносить дескриптор из списка.
Ещё про /dev/poll см. http://soldc.sun.com/articles/polling_efficient.html
Плюсы kqueue/kevent (FreeBSD, OpenBSD):
+ Не имеет ограничения на максимальное количество обслуживаемых
сокетов.
+ Имеет "вкусные фичи", которые позволяют использовать его
более эффективным образом не только для сокетов, но и для
объектов другого типа (файлов, процессов).
+ Из-за модели передачи event'ов вместо модели передачи
состояний, очень эффективен при обслуживании большого количества
клиентов, только часть из которых очень активна (типичная
ситуация для web- и другого вида серверов). Состояния неактивных
сокетов не вызывают расхода ресурсов.
+ (Igor Sysoev) kqueue/kevent эффективнее, чем /dev/poll.
Минусы:
- Не портабелен.
- Линус Торвальдс его не любит. (См.
http://www.uwsg.iu.edu/hypermail/linux/kernel/0010.3/0013.html; впрочем,
epoll повторяет тот же "silly" триплет)
Плюсы realtime signals (sigtimedwait,
http://www.kegel.com/c10k.html#nb.sigio, Linux 2.4+):
+ Не имеет ограничения на максимальное количество обслуживаемых
дескрипторов. (Однако, количество сигналов ограничено, и если дескрипторы
группировать по сигналам, внутри группы придется опрашивать все
дескрипторы.)
Минусы realtime signals:
- Есть в слишком малом количестве систем.
- Очередь сигналов может переполняться. (Linux в этом случае даёт SIGIO,
что означает необходимость итерирования всех дескрипторов. Но это лучше,
чем замалчивание переполнения очереди.)
- Хуже kqueue/kevent - только один сигнал обрабатывается за раз; kevent()
может принять и передать несколько событий за один вызов.
Плюсы epoll (Linux 2.5.44+):
+ Не имеет ограничения на максимальное количество обслуживаемых
сокетов.
+ Из-за модели передачи event'ов вместо модели передачи
состояний, очень эффективен при обслуживании большого количества
клиентов, только часть из которых очень активна (типичная
ситуация для web- и другого вида серверов). Состояния неактивных
сокетов не вызывают расхода ресурсов.
Вообще, epoll похож на kevent. 4-я версия научилась level triggering
в дополнение к edge triggering (что уже умела kqueue/kevent).
Минусы epoll:
- Не портабелен - только Linux и только после 2.5.44.
- Слабее по возможностям чем kqueue/kevent (нет наблюдения за процессами,
файлами, таймерами, завершениями AIO запросов; только одно событие
на входе и выходе системного вызова).
Еще по epoll см. http://www.uwsg.iu.edu/hypermail/linux/kernel/0210.3/1164.html
Поднятые здесь вопросы также обсуждаются в документе по адресу:
http://www.kegel.com/c10k.html
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Еще стоит посмотреть в сторону D.C. Schmidt'овкого ACE и JAWS,
если не в сторону первого так в сторону последнего как теоретически -
экспериментального исследования...
Приложение 2. YAMD
Отслеживание проблем с распределением памяти при помощи YAMD
YAMD. Рассказ от Aleksey Cheusov
Поскольку valgrind у меня иногда вылетает, я использую вместо него
YAMD+efence. Но хочу сказать, что YAMD не обязательно самый продвинутый.
Просто мне в своё время надоело искать и я остановился на том,
что меня устраивает и мои поиски закончились.
YAMD:
- Он маленький и в случае чего нетрудно будет разобраться
где и что не так.
- Нет необходимости в перекмпиляции/линковки программ для вылавливания
багов при работе с памятью по крайней мере на платформах, где есть
LD_PRELOAD (ELF).
- В нем несколько программ, но полезная ДЛЯ МЕНЯ только одна. run-yamd
Пример:
run-yamd -o /tmp/dictd.mem ./dictd --test apple
Это дело вываливает мне простыню типа
<бесполезная шапка>
INFO: Normal allocation of this block
Address 0x40023fd4, size 44
Allocated by malloc at
/lib/libc.so.6(malloc+0x2f)[0x400c9eaf]
./dictd(strcpy+0xcbf7)[0x8057133]
./dictd(strcpy+0xcda8)[0x80572e4]
./dictd(strcpy+0xce4c)[0x8057388]
./dictd(strcpy+0x10c1f)[0x805b15b]
./dictd(strcpy+0x10c44)[0x805b180]
./dictd(strcpy+0x108fb)[0x805ae37]
./dictd(strcpy+0x1a36)[0x804bf72]
./dictd(strcpy+0x1ca2)[0x804c1de]
/lib/libc.so.6(__libc_start_main+0xbe)[0x400737ee]
./dictd(strcpy+0x35)[0x804a571]
INFO: Normal deallocation of this block
Address 0x4018eff0, size 16
Allocated by malloc at
/lib/libc.so.6(malloc+0x2f)[0x400c9eaf]
./dictd(strcpy+0xcbf7)[0x8057133]
./dictd(strcpy+0xcf64)[0x80574a0]
./dictd(strcpy+0xd0cc)[0x8057608]
./dictd(strcpy+0x10125)[0x805a661]
./dictd(strcpy+0x10919)[0x805ae55]
./dictd(strcpy+0x1a36)[0x804bf72]
./dictd(strcpy+0x1ca2)[0x804c1de]
/lib/libc.so.6(__libc_start_main+0xbe)[0x400737ee]
./dictd(strcpy+0x35)[0x804a571]
Freed by free at
/lib/libc.so.6(__libc_free+0x2f)[0x400ca92f]
./dictd(strcpy+0xccba)[0x80571f6]
./dictd(strcpy+0xce90)[0x80573cc]
./dictd(strcpy+0xd063)[0x805759f]
./dictd(strcpy+0x10125)[0x805a661]
./dictd(strcpy+0x1092b)[0x805ae67]
./dictd(strcpy+0x1a36)[0x804bf72]
./dictd(strcpy+0x1ca2)[0x804c1de]
/lib/libc.so.6(__libc_start_main+0xbe)[0x400737ee]
./dictd(strcpy+0x35)[0x804a571]
WARNING: Free of null pointer
At
/lib/libc.so.6(__libc_free+0x2f)[0x400ca92f]
/lib/libc.so.6(_dl_close+0x57b)[0x4015682b]
/lib/libdl.so.2(dlopen+0x6b)[0x4017e4cb]
??:0(??)[0x4000b5a0]
/lib/libdl.so.2(dlerror+0x290)[0x4017e8b0]
/lib/libdl.so.2(dlclose+0x21)[0x4017e4f1]
/usr/lib/libltdl.so.3(lt_dlseterror+0x302)[0x4002bb82]
/usr/lib/libltdl.so.3(lt_dlclose+0xb9)[0x4002e5a9]
./dictd(strcpy+0xb7f6)[0x8055d32]
./dictd(strcpy+0x1119)[0x804b655]
./dictd(strcpy+0x1333)[0x804b86f]
./dictd(strcpy+0x2505)[0x804ca41]
/lib/libc.so.6(__libc_start_main+0xbe)[0x400737ee]
./dictd(strcpy+0x35)[0x804a571]
Attempt to free null pointer
WARNING: Memory leak
Address 0x42770fe4, size 28
Allocated by malloc at
/lib/libc.so.6(malloc+0x2f)[0x400c9eaf]
/lib/libc.so.6(textdomain+0x62a)[0x40081f4a]
/lib/libc.so.6(textdomain+0x79e)[0x400820be]
/lib/libc.so.6(textdomain+0x79e)[0x400820be]
/lib/libc.so.6(ngettext+0x217)[0x40080b07]
/lib/libc.so.6(gettext+0x845)[0x4007fd55]
/lib/libc.so.6(__dcgettext+0x2d)[0x4007f4cd]
/lib/libc.so.6(__strerror_r+0x132)[0x400cf8b2]
/lib/libc.so.6(strerror+0x2b)[0x400cf76b]
/lib/libdl.so.2(dlerror+0xbf)[0x4017e6df]
/usr/lib/libltdl.so.3(lt_dlseterror+0x395)[0x4002bc15]
/usr/lib/libltdl.so.3(lt_dlsym+0x30f)[0x4002e96f]
./dictd(strcpy+0xb4f6)[0x8055a32]
./dictd(strcpy+0xb6c0)[0x8055bfc]
./dictd(strcpy+0xeea)[0x804b426]
./dictd(strcpy+0xecf2)[0x805922e]
./dictd(strcpy+0x12e5)[0x804b821]
./dictd(strcpy+0x24b4)[0x804c9f0]
/lib/libc.so.6(__libc_start_main+0xbe)[0x400737ee]
./dictd(strcpy+0x35)[0x804a571]
WARNING: Total memory leaks:
2008 unfreed allocations totaling 91268 bytes
*** Finished at Wed Dec 4 12:36:04 2002
Allocated a grand total of 418487 bytes
2410 allocations
Average of 173 bytes per allocation
Max bytes allocated at one time: 398226
29176 K alloced internally / 11744 K mapped now / 10972 K max
Virtual program size is 32592 K
End.
- Выше только адреса. Для получения по ним имена функций со строками
нужно воспользоваться do-syms:
do-syms ./dictd < /tmp/dictd.mem > /tmp/dictd.mem2
Получаем что-=то вроде
INFO: Normal allocation of this block
Address 0x40023fd4, size 44
Allocated by malloc at
/lib/libc.so.6(malloc+0x2f) ??:0
./dictd(strcpy+0xcbf7) /shared/cheusov/prjs/dictd1/libmaa/xmalloc.c:40
./dictd(strcpy+0xcda8) /shared/cheusov/prjs/dictd1/libmaa/hash.c:80
./dictd(strcpy+0xce4c) /shared/cheusov/prjs/dictd1/libmaa/hash.c:127
./dictd(strcpy+0x10c1f) /shared/cheusov/prjs/dictd1/libmaa/timer.c:47
./dictd(strcpy+0x10c44) /shared/cheusov/prjs/dictd1/libmaa/timer.c:57
./dictd(strcpy+0x108fb) /shared/cheusov/prjs/dictd1/libmaa/maa.c:36
./dictd(strcpy+0x1a36) /shared/cheusov/prjs/dictd1/dictd.c:925
./dictd(strcpy+0x1ca2) /shared/cheusov/prjs/dictd1/dictd.c:1064
/lib/libc.so.6(__libc_start_main+0xbe) ??:0
./dictd(strcpy+0x35) ??:0
do-syms использует addr2line.
НО Я ПРЕДУПРЕЖДАЮ, ЧТО РОДНАЯ ВЕРСИЯ do-syms на Linux НЕ РАБОТАЕТ.
Поэтому я написал свою на shell:
http://www.mova.org/~cheusov/pub/do-syms
Если программа слинкована динамически, то addr2line
обламывается. Можно прикрутить к do-syms gdb с breakpoint на _init
и эти проблемы решаются. В ближайшее время этим займусь.
- ВСю необходимую мне статистику я лично собираю скриптами.
Memory leaks собираю, например,
awk '/Memory leak/, /^$/' < /tmp/dictd.mem2
Опять же скриптами можно отсеить memory leaks системных функций,
например setlocale в glibc2.2.
Вот собственно и все.
Функциональности - минимум. Все пишется скриптами.
Приложение S: ПРАВИЛА ИГРЫ В СИГНАЛЫ UNIX
Основной текст написал: Yar Tikhiy <yar@comp.chem.msu.su>
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Предисловие
Если судить по исходникам популярных программ и обсуждениям Usenet
за последние лет 20, то обработка сигналов Unix -- это игра, в
которой большинство участвует, так и не удосужившись выучить правила.
Программисты привыкли, что если код не засбоит хотя бы один раз,
то он будет работать во всех случаях. Сигналы оказались для них
хорошо замаскированной ловушкой, из которой всем нам предстоит еще
долго выкарабкиваться (о последствиях неаккуратной работы с сигналами
для безопасности см. статью "Delivering Signals for Fun and Profit":
http://razor.bindview.com/publish/papers/signals.txt).
Академическое сообщество пользователей Unix всегда поощряло
"экспериментальный" стиль программирования, когда во главу угла
ставится сам факт реализации той или иной возможности, а не качество
решения. В 1986 году даже почтенный Chris Torek допускал высказывания
вроде этого: "Хотя вызывать exit(3) из обработчика сигнала не вполне
надежно, на практике это не создает никаких проблем". Впрочем, сам
мистер Torek отрекся от подобной ереси к началу 1990-х; зато
недостатка в программистах, обрабатывающих сигналы по принципу
"авось пронесет", и книгах, дающих сомнительные советы, не ощущается
до сих пор.
Что удивительно в такой ситуации, правила "игры в сигналы" на самом
деле весьма просты. Поэтому хотелось бы их четко сформулировать и
указать на основные следствия из них. Для начала я попытаюсь это
сделать для случая традиционных сигналов POSIX в однонитевой среде.
Комментарии и дополнения приветствуются.
ПРАВИЛА ИГРЫ В СИГНАЛЫ UNIX
Правило 1
Посланный процессу сигнал может быть проигнорирован, заблокирован,
или доставлен. Под доставкой понимается действие по умолчанию или
вызов назначенного обработчика. Рассмотрим эти случаи по отдельности.
1. Если сигнал игнорируется, то он не оказывает никакого воздействия
на процесс. В частности, не прерываются по EINTR прерываемые
операции.
2. Если сигнал блокируется, то его обработчик будет вызван один раз
(независимо от того, сколько раз был послан сигнал) после снятия
блокировки.
3. Если сигнал обрабатывается по умолчанию как "отвергнуть сигнал"
(discard signal), то это полностью эквивалентно его игнорированию
(см. п. 1.1). В некоторых руководствах это действие явно именуется
"ignore". Действиями по умолчанию, в зависимости от типа сигнала,
могут также быть "завершить процесс" (возможно, с записью образа
памяти), "приостановить процесс" и "продолжить процесс".
4. Если процесс назначил сигналу собственный обработчик и сигнал
должен быть доставлен, то будет вызван этот обработчик.
Если у сигнала установлен через sigaction(2) флаг SA_RESETHAND,
то эмулируется поведение SysV signal(): обработчик сигнала будет
установлен равным SIG_DFL (обработка по умолчанию) в момент
доставки, т.е. непосредственно перед вызовом текущего
обработчика.
На время вызова обработчика текущий сигнал блокируется,
если не установлен флаг SA_NODEFER (то есть, сигнал добавляется
к маске, описанной в sa_mask). Это необходимо, чтобы
избежать зацикливания обработчика.
Обработчику передается номер текущего сигнала, что позволяет
использовать один обработчик для нескольких сигналов. Возможна
передача дополнительной информации (см. SA_SIGINFO в SUS).
Между посылкой сигнала и его доставкой может пройти
неограниченное количество времени, даже если сигнал не
заблокирован. Следовательно, один и тот же сигнал может
быть послан процессу несколько раз перед тем, как он будет
доставлен. Тем не менее, обработчик будет вызван единожды,
так как система запоминает только сам факт посылки каждого
сигнала.
Правило 2
Любая операция может быть временно прервана вызовом обработчика
асинхронного сигнала, если на момент ее выполнения сигнал не
заблокирован и не игнорируется.
Сигналы посылаются и доставляются асинхронно в подавляющем
большинстве случаев. Существует, по сути, лишь один случай
синхронной посылки сигнала: когда процесс посылает сигнал
сам себе, с помощью функций abort(3) или raise(3). Согласно
C99 и SUSv3, если в ответ на raise(3) должен быть вызван
обработчик сигнала, raise(3) может вернуть управление только
после того, как это сделает обработчик.
В системах, совместимых с POSIX, вызов функции
raise(signo);
должен быть эквивалентен вызову конструкции:
kill(getpid(), signo);
Значит, в POSIX такую конструкцию можно рассматривать как
еще один способ синхронной посылки сигнала. Раздел SUSv3,
посвященный функции kill(), подробно обсуждает посылку
процессом сигнала самому себе и указывает, что в этом случае
сигнал должен быть доставлен до того, как kill() вернет
управление.
Правило 3
Существует ровно один тип статических данных, sig_atomic_t, переменную
которого может установить обработчик асинхронного сигнала. Поведение
приложения не определено, если асинхронно вызванный обработчик
обращается к статическим данным любым другим способом.
По-видимому, это весьма жесткое ограничение связано с
поддержкой архитектур, в которых обработчик асинхронного
сигнала не может напрямую обращаться к основной памяти
процесса.
Правило 4
Если обработчик асинхронного сигнала завершается возвратом, то
текущая операция может быть продолжена или прервана, в зависимости
от типа операции и значения флага SA_RESTART для этого сигнала.
Есть несколько случаев.
1. Если для текущего сигнала не установлен флаг SA_RESTART, то
некоторые системные вызовы будут прерваны с ошибкой EINTR. Если
же флаг SA_RESTART установлен, то выполнение этих вызовов будет
продолжено.
Список прерываемых вызовов может быть приведен в sigaction(2);
это вызовы ввода-вывода и wait(2). SUSv3 такого списка не
приводит, однако говорит, что сигнал без SA_RESTART прерывает
любой вызов, который может возвращать ошибку EINTR.
2. Функции семейства sleep (sleep(3), nanosleep(2) и т.п.) будут
прерваны любым доставленным сигналом, вне зависимости от наличия у
него флага SA_RESTART.
3. Вызов connect(2) в блокирующем режиме будет перван любым
доставленным сигналом, независимо от его флагов, однако сама операция
установки соединения будет продолжена в асинхронном режиме. Об
окончании операции можно узнать, передав дескриптор в poll(2) или
select(2): он будет помечен как готовый к записи.
4. Вызовы select(2), pselect(2) и poll(2) могут быть прерваны
сигналом.
Согласно SUSv3, poll(2) прерывается любым доставленным
сигналом; будут ли select(2) и pselect(2) учитывать флаг
SA_RESTART, определяет реализация.
5. Выполнение кода процесса и остальных системных вызовов будет
продолжено.
Следствия _для асинхронных сигналов_
1. [Из п. 2] Из обработчика сигнала нельзя выполнять общий c другими
частями процесса нереентерабельный участок кода, если он не защищается
всякий раз путем блокировки соответствующих сигналов.
Здесь нереентерабельность понимается в широком смысле как
зависимость по статическим данным, требующим сериализации
доступа. Таким образом, нереентерабельной может быть как
отдельная функция, так и целая библиотека. Если несколько
участков кода обращаются к общим данным, требующим сериализации,
то все они будут взаимно нереентерабельны. В англоязычной
литературе есть более точный термин "async-signal-safe", то
есть, безопасный по отношению к асинхронным сигналам.
2. [Из п. 2, с. 1] Из обработчика сигнала нельзя вызывать
нереентерабельные библиотечные функции (н.б.ф.), если только
устройством программы не гарантируется, что на момент _каждого_
вызова _любой_ н.б.ф. все сигналы, чьи обработчики содержат вызовы
_любых_ н.б.ф., не будут заблокированы.
Так как внутренние зависимости между н.б.ф. полностью зависят
от реализации, вложенный вызов _любой_ н.б.ф. может привести
к неопределенному поведению _всех_ н.б.ф. К примеру,
изрядное количество библиотечных функций явно или неявно
вызывают malloc(3); так что разрушение структур malloc(3)
в результате вложенного вызова приведет к сбою многих б.ф.
Стандарт C99 утверждает, что из обработчика асинхронного
сигнала можно вызывать только abort(3), _exit(2), _Exit(),
а также signal(3) с первым аргументом, равным номеру текущего
сигнала [C99 #7.14.1.1]. Стандарты POSIX и SUS приводят
довольно объемистый список реентерабельных функций. В
прочих системах этот список, очевидно, определяется
реализацией.
3. [Из п. 1.4, п. 2] Выполнение обработчика сигнала может быть
прервано очередным доставленным сигналом. Чтобы избежать прерывания,
необходимо заблокировать все или некоторые сигналы на время работы
чувствительного участка в обработчике. Проще всего это делать,
указав маску sa_mask в параметре sigaction(2); эта маска будет
атомарно установлена на входе в обработчик, а на выходе из него
будет восстановлено предыдущее значение маски сигналов.
4. [Из с. 1, с. 3] Если один нереентерабельный обработчик установлен
для нескольких сигналов, то на время работы нереентерабельного участка
нужно блокировать все эти сигналы.
5. [Из п. 2, п. 3] Необходимо использовать модификатор volatile,
чтобы указать компилятору на асинхронность изменения переменной типа
sig_atomic_t.
6. [Из п. 3] Не гарантируется, что обработчик сигнала может читать
статическую переменную, даже если она -- типа sig_atomic_t.
Учитывая исторически сложившуюся практику, можно считать
чтение переменной типа sig_atomic_t из обработчика сигнала
ограниченно переносимым.
7. [Из п. 2, п. 3] Можно использовать только простое присваивание
переменным sig_atomic_t. В частности, над ними не следует использовать
операции ++ и --. Это касается как обработчиков сигналов, так и
основного потока (конечно, если соответствующие сигналы не заблокированы
на момент операций с переменной типа sig_atomic_t).
В архитектуре RISC нет атомарных арифметических операций над
ОЗУ. В архитектуре CISC они есть, но компилятор не обязан их
использовать, например, из соображений оптимизации.
8. [Из п. 3] Если стандартная библиотечная функция, вызванная из
обработчика сигнала, вернула ошибку, то значение переменной errno
может быть не определено.
9. [Из п. 2] Вызов даже реентерабельной библиотечной функции из
обработчика сигнала может привести к изменению значения переменной
errno. Следовательно, если обработчик вызывает б.ф., то он должен
вначале сохранить значение errno, а перед возвратом восстановить его.
Правило 3 и следствие 8 исключают следствие 9. Проблема
здесь в том, что современные стандарты признают исторически
сложившуюся практику обращения к статическим переменным
вопреки правилу 3, но крайне не поощряют ее. Пока что это
приводит к подобным противоречиям.
10. [Из п. 2, п. 3, с. 1] "Трюк" с возвратом из обработчика сигнала
через longjmp(3), кочующий из книги в книгу (даже W.R.Stevens
приводит его), следует использовать с большой осторожностью.
Во-первых, longjmp(3) не восстановит статических переменных libc и
структуру кучи malloc(3) на момент вызова setjmp(3), а значит, он
ничем не поможет в решении проблемы реентерабельности. Во-вторых,
вызов longjmp(3) из обработчика обладает ограниченной переносимостью.
Изначально longjmp(3) из обработчика сигнала использовался,
чтобы обойти особенность 4.2BSD. Системные вызовы 4.2BSD
всегда продолжались после обработки сигнала, если обработчик
возвращал управление. Приходилось совершать longjmp(3),
чтобы прервать системный вызов.
Еще одно историческое применение longjmp(3) из обработчика
состояло в обходе изьяна pause(2). В системах без sigsuspend(2)
или sigpause(2) невозможно было гарантировать, что сигнал
не придет до вызова pause(2). Если сигнал посылался
однократно, как SIGALRM по истечению таймера, то приложение
могло "зависнуть". Для решения этой проблемы управление
из обработчика явно передавали через longjmp(3). W.R.Stevens
приводит пример использования longjmp(3) в обработчике
именно для этого случая.
Существует как минимум одна среда, претендующая на совместимость
с POSIX, в которой выход из обработчика сигнала через
longjmp(3) приводит к сбою приложения. Речь идет о Win32
и сигнале SIGINT. Этот сигнал посылается консольному
приложению при нажатии ^C и доставляется в специально
отведенную нить, даже если приложение спроектировано как
однонитевое. Конечно, этот прецедент может показаться
далеким от реалий Unix. Однако он иллюстрирует разнообразие
особенностей, с которыми можно столкнуться в POSIX-совместимых
системах.
11. [Из п. 2] Блокировка сигналов часто должна выполняться неразрывно
с другой операцией. Для этого существует ряд стандартных механизмов
и функций.
Согласно странице руководства sigaction(2) в системах на
основе BSD, несколько одновременно ожидающих доставки
сигналов будут доставлены так, что каждый последующий сигнал
прервет обработчик предыдущего перед его первой машинной
командой. Таким образом, избежать дальнейшего вложенного
вызова обработчиков можно, лишь атомарно установив маску
сигналов перед входом в текущий обработчик. Для этого служит
маска sa_mask, передаваемая как член структуры sigaction в
sigaction(2).
Чтобы приостановить выполнение программы до прихода
определенного сигнала (или любого из заданного множества
сигналов), существует системный вызов sigsuspend(2). Его
использование предполагает, что в остальное время интересующий
нас сигнал (или их множество) заблокирован, иначе можно его
упустить до вызова sigsuspend(2). Но даже если sigprocmask(2)
вызвать непосредственно перед sigsuspend(2), все равно
образуется временное окно, на протяжении которого ожидаемый
сигнал может оказаться доставлен. Поэтому sigsuspend(2)
принимает в качестве аргумента маску сигналов, которую
атомарно устанавливает на время ожидания.
Системный вызов pselect(2) фактически совмещает в себе
функциональность select(2) и sigsuspend(2). Поэтому он
также принимает в качестве одного из аргументов маску
сигналов, которую атомарно устанавливает на время ожидания
событий.
12. [Из п. 2, с. 1] Для завершения процесса из обработчика сигнала
следует использовать _exit(2) (POSIX) или _Exit() (C99), которые
не имеют побочных эффектов, в отличие от exit(3).
Побочными эффектами exit(3) являются вызов зарегистрированных
с помощью atexit(3) деструкторов, закрытие потоков stdio,
удаление временных файлов и т.п. Все это наверняка повлечет
вызов н.б.ф.
13. [Из п. 4.1] Если хотя бы у одного сигнала, для которого установлен
обработчик, нет флага SA_RESTART и этот сигнал не заблокирован, то
по EINTR может быть прервана _любая_ стандартная библиотечная функция
ввода-вывода.
Практические соображения
При планировании проекта стоит определить границы его переносимости,
так как требования разных стандартов могут заметно отличаться или
даже противоречить друг другу, что мы уже видели ранее. Если
ограничиться средой Unix, то наиболее здравым выбором, по-видимому,
будет текущая версия SUS.
Обращение из обработчика асинхронного сигнала к статическим
данным вопреки правилу 3 может считаться ограниченно
переносимым, так как работает в большинстве Unix-подобных
систем. Использовать его можно на свой страх и риск, однако
следует иметь ввиду возможные последствия для надежности и
переносимости приложения.
Не надо относиться к сигналам как к грому и молнии. В аккуратно
написанной программе всегда известно, какие сигналы в данный момент
следует принимать и обрабатывать, а какие лучше держать заблокированными
или игнорировать. Зачастую это не требует значительных усилий или
изменений в структуре программы -- достаточно четко осознавать,
почему тот или иной сигнал не повредит, скажем, при вызове функции
ввода-вывода.
Если в вашем проекте сигналы большую часть времени разблокированы
и у некоторых из них не установлен флаг SA_RESTART, то может быть
удобно сократить до минимума число вызовов функций ввода-вывода.
Нужно быть готовым перезапустить каждый такой вызов, если он вернет
EINTR или выполнит операцию не полностью. При большом количестве
вызовов (например, при посимвольном вводе-выводе посредством stdio)
это потребует соответствующего числа практически повторяющихся
"оберток", что отрицательно скажется как на производительности, так
и на удобочитаемости исходных текстов.
Наиболее простой и безопасный подход к обработке сигналов -- когда
обработчик устанавливает флаг типа sig_atomic_t, а основной поток
проверяет его и предпринимает необходимые действия. Если при этом
необходимо прерывать некоторые операции ввода-вывода, то следует или
блокировать сигналы на время остальных операций ввода-вывода, или
особо обрабатывать ошибку EINTR после _всех_ операций ввода-вывода.
В сложных случаях может оказаться удобнее эмулировать синхронные
сигналы. Для этого можно держать сигналы заблокированными большую
часть времени и разблокировать их только в специально отведенных,
реентерабельных точках программы. Особенно удобным для этой цели
является системный вызов pselect(2), который позволяет атомарно
устанавливать маску сигналов на время своего выполнения. Конечно,
это внесет задержку между отправкой сигнала и его обработкой.
В системах, где pselect(2) отсутствует, его можно частично
эмулировать, используя запись из обработчика сигнала в канал
(pipe). Для этого необходимо:
а) создать канал;
б) установить на обоих концах канала режим неблокирующего
ввода-вывода;
в) установить обработчик, который станет записывать в этот
канал 1 байт (любой, т.к. доставка через канал не будет
гарантированной) и устанавливать флаг в переменной
типа volatile sig_atomic_t;
г) включить читаемый дескриптор сигнального канала в
обработку poll(2)/select(2);
д) разблокировать сигнал;
е) вызвать poll(2)/select(2);
ж) заблокировать сигнал;
з) при наличии данных в сигнальном канале читать их оттуда
для очистки буфера;
и) если флаг установлен:
- сбросить флаг;
- предпринять необходимые действия;
к) вернуться к д).
Смысл записи в канал состоит в том, что poll(2)/select(2)
вернет управление сразу, если сигнал уже был доставлен в
обработчик. Иначе poll(2)/select(2) стал бы ждать других
событий, и реакция на сигнал произошла бы с задержкой.
Конечно, возможна потеря сигнала, если буфер сигнального
канала уже заполнен. Однако за счет установки и проверки
флага хотя бы один экземпляр сигнала будет когда-нибудь
замечен, а это вполне согласуется с моделью сигналов POSIX.
Так как сложные схемы на основе сигналов очевидно ограничены и плохо
переносимы, с сигналами всегда стоит придерживаться принципа K.I.S.S.:
"Keep it simple, stupid". Если вы выучили правила и видите, что
нетривиальное использование сигналов причиняет слишком много
неудобств, то вам пора рассмотреть другие средства IPC для вашего
проекта.
Заключение
Автор хотел бы искренне поблагодарить читателей эхи (телеконференции
FidoNet) RU.UNIX.PROG за ценные замечания и идеи для данной статьи.
Yar Tikhiy <yar@comp.chem.msu.su>
$Id: signal.txt,v 1.48 2003/07/22 15:50:49 yar Exp $