UFO ET IT

컨텍스트 전환 내부

ufoet 2020. 11. 30. 20:25
반응형

컨텍스트 전환 내부


나는이 질문의 도움으로 내 지식의 격차를 배우고 채우고 싶습니다.

따라서 사용자는 스레드 (커널 수준)를 실행하고 있으며 이제 호출합니다 yield(내가 생각하는 시스템 호출). 이제 스케줄러는 현재 스레드의 컨텍스트를 TCB (커널 어딘가에 저장 됨)에 저장하고 실행할 다른 스레드를 선택하고 컨텍스트를로드하고 CS:EIP. 범위를 좁히기 위해 x86 아키텍처에서 실행되는 Linux에서 작업하고 있습니다. 이제 자세한 내용을 살펴 보겠습니다.

따라서 먼저 시스템 호출이 있습니다.

1)에 대한 래퍼 함수 yield는 시스템 호출 인수를 스택에 푸시합니다. 반환 주소를 누르고 시스템 호출 번호가 일부 레지스터에 푸시 된 상태로 인터럽트를 발생시킵니다 (예 :) EAX.

2) 인터럽트는 CPU 모드를 사용자에서 커널로 변경하고 인터럽트 벡터 테이블로 이동하고 거기에서 커널의 실제 시스템 호출로 이동합니다.

3) 스케줄러가 지금 호출되고 이제 TCB에 현재 상태를 저장해야한다고 생각합니다. 여기 내 딜레마가 있습니다. 이후 스케줄러는 커널 스택 아닌합니다 (의미있는 동작을 수행하기위한 사용자 스택 사용 SS하고 SP프로세스에서 어떤 레지스터를 수정하지 않고 사용자의 상태를 저장 않는 방법을 변경할 필요가). 상태를 저장하기위한 특별한 하드웨어 지침이 있다는 것을 포럼에서 읽었지만 스케줄러는 어떻게 액세스 할 수 있으며 누가 이러한 지침을 언제 실행합니까?

4) 스케줄러는 이제 상태를 TCB에 저장하고 다른 TCB를로드합니다.

5) 스케줄러가 원래 스레드를 실행하면 컨트롤이 스택을 지우고 스레드가 다시 시작되는 래퍼 함수로 돌아갑니다.

부수적 인 질문 : 스케줄러가 커널 전용 스레드 (예 : 커널 코드 만 실행할 수있는 스레드)로 실행됩니까? 각 커널 스레드 또는 각 프로세스에 대해 별도의 커널 스택이 있습니까?


높은 수준에서 이해할 수있는 두 가지 별도의 메커니즘이 있습니다. 첫 번째는 커널 진입 / 종료 메커니즘입니다. 이것은 단일 실행 스레드를 실행중인 사용자 모드 코드에서 해당 스레드의 컨텍스트에서 실행중인 커널 코드로 전환하고 다시 되돌립니다. 두 번째는 컨텍스트 전환 메커니즘 자체로, 커널 모드에서 한 스레드의 컨텍스트에서 실행되는 것에서 다른 스레드로 전환됩니다.

따라서 스레드 A가 호출 sched_yield()하고 스레드 B로 대체되면 다음과 같은 일이 발생합니다.

  1. 스레드 A가 커널에 들어가 사용자 모드에서 커널 모드로 변경됩니다.
  2. 커널 컨텍스트의 스레드 A는 커널의 스레드 B로 전환합니다.
  3. 스레드 B는 커널을 종료하고 커널 모드에서 다시 사용자 모드로 변경합니다.

각 사용자 스레드에는 사용자 모드 스택과 커널 모드 스택이 모두 있습니다. 스레드는 커널 사용자 모드 스택의 현재 값 (들어가면 SS:ESP) 및 인스트럭션 포인터 ( CS:EIP) 스레드의 커널 모드 스택에 저장하고, CPU는 커널 모드 스택으로 전환합니다 -와 int $80콜 메커니즘이 CPU 자체에 의해 수행됩니다. 나머지 레지스터 값과 플래그도 커널 스택에 저장됩니다.

스레드가 커널에서 사용자 모드로 돌아 오면 레지스터 값과 플래그가 커널 모드 스택에서 팝된 다음 사용자 모드 스택과 명령어 포인터 값이 커널 모드 스택에 저장된 값에서 복원됩니다.

