Linux 시스템은 다중 작업의 동시 실행을 지원하는 운영 체제로, 동시에 여러 프로세스를 실행할 수 있어 시스템 활용도와 효율성이 향상됩니다. 그러나 이러한 프로세스 간에 데이터 교환 및 협업이 필요한 경우 메시지 큐, 공유 메모리, 세마포어 등과 같은 일부 프로세스 간 통신(IPC) 방법을 사용해야 합니다. 그 중 신호는 상대적으로 간단하고 유연한 IPC 방법으로, 한 프로세스에서 다른 프로세스에 짧은 메시지를 보내 일부 이벤트나 예외가 발생했음을 알릴 수 있습니다. Linux 시스템에는 동기 신호와 비동기 신호라는 두 가지 유형의 신호가 있습니다. 이 기사에서는 비동기 신호의 의미, 생성, 전송, 수신, 처리 및 무시를 포함하여 Linux 비동기 신호 처리 방법을 간략하게 분석합니다.
처음 Linux 프로그래밍을 배울 때, 저는 항상 비동기식 신호 핸들이 매우 마법적인 것이라고 느꼈습니다. 사용자 프로그램은 singal과 같은 시스템 호출을 사용하여 특정 신호에 대한 신호 처리 함수(핸들 함수)를 등록할 수 있습니다.
프로그램의 바이너리 코드에는 메모리에 특정 실행 흐름이 있습니다. 비동기 신호를 받은 후 프로그램이 "중단"된 다음 핸들 기능으로 점프하여 실행되는 이유는 무엇입니까? 프로그램이 이렇게 점프하게 만드는 능력이 커널에 어떻게 있을 수 있을까요? 프로그램의 실행 코드를 일시적으로 수정하는 것은 불가능합니다.
몇 가지 커널 지식을 배운 후에 프로세스가 신호를 받은 후 즉시 "중단"되지 않는다는 것을 깨달았습니다. 대신 먼저 프로세스의 제어 구조(task_struct)에 특정 신호의 수신을 기록한 다음 process 커널 모드에서 사용자 모드로 돌아가려고 할 때 프로세스가 "중단"되고 핸들 함수가 호출됩니다.
사용자 프로세스는 언제 커널 모드에서 사용자 모드로 복귀합니까? 일반적으로 세 가지 주요 상황이 있습니다. 시스템 호출(사용자 프로세스가 적극적으로 커널에 진입), 중단(사용자 프로세스가 수동적으로 커널에 진입), 예약된 실행(사용자 프로세스가 실행 대기에서 실행으로 변경됨)입니다.
프로세스가 신호를 받은 후 커널 상태에서 사용자 상태로 돌아가는 데 일정 시간이 걸립니다. 그러나 이 시간은 일반적으로 매우 짧습니다. 적어도 클록 인터럽트는 상대적으로 높은 빈도(예: 1밀리초마다 한 번)로 사용자 프로세스를 커널로 가져옵니다(물론 실행 프로세스에 대해서만).
프로세스가 커널 모드에서 유저 모드로 복귀하려고 할 때 처리해야 할 신호가 있으면 해당 핸들 함수가 호출됩니다. (물론 핸들이 등록되지 않을 수 있으며 커널은 기본적으로 신호를 처리합니다.) 프로세스는 여전히 커널 모드에 있습니다. 커널은 사용자 모드에서 핸들 함수를 어떻게 호출합니까?
직접 전화해도 되나요? 당연히 아니지. 커널 코드는 높은 CPU 권한 수준에서 실행됩니다. 핸들 함수가 직접 호출되면 핸들 함수도 동일한 CPU 권한으로 실행됩니다. 그러면 사용자는 핸들 기능에서 원하는 것은 무엇이든 할 수 있습니다.
따라서 핸들을 호출하려면 먼저 사용자 모드로 돌아가야 합니다. 그러나 사용자 모드로 돌아간 후에는 프로그램 흐름이 더 이상 커널에 의해 제어되지 않습니다. 커널이 실제로 사용자 프로세스의 실행 코드를 일시적으로 변경할 수 있습니까?
커널의 실제 접근 방식은 매우 영리합니다. 사용자 프로세스가 커널에 들어간 후 프로세스가 돌아올 수 있도록 해당 커널 스택에 반환 주소를 남깁니다. 커널이 핸들 함수를 호출하는 방식은 스택의 반환 주소를 일시적으로 변경한 후 사용자 모드로 돌아가는 원래 프로세스에 따라 반환하는 것입니다. 결과적으로 이 반환은 핸들 함수로 이동합니다. (물론, 수정해야 하는 것은 반환 주소뿐만 아니라 전체 호출 스택입니다.)
반품 주소가 일시적으로 변경되었지만 사용자 프로세스는 결국 원래 반품 주소로 돌아갑니다. 그렇다면 원래 반환 주소와 해당 호출 스택은 어디에 저장되어야 할까요? 프로세스의 커널 스택 공간은 제한되어 있고 핸들 함수에서 발생할 수 있는 시스템 호출도 처리해야 하므로 커널이 이 정보를 커널 스택에 넣는 것은 비현실적이며 푸시만 가능합니다. 사용자 스택.
핸들 함수가 실행되면 실행 프로세스가 커널로 돌아갑니다. 마찬가지로 CPU 권한 수준이 다르기 때문에 단순히 RET 명령어를 사용하여 핸들 함수에서 커널로 돌아갈 수는 없습니다. 시스템 호출을 실행해야 합니다.
핸들이 실행된 후 커널로 복귀한 다음 커널에서 원래 반환 주소로 복귀해야 하는 이유는 무엇입니까? 원래 반품 주소로 직접 반품하시면 매우 편리합니다. 그리고 이를 수행하는 것은 어렵지 않습니다. 원래 반환 주소와 해당 호출 스택이 사용자 스택에 푸시되었습니다. 커널은 핸들 함수의 호출 스택에 대해 약간의 조작만 하면 됩니다.
1. 원래 반환 주소로 돌아간다는 것은 단순히 해당 주소로 돌아간다는 의미가 아닙니다. 전체 장면(주로 레지스터 등)을 복원해야 합니다. 물론 커널은 사용자 스택의 일부 코드를 눌러 이러한 작업을 완료할 수도 있습니다.
2. 이제 처리할 신호가 두 개 이상 있을 수 있습니다. 사용자 프로세스가 커널로 돌아가서 다른 신호를 계속 처리하도록 하는 것이 가장 좋습니다.
커널로 복귀하기 위해 먼저 커널은 핸들 함수로 복귀하기 전에 복귀 주소를 사용자 스택에 푸시하므로 핸들에서 복귀할 때 지정된 주소로 복귀할 수 있습니다. 이 지정된 주소는 실제로 프로세스의 사용자 스택에도 있습니다. 커널은 프로세스가 sigreturn이라는 시스템 호출을 호출할 수 있도록 이 주소에 여러 명령을 배치합니다(스택에 실행 가능한 코드 배치).
핸들 함수로 복귀하기 전의 사용자 스택은 대략 다음과 같습니다.
원본 데이터 -> sigreturn 호출 명령(주소를 a로 설정) -> 원본 반환 주소 및 호출 스택 -> 반환 주소(값은 a) -> 핸들의 스택 변수
커널은 핸들 함수의 호출 스택에 sigreturn 명령을 배치합니다. 이는 Linux 2.4의 관행입니다. 사용자의 핸들 함수가 호출될 때마다 너무 많은 명령어를 사용자 스택에 복사해야 하는데 이는 좋지 않습니다.
Linux 2.6에는 vsyscall 페이지라는 페이지가 있는데, 여기에는 sigreturn 명령어 호출을 포함하여 사용자 프로그램을 위해 커널이 준비한 일부 명령어가 포함되어 있습니다. 이 vsyscall 페이지는 각 프로세스의 가상 주소 공간 끝에 매핑되고 모든 사용자 프로세스에서 공유되며 사용자 프로세스에 대해서는 읽기 전용입니다. 이런 방식으로 핸들 함수의 호출 스택에 sigreturn 명령을 삽입할 필요가 없습니다. 간단히 핸들 함수의 반환 주소를 vsyscall 페이지의 해당 코드로 설정하면 됩니다.
핸들이 실행된 후 자동으로 sigreturn을 호출하여 커널로 돌아가기 위해 커널은 많은 일을 합니다. 그렇다면 사용자가 스스로 sigreturn을 호출하도록 허용하는 데 동의할 수 있습니까?
물론 이것은 가능합니다. 단지 신호 처리 메커니즘을 완전한 메커니즘으로 만들기 위해 커널은 이를 수행하지 않았습니다. 그렇지 않고 사용자가 핸들 함수에서 sigreturn을 호출하는 것을 잊어버리면 프로세스가 설명할 수 없을 정도로 중단될 수 있습니다. 그리고 컴파일러가 그러한 오류를 찾는 것은 어렵습니다.
프로세스가 sigreturn 시스템 호출을 호출하고 커널에 다시 들어간 후 원래 반환 주소와 사용자 스택에 눌러진 해당 호출 스택을 얻습니다. 결국 커널은 프로세스가 사용자 공간으로 돌아올 때 원래 반환 주소로 돌아가도록 스택을 수정합니다.
이 기사에서는 비동기 신호의 의미, 생성, 전송, 수신, 처리 및 무시를 포함하여 Linux 비동기 신호 처리 방법을 간략하게 분석합니다. 이러한 지식을 이해하고 숙달함으로써 Linux 신호 처리에 대한 핵심 지식을 마스터할 수 있으며 이를 통해 시스템의 안정성과 효율성을 향상시킬 수 있습니다. 물론 Linux 비동기 신호 핸들에는 지속적인 학습과 연구가 필요한 다른 많은 기능과 사용법이 있습니다. 이 기사가 여러분에게 영감과 도움을 줄 수 있기를 바랍니다.
위 내용은 Linux 비동기 신호 핸들에 대한 간략한 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!