Как работает ошибка сегментации под капотом?

Я не могу найти никакой информации об этом, кроме «MMU процессора посылает сигнал», и «ядро направляет его на нарушающую программу, завершая ее».

Я предположил, что он, вероятно, отправляет сигнал в оболочку, и оболочка обрабатывает его, завершая процесс нарушения и печатая "Segmentation fault" . Поэтому я протестировал это предположение, написав чрезвычайно минимальную оболочку, которую я называю crsh (crap shell). Эта оболочка ничего не делает, кроме ввода пользователя и подачи его в метод system() .

 #include <stdio.h> #include <stdlib.h> int main(){ char cmdbuf[1000]; while (1){ printf("Crap Shell> "); fgets(cmdbuf, 1000, stdin); system(cmdbuf); } } 

Поэтому я запустил эту оболочку на одном терминале (без bash ). Затем я приступил к запуску программы, которая создает segfault. Если бы мои предположения были правильными, это означало бы: a) crash crsh , закрытие xterm, b) не печатать "Segmentation fault" , или c) оба.

 braden@system ~/code/crsh/ $ xterm -e ./crsh Crap Shell> ./segfault Segmentation fault Crap Shell> [still running] 

Кажется, на первый план. Я только что продемонстрировал, что это не оболочка, которая делает это, а система внизу. Как печатается «Ошибка сегментации»? «Кто» это делает? Ядро? Что-то другое? Как сигнал и все его побочные эффекты распространяются от аппаратного обеспечения до окончательного завершения программы?

  • Поддержка Loop-устройства на самокомпилированном ядре не работает
  • Почему truncate не работает для размеров выше 2043G в ext3?
  • Безопасное изменение конфигурации ядра Linux программно
  • Объяснение процесса сборки ядра в системах Ubuntu / Debian
  • загрузка драйвера ядра ath9k_htc не выполняется
  • как ограничить адрес dma конкретным диапазоном в ядре linux?
  • Где происходит системная информация из
  • Почему имеет смысл кэшировать своп?
  • 4 Solutions collect form web for “Как работает ошибка сегментации под капотом?”

    Все современные процессоры имеют возможность прервать текущую командную инструкцию. Они сохраняют достаточно состояния (обычно, но не всегда, в стеке), чтобы дать возможность возобновить выполнение позже, как будто ничего не произошло (обычно прерывание команды перезапускается с нуля). Затем они начинают выполнять обработчик прерываний , который является всего лишь машинным кодом, но размещается в специальном месте, поэтому CPU знает, где он находится заблаговременно. Обработчики прерываний всегда являются частью ядра операционной системы: компонент, который работает с наибольшей привилегией и отвечает за контроль над выполнением всех остальных компонентов. 1,2

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

    Теперь большинство современных операционных систем имеют понятие процессов . В самом основном, это механизм, при котором компьютер может запускать более одной программы одновременно, но это также ключевой аспект того, как операционные системы настраивают защиту памяти , что является особенностью большинства (но, увы, еще не все ) современные процессоры. Он сочетается с виртуальной памятью , которая позволяет изменять сопоставление адресов памяти и фактических местоположений в ОЗУ. Защита памяти позволяет операционной системе предоставлять каждому процессу свой частный кусок ОЗУ, доступ к которому возможен только для него. Он также позволяет операционной системе (действующей от имени какого-либо процесса) назначать области ОЗУ как доступные для чтения, исполняемые, разделяемые между группой взаимодействующих процессов и т. Д. Также будет фрагмент памяти, доступ к которой возможен только с помощью ядро. 3

    Пока каждый процесс обращается к памяти только в том виде, в котором сконфигурирован центральный процессор, защита памяти невидима. Когда процесс нарушает правила, ЦП будет генерировать синхронное прерывание, запрашивая у ядра сортировку. Регулярно случается, что процесс действительно не нарушал правила, только ядро ​​должно выполнить некоторую работу до того, как процесс будет разрешен для продолжения. Например, если страница памяти процесса должна быть «выселена» в файл подкачки, чтобы освободить место в ОЗУ для чего-то еще, ядро ​​отметит эту страницу недоступной. В следующий раз, когда процесс попытается использовать его, CPU будет генерировать прерывание защиты памяти; ядро извлечет страницу из swap, вернет ее туда, где она есть, снова пометьте ее и возобновит выполнение.

    Но предположим, что этот процесс действительно нарушил правила. Он попытался получить доступ к странице, на которой никогда не было сопоставлено ОЗУ, или пыталась выполнить страницу, помеченную как не содержащую машинный код, или что-то еще. Семейство операционных систем, обычно известных как «Unix», все используют сигналы для решения этой ситуации. 4 Сигналы аналогичны прерываниям, но они генерируются ядром и обрабатываются процессами, а не генерируются аппаратным обеспечением и передаются ядром. Процессы могут определять обработчики сигналов в своем собственном коде и сообщать ядру, где они есть. Затем эти обработчики сигналов будут выполняться, прерывая нормальный поток управления, когда это необходимо. У всех сигналов есть число и два имени, один из которых является загадочным аббревиатурой, а другой – чуть менее загадочной фразой. Сигнал, который генерируется, когда процесс нарушает правила защиты памяти, является (условно) номером 11, а его имена – SIGSEGV и «Ошибка сегментации». 5,6

    Важное различие между сигналами и прерываниями заключается в том, что для каждого сигнала существует поведение по умолчанию . Если операционная система не может определить обработчики для всех прерываний, это ошибка в ОС, и весь компьютер выйдет из строя, когда процессор попытается вызвать отсутствующий обработчик. Но процессы не обязаны определять обработчики сигналов для всех сигналов. Если ядро ​​генерирует сигнал для процесса, и этот сигнал остался по умолчанию, ядро ​​будет просто идти вперед и делать все, что по умолчанию, и не беспокоить процесс. Большинство поведения по умолчанию для сигналов либо «ничего не делают», либо «завершают этот процесс и, возможно, также создают основной дамп». SIGSEGV является одним из последних.

    Итак, чтобы повторить, у нас есть процесс, который нарушил правила защиты памяти. Процессор приостановил процесс и создал синхронное прерывание. Ядро вывело это прерывание и сгенерировало сигнал SIGSEGV для процесса. Предположим, что процесс не настроил обработчик сигналов для SIGSEGV , поэтому ядро ​​выполняет поведение по умолчанию, которое заключается в завершении процесса. Это имеет все те же последствия, что и системный вызов _exit : открытые файлы закрыты, память освобождается и т. Д.

    До этого момента ничто не печатало никаких сообщений, которые человек мог видеть, и оболочка (или, в более общем плане, родительский процесс только что завершившегося процесса) не была задействована вообще. SIGSEGV переходит к процессу, который нарушил правила, а не его родитель. Следующий шаг в последовательности, однако, состоит в том, чтобы уведомить родительский процесс о прекращении его дочернего процесса. Это может произойти несколькими различными способами, наиболее простым из которых является то, что родитель уже ждет этого уведомления, используя один из системных вызовов wait ( wait , waitpid , wait4 и т. Д.). В этом случае ядро ​​просто вызовет возврат системного вызова и предоставит родительскому процессу номер кода, называемый статусом выхода . 7 Статус выхода информирует родителя о том, почему дочерний процесс был прерван; в этом случае он узнает, что ребенок был прерван из-за поведения по умолчанию сигнала SIGSEGV .

    Затем родительский процесс может сообщить об этом событию человеку, напечатав сообщение; программы оболочки почти всегда делают это. Ваш crsh не включает в себя код для этого, но это так или иначе, потому что в подпрограмме библиотеки C запускается полнофункциональная оболочка /bin/sh , «под капотом». crsh является дедушкой и бабушкой в этом сценарии; уведомление родительского процесса выставляется командой /bin/sh , которая печатает свое обычное сообщение. Затем /bin/sh сам выходит, поскольку ему больше нечего делать, и реализация system C библиотеки получает это уведомление о выходе. Вы можете увидеть это уведомление о выходе в своем коде, проверив возвращаемое значение system ; но он не скажет вам, что процесс внука умер на segfault, потому что это было поглощено промежуточным процессом оболочки.


    Сноски

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

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

    3. Ядро – это программа , но это не процесс; это больше похоже на библиотеку. Все процессы выполняют часть кода ядра, время от времени, в дополнение к их собственному коду. Могут быть несколько «потоков ядра», которые выполняют только код ядра, но они здесь не касаются.

    4. Единственная операционная система, с которой вам, скорее всего, придется иметь дело, которая не может считаться реализацией Unix, – это, конечно же, Windows. В этой ситуации он не использует сигналы. (Действительно, он не имеет сигналов, а в Windows интерфейс <signal.h> полностью подделан библиотекой C.) Вместо этого он использует что-то, называемое « структурированная обработка исключений ».

    5. Некоторые нарушения защиты памяти генерируют SIGBUS («Ошибка шины») вместо SIGSEGV . Линия между двумя указанными ниже и варьируется от системы к системе. Если вы написали программу, которая определяет обработчик для SIGSEGV , вероятно, неплохо определить один и тот же обработчик для SIGBUS .

    6. «Ошибка сегментации» – это имя прерывания, сгенерированного для нарушений защиты памяти одним из компьютеров, на которых запускался исходный Unix , возможно, PDP-11 . « Сегментация » – это тип защиты памяти, но в настоящее время термин « ошибка сегментации» в целом относится к любому виду нарушений защиты памяти.

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

    Оболочка действительно имеет какое-то отношение к этому сообщению, и crsh косвенно вызывает оболочку, которая, вероятно, является bash .

    Я написал небольшую программу на C, которая всегда вызывает ошибки:

     #include <stdio.h> int main(int ac, char **av) { int *i = NULL; *i = 12; return 0; } 

    Когда я запускаю его из своей оболочки по умолчанию, zsh , я получаю следующее:

     4 % ./segv zsh: 13512 segmentation fault ./segv 

    Когда я запускаю его из bash , я получаю то, что вы отметили в своем вопросе:

     bediger@flq123:csrc % ./segv Segmentation fault 

    Я собирался написать обработчик сигнала в своем коде, тогда я понял, что вызов библиотеки system() используемый crsh exec, является оболочкой, /bin/sh соответствии с man 3 system . Это /bin/sh почти наверняка печатает «Ошибка сегментации», так как crsh конечно же, нет.

    Если вы перезапишите crsh для использования execve() вызова execve() для запуска программы, вы не увидите строку «Ошибка сегментации». Он исходит из оболочки, вызванной system() .

    Я не могу найти никакой информации об этом, кроме «MMU процессора посылает сигнал», и «ядро направляет его на нарушающую программу, завершая ее».

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

    В общем случае, когда доступ к плохим адресам (или записывается в область только для чтения, попытка выполнить не исполняемый раздел и т. Д.), ЦП будет генерировать некоторое событие, специфичное для процессора (на традиционных архитектурах, отличных от VM, это было называемый нарушением сегментации, поскольку каждый «сегмент» (традиционно исполняемый текст «только для чтения», «данные для записи и переменной длины» и стек традиционно на противоположном конце памяти) имеет фиксированный диапазон адресов – в современной архитектуре, скорее всего, это ошибка страницы [для немаркированной памяти] или нарушение доступа [для чтения, записи и выполнения разрешений], и я сосредоточусь на этом для остальной части ответа).

    Теперь, на данный момент, ядро ​​может сделать несколько вещей. Ошибки страницы также генерируются для памяти, которая действительна, но не загружается (например, поменяется или в файл mmapped и т. Д.), И в этом случае ядро ​​будет отображать память и затем перезапускать пользовательскую программу из инструкции, вызвавшей ошибка. В противном случае он посылает сигнал. Это не совсем «прямое [исходное событие] для нарушающей программы», поскольку процесс установки обработчика сигнала отличается и в основном независим от архитектуры, и если бы программа ожидала симуляции установки обработчика прерываний.

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

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

    Ошибка сегментации – это доступ к адресу памяти, который не разрешен (не является частью процесса или пытается записать данные только для чтения, или выполнить неисполняемые данные, …). Это улавливается MMU (блок управления памятью, сегодня часть процессора), вызывая прерывание. Прерывание обрабатывается ядром, которое отправляет сигнал SIGSEGFAULT (см., Например, signal(2) ) в процесс нарушения. Обработчик по умолчанию для этого сигнала сбрасывает ядро ​​(см. core(5) ) и завершает процесс.

    В этой оболочке нет никакой руки.

    Interesting Posts

    Любой сайт, который предлагает интерактивный опыт работы с средами GNU / Linux Desktop?

    Fedora 18 Нет беспроводной

    Начальное название вкладки и автоматическое ее обновление в xfce4-терминале

    carton: команда не найдена

    Команда, действующая по-разному в файле bash_profile vs terminal

    iptables маршрутизирует маркированные пакеты с маршрутизатора на ПК и маршрутизирует их обратно на маршрутизатор

    Почему мой обмен не активируется автоматически через fstab?

    Каковы плюсы и минусы Vim и Emacs?

    Как найти устройство в RAID1 под Linux?

    vim, как запретить

    Почему корневой вывод bash окрашен, но `sudo ls` нет?

    Рекомендации дистрибутива linux для настройки сервера?

    Получение сообщения «Ошибка при попытке открыть / dev / dvd исключительно» в конце процесса записи DVD при использовании growisofs

    Вывести изменения в файл журнала

    диалоговое меню для отображения файлов и выбора одного из них

    Linux и Unix - лучшая ОС в мире.