문제

저는 커널 디자인 작업을 하고 있는데 페이징에 관해 몇 가지 질문이 있습니다.

지금까지 제가 가지고 있는 기본 아이디어는 다음과 같습니다.각 프로그램은 프로그램이 호출할 수 있는 커널 기능을 위해 예약한 섹션을 제외한 자체 4G 메모리를 갖습니다.따라서 OS는 프로그램이 작동하는 동안 사용해야 하는 페이지를 메모리에 로드하는 방법을 찾아야 합니다.

이제 무한한 양의 메모리와 프로세서 시간이 있다고 가정하면 존재하지 않는(또는 교체된) 페이지에 대한 페이지 오류를 사용하여 프로그램이 쓰거나 읽은 모든 페이지를 로드/할당할 수 있으므로 OS는 신속하게 할당하거나 교체할 수 있습니다.하지만 현실 세계에서는 이 프로세스를 최적화하여 프로그램이 사용한 모든 메모리를 지속적으로 소비하지 않도록 해야 합니다.

그래서 내 질문은 OS가 일반적으로 이 문제를 어떻게 처리하는가입니다.나의 초기 생각은 프로그램이 페이지 설정/해제를 위해 호출하는 함수를 생성하는 것이었습니다. 그런 다음 자체적으로 메모리를 관리할 수 있지만 프로그램이 일반적으로 이 작업을 수행합니까, 아니면 컴파일러가 자유 통치가 있다고 가정합니까?또한 컴파일러는 상당히 큰 메모리 세그먼트를 할당해야 하는 상황을 어떻게 처리합니까?X 페이지를 순서대로 제공하는 기능을 제공해야 합니까?

이것은 분명히 언어별 질문은 아니지만 저는 표준 C를 선호하고 C++에 능숙하므로 모든 코드 예제가 해당 또는 어셈블리에 포함되기를 바랍니다.(어셈블리는 필요하지 않습니다. 가능한 한 많은 C 코드로 작동하도록 만들고 마지막 단계로 최적화하려고 합니다.)

대답하기 더 쉬운 또 다른 사항은 다음과 같습니다.프로그램이 호출해야 하는 커널 함수를 일반적으로 어떻게 처리합니까?프로그램이 호출할 수 있는 대부분의 기본 기능/프로세스 특정 메모리를 포함하는 설정된 메모리 영역(가상 공간의 끝을 향해 생각하고 있었습니다)을 갖는 것이 괜찮습니까?거기에서 내 생각은 프로그램이 중요한 작업을 수행해야 할 때 커널 함수가 매우 멋진 작업을 수행하고 페이지를 교체하여(그래서 프로그램이 자체 공간에서 민감한 커널 기능을 볼 수 없도록) 하는 것이었지만 실제로는 그렇지 않습니다. 지금은 보안에 중점을 두고 있습니다.

그래서 세부적인 디자인보다는 전체적인 디자인 아이디어에 더 신경을 쓰는 것 같아요.나는 (어쨌든) 커널이 GCC와 완전히 호환되도록 만들고 싶고 그것이 일반 프로그램에 필요한 모든 것을 제공하는지 확인해야 합니다.

어떤 조언이라도 감사드립니다.

도움이 되었습니까?

해결책

이 모든 질문에 대한 좋은 출발점은 Unix가 이를 어떻게 수행하는지 살펴보는 것입니다.유명한 인용문에 따르면 "UNIX를 이해하지 못하는 사람은 유닉스를 제대로 재창조하지 못할 운명에 처해 있습니다."

먼저 커널 함수 호출에 대해 설명합니다.프로그램이 "사용자 모드"(IA-32의 링 3)에서 실행되고 커널은 "커널 모드"(보통 링 0)에서 실행되어야 하기 때문에 단순히 프로그램이 호출할 수 있는 어딘가에 함수를 갖는 것만으로는 충분하지 않습니다. IA-32에서) 특권적인 작업을 수행합니다.어떻게든 두 모드 사이의 전환을 수행해야 하며 이는 아키텍처에 따라 매우 다릅니다.

