Подтвердить что ты не робот

Является ли вилка (предположительно) безопасной от обработчиков сигналов в многопоточной программе?

Я действительно не уверен в требованиях POSIX к безопасности fork при наличии потоков и сигналов. fork указывается в качестве одной из функций безопасности, совместимых с асинхронным сигналом, но если есть вероятность, что в библиотечном коде зарегистрировались обработчики pthread_atfork, которые не являются безопасными по отношению к асинхронному сигналу, делает ли это отмену безопасности fork? Ответ зависит от того, может ли поток, в котором работает обработчик сигнала, находиться в середине использования ресурса, который требуется обработчикам atfork? Или сказать по-другому, если обработчики atfork используют ресурсы синхронизации (мьютексы и т.д.), Но fork вызывается из обработчика сигнала, который выполняется в потоке, который никогда не обращается к этим ресурсам, соответствует ли программа?

Основываясь на этом вопросе, если "потокобезопасное" forking реализовано внутренне в системной библиотеке с использованием идиом, предложенных pthread_atfork (получить все блокировки в обработчике предпродажа и освободить все блокировки как в обработчиках postfork родительского, так и дочернего), то fork когда-либо безопасно использовать из обработчиков сигналов в многопоточной программе? Разве не возможно, что поток, обрабатывающий сигнал, может находиться посреди вызова malloc или fopen/fclose и удерживания глобальной блокировки, что приводит к тупиковой ситуации во время fork?

Наконец, даже если fork безопасен в обработчиках сигналов, безопасно ли оно fork в обработчике сигнала, а затем возвращается из обработчика сигнала или вызов в fork в обработчике сигналов всегда требует последующий вызов _exit или одного из семейств функций exec до возврата обработчика сигнала?

4b9b3361

Ответ 1

Попытка изо всех сил ответить на все вопросы; Прошу прощения, что некоторые из них более смутные, чем это в идеале должно быть:

Если есть возможность использования кода библиотеки Зарегистрированные обработчики pthread_atfork которые не являются безопасными для асинхронного сигнала, это отрицает безопасность вилки?

Да. В документации fork указано следующее:

   When the application calls fork() from a signal handler and any of the
   fork handlers registered by pthread_atfork() calls a function that is
   not asynch-signal-safe, the behavior is undefined.

Конечно, это означает, что вы не можете использовать pthread_atfork() для своей цели, чтобы сделать многопотоковые библиотеки прозрачными для процессов, которые считают, что они однопоточные, потому что ни одна из функций синхронизации pthread не является async-signal- безопасно; это отмечено как дефект в спецификации, см. http://www.opengroup.org/austin/aardvark/latest/xshbug3.txt (поиск по "L16723" ).

Есть ли ответ зависит от того, поток, в котором обработчик сигнала бег может быть посредине используя ресурс, который нужны обработчики? Или сказал другой путь, если обработчики atfork используют ресурсов синхронизации (мьютексы, и т.д.), но fork вызывается из обработчик сигнала, который выполнен в нить, которая никогда не обращается к этим ресурсов, соответствует ли программа?

Строго говоря, ответ отрицательный, потому что согласно спецификации, функции являются либо безопасными для асинхронных сигналов, либо они не являются; нет понятия "безопасно при определенных обстоятельствах". На практике вы вполне можете уйти от него, но вы были бы уязвимы для неуклюжей, но правильной реализации, которая не разбивала свои ресурсы так, как вы ожидали.

Основываясь на этом вопросе, если "Нитебезопасное" форсирование реализовано внутренне в системной библиотеке, используя идиомы, предложенные pthread_atfork (получить все блокировки в преддверии обработчика и освободить все блокировки в обоих родительский и дочерний пост хэндлеров), то это вилка, когда-либо безопасная для использование от обработчиков сигналов в резьбовом программа? Разве не возможно, чтобы обработка потока может быть в в середине вызова malloc или fopen/fclose и проведение глобального блокировка, приводящая к вилка?

Если бы это было реализовано таким образом, тогда вы правы, fork() из обработчика сигнала никогда не будет в безопасности, потому что попытка получить блокировку может быть заблокирована, если вызывающий поток уже удерживал ее. Но это означает, что реализация с использованием такого метода не будет соответствовать.

Глядя на glibc как на один пример, он этого не делает - скорее, он принимает два подхода: во-первых, блокировки, которые он получает, являются рекурсивными (поэтому, если текущий поток уже имеет их, их количество блокировки будет просто вырос); кроме того, в дочернем процессе он просто в одностороннем порядке перезаписывает все блокировки - см. этот фрагмент из nptl/sysdeps/unix/sysv/linux/fork.c:

  /* Reset the file list.  These are recursive mutexes.  */
  fresetlockfiles ();

  /* Reset locks in the I/O code.  */
  _IO_list_resetlock ();

  /* Reset the lock the dynamic loader uses to protect its data.  */
  __rtld_lock_initialize (GL(dl_load_lock));

