리눅스는 분할을 사용하지 않고 페이징 만 사용합니까?


24

Linux 프로그래밍 인터페이스 는 프로세스의 가상 주소 공간 레이아웃을 보여줍니다. 다이어그램의 각 영역이 세그먼트입니까?

여기에 이미지 설명을 입력하십시오

에서 리눅스 커널의 이해 ,

다음은 MMU의 세그먼테이션 단위가 세그먼트 내의 세그먼트와 오프셋을 가상 메모리 주소에 맵핑하고 페이징 유닛이 가상 메모리 주소를 실제 메모리 주소에 맵핑한다는 것을 의미합니까?

MMU (Memory Management Unit)는 세그먼트 화 단위라는 하드웨어 회로를 통해 논리 주소를 선형 주소로 변환합니다. 이후 페이징 장치라고하는 두 번째 하드웨어 회로는 선형 주소를 물리적 주소로 변환합니다 (그림 2-1 참조).

여기에 이미지 설명을 입력하십시오

그렇다면 왜 리눅스가 분할을 사용하지 않고 페이징만을 사용한다고 말하는가?

80x86 마이크로 프로세서에는 세그먼테이션이 포함되어있어 프로그래머가 응용 프로그램을 서브 루틴 또는 글로벌 및 로컬 데이터 영역과 같은 논리적으로 관련된 엔티티로 분할 할 수 있습니다. 그러나 Linux는 매우 제한된 방식으로 분할을 사용합니다. 실제로 분할과 페이징은 프로세스의 물리적 주소 공간을 분리하는 데 사용될 수 있기 때문에 다소 중복됩니다. 분할은 각 프로세스에 서로 다른 선형 주소 공간을 할당 할 수 있지만 페이징은 동일한 선형 주소 공간을 다른 물리적 주소 공간에 매핑 할 수 있습니다 . Linux는 다음과 같은 이유로 분할보다 페이징을 선호합니다.

• 모든 프로세스가 동일한 세그먼트 레지스터 값을 사용하는 경우, 즉 동일한 선형 주소 세트를 공유하는 경우 메모리 관리가 더 간단합니다.

Linux의 설계 목표 중 하나는 광범위한 아키텍처로의 이식성입니다. 특히 RISC 아키텍처는 세분화에 대한 지원이 제한적입니다.

Linux 2.6 버전은 80x86 아키텍처에 필요한 경우에만 분할을 사용합니다.


판을 지정할 수 있습니까? 작성자 이름을 지정하는 것이 도움이 될 수도 있습니다. 나는 적어도 첫 번째가 저명한 인물이라는 것을 알고 있습니다. 그러나 두 제목 이름은 약간 일반적인 것으로 처음에는 책에 대해 이야기하고 있다는 것이 분명하지 않았습니다. :-).
sourcejedi

2
Re "세그먼트가 80x86 마이크로 프로세서에 포함되었습니다 ...": 그건 사실이 아닙니다. 16 비트 데이터 포인터와 64Kb 메모리 세그먼트를 가진 808x 프로세서의 레거시입니다. 세그먼트 포인터를 사용하면 세그먼트를 전환하여 더 많은 메모리를 처리 할 수 ​​있습니다. 이 아키텍처는 80x86 (포인터 크기가 33 비트로 증가)으로 이어졌습니다. 오늘날 x86_64 모델에는 64 비트 포인터가 있으며 16 비트 엑사 바이트를 처리 할 수있는 64 비트 포인터가 있습니다. 따라서 세그먼트가 필요하지 않습니다.
jamesqf

2
@jamesqf, 386의 32 비트 보호 모드는 8086에있는 16 바이트 스케일 포인터와는 상당히 다른 세그먼트를 지원하므로 단순한 레거시가 아닙니다. 그것은 물론 그들의 유용성에 대해 아무 말도하지 않습니다.
ilkkachu

@jamesqf 80186은 "86 비트"가없는 8086과 동일한 메모리 모델을 가졌습니다
Jasen