스레드 컨텍스트가 전환되면 스케줄러를 호출합니다 (스케줄러는 별도의 스레드로 실행되지 않고 항상 현재 스레드의 컨텍스트에서 실행 됨). 스케줄러 코드는 다음에 실행할 프로세스를 선택하고 switch_to()함수를 호출합니다 . 이 함수는 본질적으로 커널 스택을 전환합니다. 스택 포인터의 현재 값을 현재 스레드 ( struct task_structLinux에서 호출 됨)의 TCB에 저장하고 다음 스레드를 위해 TCB에서 이전에 저장된 스택 포인터를로드합니다. 이 시점에서 부동 소수점 / SSE 레지스터와 같이 커널에서 일반적으로 사용하지 않는 다른 스레드 상태도 저장하고 복원합니다. 전환되는 스레드가 동일한 가상 메모리 공간을 공유하지 않는 경우 (즉, 서로 다른 프로세스에 있음) 페이지 테이블도 전환됩니다.

따라서 스레드의 핵심 사용자 모드 상태가 컨텍스트 전환 시간에 저장 및 복원되지 않음을 알 수 있습니다. 커널에 들어오고 나갈 때 스레드의 커널 스택에 저장 및 복원됩니다. 컨텍스트 스위치 코드는 사용자 모드 레지스터 값을 막는 것에 대해 걱정할 필요가 없습니다.이 값은 이미 그 시점까지 커널 스택에 안전하게 저장되어 있습니다.


2 단계에서 놓친 것은 스택이 스레드의 사용자 수준 스택 (args를 푸시 한 위치)에서 스레드의 보호 수준 스택으로 전환된다는 것입니다. syscall에 의해 중단 된 스레드의 현재 컨텍스트는 실제로이 보호 된 스택에 저장됩니다. ISR이 내부와 단지 커널에 들어가기 전에,이 보호 스택은 다시로 전환당신이 말하는 커널 스택. 일단 커널 내부에서 스케줄러의 기능과 같은 커널 기능은 결국 커널 스택을 사용합니다. 나중에 스레드가 스케줄러에 의해 선택되고 시스템이 ISR로 돌아가서 커널 스택에서 새로 선택된 (또는 우선 순위가 더 높은 스레드가 활성화되지 않은 경우 전자) 스레드의 보호 수준 스택으로 다시 전환됩니다. 새로운 스레드 컨텍스트 따라서 컨텍스트는 코드에 의해이 스택에서 자동으로 복원됩니다 (기본 아키텍처에 따라 다름). 마지막으로 특수 명령어는 스택 포인터 및 명령어 포인터와 같은 최신 터치 레지스터를 복원합니다. 다시 사용자 영역으로 ...

요약하면 스레드에는 (일반적으로) 두 개의 스택이 있고 커널 자체에는 하나가 있습니다. 커널 스택은 각 커널 입력이 끝날 때 지워집니다. 2.6 이후 커널 자체가 일부 처리를 위해 스레드되므로 커널 스레드는 일반 커널 스택 옆에 자체 보호 수준 스택이 있다는 점을 지적하는 것이 흥미 롭습니다.

일부 리소스 :

  • 3.3.3 프로세스 스위치 수행리눅스 커널의 이해 , 오라일리을
  • 5.12.1 예외 중 또는 인터럽트 핸들러 절차인텔의 매뉴얼도 3a는 (sysprogramming) . 챕터 번호는 에디션마다 다를 수 있으므로 "인터럽트 및 예외 처리 루틴으로의 전송시 스택 사용"을 찾아 보면 좋은 결과를 얻을 수 있습니다.

이 도움을 바랍니다!


커널 자체에는 스택이 전혀 없습니다. 프로세스도 마찬가지입니다. 또한 스택이 없습니다. 스레드는 실행 단위로 간주되는 시스템 시민 일뿐입니다. 이로 인해 스레드 만 예약 할 수 있으며 스레드에만 스택이 있습니다. 그러나 커널 모드 코드가 많이 악용하는 한 가지 지점이 있습니다. 매 순간 시스템은 현재 활성 스레드의 컨텍스트에서 작동합니다. 이 커널로 인해 현재 활성 스택의 스택을 재사용 할 수 있습니다. 커널 코드 또는 사용자 코드 중 하나만 동시에 실행할 수 있습니다. 이로 인해 커널이 호출 될 때 스레드 스택을 재사용하고 스레드의 중단 된 활동으로 제어를 다시 반환하기 전에 정리를 수행합니다. 동일한 메커니즘이 인터럽트 핸들러에 대해 작동합니다. 동일한 메커니즘이 시그널 핸들러에 의해 이용됩니다.

