Некоторое время назад я написал простой SMTP-шлюз для автоматической обработки S/MIME, и теперь дело доходит до тестирования. Как обычно для почтовых серверов, главный процесс создает дочерний процесс для каждого входящего соединения. Хорошей практикой является ограничение количества создаваемых дочерних процессов, и я так и сделал.
При большой нагрузке (много подключений от многих клиентов одновременно) оказывается, что дочерние процессы учитываются неправильно -- проблема в уменьшении счетчика при завершении дочерних процессов. Через несколько минут интенсивной нагрузки счетчик больше фактического количества дочерних процессов (т.е. через 5 минут он равен 14, но их нет).
Я уже провел некоторые исследования, но ничего не помогло. Все зомби-процессы собраны, поэтому обработка SIGCHLD
выглядит нормально. Я думал, что это может быть проблема синхронизации, но добавление мьютекса и изменение типа переменной на volatile sig_atomic_t
(как сейчас) не дает никаких изменений. Это также не проблема с маскировкой сигнала, я пробовал маскировать весь сигнал, используя sigfillset(&act.sa_mask)
.
Я заметил, что waitpid()
иногда возвращает странные значения PID (очень большие, вроде 172915914).
Вопросы и код.
- Возможно ли, что другой процесс (т.е.
init
) пожинает некоторые из них? - Может ли процесс не стать зомби после выхода? Можно ли его получить автоматически?
- Как это исправить? Может быть, есть лучший способ их подсчета?
Разветвление ребенка в main()
:
volatile sig_atomic_t sproc_counter = 0; /* forked subprocesses counter */
/* S/MIME Gate main function */
int main (int argc, char **argv)
{
[...]
/* set appropriate handler for SIGCHLD */
Signal(SIGCHLD, sig_chld);
[...]
/* SMTP Server's main loop */
for (;;) {
[...]
/* check whether subprocesses limit is not exceeded */
if (sproc_counter < MAXSUBPROC) {
if ( (childpid = Fork()) == 0) { /* child process */
Close(listenfd); /* close listening socket */
smime_gate_service(connfd); /* process the request */
exit(0);
}
++sproc_counter;
}
else
err_msg("subprocesses limit exceeded, connection refused");
[...]
}
Close(connfd); /* parent closes connected socket */
}
Обработка сигналов:
Sigfunc *signal (int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
}
else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
#endif
}
if (sigaction(signo, &act, &oact) < 0)
return SIG_ERR;
return oact.sa_handler;
}
Sigfunc *Signal (int signo, Sigfunc *func)
{
Sigfunc *sigfunc;
if ( (sigfunc = signal(signo, func)) == SIG_ERR)
err_sys("signal error");
return sigfunc;
}
void sig_chld (int signo __attribute__((__unused__)))
{
pid_t pid;
int stat;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0) {
--sproc_counter;
err_msg("child %d terminated", pid);
}
return;
}
ПРИМЕЧАНИЕ. Все функции, начинающиеся с заглавной буквы (например, Fork()
, Close()
, Signal()
и т. д.), делают и ведут себя так же, как и их друзья в нижнем регистре (fork()
, close()
, signal()
и т. д.), но имеют лучшую ошибку. обработка - поэтому мне не нужно проверять их статусы возврата.
ПРИМЕЧАНИЕ 2: я запускаю и компилирую его в Debian Testing (kernel v3.10.11
), используя gcc 4.8.2
.
Fork
, когдаfork
терпит неудачу? - person Duck   schedule 03.01.2014exit(1)
. - person TPhaster   schedule 03.01.2014