где каждая из функций resetlock и lock_initialize в конечном счете вызывает внутренний эквивалент glibc pthread_mutex_init(), эффективно перезагружая мьютекс, независимо от каких-либо официантов.

Я думаю, что теория состоит в том, что, получив (рекурсивную) блокировку, она гарантировала, что никакие другие потоки не будут касаться структур данных (по крайней мере, таким образом, чтобы вызвать крушение), а затем сброс отдельных блокировок обеспечивает ресурсы не блокируются навсегда. (Сброс текущей блокировки потока является безопасным, так как теперь нет других потоков, чтобы бороться за структуру данных, и действительно не будет, пока не вернется какая-либо функция, использующая блокировку).

Я не уверен на 100%, что это охватывает все возможные события (не в последнюю очередь потому, что если/когда обработчик сигнала возвращается, функция, которая только что украла его замок, попытается разблокировать его, а функция внутренней рекурсивной разблокировки не будет защищайте от разблокировки слишком много раз!) - но кажется, что эффективная схема может быть построена поверх рекурсивных блокировок, устойчивых к асинхронному сигналу.

Наконец, даже если fork безопасен в обработчиков сигналов, безопасно ли вилка в обработчик сигнала, а затем вернуться из обработчик сигнала или вызов вилка в обработчике сигнала всегда требуется следующий вызов _exit или одно из семейств функций exec перед возвратом обработчика сигнала?

Я предполагаю, что вы говорите о дочернем процессе? (Если fork(), являющийся безопасным для асинхронного сигнала, означает что-нибудь, тогда его можно будет вернуть в родительском!)

Не обнаружив ничего в спецификации, которая заявляет иначе (хотя я, возможно, пропустил ее), я считаю, что это должно быть безопасно - по крайней мере, "безопасно" в том смысле, что возврат из обработчика сигнала в ребенке не означает undefined сам по себе, хотя тот факт, что многопоточный процесс только разветвлен, может означать, что exec*() или _exit(), вероятно, самый безопасный способ действий.

Ответ 2

Я добавляю этот ответ, потому что похоже, что fork() скорее всего не считается асинхронным. По крайней мере, это похоже на glibc, но, возможно, поддержка больше не существует в POSIX. Ответ, который в настоящее время отмечен как "принятый", кажется, приходит к выводу, что он безопасен, но, по крайней мере, в glibc, скорее всего, не так.

Основываясь на этом вопросе, если "потокобезопасное" forking реализовано внутри системной библиотеки с использованием идиом, предложенных pthread_atfork (получить все блокировки в обработчике предпродажей и освободить все блокировки как в обработчиках postfork родительского, так и дочернего) то fork всегда безопасен для использования с обработчиками сигналов в поточной программе? Разве не возможно, что поток, обрабатывающий сигнал, может находиться посреди вызова malloc или fopen/fclose и удерживания глобальной блокировки, что приводит к взаимоблокировке во время fork?

Действительно! Кажется, что Open Group решила удалить его из списка this очень важная причина.

IEEE 1003.1c-1995 Запрос на интерпретацию № 37 касается pthread_atfork.

Комитет по интерпретации считает, что... должны быть сделаны следующие пояснительные дополнения: Pg 78 line 864 "Кроме того, для защиты от асинхронного вызова требуются вызовы обработчиков fork, установленных pthread_atfork из вилки, вызываемой из обработчика сигналов".

glibc Ошибка 4737 определяет разрешение, которое fork() должно быть исключено из списка функций с асинхронными функциями и posix_spawn() использоваться для заполнить его место. К сожалению, это было разрешено как WONTFIX, поэтому даже manpages не обновлялись.

Ответ 3

Использование fork() в обработчике сигнала должно быть прекрасным.

pthread_atfork звучит как плохая идея, когда-либо используемая.

Чтобы ответить на ваш первоначальный вопрос, pthread не может гарантировать безопасность вызова любой функции pthread_atfork, это безопасно для асинхронного сигнала, потому что реализация сигналов ядра делает невозможным.

О, и если вы используете fork в обработчике сигнала, не разрешайте ребенку возвращаться из обработчика сигнала. Это undefined.

Ответ 4

Как я понимаю из источника fork в glibc, он использует критический раздел состояния сигнала, чтобы убедиться, что процедура наложения не прерывается сигналом.

  ss = _hurd_self_sigstate ();
  __spin_lock (&ss->critical_section_lock);

И как обработчики pthread_atfork, выполняемые после блокировки критического раздела, становятся автоматически безопасными.

Может быть, я ошибаюсь в этом, я буду признателен за исправления.

Ответ 5

Здесь https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers

вилка указана как безопасная для Async-сигнала, поэтому ее можно использовать.

POSIX

Следующая таблица из Открытого Основные характеристики группы [Open Group 2004], определяет набор функций, которые являются асинхронно-сигнальными. Приложения могут ссылаться на эти функции, без ограничений, из обработчик сигнала.

Функции с асинхронным сигналом

вилка()