대답 할 가치가 없으며, 따라서 단지 의견 : 세그먼트와 페이지는 스와핑 (예 : 페이지 스와핑과 세그먼트 스와핑)의 맥락에서만 비교할 수 있으며 해당 컨텍스트에서 페이지 스와핑은 물에서 세그먼트 스와핑을 날려 버립니다. 세그먼트를 입 / 출력으로 교체하는 경우 전체 세그먼트 (2-4GB) 를 교체해야합니다 . 이것은 x86에서 실제로 사용 된 적이 없었습니다. 페이지를 사용하면 항상 4KB 단위로 작업 할 수 있습니다. 메모리에 액세스 할 때 세그먼트와 페이지는 페이지 테이블을 통해 관련되며 사과는 오렌지와 비교됩니다.
vhu

답변:


20

x86-64 아키텍처는 긴 모드 (64 비트 모드)에서 분할을 사용하지 않습니다.

CS, SS, DS 및 ES의 세그먼트 레지스터 중 4 개는 0으로, 2 ^ 64로 제한됩니다.

https://ko.wikipedia.org/wiki/X86_memory_segmentation#Later_developments

더 이상 OS가 사용 가능한 "선형 주소"의 범위를 제한 할 수 없습니다. 따라서 메모리 보호를 위해 분할을 사용할 수 없습니다. 페이징에만 전적으로 의존해야합니다.

레거시 32 비트 모드에서 실행될 때만 적용되는 x86 CPU의 세부 사항에 대해 걱정하지 마십시오. 32 비트 모드의 Linux는 많이 사용되지 않습니다. 심지어 "수년간 양성 방치 상태"로 간주 될 수도 있습니다. Fedora [LWN.net, 2017] 에서 32 비트 x86 지원을 참조하십시오 .

(32 비트 리눅스도 세그먼테이션을 사용하지 않습니다. 그러나 당신은 저를 믿지 않아도됩니다. 무시할 수 있습니다 :-).


그것은 약간 과장된 표현입니다. 기본 / 제한은 레거시 원래 8086 세그먼트 (CS / DS / ES / SS)의 긴 모드에서 0 / -1로 고정되지만 FS 및 GS에는 여전히 임의의 세그먼트 기반이 있습니다. CS에로드 된 세그먼트 디스크립터는 CPU가 32 비트 모드에서 실행되는지 64 비트 모드에서 실행되는지를 판별합니다. x86-64 Linux의 사용자 공간은 스레드 로컬 스토리지 ( mov eax, [fs:rdi + 16])에 FS를 사용합니다 . 커널은 GS (after swapgs)를 사용 하여 syscall진입 점 에서 현재 프로세스의 커널 스택을 찾습니다 . 그러나 예, 세분화는 주요 OS 메모리 관리 / 메모리 보호 메커니즘의 일부로 사용되지 않습니다.
Peter Cordes

이것은 기본적으로이 질문에서 인용 한 "2.6 버전의 Linux는 80x86 아키텍처에 필요한 경우에만 분할을 사용합니다"라는 의미입니다. 그러나 두 번째 단락은 기본적으로 잘못되었습니다. Linux는 기본적으로 32 비트 및 64 비트 모드에서 분할을 동일하게 사용합니다. 즉, 일반 명령어에 의해 암시 적으로 사용되는 클래식 세그먼트 (CS / DS / ES / SS)의 경우 base = 0 / limit = 2 ^ 32 또는 2 ^ 64입니다. 32 비트 리눅스 코드에서 걱정할 "추가적인 것"은 없다. HW 기능은 있지만 사용되지는 않습니다.
Peter Cordes

@ PeterCordes 당신은 기본적으로 대답을 잘못 해석하고 있습니다 :-). 그래서 나는 논쟁을 모호하게 만들려고 편집했다.
sourcejedi

좋은 개선, 이제 오해의 소지가 없습니다. x86 세그먼테이션은 osdev 시스템 관리 및 TLS에만 사용되므로 x86 세그먼테이션을 완전히 무시할 수 있고 무시해야한다는 점에 전적으로 동의합니다. 결국 그것에 대해 배우고 싶다면 플랫 메모리 모델로 x86 asm을 이미 이해 한 후에 이해하기가 훨씬 쉽습니다.
Peter Cordes

8

x86에는 세그먼트가 있으므로 사용할 수 없습니다. 그러나 cs(코드 세그먼트) 및 ds(데이터 세그먼트) 기본 주소는 모두 0으로 설정되므로 세그먼트 화는 실제로 사용되지 않습니다. 예외적으로 스레드 로컬 데이터는 일반적으로 사용되지 않는 세그먼트 레지스터 중 하나가 스레드 로컬 데이터를 가리 킵니다. 그러나 이는 주로이 작업에 대한 범용 레지스터 중 하나를 예약하지 않도록하기위한 것입니다.

