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
인터럽트 발생 시 스택
- 유저 모드에서 swi나 인터럽트 등이 발생하는 경우는 조금 전 설명한 것과 같이 해당 태스크가 소유한 커널 스택을 이용한다.
- arm 커널은 유저 모드에서 irq 모드를 통해 svc 모드로 전환될 때 irq 모드용 12바이트의 미니 스택을 운용하는데 이의 설명은 생략한다
- 커널 모드에서 인터럽트 등이 발생하는 경우는 다음과 같이 2개로 분리된다.
- 커널 스레드 동작 중 인터럽트가 발생한 경우
- arm: 커널 스레드가 소유한 스택을 그대로 사용한다.
- arm64: 인터럽트 스택을 사용한다.
- 인터럽트 context 수행 중 더 높은 우선 순위 등의 인터럽트가 nest 된 경우
- arm: 현재 사용하고 있는 상태의 커널 스택을 사용한다.
- arm64: 인터럽트 스택을 사용한다.
- 참고: arm64: Introduce IRQ stack | LWN.net
- 커널 스레드 동작 중 인터럽트가 발생한 경우
- 아키텍처에 따라 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가 나중에 변경된 태스크이므로 인터럽트가 발생하면 레지스터 백업은 이 스택을 사용한다.