내가 이해한 바에 따르면 칩의 MMU는 가상 주소를 가져와 이를 물리적 메모리 주소로 변환합니다. MMU는 다음 작업을 수행합니다.
(1) 프로세스별 페이지 테이블을 참조하세요.
(2) 가상 주소에 해당하는 페이지가 Resident Set에 있으면 해당 주소를 물리 주소로 변환한다.
(3) 가상 주소에 해당하는 페이지가 상주 세트에 없으면 페이지 폴트가 발생하여 커널에서 처리됩니다.
이제 프로세스의 여러 부분에서 , 및 와 같은 brk()
페이지 생성 및 삭제를 위한 시스템 호출이 필요하다는 것을 이해합니다 . 따라서 이러한 시스템 호출이 이루어질 때마다 커널은 항상 프로세스의 페이지 테이블을 업데이트할 기회를 갖습니다.sbrk()
mmap()
munmap()
그러나 실행 중인 프로세스에서는 %rsp
스택 포인터가 10,000만큼 감소하도록 요구하여 스택 영역을 늘릴 수 있으며, 이로 인해 스택 깊이의 증가를 수용하기 위해 여러 페이지를 할당해야 할 수도 있습니다.
MMU에 대한 나의 이해가 정확하다면 %rsp
변경이 발생할 때 MMU는 페이지 오류를 생성하지 않을 것입니다(주소가 처음부터 프로세스 테이블에 없기 때문입니다). 이 경우 MMU는 커널에 알리기 위해 무엇을 합니까?
답변1
실행 중인 프로세스는 스택 포인터 %rsp를 10,000만큼 줄여 스택 영역을 늘릴 수 있으며, 이로 인해 스택 깊이의 증가를 수용하기 위해 여러 페이지를 할당해야 할 수도 있습니다.
MMU에 대한 나의 이해가 정확하다면, %rsp가 변경되더라도 MMU는 페이지 폴트를 생성하지 않을 것입니다(주소가 처음부터 프로세스 테이블에 없기 때문입니다). 이 경우 MMU는 커널에 알리기 위해 무엇을 합니까?
%rsp를 변경해도 페이지 오류가 발생하지 않습니다. 페이지 폴트는 메모리를 읽거나 쓸 때만 발생합니다.
매핑되지 않은 페이지를 터치하면언제나페이지 오류가 발생하지만 커널의 페이지 오류 처리기는 이것이 "유효한" 페이지 오류인지 판단하고 논리적 매핑을 증가시키고 페이지를 하드웨어 페이지 테이블에 연결할 수 있습니다. 또는 "유효하지 않음"이라고 판단하고 SIGSEGV를 보냅니다.
스택 성장은 특별한 경우입니다. 일반적으로 페이지 폴트는 기존 매핑(예: 지연된 할당, 쓰기 시 복사 또는 페이지가 스왑 공간이나 이를 지원하는 파일로 페이지 아웃되는 경우) 내부에서만 작동합니다. 매핑된 논리 상태는 하드웨어 페이지 테이블과 일치할 필요가 없으므로 "소프트" 또는 "하드" 페이지 오류가 발생할 수 있습니다. 바라보다"push" 또는 "sub" x86 명령어를 사용할 때 스택 메모리는 어떻게 할당됩니까?자세한 내용은.
일부 구현에는 스택 맵 확장을 위한 특별한 경우가 있습니다. 다른 사람들은 이 작업을 수행하지 않고 전체 스택 영역을 미리 매핑합니다. 그러면 페이지 폴트도 같은 방식으로 작동합니다 mmap()
. 구체적인 통화 방법 mmap()
은실스택이 할당됩니다. 적어도 이것은 다음을 사용하여 pthread를 생성할 때의 기본값입니다.glibc. 대조적으로, 초기 스레드의 스택은 커널에 의해 생성되어 성장을 허용하는 특별한 경우를 구현합니다. Linux가 스택을 확장하는 방법에 대한 자세한 내용은 아래를 참조하세요.
스택 끝을 건너뛰면 다른 매핑에 대한 잘못된 액세스가 발생할 수 있습니다. 즉, 페이지 폴트를 유발하지 않고 해당 메모리를 손상시킬 수 있습니다. 이는 "스택 충돌" 보안 취약점입니다. 악의적인 입력으로 인해 이러한 일이 발생할 수 있습니다. 예를 들어 alloca
매우 큰 배열이나 C99 가변 길이 배열이 할당되어 스택 포인터가 항목을 건너뛰게 됩니다.
이러한 스택 오버플로를 모두 감지하는 보장된 방법은 1) 스택 끝에 "가드 페이지"를 매핑하고 2) 스택 메모리를 할당할 때 한 번에 한 페이지씩 검색하는 것입니다. 이 글을 쓰는 시점에서는,GCC는 스택 프로브 생성을 완전히 지원하지 않습니다..
x86-64 System V ABI에는 스택이 매번 1페이지 이상 증가할 때 정확성을 보장하기 위해 스택 프로브가 필요하지 않습니다. 따라서 gcc는 명시적으로 지시한 경우에만 스택 프로브를 내보냅니다. (대부분의 x86 아키텍처가 아닌 Linux에서 사용하는 ABI는 동일하다고 생각합니다.) Linux에서 스택 검색은 스택 크기 제한을 초과하여 스택을 늘리려고 할 때 프로그램에서 오류가 발생하는지 확인하는 데만 필요합니다.
(재미있는 사실: Windows하다스택에 큰 배열을 할당하려면 각 페이지를 순차적으로 터치해야 합니다. Windows용 컴파일러는 스택 포인터를 두 페이지 이상 이동하거나 변수를 이동할 때 정확성을 보장하기 위해 항상 스택 프로브를 내보내야 합니다.할 수 있다한 페이지보다 큽니다. )
굳이 스택을 늘리는 이유는 무엇입니까?
고정 크기 스택 맵을 사용하는 데에는 적어도 한 가지 단점이 있는 것 같습니다. mmap()을 사용하여 이러한 스택을 생성하면 물리적 메모리를 할당하지 않더라도 Linux는 이를 "커밋된" 메모리로 처리합니다.
Linux는 기본적으로 RAM+스왑 오버 커밋을 허용하지만 경험적 방법을 사용하여 "명백한 주소 공간 오버 커밋"을 거부합니다. 정말로 노력할 때사용메모리가 RAM+스왑을 초과하면 OOM(Out of Memory Killer)은 충분한 메모리가 확보될 때까지 실행할 프로그램을 선택하기 시작합니다. 할당 거부와 같은 다양한 정책을 구성할 수 있으며 이로 인해 커밋이 RAM/2 + 스왑을 초과하게 됩니다.
바라보다vm.overcommit_memory 및 vm.overcommit_ratio.
이 참고 사항은 아래 Windows 블로그 게시물에도 언급되어 있습니다. 아마도 구현 차이는 Linux 남용 및 OOM 킬러에 대한 사람들의 불만의 요인일 수 있습니다. :-).
[glibc와 같은 C 런타임]할 수 있다처음에는 대부분 쓸 수 없거나 읽을 수 없도록 만들고 실패 시 변경하지만 신호 처리기가 필요하며 이 솔루션은 애플리케이션의 신호 처리기를 방해하기 때문에 POSIX 스레드 구현에서 허용되지 않습니다. --StackOverflow의 사용자 "R.."
MAP_GROWSDOWN
Linux는 위에서 인용한 것과 기본적으로 동일하지만 커널에서 구현되는 대체 메커니즘을 제공합니다 . 이는 프로세스의 초기 스택을 생성할 때 커널이 사용하는 것입니다. 그러나 이는 Linux가 메인 스택이 최대값까지 증가할 수 있도록 가상 메모리를 예약하기 때문에 실제로 의미가 있습니다 ulimit -s
. 안전하게 + 올바르게 작동하게 만드는 일부 "마법"은 를 통해 얻을 수 없으므로 mmap(MAP_GROWSDOWN)
스레드 스택에서는 작동하지 않습니다. 그렇지 않으면 이것이 유효한 선택이 될 것입니다.
"R.."은 계속해서 스레드 스택의 주문형 제출을 지원하도록 커널을 변경할 것을 제안합니다.
다양한 참고 자료:
- Linux의 LLVM은 스택 탐색을 구현하지 않습니다. 이것이 되었다미해결 문제메모리에 안전한 Rust 언어의 경우 이 문제가 이제 해결되었습니다("모든 레벨 1 플랫폼에서 작동").
- 일부 소스에서는 GCC가 스택 탐색을 구현한다고 언급하지만
-fstack-check
"불행히도-fstack-check
우리 목적에 적합하지 않습니다."몇 가지 이유. - 최근의:"대부분의 대상은 스택 충돌 보호를 완벽하게 지원하지 않습니다. 그러나 이러한 대상에서는
-fstack-clash-protection
동적 스택 할당이 보호됩니다 .-fstack-clash-protection
대상에서 지원하는 경우 정적 스택 할당에 대해 제한된 보호를 제공할 수도 있습니다.-fstack-check=specific
". - CVE-2010-2240- 리눅스는 마침내
MAP_GROWSDOWN
. - 스택 충돌2017 - Linux는 마침내 스택 프로브 없이는 한 페이지로는 충분하지 않다는 것을 깨닫습니다.
- 의무적 인StackClash에 관한 LWN.net 기사.
- Windows 구현을 설명하는 Raymond Chen의 블로그 게시물의 일부:https://devblogs.microsoft.com/oldnewthing/?p=29563