분할 오류는 뒤에서 어떻게 작동합니까?

분할 오류는 뒤에서 어떻게 작동합니까?

"CPU의 MMU가 신호를 보낸다"와 "커널이 해당 프로그램을 문제의 프로그램으로 지시하고 종료한다"는 것 외에는 이에 대한 정보를 찾을 수 없는 것 같습니다.

나는 그것이 문제가 되는 프로세스를 종료하고 인쇄함으로써 이를 처리할 셸에 신호를 보낼 것이라고 가정했습니다 "Segmentation fault". 그래서 저는 미니멀리스트 쉘을 작성하여 이 가설을 테스트했습니다.CRSH(스크랩 쉘). 쉘은 사용자 입력을 받아 메소드에 제공하는 것 외에는 아무 작업도 수행하지 않습니다 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]

첫 번째 요점으로 돌아간 것 같아요. 나는 이 작업을 수행하는 것이 셸이 아니라 기본 시스템이라는 것을 방금 증명했습니다. "분할 오류"는 어떻게 인쇄됩니까? "누가" 이런 일을 하고 있나요? 핵심? 다른 건 없나요? 신호와 모든 부작용이 하드웨어에서 프로그램의 최종 종료까지 어떻게 전파됩니까?

답변1

모든 최신 CPU는 다음을 수행할 수 있습니다.방해하다현재 실행 중인 기계 명령입니다. 그들은 충분한 상태를 저장합니다(보통 항상 그런 것은 아니지만).다시 덮다아무 일도 일어나지 않은 것처럼 나중에 실행합니다(일반적으로 중단된 명령은 처음부터 다시 시작됩니다). 그런 다음 실행을 시작합니다.인터럽트 핸들러, 이는 기계어 코드에 불과하지만 CPU가 미리 위치를 알 수 있도록 특별한 위치에 배치됩니다. 인터럽트 핸들러는 항상핵심운영 체제: 최대 권한으로 실행되고 다른 모든 구성 요소의 실행을 감독하는 구성 요소입니다. 1,2

인터럽트는 다음과 같습니다.동기화, 즉 현재 실행 중인 명령이 수행하는 작업에 대한 직접적인 응답으로 CPU 자체에 의해 트리거된다는 의미입니다.비동기식, 이는 외부 이벤트(예: 네트워크 포트에 도착하는 데이터)로 인해 예측할 수 없는 시간에 발생함을 의미합니다. 일부에서는 비동기 인터럽트에 대해 "인터럽트"라는 용어를 사용하고 동기 인터럽트를 "트랩", "오류" 또는 "예외"로 지칭하지만 이러한 단어에는 다른 의미가 있으므로 "동기 인터럽트"를 고수하겠습니다.

요즘 대부분의 최신 운영체제에는 다음과 같은 개념이 있습니다.프로세스. 가장 기본적으로 이는 컴퓨터가 여러 프로그램을 동시에 실행할 수 있는 메커니즘이지만 운영 체제 구성 방식의 핵심 측면이기도 합니다.메모리 보호, 이는 대부분의 기능입니다(그러나 아쉽게도 아직은 그렇지 않습니다).모두) 최신 CPU. 그것은 동반된다가상 메모리, 이는 메모리 주소와 RAM의 실제 위치 간의 매핑을 변경하는 기능입니다. 메모리 보호를 통해 운영 체제는 각 프로세스에만 액세스할 수 있는 자체 RAM 블록을 제공할 수 있습니다. 또한 운영 체제(프로세스 대신)가 RAM 영역을 읽기 전용, 실행 가능, 협력 프로세스 그룹 간 공유 등으로 지정할 수 있습니다. 코어에서만 사용할 수 있는 메모리 블록도 있습니다.

