Environment: Linux Kernel v5.14.14
리눅스 커널 소스를 분석하다 보면 두 개의 비슷한 코드를 볼 수 있다. 둘 다 local_irq 접두사를 가지고 있는데 하나는 disable, 다른 하나는 save를 달고 있다. 비슷하지만 하는 일이 완전히 다른 둘의 차이점을 잊지 않기 위해 글을 남긴다.
※ IRQ : Interrupt ReQuest
local_irq_disable() / local_irq_enable()
functionality
인터럽트 컨텍스트 상황에서 해당 라인의 CPU의 다른 인터럽트를 비활성화/활성화한다. local_irq_disable() ~ local_irq_enable() 사이 컨텍스트에서 이를 보장한다.
source code
include/linux/irqflags.h
#define raw_local_irq_disable() arch_local_irq_disable()
#define raw_local_irq_enable() arch_local_irq_enable()
#define local_irq_enable() do { raw_local_irq_enable(); } while (0)
#define local_irq_disable() do { raw_local_irq_disable(); } while (0)
매크로 함수임을 먼저 확인할 수 있다. local_irq_{disable,enable}() wrapper 매크로 함수가 내부에서 raw_local_irq_{disable,enable}()를 다시 호출한다. 이는 다시 아키텍처 별로 선언된 함수를 호출하게 된다.
#define arch_local_irq_enable arch_local_irq_enable
static inline void arch_local_irq_enable(void)
{
asm volatile(
" cpsie i @ arch_local_irq_enable"
:
:
: "memory", "cc");
}
#define arch_local_irq_disable arch_local_irq_disable
static inline void arch_local_irq_disable(void)
{
asm volatile(
" cpsid i @ arch_local_irq_disable"
:
:
: "memory", "cc");
}
arm 아키텍처를 기준으로 살펴보면 [ PRIMASK register value에게 "cpsid i" -> 인터럽트 비활성화 / "cpsie i" -> 인터럽트 활성화 ] 이렇게 동작하도록 되어있다.
local_irq_save() / local_irq_restore()
functionality
save부터 restore까지 지정한 구간 내에서 동작한다. 해당 코드를 실행하는 도중 인터럽트가 발생해서 임계 영역(critical section)이 오염되어 발생하는 동기화 문제를 방지하기 위해 컨텍스트를 저장하는 동작을 수행한다.
source code
include/linux/irqflags.h
#define raw_local_irq_save(flags) \
do { \
typecheck(unsigned long, flags); \
flags = arch_local_irq_save(); \
} while (0)
#define raw_local_irq_restore(flags) \
do { \
typecheck(unsigned long, flags); \
raw_check_bogus_irq_restore(); \
arch_local_irq_restore(flags); \
} while (0)
#define local_irq_save(flags) \
do { \
raw_local_irq_save(flags); \
if (!raw_irqs_disabled_flags(flags)) \
trace_hardirqs_off(); \
} while (0)
#define local_irq_restore(flags) \
do { \
if (!raw_irqs_disabled_flags(flags)) \
trace_hardirqs_on(); \
raw_local_irq_restore(flags); \
} while (0)
local_irq_{disable,enable}과 비슷한데 local_irq_{disable,enable}는 config 설정에 따라서 trace 함수들을 넣어주는 코드가 따로 있다면, 여기서는 irq가 disabled 상태가 아닐 때만 trace 할 수 있도록 만들어놨다.
Example
워크큐에 대해 공부하다 정리하고 있는 건데 이와 관련해서 조금 정리해본다.
kernel/workqueue.c
/**
* queue_work_on - queue work on specific cpu
* @cpu: CPU number to execute work on
* @wq: workqueue to use
* @work: work to queue
*
* We queue the work to a specific CPU, the caller must ensure it
* can't go away.
*
* Return: %false if @work was already on a queue, %true otherwise.
*/
bool queue_work_on(int cpu, struct workqueue_struct *wq,
struct work_struct *work)
{
bool ret = false;
unsigned long flags;
local_irq_save(flags); // FROM HERE
if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) {
__queue_work(cpu, wq, work);
ret = true;
}
local_irq_restore(flags); // TO HERE
return ret;
}
EXPORT_SYMBOL(queue_work_on);
워크를 워크큐에 큐잉하는 과정에서 부르는 함수 중 하나다. save, restore 사이에 큐잉 핵심 함수인 __queue_work() 함수를 넣어주는 모습을 확인할 수 있다.