리눅스가 x86에서 분할을 사용하지 않는다고 말하지는 않습니다. 이미 한 부분을 강조했다. 리눅스는 매우 제한된 방식으로 분할을 사용한다 . 두 번째 부분은 Linux가 80x86 아키텍처에 필요한 경우에만 분할을 사용한다는 것입니다

페이징이 더 쉽고 휴대하기 쉬운 이유를 이미 인용했습니다.


7

다이어그램의 각 영역이 세그먼트입니까?

아니.

세그먼테이션 시스템 (x86의 32 비트 보호 모드)은 별도의 코드, 데이터 및 스택 세그먼트를 지원하도록 설계되었지만 실제로 모든 세그먼트는 동일한 메모리 영역으로 설정됩니다. 즉, 0에서 시작하여 메모리 끝에서 끝납니다 (*) . 따라서 논리 주소와 선형 주소가 동일합니다.

이것을 "플랫 (flat)"메모리 모델이라고하며, 고유 한 세그먼트가 있고 그 안에 포인터가있는 모델보다 다소 단순합니다. 특히, 세그먼트 선택기는 오프셋 포인터와 함께 포함되어야하므로 세그먼트 화 된 모델에는 더 긴 포인터가 필요합니다. (16 비트 세그먼트 선택기 + 32 비트 플랫 포인터와 비교하여 총 48 비트 포인터의 경우 32 비트 오프셋)

64 비트 롱 모드는 플랫 메모리 모델 이외의 세그먼테이션도 지원하지 않습니다.

286에서 16 비트 보호 모드로 프로그래밍하려면 주소 공간이 24 비트이지만 포인터는 16 비트이므로 세그먼트가 더 필요합니다.

(* 32 비트 Linux가 커널 / 사용자 공간 분리를 처리하는 방법을 기억할 수 없다는 점에 유의하십시오. 분할은 사용자 공간 세그먼트를 설정하여 커널 공간을 포함하지 않도록 제한합니다. 페이징은 페이지 당 보호 수준.)

그렇다면 왜 리눅스가 분할을 사용하지 않고 페이징만을 사용한다고 말하는가?

x86에는 여전히 세그먼트가 있으며 비활성화 할 수 없습니다. 그들은 가능한 한 적게 사용됩니다. 32 비트 보호 모드에서는 플랫 모델에 대해 세그먼트를 설정해야하며 64 비트 모드에서도 여전히 존재합니다.


허, 32 비트 커널이 사용자 공간이 2G 또는 3G 이상에 액세스하지 못하게하는 CS / DS / ES / SS에 세그먼트 제한을 설정하여 페이지 테이블을 변경하는 것보다 더 저렴하게 Meltdown을 완화 할 수 있다고 생각합니다. Meltdown vuln은 페이지 테이블 항목의 커널 / 사용자 비트에 대한 해결 방법으로, 커널 전용으로 매핑 된 페이지에서 사용자 공간을 읽을 수 있습니다. VDSO 페이지는 4G의 맨 위에 매핑 될 수 있지만, wrfsbase보호 된 / 복합 모드에서는 긴 모드 만 유효 하지 않습니다. 32 비트 커널 사용자 공간에서는 FS 기준을 높게 설정할 수 없습니다.
Peter Cordes

64 비트 커널에서 32 비트 사용자 공간은 64 비트 코드 세그먼트로 멀리 이동할 수 있으므로 Meltdown 보호를위한 세그먼트 제한에 의존 할 수 없으며 순수한 32 비트 커널에서만 가능합니다. (물리적 RAM이 많은 머신 (예 : 스레드 스택에 낮은 메모리 부족)에 큰 단점이 있습니다.) 예, Linux는 페이징으로 커널 메모리를 보호하여 사용자 공간에서 기본 / 제한 = 0 / -1을 유지합니다. 세그먼트 (스레드 로컬 스토리지에 사용되는 FS / GS 아님).
Peter Cordes