각 프로세스가 CPU 구성이 허용하는 방식으로만 메모리에 액세스하는 한 메모리 보호는 보이지 않습니다. 프로세스가 규칙을 위반하면 CPU는 동기식 인터럽트를 생성하여 커널이 이를 처리하도록 요구합니다. 프로세스가 완료되지 않는 경우가 종종 발생합니다.진짜규칙에 반하여 프로세스를 계속하려면 커널만 일부 작업을 수행하면 됩니다. 예를 들어, 다른 콘텐츠를 위한 RAM 공간을 확보하기 위해 프로세스 메모리 페이지를 스왑 파일로 "제거"해야 하는 경우 커널은 해당 페이지를 액세스할 수 없는 것으로 표시합니다. 다음에 프로세스가 이를 사용하려고 하면 CPU는 메모리 보호 인터럽트를 생성합니다. 커널은 스왑에서 페이지를 검색하고 원래 위치에 다시 놓은 다음 다시 액세스 가능하다고 표시하고 실행을 재개합니다.

하지만 이 과정이 규칙을 어겼다고 가정해 보세요. RAM에 매핑된 적이 없는 페이지에 액세스하려고 하거나 기계어 코드가 없는 것으로 표시된 페이지를 실행하려고 합니다. 일반적으로 "유닉스(Unix)"라고 불리는 운영 체제 제품군신호이 상황을 처리하기 위해. 4신호는 인터럽트와 유사하지만 하드웨어에서 생성되고 커널에서 처리되는 것이 아니라 커널에서 생성되고 프로세스에서 처리됩니다. 프로세스를 정의할 수 있습니다.신호 처리기자신의 코드로 작성하고 커널에 위치를 알려줍니다. 그런 다음 이러한 신호 처리기가 실행되어 필요한 경우 정상적인 제어 흐름을 중단합니다. 신호에는 모두 숫자와 두 개의 이름이 있습니다. 그 중 하나는 비밀스러운 약어이고 다른 하나는 약간 덜 비밀스러운 문구입니다. 프로세스가 메모리 보호 규칙을 위반할 때 생성되는 신호는 (관례상) 11번이고 이름은 SIGSEGV"세그먼테이션 오류"입니다. 5,6

신호와 인터럽트의 중요한 차이점은 다음과 같습니다.기본 동작각 신호에 대해. 운영 체제가 모든 인터럽트에 대해 핸들러를 정의할 수 없는 경우, 이는 운영 체제의 버그이며 CPU가 누락된 핸들러를 호출하려고 하면 전체 컴퓨터가 충돌합니다. 그러나 프로세스는 모든 신호에 대해 신호 처리기를 정의할 의무가 없습니다. 커널이 프로세스에 대한 신호를 생성하고 신호가 기본 동작을 유지하는 경우 커널은 프로세스를 방해하지 않고 기본 작업을 계속 수행합니다. 대부분의 신호에 대한 기본 동작은 "아무 것도 하지 않음" 또는 "이 프로세스를 종료하고 코어 덤프를 생성할 수도 있음"입니다. SIGSEGV후자 중 하나입니다.

요약하자면, 메모리 보호 규칙을 위반하는 프로세스가 있습니다. CPU는 프로세스를 일시 중지하고 동기식 인터럽트를 생성합니다. 커널은 인터럽트를 처리하고 SIGSEGV프로세스에 대한 신호를 생성합니다. 과정을 가정해보자아니요SIGSEGV커널이 프로세스를 종료하는 기본 동작을 수행하도록 신호 처리기를 설정합니다 . 이는 다음 모두와 동일한 효과를 갖습니다._exit시스템 호출: 열려 있는 파일 닫기, 메모리 해제 등

지금까지 인간이 볼 수 있는 메시지를 인쇄한 것은 아무것도 없으며 쉘(또는 더 일반적으로상위 프로세스방금 종료된 프로세스)는 전혀 관여하지 않습니다. SIGSEGV규칙을 위반하는 프로세스로 이동하여,아니요그 부모. 이것다음그러나 시퀀스의 단계는 하위 프로세스가 종료되었음을 상위 프로세스에 알리는 것입니다. 이는 몇 가지 다른 방법으로 발생할 수 있으며, 그 중 가장 간단한 방법은 다음 중 하나를 사용하여 부모가 이미 이 알림을 기다리고 있는 경우입니다.wait시스템 호출( wait, waitpidwait4). 이 경우 커널은 단순히 시스템 호출이 반환되도록 하고 상위 프로세스에 다음을 제공합니다.종료 상태. 7 종료 상태를 부모에게 알립니다.SIGSEGV자식 프로세스는 종료됩니다. 이 경우 신호의 기본 동작으로 인해 자식 프로세스가 종료되었음을 알게 됩니다 .

