User stack vs Kernel stack

user task가 생성될때마다 스택이 각각 유저 스택과 커널 스택이 하나씩 만들어진다.

user stack

  • 유저 스택의 크기는 스레드 생성 시 지정될 수 있고, default 크기는 역시 아키텍처마다 다른다. 최대 사이즈 확인 방법은 “ulimit -a”를 사용한다.(32bit ARM은 8MB)
$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 14846
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 14846
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

 

kernel stack

  • task 생성 시 마다 kernel stack이 생성된다.
    • 사이즈는 유저 스택보다 훨씬 적으며 커널 버전 및 커널 옵션에 따라 조금씩 다르며 보통 1 ~ 4 페이지를 사용한다.
    • 예)
      • arm: 2 page (8K)
      • 4K 페이지를 사용하는 arm64: 4 page (16K)
      • 16K 페이지를 사용하는 arm64: 1 page (16K)

 

Exception 처리용 stack

  • arm 아키텍처에서는 각 exception에 대해 하나씩 있는 mini 스택을 사용한다.
    • exception이 발생되어 해당 exception 핸들러에 진입한 경우에는 각 exception에서 사용하는 3 word의 mini 스택을 이용하고 곧바로 svc 모드로 바꾼 후에는 svc용 스택(커널 스택)을 이용해 처리를 계속한다.

 

context switch시 스택 사용

  • user mode에서 task(thread)가 수행되고 있을 때 syscall을 하여 kernel mode로 jump하고 context switching을 하게되는데 이 때 부터는 해당 유저 task(thread)가 소유한 kernel stack을 사용한다.
  • 다시 kernel mode에서 user mode로 context switching을 하여 돌아가게 되면 task(thread)용 user stack을 사용한다.
  • 참고로 arm에서 sp 레지스터는 각 모드에서 각각 운영되며 arm64에서는 exception level별로 각각 운영된다.
    • arm: sp_usr, sp_svc, sp_irq, …
    • arm64: el0_sp, el1_sp, el2_sp

stack2

 

인터럽트 발생 시 스택

  • 유저 모드에서 swi나 인터럽트 등이 발생하는 경우는 조금 전 설명한 것과 같이 해당 태스크가 소유한 커널 스택을 이용한다.
    • arm 커널은 유저 모드에서 irq 모드를 통해 svc 모드로 전환될 때 irq 모드용 12바이트의 미니 스택을 운용하는데 이의 설명은 생략한다
  • 커널 모드에서 인터럽트 등이 발생하는 경우는 다음과 같이 2개로 분리된다.
    • 커널 스레드 동작 중 인터럽트가 발생한 경우
      • arm: 커널 스레드가 소유한 스택을 그대로 사용한다.
      • arm64: 인터럽트 스택을 사용한다.
    • 인터럽트 context 수행 중 더 높은 우선 순위 등의 인터럽트가 nest 된 경우
      • arm: 현재 사용하고 있는 상태의 커널 스택을 사용한다.
      • arm64: 인터럽트 스택을 사용한다.
  • 아키텍처에 따라 hardirq, softirq용 스택을 분리하여 운용하기도 한다.
    • arm의 경우 hardirq는 위에서 설명한 조건에 따라 적절한 커널 스택을 사용하고 softirq는 ksoftirqd가 소유한 커널 스택을 사용한다.
    • arm64의 경우 hardirq는 인터럽트용 스택을 사용하고 softirq는 커널옵션에 따라 ksoftirqd가 소유한 커널 스택을 사용하게 할 수 있다.

 

예) arm 시스템, 어떠한 태스크도 스케줄되지 않은 cpu에서 인터럽트 발생하는 경우?

  • 이미 커널 스레드인 idle 태스크가  스케줄링되어 동작하고 있다. arm의 경우 wfi등이 수행되어 절전 모드로 내부 클럭이 정지된 상태이다.
    • 인터럽트가 발생하는 경우 wfi에 의해 멈춘 cpu가 계속 동작하고 idle 태스크가 소유한 커널 스택을 사용하여 인터럽트 context가 진행된다.
  • idle 태스크는 커널 부트업 시  각 cpu별로 초기화 수행 후 idle 태스크로 이름이 바뀌어 idle 스케줄러에 등록되어 어떠한 태스크도 스케줄되지 않을 때에만 동작하는 특수한 태스크이다.
    • idle 태스크의 pid는 모든 cpu에서 공통으로 0이다.
  • idle 태스크는 부트업시 사용하던 init_task가 나중에 변경된 태스크이므로 인터럽트가 발생하면 레지스터 백업은 이 스택을 사용한다.

참고

댓글 남기기