IA-32에서 전통적인 방법은 소프트웨어 인터럽트와 함께 IDT의 게이트를 사용하는 것입니다(Linux는 int 0x80을 사용합니다).최신 프로세서에는 다른(더 빠른) 방법이 있으며, 사용 가능한 방법은 CPU가 AMD 제품인지 Intel 제품인지, 그리고 특정 CPU 모델에 따라 다릅니다.이러한 변형을 수용하기 위해 최신 Linux 커널은 모든 프로세스의 주소 공간 상단에 있는 커널에 의해 매핑된 코드 페이지를 사용합니다.따라서 최근 Linux에서는 시스템 호출을 수행하려면 이 페이지에서 함수를 호출하면 커널 모드로 전환하는 데 필요한 모든 작업이 수행됩니다(커널에는 해당 페이지의 복사본이 두 개 이상 있으며 사용할 복사본을 선택합니다). 프로세서 기능에 따라 부팅 시).

이제 메모리 관리입니다.이것은 거대한 주제;당신은 그것에 대해 큰 책을 쓸 수 있고 주제를 다 다루지 않을 수도 있습니다.

적어도 있다는 점을 명심하세요. 기억의 견해:물리적 보기(하드웨어 메모리 하위 시스템 및 종종 외부 주변 장치에 표시되는 페이지의 실제 순서)와 논리적 보기(CPU에서 실행되는 프로그램에 표시되는 페이지 순서)입니다.둘 다 혼동하기는 매우 쉽습니다.할당을 하게 됩니다 물리적 페이지를 지정하고 논리적 프로그램 주소 또는 커널 주소 공간.단일 물리적 페이지에는 여러 논리 주소가 있을 수 있으며, 서로 다른 프로세스의 서로 다른 논리 주소에 매핑될 수 있습니다.

커널 메모리(커널용으로 예약됨)는 일반적으로 모든 프로세스의 주소 공간 상단에 매핑됩니다.하지만 커널 모드에서만 접근이 가능하도록 설정되어 있습니다.메모리의 해당 부분을 숨기기 위해 화려한 트릭이 필요하지 않습니다.하드웨어는 액세스를 차단하는 모든 작업을 수행합니다(IA-32에서는 페이지 플래그 또는 세그먼트 제한을 통해 수행됨).

프로그램은 여러 가지 방법으로 나머지 주소 공간에 메모리를 할당합니다.

  • 메모리의 일부는 커널의 프로그램 로더에 의해 할당됩니다.여기에는 프로그램 코드(또는 "텍스트"), 프로그램 초기화 데이터("데이터"), 프로그램 초기화되지 않은 데이터("bss", 0으로 채워짐), 스택 및 여러 가지 잡동사니가 포함됩니다.할당할 양, 초기 내용은 무엇이어야 하는지, 사용할 보호 플래그 및 기타 여러 사항은 로드할 실행 파일의 헤더에서 읽어옵니다.
  • 전통적으로 Unix에는 늘어나거나 줄어들 수 있는 메모리 영역이 있습니다. (상한선은 다음을 통해 변경될 수 있습니다.) brk() 시스템 호출).이는 전통적으로 힙(C 라이브러리의 메모리 할당자)에서 사용됩니다. malloc() 인터페이스 중 하나이며 힙을 담당합니다.)
  • 종종 커널에 파일을 주소 공간 영역에 매핑하도록 요청할 수 있습니다.해당 영역에 대한 읽기 및 쓰기는 (페이징 매직을 통해) 백업 파일로 전달됩니다.이것은 일반적으로 호출됩니다 mmap().익명으로 mmap, 어떤 파일에서도 지원되지 않는 주소 공간의 새 영역을 할당할 수 있지만 그 외에는 동일한 방식으로 작동합니다.커널의 프로그램 로더는 종종 다음을 사용합니다. mmap 프로그램 코드의 일부를 할당합니다(예를 들어, 프로그램 코드는 실행 파일 자체에 의해 지원될 수 있습니다).

