[리눅스 커널 구조 원리] #7. 컨텍스트 정보

문연수·2025년 3월 23일
0

1. preempt_count

thread_info 구조체의 필드 중 preempt_count 는 프로세스의 컨텍스트 정보를 저장한다. 그리고 다음의 상황에서 preempt_count 필드의 값이 바뀌게 된다:

  • 인터럽트 컨텍스트 실행 시작 및 종료 설정
  • Soft IRQ 컨텍스트 실행 시작 및 종료 설정
  • 프로세스 선점 스케줄링 가능 여부

irq_enter() 함수가 호출되면 irq_enter_rcu() 가 호출되고 이어서 __irq_enter_raw() 매크로가 preempt_count_add() 매크로를 호출하여 최종적으로 preempt_count 의 값을 바꾸게 된다.

2. in_interrupt()

현재 컨텍스트가 인터럽트인지 확인하는 것은 in_interrupt() 매크로로 확인 가능하다:

in_interrupt() 는 최종적으로 preempt_count() & (NMI_MASK | HARDIRQ_MASK | SOFTIRQ_MASK) 로 expand 된다. 인터럽트를 끝낼 때에는 irq_exit() 함수를 호출하게 되고 이 과정에서 다시 preempt_count 의 값을 초기화하게 된다:

irq_exit() -> __irq_exit_rcu() -> preempt_count_sub() 순서로 호출되어 preempt_count 의 값을 변경한다. 이후 invoke_softirq() 함수를 호출하여 Soft IRQ 의 처리를 수행하게 된다. 이는 아래에서 다시 자세하게 기술한다.

3. Soft IRQ 컨텍스트

 프로세스가 Soft IRQ 서비스를 처리할 때 preempt_count 필드에 SOFTIRQ_OFFSET 매크로의 값을 저장한다. 이는 handle_softirqs() 함수에서 이뤄지며 다음의 순서로 호출된다: invoke_softirq() -> do_softirq_own_stack() -> call_on_irq_stack() -> ____do_softirq() -> __do_softirq() -> handle_softirqs().

 책에서는 __do_softirq() 함수로 나와있는데 최신 버전의 커널(rpi-6.6.y) 에서는 handle_softirqs() 가 이 기능을 수행한다. 이름만 다르고 내용은 거의 동일하다.

 여기에서 softirq_handle_begin() 함수가 __local_bh_disable_ip() 함수를 호출하고 이 함수는 preempt_count_add(cnt); 매크로를 호출한다. 여기에서 cnt 는 두 번째 인자인 SOFTIRQ_OFFSET 이다. 이를 통해 preempt_count 의 값을 업데이트한다.

Soft IRQ 컨텍스트 여부는 in_softirq() 함수를 호출하여 확인해볼 수 있다. 이는 preempt_count() & SOFTIRQ_MASK 로 확장된다. 그러므로 in_interrupt() 상태에서는 무조건 in_softirq() 상태가 true 이다.

Soft IRQ 의 종료 처리는 handle_softirqs() 함수 마지막에 softirq_handle_end() 함수를 호출하면서 이뤄진다. 이 함수는 다시 __local_bh_enable() 함수를 호출하게 되고 최종적으로 preempt_count 를 업데이트한다.

4. 선점 스케줄링

 책에서 나온 내용이 너무 이해가 안되서 직접 코드를 하나 하나 뜯어가면서 분석했다. 일단 인터럽트가 발생하는 최초의 호출은 ARM 기준에서는 Exception Vector 일 것이고, 그 다음에는 관련된 서브루틴으로 점프하는 것이다. 서브루틴의 명칭은 ELn 에 따라, 그리고 Exception 종류에 따라 달라지게 되는데 크게 하는 일은 다음과 같다:

  1. 커널은 현재 실행 중인 태스크가 시그널을 받았는지 확인하고, 받았다면 거기에 대한 적절한 처리를 진행한다. (프로그램 강제 종료, 시그널 핸들러 호출 등)
  2. 다시 스케쥴링해야 할 필요가 있다면 스케쥴러를 호출한다. (EL1 한정)
  3. 커널 내 연기된 루틴들이 존재하면 이들을 수행한다.

책에서는 2번의 상황에 대해서 설명하고 있는데 kernel/sched.c 코드에서는 다시 스케줄링하는 코드를 찾아볼 수 없다. 이건 아키텍처 specific 한 코드에 정의하고 있다:

arch/arm64/kernel/entry.s 에서는 다음과 같이 Excpetion Vector Table 을 정의하고 있는데 저 kernel_ventry 가 도무지 이해가 안되서 대신 object file 을 dump 해서 확인해보았다.

kernel_ventry 는 매크로로 저 내용을 el1t_64_irq 와 같은 형식으로 확장하는 것 같다:

 뭐가 되었든 el1h_irq_handler 는 반드시 호출되게 되어 있고 이 과정에서 arm64_preempt_schedule_irq() 가 호출되기 때문에 arm64_preempt_schedule_irq() -> preempt_schedule_irq() 을 거쳐 __schedule() 이 호출될 수 있다.

arm64_preempt_schedule_irq() 함수는 preempt_count 의 값이 0 인지 아닌지 확인해서 선점 스케줄 여부를 확인한다. 0 이라면 선점 스케줄이므로 몇 가지 조건을 더 검사한 뒤에 schdule 한다.

5. CPU 필드

thread_info 구조체의 cpu 필드에는 프로세스가 실행 중인 CPU 번호를 저장한다. 그렇다면 이 CPU 번호는 어떻게 획득할 수 있을까? 이는 커널에서 제공하는 smp_processor_id() 매크로를 호출하여 획득할 수 있다:

 책에서는 raw_smp_processor_id()current_thread_info()->cpu 로 구현했다고 했으나 최신 구현에서는 다르게 구현이 되어 있어서 commit log 를 살펴 보았다:

 정확하게 이해하는건 아니지만 핵심적인 내용은 CONFIG_THREAD_INFO_IN_TASK 가 활성화 됐을 때, 헤더간의 복잡성으로 인해 저수준의 아키텍처 코드에 접근할 수 없다는 뜻인 것 같다.

- set_task_cpu()

thread_info 구조체에 실행 중인 CPU 번호를 저장하기 위해 set_task_cpu() 함수를 호출할 수 있다:

 초기화는 다음과 같이 이뤄지게 된며 이를 읽어올 때에는 task_cpu() 라는 이름의 함수를 호출한다.

참고자료

[출처] https://lwn.net/Articles/831678/
[출처] https://thinkty.net/general/2024/04/29/bottom_half.html
[출처] https://bluemoon-1st.tistory.com/77
[출처] https://velog.io/@mythos/커널-스터디iamroot-18기-2주차-내용-정리-2

profile
2000.11.30

0개의 댓글

관련 채용 정보