차례로 스레드 스택은 두 개의 분리 된 부분으로 나뉘는데, 그중 하나는 사용자 스택 (스레드가 사용자 모드에서 실행될 때 사용되기 때문에)이라고하고 두 번째는 커널 스택 (스레드가 커널 모드에서 실행될 때 사용되기 때문에)이라고합니다. . 쓰레드가 사용자와 커널 모드 사이의 경계를 넘으면 CPU는 자동으로 한 스택에서 다른 스택으로 전환합니다. 두 스택 모두 커널과 CPU에서 다르게 추적됩니다. 커널 스택의 경우 CPU는 스레드의 커널 스택 상단에 대한 포인터를 영구적으로 기억합니다. 이 주소는 스레드에 대해 일정하기 때문에 쉽습니다. 스레드가 커널에 들어갈 때마다 빈 커널 스택을 발견하고 사용자 모드로 돌아갈 때마다 커널 스택을 정리합니다. 동시에 CPU는 스레드가 커널 모드에서 실행될 때 사용자 스택 상단에 대한 포인터를 염두에 두지 않습니다. 대신 커널에 들어가는 동안 CPU는 커널 스택 상단에 특수 "인터럽트"스택 프레임을 만들고 해당 프레임에 사용자 모드 스택 포인터의 값을 저장합니다. 스레드가 커널을 종료하면 CPU는 정리 직전에 이전에 생성 된 "인터럽트"스택 프레임에서 ESP 값을 복원합니다. (레거시 x86에서는 int / iret 명령어 쌍이 커널 모드에 들어가고 나갑니다)

커널 모드로 들어가는 동안 CPU가 "인터럽트"스택 프레임을 생성 한 직후 커널은 나머지 CPU 레지스터의 내용을 커널 스택에 푸시합니다. 커널 코드에서 사용할 수있는 레지스터의 값만 저장합니다. 예를 들어 커널은 SSE 레지스터를 건드리지 않기 때문에 SSE 레지스터의 내용을 저장하지 않습니다. 마찬가지로 CPU에게 제어권을 다시 사용자 모드로 되돌 리도록 요청하기 직전에 커널은 이전에 저장된 콘텐츠를 레지스터로 다시 팝합니다.

Windows 및 Linux와 같은 시스템에는 시스템 스레드라는 개념이 있습니다 (커널 스레드라고 자주 부르며 혼란 스러움). 시스템 스레드는 일종의 특수 스레드입니다. 커널 모드에서만 실행되고 이로 인해 스택의 사용자 부분이 없기 때문입니다. 커널은 보조 하우스 키핑 작업에이를 사용합니다.

Thread switch is performed only in kernel mode. That mean that both threads outgoing and incoming run in kernel mode, both uses their own kernel stacks, and both have kernel stacks have "interrupt" frames with pointers to the top of the user stacks. Key point of the thread switch is a switch between kernel stacks of threads, as simple as:

pushad; // save context of outgoing thread on the top of the kernel stack of outgoing thread
; here kernel uses kernel stack of outgoing thread
mov [TCB_of_outgoing_thread], ESP;
mov  ESP , [TCB_of_incoming_thread]    
; here kernel uses kernel stack of incoming thread
popad; // save context of incoming thread from the top of the kernel stack of incoming thread

Note that there is only one function in the kernel that performs thread switch. Due to this each time when kernel has stacks switched it can find a context of incoming thread on the top of the stack. Just because every time before stack switch kernel pushes context of outgoing thread to its stack.

Note also that every time after stack switch and before returning back to the user mode, kernel reloads the mind of CPU by new value of the top of kernel stack. Making this it assures that when new active thread will try to enter kernel in future it will be switched by CPU to its own kernel stack.

Note also that not all registers are saved on the stack during thread switch, some registers like FPU/MMX/SSE are saved in specially dedicated area in TCB of outgoing thread. Kernel employs different strategy here for two reasons. First of all not every thread in the system uses them. Pushing their content to and and popping it from the stack for every thread is inefficient. And second one there are special instructions for "fast" saving and loading of their content. And these instructions doesn't use stack.

Note also that in fact kernel part of the thread stack has fixed size and is allocated as part of TCB. (true for Linux and I believe for Windows too)

참고URL : https://stackoverflow.com/questions/12630214/context-switch-internals

반응형