어떤 방식으로든 할당되지 않은(또는 커널용으로 예약된) 주소 공간 영역에 액세스하는 것은 오류로 간주되며 Unix에서는 신호가 프로그램에 전송됩니다.

컴파일러는 실행 파일 헤더에 메모리를 지정하여 메모리를 정적으로 할당합니다.커널의 프로그램 로더는 프로그램을 로드할 때 메모리를 할당하거나 동적으로(언어의 표준 라이브러리에서 함수를 호출하여) 일반적으로 C 언어 표준 라이브러리의 함수를 호출한 다음 커널을 호출하여 메모리를 할당하고 세분화합니다. 필요하다면).

이 모든 것의 기본을 배우는 가장 좋은 방법은 운영 체제에 관한 여러 책 중 하나를 읽는 것입니다. 특히 Unix 변형을 예로 사용하는 책을 읽는 것입니다.StackOverflow에 대한 답변보다 더 자세히 설명하겠습니다.

다른 팁

이 질문에 대한 대답은 아키텍처에 따라 크게 달라집니다.나는 당신이 x86에 대해 이야기하고 있다고 가정하겠습니다.x86을 사용하면 커널은 일반적으로 다음 세트를 제공합니다. 시스템 호출, 이는 커널로의 미리 결정된 진입점입니다.사용자 코드는 특정 지점에서만 커널에 들어갈 수 있으므로 커널은 사용자 코드와 상호 작용하는 방식을 신중하게 제어합니다.

x86에는 시스템 호출을 구현하는 두 가지 방법이 있습니다.인터럽트 및 sysenter/sysexit 명령어를 사용합니다.인터럽트를 사용하면 커널은 인터럽트 설명자 테이블 (IDT)는 커널에 대한 가능한 진입점을 정의합니다.그런 다음 사용자 코드는 다음을 사용할 수 있습니다. int 커널을 호출하기 위해 소프트 인터럽트를 생성하는 명령입니다.인터럽트는 하드웨어에 의해 생성될 수도 있습니다(소위 하드 인터럽트).이러한 인터럽트는 일반적으로 소프트 인터럽트와 구별되어야 하지만 반드시 그럴 필요는 없습니다.

sysenter 및 sysexit 명령은 인터럽트 처리가 느리기 때문에 시스템 호출을 수행하는 더 빠른 방법입니다.나는 그것들을 사용하는 데 익숙하지 않기 때문에 이것이 귀하의 상황에 더 나은 선택인지 여부에 대해 언급할 수 없습니다.

어느 것을 사용하든 시스템 호출 인터페이스를 정의해야 합니다.인터럽트를 생성하면 스택이 커널 스택으로 전환되므로 스택이 아닌 레지스터에 시스템 호출 인수를 전달하고 싶을 것입니다.이는 사용자 모드 측에서 시스템 호출을 수행하고 커널 측에서 시스템 호출 인수를 수집하고 레지스터를 저장하기 위해 어셈블리 언어 스텁을 거의 확실히 작성해야 함을 의미합니다.

모든 것이 준비되면 페이지 오류 처리에 대해 생각할 수 있습니다.페이지 오류는 사실상 또 다른 유형의 인터럽트입니다. 사용자 코드가 페이지 테이블 항목이 없는 가상 주소에 액세스하려고 하면 인터럽트 14가 생성되고 오류 코드로 오류 주소도 얻게 됩니다.커널은 이 정보를 취한 다음 디스크에서 누락된 페이지를 읽고, 페이지 테이블 매핑을 추가하고, 사용자 코드로 다시 점프하기로 결정할 수 있습니다.

에서 제공하는 자료 중 일부를 살펴보는 것이 좋습니다. MIT 운영 체제 수업.참고자료 섹션을 확인해 보세요. 좋은 내용이 많이 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top