Некоторое время назад я написал простой 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
.
Рассмотреть возможность временного вызова кода sig_chld(), например, в потоке. Вместо функции обработки сигнала. У обработчиков сигналов, подобных вашим, есть вероятность, что они не будут корректно завершены, когда будет подавлен сигнал. Кажется, это ваша проблема. –
Что делает ваша функция 'Fork', когда' fork' терпит неудачу? – Duck
Он печатает сообщение об ошибке и выходит из вызова 'exit (1)'. – TPhaster