그런 다음 상위 프로세스는 메시지를 인쇄하여 사람에게 이벤트를 보고할 수 있습니다. 쉘 프로그램은 거의 항상 이를 수행합니다. 이 작업을 수행하는 코드는 포함하지 않지만 crshC 라이브러리 루틴으로 인해 발생합니다.system/bin/sh"내부적으로" 완전한 기능을 갖춘 쉘을 실행합니다 . crsh~이다조부모이 경우 상위 프로세스 알림은 필드로 표시되며 /bin/sh일반적인 메시지를 인쇄합니다. 그런 다음 /bin/sh아무 작업도 수행하지 않고 자체적으로 종료되며 C 라이브러리의 system수신 구현은저것종료 알림. 의 반환 값을 확인하여 system코드에서 종료 알림을 볼 수 있습니다. 하지만 손자 프로세스가 중간 쉘 프로세스에 의해 소비되었기 때문에 세그폴트 때문에 종료되었다는 사실은 알려주지 않습니다.


각주

  1. 일부 운영 체제에서는 구현되지 않습니다.장치 드라이버그러나 커널의 일부로서 모든 인터럽트 핸들러는 메모리 보호를 구성하는 코드와 마찬가지로 여전히 커널의 일부여야 합니다. 왜냐하면 하드웨어는 아무것도 허용하지 않기 때문입니다.하지만커널은 이런 일을 합니다.

  2. 커널보다 더 높은 권한을 가진 "하이퍼바이저" 또는 "가상 머신 관리자"라는 프로그램이 있을 수 있지만 이 답변의 목적에 따라 '예'로 간주될 수 있습니다.하드웨어.

  3. 커널은프로그램, 하지만 그것은아니요프로세스는 도서관과 비슷합니다. 자체 코드를 실행하는 것 외에도 모든 프로세스는 때때로 커널 코드의 일부를 실행합니다. "커널 스레드"가 많을 수 있습니다.오직커널 코드가 실행되지만 우리와는 아무 관련이 없습니다.

  4. 더 이상 처리해야 할 유일한 운영 체제할 수 없다Unix의 구현으로 간주되는 것은 물론 Windows입니다. 이 경우 신호를 사용하지 않습니다. (사실 이건 아니다.가지다Signal; Windows에서는 <signal.h>인터페이스가 C 라이브러리에 의해 완전히 위조되었습니다. ) "라는 것을 사용합니다.구조적 예외 처리"반대로요.

  5. 대신 일부 메모리 보호 위반 SIGBUS("버스 오류")이 생성됩니다 SIGSEGV. 둘 사이의 경계는 잘 정의되어 있지 않으며 시스템마다 다릅니다. 에 대한 핸들러를 정의하는 프로그램을 작성하는 경우 SIGSEGV에 대해 동일한 핸들러를 정의하는 것이 좋습니다 SIGBUS.

  6. "분할 오류"는 프로그램을 실행하는 컴퓨터 중 하나에서 메모리 보호 위반으로 인해 생성된 인터럽트의 이름입니다.원래 유닉스,아마도플라즈마 11. "분할"는유형메모리 보호, 하지만 이제 "세그먼트화"라는 용어가 사용됨잘못"는 일반적으로 모든 유형의 메모리 보호 위반을 나타냅니다.

  7. 모든 것다른상위 프로세스는 하위 프로세스가 종료되었음을 통보받을 수 있으며, 결국 상위 프로세스는 wait종료 상태를 호출하고 수신합니다. 방금 다른 일이 먼저 일어났습니다.

답변2

쉘은 메시지와 관련이 있으며 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

내 코드에 신호 처리기를 작성하려고 했는데 exec system()에서 사용하는 라이브러리 호출 crsh이 셸이라는 /bin/sh것을 깨달았습니다 man 3 system. /bin/sh물론 crsh.