PAE (하드웨어 페이지 테이블)에서 NX 비트가 지원되기 전에 일부 초기 보안 패치는 분할을 사용하여 사용자 공간 코드에 대해 실행 불가능한 스택을 만들었습니다. 예 : linux.com/news/exec-shield-new-linux-security-feature (Ingo Molnar의 게시물은 "Solar Designer의 우수한"비 실행 스택 패치 ""에 대해 언급 함)
Peter Cordes

3

Linux x86 / 32는 모든 세그먼트를 동일한 선형 주소 및 한계로 초기화한다는 의미에서 세그먼트 화를 사용하지 않습니다. x86 아키텍처에는 프로그램에 세그먼트가 있어야합니다. 코드는 코드 세그먼트에서만 실행할 수 있고 스택은 스택 세그먼트에만있을 수 있으며 데이터는 데이터 세그먼트 중 하나에서만 조작 할 수 있습니다. Linux는 모든 세그먼트를 동일한 방식으로 설정하여 (책에서 언급하지 않은 경우를 제외하고)이 메커니즘을 무시하므로 세그먼트에서 동일한 논리 주소가 유효합니다. 이것은 실제로 세그먼트가없는 것과 같습니다.


2

다이어그램의 각 영역이 세그먼트입니까?

이들은 "세그먼트"라는 단어를 거의 완전히 다르게 사용합니다.

  • x86 세그먼테이션 / 세그먼트 레지스터 : 최신 x86 OS는 32 비트 모드에서 모든 세그먼트가 동일한 base = 0과 limit = max를 갖는 플랫 메모리 모델을 사용합니다. 하드웨어는 64 비트 모드에서와 동일 하게 세그먼테이션을 수행합니다. . 64 비트 모드에서도 스레드 로컬 스토리지에 사용되는 FS 또는 GS는 제외합니다.
  • 링커 / 프로그램 로더 섹션 / 세그먼트. ( ELF 파일 형식의 섹션과 세그먼트의 차이점은 무엇입니까 )

사용법은 공통된 기원을 갖습니다 . 세그먼트 메모리 모델 사용하는 경우 (특히 페이징 된 가상 메모리가없는 경우) 데이터 및 BSS 주소는 DS 세그먼트베이스에 상대적이며 SS베이스에 상대적으로 스택 및 CS 기본 주소.

따라서 세그먼트베이스를 기준으로 16 비트 또는 32 비트 오프셋을 변경하지 않고도 여러 다른 프로그램을 다른 선형 주소에로드하거나 시작 후 이동할 수 있습니다.

그러나 포인터와 관련된 세그먼트를 알아야하므로 "먼 포인터"등이 있습니다. (실제 16 비트 x86 프로그램은 종종 코드로 데이터에 액세스 할 필요가 없었으므로 64k 코드 세그먼트를 어딘가에, DS = SS가있는 또 다른 64k 블록을 사용할 수 있습니다. 또는 모든 세그먼트베이스가 동일한 작은 코드 모델).


x86 세그먼트 화가 페이징과 상호 작용하는 방법

32/64 비트 모드에서의 주소 매핑은 다음과 같습니다.

  1. segment : offset (오프셋을 보유하는 레지스터에 의해 암시되거나 명령 접두어로 재정의 된 세그먼트 기준)
  2. 32 또는 64 비트 선형 가상 주소 = 기본 + 오프셋. (Linux와 같은 플랫 메모리 모델에서는 포인터 / 오프셋 = 선형 주소도 있습니다. FS 또는 GS와 관련된 TLS에 액세스하는 경우는 제외)
  3. TLB에 의해 캐시 된 페이지 테이블은 32 (레거시 모드), 36 (레거시 PAE) 또는 52 비트 (x86-64) 실제 주소에 선형으로 매핑됩니다. ( /programming/46509152/why-in-64bit-the-virtual-address-are-4-bits-short-48bit-long-compared-with-the ).

    이 단계는 선택 사항입니다. 제어 레지스터에서 비트를 설정하여 부팅 중에 페이징을 활성화해야합니다. 페이징이 없으면 선형 주소는 실제 주소입니다.

세분화 않는 것을주의 하지 당신이 하나의 프로세스 (또는 스레드)에서 가상 주소 공간 이상의 32 또는 64 비트를 사용하게 에만 오프셋 자신의 비트 수가있다로 플랫 (선형) 주소 공간의 모든 매핑되기 때문에. 16 비트 x86의 경우에는 그렇지 않았으며, 분할은 실제로 16 비트 레지스터와 오프셋이있는 64k 이상의 메모리를 사용하는 데 실제로 유용했습니다.


