Является ли fork (предполагается) безопасным от обработчиков сигналов в многопоточной программе? - PullRequest
21 голосов
/ 15 декабря 2010

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

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

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

Ответы [ 5 ]

12 голосов
/ 31 декабря 2010

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

Если есть вероятность того, что код библиотеки имеет зарегистрированные обработчики 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 не является асинхронной-сигнальной. безопасный; это указано как дефект в спецификации, см. http://www.opengroup.org/austin/aardvark/latest/xshbug3.txt (поиск "L16723").

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

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

Опираясь на этот вопрос, если реализовано "потокобезопасное" разветвление внутренне в системной библиотеке с помощью идиомы, предложенные 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%, что это покрывает все возможные ситуации (не в последнюю очередь потому, что если / когда обработчик сигнала вернется, функция, у которой только что была украдена блокировка, попытается разблокировать ее, а внутренняя функция рекурсивной разблокировки не защищать от разблокировки слишком много раз!) - но кажется, что работоспособная схема может быть построена поверх рекурсивных блокировок, защищенных от асинхронных сигналов.

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

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

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

4 голосов
/ 22 января 2014

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

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

В самом деле! Похоже, Открытая группа решила удалить его из списка для по этой самой причине .

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

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

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

2 голосов
/ 15 декабря 2010

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

pthread_atfork звучит как плохая идея для использования.

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

О, и, если вы включите обработчик сигнала, не позволяйте дочернему элементу возвращаться из обработчика сигнала.Это не определено.

0 голосов
/ 29 декабря 2010

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

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

POSIX

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

Асинхронные - безопасные по сигналу функции

fork ()

0 голосов
/ 29 декабря 2010

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

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

И как pthread_atfork обработчики выполняются после блокировки критической секции - они автоматически становятся безопасными по сигналу.

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

...