crsh시스템 호출을 사용 하도록 프로그램을 다시 작성하면 execve()"세그먼트 실패" 문자열이 표시되지 않습니다. 이는 호출 쉘에서 나옵니다 system().

답변3

"CPU의 MMU가 신호를 보낸다"와 "커널이 해당 프로그램을 문제의 프로그램으로 지시하고 종료한다"는 것 외에는 이에 대한 정보를 찾을 수 없는 것 같습니다.

이것은 다소 왜곡된 요약입니다. Unix 신호 메커니즘은 프로세스를 시작하는 CPU 관련 이벤트와 완전히 다릅니다.

일반적으로 CPU는 잘못된 주소에 액세스하거나 읽기 전용 영역에 기록하고 실행 가능한 섹션 등), 각 "세그먼트"(전통적으로 읽기 전용 실행 가능 "텍스트", 쓰기 가능한 가변 길이 "데이터" 및 전통적으로 메모리의 다른 쪽 끝에 있는 스택)가 고정된 주소 범위를 갖기 때문입니다. 최신 아키텍처에서는 페이지 오류(매핑되지 않은 메모리의 경우) 또는 액세스 위반(읽기, 쓰기 및 실행 권한 문제의 경우)일 가능성이 더 높습니다. 이에 대해서는 나머지 답변에서 집중적으로 설명하겠습니다.

이제 커널은 여러 가지 작업을 수행할 수 있습니다. 유효하지만 로드되지 않은 메모리(예: 스왑 아웃 또는 매핑된 파일 등)에 대해서도 페이지 오류가 발생할 수 있습니다. 이 경우 커널은 메모리를 매핑한 다음 페이지를 발생시킨 명령에서 사용자 프로그램을 다시 시작합니다. 잘못. 실수. 그렇지 않으면 신호를 보냅니다. 신호 처리기를 설치하는 프로세스는 인터럽트 처리기 설치를 에뮬레이션하는 의도된 프로그램이 아니라 대부분 아키텍처와 독립적이기 때문에 "[원래 이벤트]를 문제의 프로그램으로 전달"하는 것은 아닙니다.

사용자 프로그램에 시그널 핸들러가 설치되어 있다면 스택 프레임을 생성하고 사용자 프로그램의 실행 위치를 시그널 핸들러로 설정하는 것을 의미합니다. 모든 신호에 대해 동일한 작업이 수행되지만 분할 위반의 경우 일반적으로 신호 처리기가 반환되면 오류를 일으킨 명령을 다시 시작하도록 배열됩니다. 사용자 프로그램은 예를 들어 메모리를 문제가 되는 주소에 매핑하여 버그를 수정했을 수 있습니다. 이것이 가능한지 여부는 아키텍처에 따라 다릅니다. 신호 처리기는 프로그램의 다른 위치로 이동할 수도 있습니다(보통 다음을 통해).진행을또는 예외를 발생시켜) 잘못된 메모리 액세스를 초래한 작업을 중단합니다.

사용자 프로그램이 신호 처리기를 설치하지 않으면 프로그램이 직접 종료됩니다. 일부 아키텍처에서는 신호가 무시되면 명령이 반복적으로 다시 시작되어 무한 루프가 발생할 수 있습니다.

답변4

분할 오류는 허용되지 않는 메모리 주소에 대한 액세스입니다(프로세스의 일부가 아니거나 읽기 전용 데이터 쓰기를 시도하거나 실행할 수 없는 데이터를 실행하는 등...). 이는 MMU(현재 CPU의 일부인 메모리 관리 장치)에 의해 포착되어 인터럽트를 발생시킵니다. 인터럽트는 SIGSEGFAULT문제가 있는 프로세스에 신호를 보내는 커널에 의해 처리됩니다 (예제 참조). signal(2)이 신호의 기본 핸들러는 코어를 덤프하고(참고자료 참조 core(5)) 프로세스를 종료합니다.

쉘은 전혀 관여하지 않습니다.

관련 정보