CPU는 세그먼트베이스를 포함하여 GDT (또는 LDT)에서로드 된 세그먼트 디스크립터를 캐시합니다. 포인터를 역 참조 할 때 포인터가있는 레지스터에 따라 세그먼트의 기본값은 DS 또는 SS입니다. 레지스터 값 (포인터)은 세그먼트베이스에서 오프셋으로 처리됩니다.

세그먼트베이스는 일반적으로 0이므로 CPU는이를 특수하게 처리합니다. 당신이 경우 또는 다른 관점에서, 이렇게 비 제로 세그먼트 기반을 가지고, 부하는 별도의 대기 시간이 있기 때문에 기본 주소가 적용되지 않는 추가 우회의 "특별한"(일반)의 경우.


리눅스가 x86 세그먼트 레지스터를 설정하는 방법 :

CS / DS / ES / SS의 기본 및 한계는 32 비트 및 64 비트 모드에서 모두 0 / -1입니다. 모든 포인터가 동일한 주소 공간을 가리 키기 때문에이를 플랫 메모리 모델이라고합니다.

(AMD CPU 아키텍트 는 PAE 또는 x86으로 페이징함으로써 훨씬 더 나은 방식으로 제공되는 비 실행 보호를 제외하고는 주류 OS가 어쨌든 그것을 사용하지 않았기 때문에 64 비트 모드에 플랫 메모리 모델을 적용 하여 세분화를 중화했습니다. 64 페이지 표 형식)

  • TLS (Thread Local Storage) : FS 및 GS는 long 모드에서 base = 0으로 고정 되지 않습니다 . (386 rep을 사용 하여 새로워졌으며 ES를 사용 하는 -string 명령 조차 포함하지 않고 모든 명령에서 암시 적으로 사용되지 않았습니다 ). x86-64 Linux는 각 스레드의 FS 기본 주소를 TLS 블록의 주소로 설정합니다.

    예를 들어 mov eax, [fs: 16]16 바이트의 32 비트 값을이 스레드의 TLS 블록으로로드합니다.

  • CS 세그먼트 디스크립터는 CPU의 모드를 선택합니다 (16/32/64 비트 보호 모드 / 장기 모드). Linux는 모든 64 비트 사용자 공간 프로세스에 단일 GDT 항목을 사용하고 모든 32 비트 사용자 공간 프로세스에 다른 GDT 항목을 사용합니다. (CPU가 제대로 작동하려면 DS / ES도 유효한 항목으로 설정되어야하며 SS도 마찬가지입니다). 또한 권한 수준 (커널 (링 0) 대 사용자 (링 3))을 선택하므로 64 비트 사용자 공간으로 돌아갈 때에도 커널은 CS를 표준 대신 사용 iret하거나 sysret대신에 변경하도록 정렬해야합니다. 점프 또는 재시도 명령.

  • x86-64에서 syscall진입 점은 swapgsGS를 사용자 공간의 GS에서 커널로 뒤집는 데 사용됩니다.이 스레드는이 스레드의 커널 스택을 찾는 데 사용됩니다. (스레드 로컬 스토리지의 특수한 경우). 이 syscall명령은 커널 포인터를 가리 키도록 스택 포인터를 변경하지 않습니다. 커널이 진입 점 1에 도달하면 여전히 사용자 스택을 가리키고 있습니다.

  • 또한 이러한 디스크립터의 기본 / 제한이 긴 모드에서 무시 되더라도 CPU가 보호 모드 / 롱 모드에서 작동하려면 DS / ES / SS를 유효한 세그먼트 디스크립터로 설정해야합니다.

따라서 기본적으로 x86 세그먼트는 TLS 및 하드웨어에서 요구하는 필수 x86 osdev 작업에 사용됩니다.


각주 1 : 재미있는 역사 : AMD64 실리콘이 출시되기 몇 년 전부터 커널 개발자와 AMD 설계자 사이에 메일 링 메일 보관소가있어 디자인이 조정되어 syscall사용할 수있었습니다. 자세한 내용은 이 답변의 링크를 참조 하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.