fork() может потерпеть неудачу: это важно

Автор оригинала: Rachel Kroll
  • Перевод
Эх, fork(). Одни процессы порождают другие. Кажется, у меня есть история об этом.

Форк может потерпеть неудачу. Понимаете? В самом деле, понимаете? Это очень серьёзно. Форк может завершиться с ошибкой. Так же, как и malloc. Нечасто, но когда такое происходит, нельзя просто взять и игнорировать это. Вы должны что-то предпринять в таком случае.

Похоже, всем известно, что fork возвращает дочернему процессу 0, а родителю некоторое положительное число — pid ребенка. Он выдаёт этот номер, который используется позже.

Угадайте, что происходит, когда вы не проверяете ответ на ошибку? Да, вы обработаете "-1" (ошибка форка) в качестве валидного pid.

Это только начало. Настоящая боль начинается позже, когда приходит время послать сигнал. Может, вы захотите закрыть дочерний процесс.

Вы делаете kill(pid, signal). Например, kill(pid, 9).

Что происходит, когда когда pid равен -1? Это действительно важно знать. Реально важно.





Здесь я вставлю со справочной страницы kill(2) со своего Linux.

Если pid равен -1, то sig отправляется каждому процессу, для которого вызывающий процесс имеет разрешение на отправку сигналов, за исключением процесса 1(init)...

Видите? Убить pid -1 эквивалентно уничтожению всех остальных процессов. Если вы рут, то на этом всё закончилось. Остались вы и init. Всё остальное ушло, ушло, ушло.

У вас есть код, который управляет процессами? Когда-нибудь находили машину полностью мертвой, за исключением текстовой консоли getty/login (которых перезапускает init, естественно) и менеджера процессов? Наверное, винили в этом oomkiller в ядре?

Возможно, он не виноват. Лучше проверьте, что вы не запустили kill(-1).

В Unix достаточно ловушек и медвежьих капканов на любой вкус.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 256

    +2
    Они выбивают этот номер, а затем используют его позже.

    В камне выбивают? Кто "они"?

      +1

      А в оригинале там и вовсе "sock it away" — откладывают

        +2

        Типа на чёрный день. До лучших времён. Сохраняют. Да. "Выбивают в камне" подходит.
        И да, в оригинале и вовсе "fork can fail", а мы тут имеем что имеем: "форк может глюкануть". Перевод кэн глюкануть.

      +28

      По-моему, это автора глюкануло. А fork() может ошибку вернуть.

        +3
        это автора перевода глюкануло. В оригинале fork can fail.
          +1

          Да, именно так (я смиренно полагал себя больше похожим на человека, который не будет спорить с оригиналом в статье-переводе, но имею что имею).


          Впрочем, был разочарован и тем, и другим. Заголовок со словом «глючить» был кликбейтом. По нему я ожидал увидеть какое-нибудь месиво, когда «мы форкаем 200000 раз в секунду и вот на 200001-м начинается неведомое, мы ловили баг в ядре месяц всем отделом», а тут -1, блин. Во-вторых, оригинал тоже написан довольно удивительным языком (на мой вкус, разговорным) и его можно было в два-три твита втолкать.


          При этом периодически ликбез проводить нужно, и опять люди могут принести довод «форма или содержание», но вот конкретно тут можно было несложно и содержание хорошее сделать, и форму. А не переводить дословно многоточия. В результате 180 комментариев про «документацию никто не читает». Так её ещё и не пишут иногда. Горе горькое, в общем.

        +25
        Форк может глюкануть. Понимаете? В самое деле, понимаете?

        А в чем с этим проблема?
        Может не хватить памяти, может не хватить прав, может не хватить места, да мало ли причин.
        Почему именно глюк виноват?

        Похоже, всем известно, что fork возвращает дочернему процессу 0, а родителю некоторое положительное число — pid ребенка.

        Или -1, если не получилось:
        RETURN VALUE
        Upon successful completion, fork() returns 0 to the child process and returns the process ID of the child process to the parent process. Otherwise, -1 is returned to the parent process, no child process is created, and errno is set to indicate the error.

        Угадайте, что происходит, когда вы не проверяете ответ на ошибку? Да, вы возьмёте "-1" (глюк форка) как pid.

        Происходит то, что происходит, если не читать документацию и не обрабатывать ошибки.

        Это только начало. Настоящая боль начинается позже

        Но ведь не форк в этом виноват?
          +19

          Я понял. Надо писать софт без багов, а софт с багами писать не надо. Всё просто, да?

            +17
            Надо читать документацию по функции, которая была многократно проверена и оттестирована миллионами программистов, начиная с 80-х годов.
              +17

              Читать-то много чего надо, но простое чтение документации само по себе ошибку не исправит, надо ещё и возвращаемое значение на -1 проверить же.


              Многие API устроены более "прощающим" образом: значение, сигнализирующее об ошибке, непригодно к использованию и вызывает другую ошибку при попытке использования. Например, malloc при ошибке возвращает нулевой указатель, обращение по которому в большинстве систем вызывают SIGSEGV. Это неприятно — но дальше процесса проблема не распространяется.


              Информация о том, что пара fork+kill лени в обработке ошибок не прощает, важна.

                +3
                Читать-то много чего надо, но простое чтение документации само по себе ошибку не исправит, надо ещё и возвращаемое значение на -1 проверить же.


                Ну, справедливости ради, конкретно в этом данном случае чтение документации в процессе разработки ошибку исправит. Точнее, не даст допустить…
                  +1

                  Неа, если программист уже настроился что пишет "наколеночную поделку" и решил на всякие редкие случаи сознательно забить — ничто не подскажет ему, что -1 является очень опасным значением для pid_t.

                    +1
                    Неа, если программист уже настроился что пишет «наколеночную поделку» и решил на всякие редкие случаи сознательно забить


                    Это как раз тот случай, когда программист доку не прочитал. Собственно, ограничить диапазон pid_t положительными числами много ума не надо. При этом в качестве возвращаемых значений практически во всех юниксовых апи используется ровно 2 модели: «0 — ок, !0-код ошибки» (которая, очевидно, неприменима для конкретно взятого кейса) и ">0 — искомое значение, <0 — код ошибки * (-1)" (которая, очевидно, наш кейс).

                    Никакого «срыва покровов» не происходит. Если так «низкоуровнево» не хочется — юзаем высокоуровневые обертки, где таковой проблемы нет. А если хочется «низкоуровнево» — ну, страдаем и читаем документацию.
                      +4
                      Большинство API при ошибке возвращают непригодное значение. Например, если мы сделаем open-read-write-close и случится ошибка на первом этапе (файл не найден или нет прав), то каскадно выдадут ошибку все последующие системные вызовы и ничего страшного не случится. Аналогично с сокетами и т. д.

                      То есть в целом, в одноразовом решении автор может забить на проверку ошибок и в редких ошибочных ситуациях программа просто не выполнит свою задачу, в худшем случае упадёт (как в случае с ошибкой в malloc). Возможно, повредит данные, с которыми работала (но программист знает с какими данными работает и учитывает риски их повреждения, принимая решение написать что-то быстро).

                      А fork+kill выделяется и большого количества других API, потому что невалидный результат одного является валидным результатом для другого. Да ещё и насколько валидным — рассылка сигнала всем процессам в системе. Эффект от сбоя выходит далеко за пределы программы и даже явным образом обрабатываемых ею данными. Это грабли, на которые можно наступить (в реальном мире все люди иногда ошибаются, просто кто-то это делает реже, кто-то чаще).
                        0
                        ну для работы функций в С часто используется unsigned, а возврат часто signed и -1 при ошибке, так повелось со времен ассемблера, так как какой ни будь 0xFFFF не является валидным значением, но в «высших» языках это -1
                          0
                          Большинство API при ошибке возвращают непригодное значение.


                          Ну вот fork(), при ошибке возвращает -1, что явным образом является непригодным в качестве pid значением. Все верно же? Все нормально, получается, с fork'ом?

                          Возможно, повредит данные, с которыми работала (но программист знает с какими данными работает и учитывает риски их повреждения, принимая решение написать что-то быстро).


                          Ну вот kill с -1 — просто убьет все процессы пользователя, от которого запущена. Одноразовое наколеночное решение с отношением «насрать даже, если какие-то данные повредит» — вполне нормально, почему нет. Если не хотите завершения процессов — таки прочтите доку на kill.

                          А fork+kill выделяется и большого количества других API


                          Ну вот нет такого API fork+kill. Есть API fork и есть API kill. Что то вроде (осторожно, псевдокод) `int result = process.fork().doSomething().kill()` у вас не получится. fork() возвращает int, т.е. скаляр, обычное целое число. Корректно интерпретировать это целое число — ваша работа. kill() принимает на вход целое число и возвращает другое целое число — передать корректное значение и корректно интерпретировать число на выходе — ваша обязанность.

                          Все unix-API написаны достаточно давно, и возвращают целые числа. Да, их надо интерпретировать. Да, вам, возможно, это неудобно. Но причина такому поведению есть — производительность. В вашем случае «одноразовое наколеночное решение» эта штука (производительность) не так уж чтобы прям настолько важна. Поэтому для вас предпочтительным решением будет таки не использовать напрямую fork() и kill(). Вот прям никогда не использовать. Над ними вагон и маленькая тележка более-менее удобных оберток, «представляющих удобную абстракцию процесса».

                          потому что невалидный результат одного является валидным результатом для другого


                          Ну, во первых, для второго это таки не валидный результат, а валидный параметр. При этом откуда взялось понятие «невалидный результат». Нормальный результат fork() -1, означающий, что процесс не удалось форкнуть. Его можно интерпретировать, его можно обрабатывать. Единстенное, что нужно понимать — fork() не возвращает процесс. Форк возращает числовой идентификатор процесса, он возвращает число. Если это число отрицательное, значит форк не удался, и с этим что-то надо делать. Отрицательное значение вы обязаны проверить либо на этапе собственно форка, либо на входе в kill().

                          В общем и целом, имхо, тут как всегда. Либо крестик снимите, либо рясу в трусы не заправляйте. Либо вы пишете действительно низкоуровневые вещи, и вам нужен «в прямую» fork() и kill(), и тогда непонятны стенания по поводу «ой, там документацию читать надо». Низкоуровневые вещи — это всегда trade-in между удобством API и производительностью. Либо, если вы пишете пользовательский софт — почему не пользуетесь сейфовыми API-обертками, в которых «доку читать не надо»?
                            +2
                            Ну вот fork(), при ошибке возвращает -1, что явным образом является непригодным в качестве pid значением. Все верно же? Все нормально, получается, с fork'ом?

                            Неа, не является. Если можно передать в kill, и kill от этого не вернёт ошибки — какое же оно непригодное?


                            Если не хотите завершения процессов — таки прочтите доку на kill.

                            Так доки на kill недостаточно, чтобы понять где проблема — нужно читать обе доки, и на kill, и на fork.


                            Поэтому для вас предпочтительным решением будет таки не использовать напрямую fork() и kill(). Вот прям никогда не использовать.

                            Соглашусь. Более того, так лучше делать даже там, где производительность важна. Осталось понять как другой программист может прийти к тому же самому выводу не читая постов подобных обсуждаемому.

                              –1
                              Неа, не является.


                              Кхм, с чего это -1 не является некорректным значением pid? Давайте попробуем расшифровать: знакомьтесь, pid — process identificator, идентификатор процесса. Вы, видимо, сможете мне показать процесс, имеющий идентификатор -1?

                              Если можно передать в kill, и kill от этого не вернёт ошибки — какое же оно непригодное?


                              Если число можно передать в kill, это еще совершенно не говорит о том, что это число — pid.

                              Так доки на kill недостаточно, чтобы понять где проблема — нужно читать обе доки, и на kill, и на fork.


                              Не, любой одной из — достаточно. Из доки на fork вы узнаете, что возвращаться могут не только целые положительные числа (собственно, тут и доки на fork не надо, оно в целом в api так устроено, любой вызов потенциально может вернуть код ошибки). Более того, при форке вы один хрен проверяете код возврата, т.к. есть еще значение «0». Если вы не проверяете его, то вы делаете что-то странное. Например, в случае ошибки вы посылаете kill туда, куда прав хватает, а в случае корректного fork'а — посылаете из дочернего процесса kill всем процессам в группе, которой принадлежит процесс.

                              Еще раз: если у вас возникла проблема с -1, вы изначально еще на этапе fork'а делаете что-то странное.

                              В случае прочтения доки на kill вы видите, что на вход принимается либо явный id процесса, либо что-то из специальных значений, поведение которых вы явно не хотите лицезреть. Тогда вы вставляете проверку перед вызовом kill.

                              Если вы этого не делаете, вы, видимо, не читали доку ни на fork, ни на kill.

                              Более того, так лучше делать даже там, где производительность важна.


                              Ну, пока именно обертки над fork/kill не станут ботлнеком всей высоконагруженной системы (спойлер: для прикладной системы — никогда).

                              Осталось понять как другой программист может прийти к тому же самому выводу не читая постов подобных обсуждаемому.


                              Ну, пост таки про то, что «glibc — ужас-ужас», а не про специфику. Тут вопрос в том, скольких потенциально хороших специалистов оно отпугнет.
                                0
                                Тут вопрос в том, скольких потенциально хороших специалистов оно отпугнет.

                                Тот случай, когда лучше отпугнуть.

                              –2
                              Все unix-API написаны достаточно давно, и возвращают целые числа. Да, их надо интерпретировать. Да, вам, возможно, это неудобно. Но причина такому поведению есть — производительность.

                              Если это так, то какого хрена у нас есть kill, работающий совершенно по другому при параметре -1, вместо раздельных kill и killall? Обработка особого значения параметра требует отдельного if внутри kill, так что это не максимальная производительность.

                                0
                                Если это так, то какого хрена у нас есть kill, работающий совершенно по другому


                                Не понял, в чем по другому? kill(), ровно как и все остальные, возвращает целое число — можете проверить спеку. Причем kill прямо в явную int возвращает. Что не так?
                                  +1
                                  Вопрос был не про то, что он возвращает, а про то, что он принимает. А принимает он значение -1, которое для вызова, порождающего процессы, является «сообщение» об ошибке. И да, напрашивается именно отдельный вызов для рассылки сигнала по площадям, а не с помощью специфического значения, которое в той же «предметной области» — управлении процессами — имеет совсем другой смысл, при том, что вызову kill зачастую отдаётся именно то, что создаёт fork(), а не результат последующих преобразований. А жёсткая необходимость проверки результата fork() — это как раз bug by design, костыль. Потому что, как было написано выше, в других случаях передача значения, являющегося индикатором ошибки у функций из той же «предметной области», приводит к сбою, а не к успешному выполнению с фатальными для системы последствиями. Например, попытка записи по тому дескриптору файла, который вернул неудачный вызов open(), приведёт к сбою и завершению программы, а не к мочаливому затиранию, к примеру, корневой директории.

                                  P.S. Кстати, если уж надо проверять, а проверять надо, то почему тогда на впилить проверку именно в код kill()? Проверка всё равно быть должна, так что пусть будет в одном месте. Я понимаю, что если экономить байты и такты, причём, именно байты и так ты в штуках, это не лучший вариант, но всё же.
                                    –1
                                    Вопрос был не про то, что он возвращает, а про то, что он принимает.


                                    Вопрос был именно про то, что возвращает. Исходный посыл был следущий: «Все unix-API написаны достаточно давно, и возвращают целые числа.» Ну, они действительно так написаны, и действительно возращают коды возврата в виде целых чисел. На это возникло возражение: «какого хрена у нас есть kill, работающий совершенно по другому». kill() ровно так же возвращает целое число в качестве кода возврата, несоответствия не вижу. Принимать только положительные целые числа никто не обещал, список входных параметров — индивидуален для каждого метода.

                                    А принимает он значение -1, которое для вызова, порождающего процессы, является «сообщение» об ошибке.


                                    Воот, мне кажется, вы начинаете догадываться, что происходит. Для примера, есть число 2. Это просто число. Есть 2 однополых друга, есть 2 партнера в сексе. Вы же не возмущаетесь, что «и там, и там — 2, в чем разница, почему 2 друзьям нельзя», вы же понимаете, что это принципиально разные 2. Вот и здесь fork() возвращает код возврата -1, а kill() на входе просит идентификатор процесса или (в конкретном случае -1) специпальное wildcard-значение. Зачем вы во входной параметр kill суете код возврата fork? Это разные вещи, а чисел — одно единственное бесконечное множество.

                                    в той же «предметной области» — управлении процессами


                                    В предметной области unix-api (которые таки да, древние, и т.д. и т.п.) принято явным образом понимать, что внутри что у кода возврата, что у дескриптора открытого файла, что у pid процесса — совершенно одинаковые числа. То, что возвращает вам любой вызов unix-api — это просто число. Даже процесс (любой) при завершении возвращает число (0 — удачно, все остальное — код ошибки). Ни один вызов unix-api не возвращает в явную что-то отличное от кода возврата. Там прямо так и написано в fork() — код возврата, интерпретировать так и так. Нигде не написано, что это pid процесса.

                                    В C достаточно бедная система типов, поэтому принято явно разделять входные параметры и выходные. Пихать выходной параметр одной функции в другую без любых проверок — явный моветон. Это примерно как в той же Java не обрабатывать исключения или в Go тупо underscor'ить ошибки.

                                    оторое в той же «предметной области» — управлении процессами — имеет совсем другой смысл


                                    Смысл кодов возврата внутри указанной предметной области строго идентичен. Коды возврата что у fork(), что у kill() ведут себя одинаково.

                                    вызову kill зачастую отдаётся именно то, что создаёт fork(), а не результат последующих преобразований


                                    Ну, допустим, зря и говнокод.

                                    А жёсткая необходимость проверки результата fork() — это как раз bug by design, костыль.


                                    Погодите-погодите… Вы же мне подскажете язык/среду в которой в явную считается хорошим тоном игнорировать ошибки? Или таки везде принято проверять возвращаемое значение, не только в C?

                                    передача значения, являющегося индикатором ошибки


                                    … во всех без исключения случаях не является разумным подходом, например.

                                    с фатальными для системы последствиями


                                    Хм, прям фатальными-фатальными? От завершения сеанса кто-то умер уже?

                                    Кстати, если уж надо проверять, а проверять надо, то почему тогда на впилить проверку именно в код kill()


                                    Проверку чего вы собираетесь в код kill впилить? Вот есть у нас числа 3857, 0, -1, -100 — какое из них некорректное входное значение для kill?

                                      0
                                      Есть 2 однополых друга, есть 2 партнера в сексе. Вы же не возмущаетесь, что «и там, и там — 2, в чем разница, почему 2 друзьям нельзя», вы же понимаете, что это принципиально разные 2.

                                      А почему, собственно говоря, нельзя?

                        +4
                        Ну, справедливости ради, конкретно в этом данном случае чтение документации в процессе разработки ошибку исправит. Точнее, не даст допустить…
                        Не даст допустить язык с алгебраическими типами данных, в котором значение перед использованием обязаны проверить, вместе с пожизненным запретом писать на языках без него некоторым людям >_<
                          0
                          ну любой стиль программирования с ассертами на валидное избавит от этой ошибки
                          Если взять любой низкоуровневый код в продакшне, то там будет дофига ассертов, что является хорошим стилем, но плохо выглядит в коде и в примерах использования часто ассерты опускают
                            +4

                            Но ассерты нужно писать, чтобы сделать проверку, и отсутствие ассерта нельзя найти грепом. В случае АДТ лишние действия нужны для игнора ошибки, и эти действия вполне себе грепаются.

                              +5

                              Не избавит. Ситуация редкая и на отладочной сборке имеет все шансы никогда не случиться.
                              А на релизной ассерты превращаются "в тыкву".

                              –2
                              Конечно, запретить было бы хорошо, но…
                              Предполагаю, однако, что те самые «некоторые люди», склонные допускать ошибку из статьи, также вряд ли способны и освоить язык с алгебраическими типами данных.
                              А программисты-то нынешнему народному хозяйству нужны, и много. И терять таких вот «некоторых людей», которые уже чему-то обучены — это не выгодно экономически.
                              Куда проще IMHO этим «некоторым людям» запретить пользоваться низкоуровневыми языками, дозволяющими прямой доступ к потенциально опасным системным вызовам. Тем более, что эти «некоторые люди» обычно и так не рвутся использовать низкоуровневые языки, как слишком недружественные.
                                +2
                                Предполагаю, однако, что те самые «некоторые люди», склонные допускать ошибку из статьи, также вряд ли способны и освоить язык с алгебраическими типами данных.
                                АДТ это очень просто, и если захотеть, это можно воткнуть хоть в бейсик.
                                А программисты-то нынешнему народному хозяйству нужны, и много. И терять таких вот «некоторых людей», которые уже чему-то обучены — это не выгодно экономически.
                                Эту концепцию при желании можно уместить в один урок, это если говорить в терминах c будет enum с union в одном флаконе, а следит за этим компилятор. Другие решения, вроде того, что приняты в c(возврат особого значения), go(возврат двух значений) проще будут разве что на бумаге, либо в момент реализации компилятора.
                            +1
                            Например, malloc при ошибке возвращает нулевой указатель

                            У меня для вас новость, и она вам не понравится.
                            В реальности malloc примерно никогда не возвращает nullptr, а сигнал прилетает в момент обращения к памяти. И, возможно, не вам.
                              0

                              То, что вместо nullptr в большинстве случаев прилетает сигнал — это ещё лучше, ведь это означает что никаких последствий кроме вылета точно не будет. То, что сигнал прилетает "возможно, не вам" — иногда хорошо, иногда плохо, но в любом случае никак на код не влияет.

                                0
                                Вместо nullptr прилетает не сигнал, а указатель-мина. Тронешь-взорвётся.
                                Ничего хорошего в швырянии сигналами ОС нет. Например, открытые на запись файлы окажутся дважды неоткрываемыми после перезапуска программы: сначала потому, что они были эксклюзивно заблокированными на запись подорвавшимся на мине процессом (и нет, terminate сам ничего не закрывает), а после перезагрузки ОС — окажутся просто битыми, потому что запись тупо не успела отработать.
                                Написание своего обработчика сигнала, особенно того, который всё равно закончится terminate, требует черного пояса по системному программированию и знания подкапотной магии конкретного компилятора. Потому что сигналы прилетают действительно асинхронно, например, посредине входа в какую-нибудь функцию, когда стековый фрейм содержит мусор, у вас есть неполностью сконструированные объекты, да и указатели-мины продолжают «действовать». И да, вы должны писать реентерабельный код, который может быть вызван несколько раз подряд параллельно.
                                  0

                                  Всё это нисколько не отменяет того факта, что возвращаемое значение malloc можно не проверять если нет задачи писать надёжный и переносимый код.


                                  В отличии от возвращаемого значения fork.

                                    0
                                    Вообще-то результат fork нужно проверять, чтобы понять, оказались ли мы в потомке или в родителе: у них очевидно будет разная логика.
                                    «Нет задачи писать надёжный код» это как? Даже стиль разработки «фигак-фигак и в продакшн» предполагает исправление багов в будущем.
                                      +2
                                      Вообще-то результат fork нужно проверять, чтобы понять, оказались ли мы в потомке или в родителе: у них очевидно будет разная логика.

                                      Ага, например вот так:


                                      if (pid = fork()) {
                                          // ...
                                      } else {
                                          // ...
                                      }

                                      Это пример из книжки по операционным системам! :-)

                                        –1
                                        ну и неправильный пример кстати. Должно быть:
                                        if (pid = fork() > 0) {
                                            // parent
                                        } else if (!pid) {
                                            // child
                                        } else {
                                            // oops
                                        }
                                          +4

                                          Ну разумеется он неправильный. Писали бы всюду правильные примеры — не было бы обсуждаемого поста.


                                          PS Тогда уж if ((pid = fork()) > 0) { в первой строчке должно быть.

                                            –1
                                            Ну тогда уж if( (int pid = fork()) > 0 ) :)
                                              +1

                                              А это скомпилируется?


                                              PS тогда уж pid_t...

                                                +3
                                                Ну вот ещё чего захотели, чтобы примеры — и компилировались!
                                0
                                В реальности malloc примерно никогда не возвращает nullptr

                                Такое постоянно происходит, когда размер виртуальной памяти процесса таки ограничен через rlimits. Если вы это не используете, то очень зря.
                                Вы правы в том, что надо постоянно иметь в виду, что отказ в памяти может происходить неожиданно и асинхронно (при использовании, а не запросе), но не надо таки обобщать это на 100% случаев.

                              +8

                              /usr/share/man на моей машине содержит 5060529 строк. И это только man-страницы. А рядом стандарты, rfc, документация к языку программирования, маленький комплект документов по xml/html/css/js, что у нас нам ещё? А, у каждой библиотеке тоже пачка документации.


                              Читаем. Но будем реалистами, вы не прочитали и 1% документации от технологий, которыми пользуетесь. Вот, например. Всё прочитали? http://refspecs.linux-foundation.org/IA64-softdevman-vol1.pdf Закончите, там есть шкаф документации по UEFI.

                                0
                                Если я использую какую-то функцию, которую я до этого не использовал, я беру и читаю мануал по ней, т.к. я пишу на C и частенько даже не на уровне ОС, а в этом случае у меня слишком мало ног, за день закончатся. Ну а если не проверять возвращаемые значения, тем более на C, то пардоньте, ССЗБ в полной своей красоте.
                                  +4
                                  Простите, но я никак не понимаю, каким образом возврат -1 в форке является багов, если это документированная функция. Это не креш функции, не внезапный выход по эксепшену. Это задокументированный функционал, что если форк не смог создать процесс, он возвращает -1
                                    0
                                    Речь не о возврате, а о дальнейшем некорректном использовании возвращаемого значения.
                                      +1
                                      Так это, получается, в kill() ошибка тогда?

                                      Или таки «некорректная интерпретация и игнорирование ошибок — зло»?
                                        0
                                        Так это, получается, в kill() ошибка тогда?

                                        Нет.
                                          +6

                                          Ошибка в дизайне системного API. Не стоило жалеть отдельного имени для killall или там killpg. Да, это можно назвать ошибкой в kill.

                                            +2
                                            Мне кажется, проблема несколько глубже. Она скорее в неверном понимании системного API. Системное API — штука низкоуровневая, экономная к ресурсам, и этим, видимо, объясняется тот факт, что оно практически полностью основывается на принципе возврата цифровых кодов результата выполнения операций. То, что возвращает fork() — это код возврата выполнения метода. Далее он может быть интерпретирован как PID процесса в случае положительного числа или как признак ошибки во всех остальных случаях. Ключевое тут — он может (и должен) быть интерпретирован.

                                            kill() принимает на вход PID процесса, который нужно «убить» (или специальное значение -1 для того, чтобы «убить всех»). Косяк именно в понимании API. kill() хочет PID, а ему суют код возврата fork().

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

                                            Еще раз: fork() отдает код возврата, kill() на вход просит pid или -1. Почему кто-то отдает на вход kill() код возврата, а потом жалуется, что ему прилетело граблей по лбу — непонятно.
                                              +4
                                              Еще раз: fork() отдает код возврата

                                              pid_t fork(void);

                                              kill() принимает на вход PID процесса, который нужно «убить» (или специальное значение -1 для того, чтобы «убить всех»

                                              Наличие подобных специальных значений давно уже считается антипаттерном.


                                              В общем и целом, не совсем понятно, с какой целью прикладники ручками лезут в fork()/kill()

                                              Потому что они не читали обсуждаемого поста и подобных ему, и не знают что наобум туда лезть опасно (и не узнают если этот "слишком очевидный" пост убрать!). Ну и плюс не всегда есть нормальные обёртки без подводных камней.


                                              и жалуются при этом, что системное API концептуально отличается от привычного им прикладного

                                              А вот это как раз понятно почему. Потому что нет у системного API какой-то особой специфики, запрещающей делать его удобным в использовании.


                                              Ладно бы это было API системных вызовов, где надо номера вызовов экономить (кстати, а зачем?) — так ведь это API на языке высокого уровня, где нет никаких причин "лепить" несколько режимов работы на одну функцию.

                                                0
                                                pid_t fork(void);


                                                Этим вы что показать хотели? То, что fork() возвращает значение типа pid_t? Я еще вот такую штуку могу показать:

                                                #define __PID_T_TYPE __S32_TYPE


                                                pid_t — это просто 32-битный знаковый int. Это не pid процесса, это целое число в диапазоне -2147483648...2147483647. Заметьте, не pid, а pid_t, т.е. тип, предназначенный для работы с pid'ами.

                                                Наличие подобных специальных значений давно уже считается антипаттерном.


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

                                                Ну и плюс не всегда есть нормальные обёртки без подводных камней.


                                                Над управлением процессами — есть. Миллион штук.

                                                Потому что нет у системного API какой-то особой специфики, запрещающей делать его удобным в использовании.


                                                Он удобен. Он прост. Просто нужно понять, что любой вызов возвращает числовой код возврата, который всенепременно нужно интерпретировать, а на вход принимает какое-то значение, возможно интерпретированное из кодов возврата предыдущих вызовов.

                                                Любое изменение в пользу бОльшего удобства, вероятнее всего, ударит по производительности. 2% производительности в системных вызовах в легкую положат производительность вашего прикладного решения в разы.

                                                Ладно бы это было API системных вызовов, где надо номера вызовов экономить (кстати, а зачем?)


                                                Ну, например, чтобы оно на микроволновке работало…

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


                                                Это системное API.
                                                  +1
                                                  Любое изменение в пользу бОльшего удобства, вероятнее всего, ударит по производительности. 2% производительности в системных вызовах в легкую положат производительность вашего прикладного решения в разы.

                                                  Не верю. Скорее поверю, что изменение этого API никак не повлияет на производительность. Всё-таки что fork, что kill кучу раз в секунду не вызываются.


                                                  Это системное API.

                                                  А дальше? Почему в системном API нужно лепить на одну функцию несколько режимов работы?

                                                    0
                                                    Не верю. Скорее поверю, что изменение этого API никак не повлияет на производительность.


                                                    Зря не веришь. Собственно, есть `pid_t fork(void)`, например. А также тысячи других методов, все как один возвращающие тот или иной вариант int'а. На что будем менять, при условии, что апи все еще на C и поломать юзерспейс нельзя?

                                                    Вы же согласны, надеюсь, что АПИ в целом должен быть как можно более однообразен? Т.е. менять таки надо все методы АПИ? В связи с этим предлагайте, какой тип в пределах C можно использовать для более явного определения фейл/не фейл? В структуру паковать? Boxing/Unboxing небесплатный. Возвращать указатель на int? Вы уверены, что идея унести все это дело в кучу — хорошая, и никак не скажется на производительности? Из более-менее быстрого — разве что битовые маски возвращать? Но вы уверены, что битовые маски будут более очевидны?

                                                    В общем и целом альтернативы-то особо не видно? Или у вас таки есть решение?

                                                    Почему в системном API нужно лепить на одну функцию несколько режимов работы?


                                                    Я вот не уверен, что pid > 0 и pid = -1 — это разные режимы работы kill. Надо, очевидно, в код смотреть.
                                                      0
                                                      В общем и целом альтернативы-то особо не видно?


                                                      Смотрим на Windows API (CreateProcessEx vs fork, например), возвращаемся кушать кактус POSIX — там всё просто и очевидно ;)
                                                        0
                                                        Погодите, но Windows API, разве, не плюсовое?
                                                          0

                                                          С фига ли оно плюсовое?

                                                            0
                                                            В нутре — самое обычное C, структуры по 20 полей и zero terminated strings.
                                                              0
                                                              Процессы в Windows по дефолту гораздо тяжелее, чем в Линукс. Количество аттрибутов и диспетчер очереди также сложнее.
                                                              С одной стороны в винде больше информации и возможностей управления, с другой стороны запустить и остановить 1000 процессов в Линуксе — в разы быстрее.
                                                              0
                                                              Кстати говоря, в WinAPI нормальной альтернативы fork() в принципе нету. Она есть в Native API, но тоже со своими сложностями.
                                                              Поэтому в Cygwin и даже WSL1 разработчикам пришлось эмулировать fork() с помощью костылей…
                                                              0
                                                              разве что битовые маски возвращать?

                                                              Причем, оно уже возвращается как битовая маска, совмещенная с реальным значением: signed же.

                                                                +1
                                                                Ну да, я о том же. Вполне допустимо как битовую маску интерпретировать. Минус-бит есть — зафейлилось.
                                                                0
                                                                В общем и целом альтернативы-то особо не видно? Или у вас таки есть решение?

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


                                                                Я вот не уверен, что pid > 0 и pid = -1 — это разные режимы работы kill. Надо, очевидно, в код смотреть.

                                                                Один тот факт, что в документации понадобилась отдельная строчка для описания случая с -1 — достаточен для того, чтобы считать это разными режимами работы.


                                                                Но если вам интересен код — то вот он: https://github.com/torvalds/linux/blob/df561f6688fef775baa341a0f5d960becd248b11/kernel/signal.c#L1560-L1561

                                                                  0
                                                                  А вы точно уверены, что прямо всенепременно надо? Какбэ, kill() подразумевает, что мы как-то там процессами порулить пытаемся, можно и доку разок прочитать, она небольшая.

                                                                  А вот что будет при изменении сигнатуры kill() для прикладного уровня — это сложнопредсказуемая вещь. Едиснтвенное, что можно сейчас прямо сделать — запилить kill_single(pid, signal) и kill_all(signal) в виде оберток над kill. Только кто ими будет пользоваться?
                                                                    +1

                                                                    Если бы kill не было изначально — то kill_single использовали бы все.


                                                                    Если начать менять API сейчас — то пользоваться kill_single будут те, кто пишет программу для гипотетического нового стандарта POSIX и умеет читать предупреждения компилятора об устаревших функциях.

                                                                      –2
                                                                      Ну вот, опять мы мануалы не читаем.

                                                                      The kill() system call can be used to send any signal to any process group or process.


                                                                      Переведу: kill() — системный вызов, который может быть использован для отправки сигнала любой группе процессов или прицессу.

                                                                      И вот этот системный вызов kill() делает именно то, что декларирует. Положительный pid — посылает конкретному процессу. 0 — каждому процессу, принадлежащему группе вызывающего процесса. -1 — каждому процессу, которому вызывающий процесс имеет право слать сигналы. Любое отрицательное число — айдишник группы, которой слать.

                                                                      kill() — это не «убивалка процесса». Это инструмент отправки сигналов. И этот самый инструмент делает именно то, что декларирует, депрекейтить его никто не будет.

                                                                      Поэтому kill_single — ересь, а не сигнатура фукнции. kill_pid, kill_group, kill_current, kill_owned — это набор методов, на который вы предлагаете распилить один единственный kill. При этом внутри они все будут делать примерно одно и то же. Ну и зачем, спрашивается? Просто заради «порадовать внутреннего перфекциониста» распилить один отлично работающий метод на 4 дублирующих функционал?
                                                                        +1
                                                                        И вот этот системный вызов kill() делает именно то, что декларирует. Положительный pid — посылает конкретному процессу. 0 — каждому процессу, принадлежащему группе вызывающего процесса. -1 — каждому процессу, которому вызывающий процесс имеет право слать сигналы. Любое отрицательное число — айдишник группы, которой слать.

                                                                        С этим я не спорю, но вот зачем под одним именем объединять 3 разные вещи — совершенно не понятно.


                                                                        Я бы с вами согласился если бы айдишники групп процессов были отрицательными числами. Но это положительные числа, которым надо сменить знак перед передачей в kill. А потом kill в отдельной ветке сменит знак обратно, чтобы получить айдишник группы. Ну и нахера эти сложности на пустом месте?


                                                                        Поэтому kill_single — ересь, а не сигнатура фукнции. kill_pid, kill_group, kill_current, kill_owned — это набор методов, на который вы предлагаете распилить один единственный kill. При этом внутри они все будут делать примерно одно и то же. Ну и зачем, спрашивается?

                                                                        Во-первых, для упрощения ментальной модели.


                                                                        Во-вторых, для снижения вероятности ошибки за счёт защитного программирования.


                                                                        В-третьих, это может улучшить производительность, поскольку подобные функции лучше "дружат" с предсказателем переходов, нежели перегруженные (точнее, могло бы улучшить производительность если бы не причины, перечисленные мною ранее).

                                                                          0
                                                                          С этим я не спорю, но вот зачем под одним именем объединять 3 разные вещи — совершенно не понятно.


                                                                          4 же!)))

                                                                          А с точки зрения реализации, видимо, тупо 2.

                                                                          Положительные pid — конкретный pid процесса. Неположительные — умножаем на -1 и считаем gid'ом. 0, видимо, броадкаст в пределах текущей группы, 1 — традиционно init, который отсекает то, на что прав нет, остальные значения — конкретные айдишники групп.

                                                                          Т.е. на уровне glibc метода 2, сценария 4. Видимо, пока срались, каждый на белом коне, как пилить, решили оставить 1.

                                                                          Во-первых, для упрощения ментальной модели.


                                                                          Ну, тут ментальная модель разойдется с реальной. И это таки +3 метода, которые, при всей их примитивности, таки придется поддерживать.

                                                                          Во-вторых, для снижения вероятности ошибки за счёт защитного программирования.


                                                                          Тут у нас случай был — человек не проверил возвращаемое значение от fork и как есть в kill сунул. Ну, мне кажется, защитное программирование тут не поможет…

                                                                          В-третьих, это может улучшить производительность, поскольку подобные функции лучше «дружат» с предсказателем переходов, нежели перегруженные .


                                                                          Самое частое использование, видимо, таки kill с pid > 0. Да и не так часто оно нужно. А на фоне вороха поддерживаемых архитектур перегружать таки придется, вроде как. Ну, видимо, экономия на спичках будет.

                                                                          В общем и целом, API не самый удобный, но не настолько катастрофический, как тут в статье пытаются описать, имхо. Поэтому и «исправлять катастрофическую ситуацию» никто не будет, даже если найдется энтузиаст, готовый за это взяться. Некоторое количество крупных контор могут не понять, заради чего им переписывать код, работающий поверх стопроцентно валидного и на тыщу раз известного метода, в угоду внутреннему перфекционизму…
                                                                            0
                                                                            Тут у нас случай был — человек не проверил возвращаемое значение от fork и как есть в kill сунул. Ну, мне кажется, защитное программирование тут не поможет…

                                                                            Вообще-то именно от таких тупых ошибок оно и поможет.

                                                                              –1
                                                                              Но это же вы про то, что пользовательский код с позиций защитного программирования написан должен быть, да? Тогда возражений не имею.
                                                                                0

                                                                                Неа, я именно про библиотечный код пишу. Он должен быть написан с позиций защитного программирования и не полагаться на аналогичное свойство пользовательского кода — по крайней мере, до тех пор пока это не сказывается на производительности.

                                                                      0
                                                                      А что, механизма устаревания апи там нет? В чём проблема пометить функцию устаревшей и когда-нибудь через 20 лет её удалить?
                                                                        0
                                                                        Механика устаревания есть. Вопрос в том, зачем депрекейтить один хорошо работающий метод в пользу 4 других, работающих не так хорошо и дублирующих друг друга?
                                                                          0

                                                                          Откуда 4? И почему они будут дублировать, если код в разных ветках разный? И почему они будут работать не так хорошо? Сплошные вопросы.

                                                                            –1
                                                                            Откуда 4?


                                                                            The kill() system call can be used to send any signal to any process group or process.
                                                                            If pid is positive, then signal sig is sent to the process with the ID specified by pid.
                                                                            If pid equals 0, then sig is sent to every process in the process group of the calling process.
                                                                            If pid equals -1, then sig is sent to every process for which the calling process has permission to send signals, except for process 1 (init), but see below.
                                                                            If pid is less than -1, then sig is sent to every process in the process group whose ID is -pid.


                                                                            Сценария таки 4: конкретный процесс, группа вызывающего процесса, все процессы, на который у вызывающего хватает прав, конкретная группа процессов.

                                                                            И почему они будут дублировать, если код в разных ветках разный?


                                                                            Ну, собственно, глубоко не копал, но ветки таки в коде всего 2. Отдельно конкретный pid убить и вторая if (pid <= 0). code.woboq.org/userspace/glibc/signal/kill.c.html#34

                                                                            Т.е. у вас будет 4 метода на 2 ветки кода. Т.е. либо оборачиваем, либо копи-пастим.

                                                                            И почему они будут работать не так хорошо?


                                                                            Потому что kill уже сейчас уже работает, а ваши 4 еще не написаны?

                                                                            Зачем депрекейтить уже существующий абсолютно валидный метод? Зачем пилить этот метод на 4 других, реализация которых либо будет тупо оберткой над тем, который мы попилили, либо будет дублировать себя? В каких сценариях некорректная обработка ошибок и передача чего ни попадя в системные API становится проблемой именно системного API? Почему не написать враппер для особенно страждущих? Кто этим займется? Сколько это будет в итоге стоить? Сплошные вопросы…
                                                                    +2
                                                                    В структуру паковать? Boxing/Unboxing небесплатный.

                                                                    Вообще-то тут две проблемы:


                                                                    1. Что возвращаемое значение функции только одно (а эмуляция в виде структуры в случае сложнее чем complex<> будет требовать скрытого указателя). Посмотреть любое классическое соглашение о вызове — там 4-6-10 параметров функции, но только одно возвращаемое значение. Во всяких Go это переламывают, но медленно.

                                                                    А если бы возвращаемых значений было несколько — вот этот костыль с -1 плюс errno был бы тупо не нужен, подобная системная функция возвращала бы уже пару значений — типа <pid,error>, <fd,error> и так далее.


                                                                    Более того, такие функции изначально есть! pipe() костылирует три значения через явно переданный массив для дескрипторов.


                                                                    1. Запись в errno может быть дорогой. Во-первых, ядро его напрямую не отделяет: для Linux, например, значения -4096...-1 транслируются в код ошибки, а для FreeBSD/x86, например, флаг CF означает, что значение — код ошибки, а не результат. А потом его ещё и надо записать в память, причём сейчас практически всегда разрезолвить ещё thread-local адрес конкретной errno. Да, на двух регистрах было бы всяко дешевле — и понятнее нормальному компилятору, что тут делать.

                                                                    Так что тут косвенный unboxing на двух регистрах давно уже дешевле. И ответ: да, производительность только улучшилась бы.


                                                                    Я вот не уверен, что pid > 0 и pid = -1 — это разные режимы работы kill. Надо, очевидно, в код смотреть.

                                                                    Очевидно разные. Там даже три режима:
                                                                    pid>0 — конкретный процесс
                                                                    pid==-1 — все кого можем
                                                                    pid<-1 — -pid задаёт pgid

                                                                      +1
                                                                      А если бы возвращаемых значений было несколько — вот этот костыль с -1 плюс errno был бы тупо не нужен, подобная системная функция возвращала бы уже пару значений — типа <pid,error>, <fd,error> и так далее.


                                                                      Это да, но ядро же на glibc на C же пишут. Поэтому имеем то, что имеем, и без коренных изменений языка ничего не поменяется.

                                                                      Так что тут косвенный unboxing на двух регистрах давно уже дешевле.


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

                                                                      Очевидно разные. Там даже три режима:
                                                                      pid>0 — конкретный процесс
                                                                      pid==-1 — все кого можем
                                                                      pid<-1 — -pid задаёт pgid


                                                                      Их либо 2 (с точки зрения имплементации), либо 4 (с точки зрения ментальной модели).
                                                                      4 такие:
                                                                      > 0 — конкретный процесс по PID
                                                                      0 — все в группе процесса
                                                                      -1 — все, на какие права есть
                                                                      < -1 — все в указанной группе
                                                                      При этом они мапятся тупо в 2:
                                                                      >1 — по PID
                                                                      <1 — по GID, просто 0 — wildcard для текущего, 1 — gid init'а, остальное — другие группы.
                                                                        +3
                                                                        Тут смысл в том, что в текущей ситуации при корректной успешной работе функции анбоксинга нет

                                                                        Он есть. Реализации линуксовых сисколлов в glibc по сути работают так:


                                                                        ssize_t ret = syscall(...);
                                                                        if (ret < 0 && ret >= -4096) {
                                                                          *errno_ptr() = -ret;
                                                                          return -1;
                                                                        } else {
                                                                          return ret;
                                                                        }

                                                                        Вот эта проверка с возможной заменой и есть анбоксинг, только манера специфическая.


                                                                        Их либо 2 (с точки зрения имплементации), либо 4 (с точки зрения ментальной модели).

                                                                        Ну да, про 0 я пропустил. Но с точки зрения реализации оно таки мапится на:


                                                                        1. Конкретный pid — делается лукап по нему (где-то есть мапа всех процессов в данном пространстве pidʼов; дерево или хэш-таблица — зависит от свойства местности).
                                                                        2. -1 — Пробегается явным образом список всех процессов (может быть та же мапа).
                                                                        3. 0 или <-1 — находится группа процессов и итерирование по ней, если она явно как-то выделена (например, подсписок); может быть опять же по всем видимым процессам с их фильтрацией.

                                                                        Совмещение случаев 2 и 3 возможно, а вот 1 наверняка нет, потому что быстрый поиск (быстрее линейного от количества процессов) по pid — обязателен для нормальной работы системы, и если он есть, то незачем и тут делать перебор всех процессов.


                                                                        При этом они мапятся тупо в 2:

                                                                        Угу.


                                                                        Но это не причина объединять интерфейсы. Внутри много вещей реализовано одинаково (например, read фактически враппер вокруг readv), но зачем такие опасные комбинации?


                                                                        но ядро же на glibc на C же пишут. Поэтому имеем то, что имеем, и без коренных изменений языка ничего не поменяется.

                                                                        Увы. Ну лет через 20 и это сдвинется.

                                                                0
                                                                pid_t fork(void);


                                                                а вы можете найти ту древнюю запись этой функции когда она именно создавалась, а не современное объявление?

                                                                Тут косяк опять же не в функции, а в том, кто её в современном виде обозвал pid_t. Попробую дома поискать книгу по Linux старенькую, не помню что там был pid_t
                                                                  0
                                                                  Функцию же никто не обзывал. pid_t — это просто тип возвращаемого значения.
                                                                    +1
                                                                    раньше он было просто int и считался именно возвращаемым значением
                                                                    также как и read возвращает -1 и -1 как возвращаемое значение почти всегда говорит об ошибке. А вот тому кто его обозвал pid_t нужно сказать «спасибо»
                                                                      –2
                                                                      Ну, очевидно, это произошло где-то в момент переползания на 64-битную архитектуру. Тупо тайпдефом определили для кроссплатформенности и возможности разом поменять. Название, возможно, не самое удачное, но тут уж так мир устроен.

                                                                      Просто тот, кто потом доку читает, почему-то уверен, что pid_t — это значение, возвращаемое fork'ом. Классическая невнимательность. А это — тупо тип.
                                                                        +2
                                                                        Ну, очевидно, это произошло где-то в момент переползания на 64-битную архитектуру.

                                                                        "Очевидность" вас подвела. pid_t родился, когда 32767 процессов стало недостаточно, и перевели с 16- на 32-битные значения. Было это ещё в самом начале 1990-х, синхронно как минимум у BSD и Linux. У SysV, возможно, даже раньше.


                                                                        Просто тот, кто потом доку читает, почему-то уверен, что pid_t — это значение, возвращаемое fork'ом. Классическая невнимательность. А это — тупо тип.

                                                                        Я не знаю, кто этот "тот" — я никогда так не читал и не слышал такого заблуждения ни от кого из коллег.

                                                                          0
                                                                          не знаю, кто этот «тот» — я никогда так не читал и не слышал такого заблуждения ни от кого из коллег.


                                                                          Прочтите чуть выше ветвь, там как раз всплывает заблуждение на тему «ну вот смотрите на сигнатуру, pid_t fork() — это он pid же возвращает»
                                                                        +2
                                                                        На самом деле, видимо, косяк тут в сигнатуре kill.

                                                                        int kill(pid_t pid, int sig);


                                                                        pid_t — фиг с ним. А вот то, что аргумент обозван pid (что явно трактуется как process id) — это уже не совсем корректно, т.к. там и gid*(-1) может встретиться, и пара спец-значений. Переименовать в receiver какой-нибудь, и уже лучше будет, наверное.
                                                                  +5
                                                                  Если бы в древности не занимались экономией на спичках…
                                                                  int kill(pid_t pid, int sig, int kill_flags)

                                                                  или
                                                                  int killall(int sig)
                                                                  

                                                                  Чем плохо?
                                                                    –1
                                                                    Вся история начинается таки с pid_t fork(void). И с искренней уверенности, что pid_t — это всенепременно pid процесса.

                                                                    Весь сыр-бор оттого, что вместо pid'а процесса в kill() отдают код возврата fork'а. При любом валидном pid имеющийся kill делает ровно то, что от него ожидают, поэтому переписывание kill — ну, такое себе извращение, да и вам никто не мешает тупо напилить обертку над kill вида kill_one + kill_all, которые заставят вашЧудесныйКилл вести себя именно так, как вы ожидаете.

                                                                    В целом, описанное поведения kill к фатальным последствиям не приводит, ФС не крашит, блок питания не сжигает, фондовые биржи не обрушивает. Без рутовых прав оно просто убьет ваш текущий сеанс, причем в зависимости от сигнала может еще и аккуратно завершить. Ну, такая себе паника. Те несуразности, которые могут к серьезным последствиям привести — их правят, а непонимание того, что kill — достаточно серьезный инструмент, могущий в некоторые последствия… Ну, кто тут кому злобный буратино?
                                                                      +2
                                                                      да и вам никто не мешает тупо напилить обертку над kill


                                                                      Мне никто не мешает и код возврата от fork проверить ;) Раз уж в POSIX так принято — приходится использовать существующее API, с его magick numbers.
                                                                        –1
                                                                        Мне никто не мешает и код возврата от fork проверить ;)


                                                                        Ну вот, главное же — понимать, что fork код возврата отдает. И понимать, что такое собственно форк. Сфейлиться ведь может что угодно, поэтому первое, что принято делать (правило вежливости, что ли) — выяснять, как определить, сфейлилось/нет.

                                                                        Идея «пусть софтина работает дальше, пусть оно как-то само поломается» — ну, такое себе… И это нормальное отношение не только при работе с системными апи, но и с прикладными. Я прикладник, поэтому не проверяю ошибки — говно позиция.

                                                                        А magic numbers — оно историческое же, да и не так оно плохо, как его малюют. Бывает и хуже, а «более лучших системных апи» я не особо наблюдаю, справедливости ради.
                                                                          +1

                                                                          Более лучших системных API вы в языке Си не наблюдаете из соображений обратной совместимости. Это не означает, что если бы изначально предложили другое API — оно бы обязательно оказалось хуже.


                                                                          А вот в других языках они есть, тут в комментариях уже приводили варианты fork на Питоне и на Rust.

                                                                            –1
                                                                            тут в комментариях уже приводили варианты fork на Питоне и на Rust


                                                                            И то, и другое — обертки над C-шным методом. Если нужен «более другой АПИ» — всегда есть обертки, пользуйтесь ими. А «переписать системное АПИ на python» — в голос, что называется…

                                                                            Более лучших системных API вы в языке Си не наблюдаете из соображений обратной совместимости. Это не означает, что если бы изначально предложили другое API — оно бы обязательно оказалось хуже.


                                                                            Я их несколько видел. Существенно лучше — нет. Вся «прелесть ситуации» в том, что C-API — это штука, которую можно заюзать откуда угодно, все остальные решения будут местечковыми обертками, нужными 3,5 пользователей.
                                                                              +2
                                                                              И то, и другое — обертки над C-шным методом.

                                                                              А вот Go или freepascal (если верно вспомнил, как этот паскаль зовётся) делают собственные врапперы сисколлов. И могут при этом применять другие варианты, чем glibc (например, последняя всегда мапит open() в openat(), что не обязательно повторяется).


                                                                              C в этом плане хорош только тем, что синхронность реализации в glibc контролируется командой ядра.


                                                                              Я их несколько видел. Существенно лучше — нет.

                                                                              Именно в таких вопросах, как отдача кода ошибки или избавление от подобного потенциального конфликта — альтернативы вполне могут быть лучше.

                                                            +1

                                                            Баг в том, что это значение не обработано.

                                                          +2

                                                          Или не полагаться на документацию (и на то, что все будут полностью её читать), а использовать типы, чтобы гарантировать, что программа просто не может получить неправильный pid.

                                                            –3
                                                            А, ну да. И все-все-все срочно переписать на Rust…
                                                              +1

                                                              Было бы, кстати, неплохо так и сделать. По крайней мере, код на Си без плюсов — в первую же очередь. Жаль только, что эта мечта нереализуема...

                                                                0
                                                                Ну, идея переписать ядро на Rust — утопия
                                                                  –1
                                                                  Мне кажется что народ не понимает что такое C и код ядра
                                                                  Если коротко, то переопределив getC и putС в C на своей платформе, то можно будет компилить практически любой исходный код написанный на C.
                                                                    –1
                                                                    Мне кажется что народ не понимает что такое C и код ядра


                                                                    Чисто для самопроверки: мне кажется, что C — это человекочитаемый относительно-платформонезависимый ассемблер. Я прав/нет?

                                                                    А Rust, в моем далеком от расто-движухи понимании, какая-то достаточно странная (не отрицаю, возможно, «опередившая время», «прорывная» и прочее) мало кому нужная штука с неочевидными преимуществами, неустоявшимся апи и непонятной сферой применения. Больше всего меня смущает такая штука забавная, практически уже закон интернета: в любом обсуждении нюансов любого языка всенепременно возникает человек, демонстрирущий якобы-очевидные сферические преимущества Rust'а над обсуждаемым языком, всеми прочими языками, причем во всех проявлениях. Печально в этом то, что реальных проектов, демонстрирующих «успехи» не прилагается…
                                                                      0
                                                                      Печально в этом то, что реальных проектов, демонстрирующих «успехи» не прилагается

                                                                      Вы это сейчас серьёзно?

                                                                        0
                                                                        В основном переписывание инфраструктурных боттлнек-сервисов. Флагманского продукта не наблюдается. Не подумайте, я не злорадствую, исключительно успехов желаю, даже rust-by-example уже до 3 страницы дочитал.
                                                                        0
                                                                        Я прав/нет?

                                                                        ага

                                                                        А Rust, в моем далеком от расто-движухи понимании, какая-то достаточно странная мало кому нужная штука

                                                                        а это уже C++ без возможности выстрелить себе в колено с «современным» синтаксисом.

                                                                        непонятной сферой применения

                                                                        Сфера понятная, те кто пишет на kotlin/swift подобных языках сможет делать байткод нормальный без вникания в сами байтики и память.
                                                                          –2
                                                                          Сфера понятная, те кто пишет на kotlin/swift подобных языках сможет делать байткод нормальный без вникания в сами байтики и память.


                                                                          Может быть, в том и проблема? В смысле, в «революционном подходе»? Зачем Kotlin тот же нужен — примерно понятно. Это просто чуть-чуть другая Java. И относительный взлет Котлина связан с тем, что в бюджетных андроидах java древняя, а kotlin под древнюю JVM позволяет писать на чуть-чуть другой современной Java. Дальше, конечно, ребят понесло… И нативный, и вебный и все-все-все. Но площадка для старта понятна, и трудозатраты на миграцию не слишком высокие.

                                                                          А Rust — революционный все-по-новому язык, где еще все шишки собрать не успели, а выгоды неочевидны. Причем его несут-то в массы с помпой «идеальная замена языку X (вставьте любой вообще язык)». При этом, в моём понимании, например, тому же C он замена очень и очень слабая (по крайней мере в тех областях, где прямо C и прямо оправдан). Замена Go? В областях, где действительно обоснованно применяется Go (круды крудить да ресты рестить), Rust — из пушки по воробьям без видимых причин. Доходит до того, что Wordpress на Rust переписать предлагают…

                                                                          А из реально мне, как человеку со стороны, видимых ниш — ну, Rust — он C++ конкурент в определенных сферах применения.
                                                                            +1
                                                                            При этом, в моём понимании, например, тому же C он замена очень и очень слабая (по крайней мере в тех областях, где прямо C и прямо оправдан).

                                                                            Можете раскрыть мысль? Мне на ум приходят только платформы, которые rustc пока не поддерживает.

                                                                              0
                                                                              Это просто чуть-чуть другая Java

                                                                              ну вот нет
                                                                              А Scala тогда что по вашему? :)
                                                                              Rust — революционный все-по-новому язык

                                                                              И эта «революция» похожа на котлин/свифт
                                                                            0
                                                                            А Rust, в моем далеком от расто-движухи понимании, какая-то [...] мало кому нужная штука с неочевидными преимуществами, неустоявшимся апи и непонятной сферой применения.

                                                                            Простите, а что считается "устоявшимся API"?

                                                                              +1
                                                                              больше всего меня смущает такая штука забавная, практически уже закон интернета: в любом обсуждении нюансов любого языка всенепременно возникает человек, демонстрирущий якобы-очевидные сферические преимущества Rust'а над обсуждаемым языком, всеми прочими языками, причем во всех проявлениях.
                                                                              Вы сейчас описали парадокс Блаба
                                                                  +1
                                                                  Не проверять ошибки плохо, пнятненько? ;)
                                                                    +2

                                                                    Да, понятненько. Читать за пределами выделенной памяти — плохо. Конкурентно менять структуру неатомарным образом — плохо. Не валидировать входые данные — плохо.


                                                                    … Почему же это "плохо" происходит снова и снова? Наверное, потому что люди упорно отказываются писать софт без багов, хотя им стопятьсот раз говорили, что баги — это плохо.


                                                                    А вы пишите софт без багов?

                                                                      +1
                                                                      Почему же это «плохо» происходит снова и снова?


                                                                      Потому, что сделать хорошо сложнее, чем как попало. А люди они ленивые…

                                                                      А вы пишите софт без багов?


                                                                      Кто без греха, пусть первым бросит камень? ;) Каюсь, зело прегрешен бываю. Но не упорствую в своих прегрешениях, епитимью, тестировщиками наложенную, исполняю со скрежетом зубовным, но без роптаний :D
                                                                        0

                                                                        Последний раз, когда я читал теорию алгоритмов, там утверждалась невозможность статического анализа для произвольно взятой программы, так что тут вопрос не "сложности" и "лени", а теоретической невозможности делать предсказания о поведении произвольно взятого кода.

                                                                          0
                                                                          Из теоретической невозможности делать предсказания о поведении произвольно взятого кода разве следует, что в любой программе не нужно делать никаких проверок?
                                                                            +7

                                                                            Нет, конечно. Но глядя на код вы не можете сказать, сделаны там все проверки или нет. В ряде языков пошли на экстремальные меры для того, чтобы обеспечить exhaustive pattern matching, и вот эту конкретную ошибку условный Rust бы просто не пропустил; но глядя на код вы не можете сказать, все ли случаи покрыты.


                                                                            Это называется баги (и они бывают, в т.ч. и по невнимательности). Например, я бы на -1 проверил, но вот что -1 у kill имеет специальный семантический смысл, я без этой статьи никогда бы не узнал.

                                                                              0
                                                                              я бы на -1 проверил, но вот что -1 у kill имеет специальный семантический смысл, я без этой статьи никогда бы не узнал


                                                                              Потому что при выполненной проверке на -1 != fork() статьи не случилось бы ;)
                                                                                0

                                                                                Нельзя так проверять, иначе 0 и >0 в одной ветке окажутся. А второй раз вызвать форк — 4 процесса вместо двух. Это баг (ц)!

                                                                          +3
                                                                          Обычно баг в программе проявляется локально — то есть программа падает, максимум портит данные, с которыми работала. Есть огромное количество случаев, когда потеря данных/необъяснимое падение приложения в некоторых ситуациях не имеет серьёзных последствий. В таких случаях программист, поджимаемый ленью или сроками может проигнорировать часть ошибок и документации.

                                                                          А данное API подкладывает грабли, делающие сбой в программе совершенно нелокальным и влияющим на абсолютно несвязанные с программой процессы.
                                                                      +6
                                                                      Причём тут баги? В статье человек ответственность переносит на других.
                                                                      Форк может глюкануть. Так же, как и malloc

                                                                      Глючит программа погромиста, а вышеописанные функции действуют согласно описанию. Возврат ошибки это нормальное поведение, а не «глюк».
                                                                        +6

                                                                        Большинство багов — это человек "не подумал".


                                                                        В статье человек описывает последствия "не подумал" в этом месте — у него не просто "не работает", но "не работает с катастрофическими последствиями".


                                                                        (это очень интересная черта русскоязычного интернета — тут не прощают ошибок, и любой рассказ человека про свои ошибки встречают комментариями на тему "какой он идиот"/"некачественный программист" и т.д.).


                                                                        В то же самое время, в англоязычной среде, честный рассказ про свои ошибки воспринимается с благодарностью — человек рассказал и поделился критическим опытом, на котором можно учиться.

                                                                          +4

                                                                          Простите за баян...


                                                                          Американский форум- задал вопрос, тебе на него обстоятельно и вежливо ответят.
                                                                          Еврейский форум- задал вопрос, тебе зададут встречный вопрос.
                                                                          Русский форум-задал вопрос, тебе ещё 2 часа будут объяснять какой ты мудак!
                                                                          +8
                                                                          Это переводчик мгимо финишд, в оригинале fork() can failed, что не означает что это что-то ненормальное, и никакого глюканула тут нет.
                                                                          –1
                                                                          Ну например вы можете написать fdisk, который при создании раздела не проверяет доступное место и может затереть существующие, и выпустить его в обновлении релиза дистрибутива, где его будут запускать от рута на продакшене.

                                                                          И конечно виноват в этом будет видимо кто? биос?
                                                                            0
                                                                            Ну вот в OpenBSD такой fdisk(во всяком случае, был в последний раз, когда видел). Какие вы ему границы раздела вводите — с такими и создает. И виноват, кхм, тот, кто вводит.
                                                                              +2

                                                                              Будет баг. Кому-то надо будет пойти и написать тест на этот случай, а потом пофиксить этот баг.


                                                                              Я не понимаю вашей мысли. Моя мысль: баги — плохо; баги — неизбежно. Учиться на чужом опыте — хорошо.

                                                                            0
                                                                            да мало ли причин.

                                                                            Например, у вас Mac OS, не установлена environment variable OBJC_DISABLE_INITIALIZE_FORK_SAFETY и немного не повезло.
                                                                            +10

                                                                            ffff… Спасибо, перечитал man kill. Буду знать.

                                                                              –13
                                                                              Не читаем доку, отстреливаем себе ногу и пишем сразу на хабр — это новый тренд хабра?
                                                                                +22

                                                                                Вот я с линуксами 15 лет, а про "интересное" поведение kill с отрицательными значениями узнал только сегодня. Буду знать. Автору спасибо.


                                                                                А вы, видимо, читали все доки к тому, чем пользуетесь, да?

                                                                                  –8
                                                                                  Кода я дергал форк в Python, то да — прочитал сначала как он работает, потом написал и обработка pid = -1 у меня была сделана сразу.
                                                                                    +20

                                                                                    Э-э-э, но ведь как раз на Питоне вызов os.fork не может вернуть -1! Зачем вы этот случай обрабатываете?

                                                                                      –10
                                                                                      Там оно через ексепшен «приходит».
                                                                                      Но сути не меняет как получить ошибку главное не идти дальше если форк не удался.
                                                                                      И уж точно не вызывать kill после провала форка.
                                                                                  +2
                                                                                  А «Examples» в доках тоже обязательно читать или достаточно основной части man?

                                                                                  Даже касательно того же kill, например, в man kill FreeBSD поведение PGID затронуто исключительно в части примеров, без описания оного в параметрах программы.
                                                                                    +1

                                                                                    Ничего не путаете? По вашей ссылке


                                                                                    The following PIDs  have special meanings:
                                                                                    
                                                                                         -1      If superuser, broadcast the signal to all processes; otherwise
                                                                                             broadcast to all processes belonging to the user.

                                                                                    находится в разделе Description не самого длинного на свете мана.

                                                                                      0

                                                                                      Он не на -1 жаловался, а на PGID же.

                                                                                        +2
                                                                                        Именно так, "-1" в основном разделе, а остальные «отрицательные случаи» только в примерах
                                                                                        Terminate the process group with PGID 117:

                                                                                        kill — -117
                                                                                          +1

                                                                                          А, да. Но прикол в том, что все побежали проверять man kill, хотя в статье прямым текстом описывается поведение системных вызовов fork() и kill(). И прямым текстом в статье посылают прочитать man 2 fork и man 2 kill

                                                                                    +7
                                                                                    Пишу программы под Unix с 1994 года (в том числе демоны). Не знал. Спасибо!
                                                                                      0

                                                                                      Когда-то давно я запускал "ловилку паролей", которая после попытки поймать пароль делала kill -9 -1, фактически выгружая мой сеанс. Потом за неё атата получил, само собой, но опыт остался.

                                                                                        0
                                                                                        что за ловилка паролей? Первый раз такое слышу.
                                                                                          +2

                                                                                          Я так понял, визуально похожая на экран логина утилита, запущенная от юзера. Когда приходит следующий юзер, то вбивает свои данные, его пароль "ловится" и кладётся в файл, а дальше происходит вышеописанный разлогин уже в настоящий экран входа.

                                                                                        –13

                                                                                        Меня сейчас, скорее всего запинают, ну да ладно.
                                                                                        Я не проверяю результат fork, clone и malloc на возможность ошибки. Потому что я считаю, что это не моя проблема.
                                                                                        Я не пишу код ядра, не пишу драйвера, я делаю вещи попроще и повыше. И я считаю, что если такие базовые вещи сломались и вернули что-то не то, то у моего приложения всё равно нет шансов выжить. Если malloc или fork вернули ошибку, то значит всё плохо на уровне системы. Как мне это обработать? Что мне делать? Упасть? Ну, приложение и так упадет, если попытаюсь что-то сделать не проверив ошибку.


                                                                                        Естественно, я так делаю не всегда. Как-то я допиливал одной утилите возможность работать в режиме демона. Там были подобные проверки, чтобы администратор, подключившись к машине, мог отследить логи и исправить это дело. Но, повторюсь, основное, чем я занимаюсь, работает под контролем пользователя и, я думаю, что что-то не так пользователь заметит на уровне всей системы, потому что ломаться будет всё, а не только моё приложение.


                                                                                        У вас есть шанс меня переубедить, но пока что моя вера в причины, по которым я этого не проверяю, тверда.

                                                                                          0

                                                                                          Проблема-то в том, что без проверки на -1 программа сама собой не упадёт. Я согласен, что для большинства программ "упасть" — лучший вариант, но в случае fork чтобы правильно упасть — нужно добавить проверку.

                                                                                            –3

                                                                                            Справедливости ради, когда я делаю fork, я чаще всего буду общаться с форком через пайпы или файлы. В первом случае я словлю либо EOF, либо SIGPIPE. Во втором, либо файл не будет существовать, либо он создастся и дочерний процесс закончит работу.


                                                                                            Когда не нужно общение между процессами, то дочерний процесс тоже отработает, либо упадет где-то ещё опять же из-за проблем в системе. То есть мне опять же нечего тут делать. Это слишком глобальная проблема. Это всё равно что не забыть выключить утюг убегая из дома, потому что в нём пожар.


                                                                                            А делать kill для дочернего процесса… Ну как-то такое себе. Я всё таки жду, что он отработает правильно :)

                                                                                              –2

                                                                                              У меня одна грубая ошибка во втором абзаце, извините. Конечно же про дочерний процесс речи не идет. Но если нет общения между процессами, то мы отправили делаться задачу, которая просто не выполнится. Раз общения нет, то мы не ждём результата и просто двигаемся дальше и всё равно падаем ещё где-то из-за проблем в системе.

                                                                                                +1

                                                                                                А потом вы в какой-то момент ловите несколько "сирот", активно потребляющих ресурсы при упавшем родителе, и решаете на всякий случай посылать им SIGTERM при падении. После чего оказывается, что SIGPIPE больше не спасает вас от описанной в посте проблемы, а порождает её...

                                                                                            +9
                                                                                            Статья как раз о том, что код ошибки одной функции может оказаться валидным (но неожиданным для программиста) параметром для другой функции. Ошибки можно и не обрабатывать, ага. Но результат работы программы может не понравиться. ;)
                                                                                              +2
                                                                                              Что мне делать? Упасть?
                                                                                              Можно рухнуть камнем вниз, а можно аккуратно, ничего не сломав лечь.
                                                                                                0
                                                                                                Ага, рухнуть кому-нибудь на голову.
                                                                                                +1
                                                                                                Если malloc или fork вернули ошибку, то значит всё плохо на уровне системы.


                                                                                                Malloc в некоторых реализациях libc может вернуть ошибку в случае, если поломан механизм переаллоцирования. Например, если был вызван free() на уже удалённый адрес. Т.е. плохо не в системе, а в софте.
                                                                                                    0
                                                                                                    Ну так делай проверку и abort. Это же абсолютно несложно.
                                                                                                      +1
                                                                                                      Ну там может файлы нужно закрыть, fsync дернуть, например, перед смертью или чего в лог напечатать?
                                                                                                      fork() может вернуть -1, если pid-ы кончились, как вариант.
                                                                                                      Зависит, конечно, от критичности приложения и того, какие гарантии оно дает. Если это, БД, например и в этот момент идет запись данных/индекса, хорошо ли падать и оставлять это в неконсистентном виде, так, что оно потом не поднимется совсем без ручного вмешательства?
                                                                                                        –2
                                                                                                        Я не пишу код ядра, не пишу драйвера, я делаю вещи попроще и повыше.

                                                                                                        "Зочем писать грамотный код? Я выше этого!"

                                                                                                        +15

                                                                                                        Исправил!


                                                                                                        if (fork() == 0) {
                                                                                                          ...
                                                                                                        } else if (fork() == -1) {
                                                                                                          ...
                                                                                                        } else {
                                                                                                          ...
                                                                                                        }

                                                                                                        Спойлер

                                                                                                        Шутка

                                                                                                          +2
                                                                                                          Бомба!
                                                                                                            +5
                                                                                                            В блоке else нужно не забыть добавить
                                                                                                            pid = fork();
                                                                                                            +1
                                                                                                            После чтения комментов я понял, что читать документацию не нужно, т.к. все равно всю документацию не прочитаешь, и код возврата проверять не нужно, т.к. все равно все пишут с багами.
                                                                                                              +6
                                                                                                              И софт писать не нужно — всё равно на выброс!
                                                                                                                +1
                                                                                                                зря что ли тестеры свой хлеб едят? :)
                                                                                                                  +6

                                                                                                                  Ну вообще было бы здорово, если бы хотя бы подобный уровень семантики функций был виден на уровне типов.

                                                                                                                    +1

                                                                                                                    Вот именно!

                                                                                                                      0

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

                                                                                                                    +5

                                                                                                                    Какова подача сего факта в статье: может глюкануть — прям поражает. Я не понимаю, за что так заплюсовали статью? За описанное поведение?


                                                                                                                    На счёт документации — её не обязательно читать всю на свете. Нужно читать и изучать как минимум то, что используешь здесь и сейчас.

                                                                                                                      –2
                                                                                                                      А раньше люди не только документацию детально читали, но и книжки. Что-нибудь типа «UNIX: взаимодействие процессов». А иногда и код ядра. И уж конечно, что возвращают fork, open итд знали.
                                                                                                                        +3

                                                                                                                        Я часто читаю код ядра. Это очень, очень тяжело. Как-то я пытался понять, с чем едят SIG_DFL. (Это такой интересный псевдосигнал, который простые смертные в userspace не видят). Но за всю свою жизнь я пристально прочитал, ну дай бог, тысяч пять строк ядра.


                                                                                                                        А их там… Я понимаю, что раньше были любители "читать код ядра". Сейчас скорость MR в ядро такая, что никто (даже Линус!) не может прочитать всё.


                                                                                                                        Но, возможно, ваши легендарные читатели на это способны, да.

                                                                                                                          +1

                                                                                                                          Вот как раз в подобных книжках я и видел примеры кода, который не проверял возвращаемое значение на -1.

                                                                                                                            0
                                                                                                                            ye хз, для fork всегда был код вида
                                                                                                                            int pid = fork();
                                                                                                                            if(pid ==0){
                                                                                                                            // current
                                                                                                                            } else if(pid == -1)
                                                                                                                            // error
                                                                                                                            else {
                                                                                                                            // created
                                                                                                                            }
                                                                                                                          0
                                                                                                                          Давно интересовало — а почему fork? Откуда он взялся исторически, и почему такая странная (ИМХ) форма?
                                                                                                                          Кажется, гораздо более естественный способ реализации многопоточности — с помощью функции создания потока, которой в качестве аргумента передается указатель на функцию потока. pthread_create или что-то подобное.
                                                                                                                            +4

                                                                                                                            Когда вы создаёте процесс, вам надо настроить ему 100500 параметров. Stdin, stdout, лимиты, окружение, стек, рабочий каталог, etc, etc. Ещё нужно предусмотреть как ему передать произвольное число fd (включая сетевые сокеты).


                                                                                                                            Этих параметров очень много. Либо вы делаете развесистое API (как сейчас пишут новое API для создания процесса с использованием proc_fd), либо вы просто говорите "скопировать с меня". Второе явно проще. Процесс подготавливает всё, что нужно, и "форкается".

                                                                                                                              +2

                                                                                                                              А потом ловит баги с того, что ресурсы, которые раньше находились в его единоличном владении, теперь расшарены с потомком. Нет уж, спасибо.

                                                                                                                                +1

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

                                                                                                                                0

                                                                                                                                И еще память получается общая до первого изменения. Удобно для трюков с prefork серверами.

                                                                                                                                0
                                                                                                                                если полезть под кат, то часто создание потока делается через fork, и это часто справедливо для posix систем, разница в том, что fork скрыт от глаз и вся инициализация делается библиотечной функцией.
                                                                                                                                  0
                                                                                                                                  если полезть под кат, то часто создание потока делается через fork

                                                                                                                                  Не так. Сейчас обычно есть общий вызов (rfork, clone), флагами которого задаётся желаемый объём независимости потомка.
                                                                                                                                  (Не является ли это ещё одним примером обсуждаемого антипаттерна?)

                                                                                                                                  +1
                                                                                                                                  Например, потому, что клонирование адресного пространства намного дешевле, чем создание его с нуля.
                                                                                                                                  Android со своим zygote — живой пример.
                                                                                                                                  Правда, там проблемы с ASLR отсюда, но кого и когда это волновало…
                                                                                                                                    +2
                                                                                                                                    Java-машину быстро запустить с нуля так вообще сложно.
                                                                                                                                    А вот запустить заранее и потом fork()-ать — пожалуйста.
                                                                                                                                      0

                                                                                                                                      Я Вас дополню — Gradle + Junit используют fork() для JVM тестов. А потому собирать Java/Kotlin лучше на Linux машинах, так как быстрее процесс клонируется.

                                                                                                                                        0
                                                                                                                                        Вообще, конечно, немного стремно fork()-ать процесс с Java-машиной.
                                                                                                                                        Неизвестно, как различные ресурсы переживают fork.

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

                                                                                                                                        Вобщем, надо следить, какие ресурсы уходят в fork().
                                                                                                                                        Это может быть сложно, если есть много библиотек. Все не проконтролируешь.
                                                                                                                                        А тут еще и GC может вмешаться и добавить неопределенного поведения, в зависимости от того, успел он сработать до fork() или не успел.

                                                                                                                                        Короче, fork() — это лютая утечка абстракций.
                                                                                                                                    +2
                                                                                                                                    Это одно из преимуществ Unix-подобных систем: новый процесс запускается практически мгновенно.
                                                                                                                                    0
                                                                                                                                    Очевидно, что при при вызове форка потребляются какие-то ресурсы, что может привести к отказу. То есть вопрос, что же делать при отказе и как его обнаружить должен был возникнуть даже без чтения документации.
                                                                                                                                      +1
                                                                                                                                      вопрос, что же делать при отказе и как его обнаружить


                                                                                                                                      Есть стиль программирования, где принято ничего не делать и надеяться что оно само где-то там упадет, или выдаст exception.
                                                                                                                                        –1
                                                                                                                                        Бесзащитное программирование?
                                                                                                                                          +1
                                                                                                                                          оптимистичное же
                                                                                                                                          0
                                                                                                                                          Есть стиль прог