Post

[Linux] Softirq

sofrirq는 Linux Kernel에서 사용하는 인터럽트 서비스 루틴(Interrupt Service Routine, ISR)의 일종입니다. 해당 루틴은 인터럽터 처리에서 Bottom half(하반부)에서 사용되게 됩니다.

💡 여기서 Top half(상반부)는 인터럽트가 발생하면 즉시 실행되는 부분이고, 복잡하거나 오랜 시간이 걸리는 작업은 Bottom half(하반부)로 옮기는 역할을 합니다.

해당 포스터는 Linux Kernel v6.7.1 기준으로 작성되었습니다.

Softirq 서비스

softirq는 다음과 같은 요청을 처리하게 됩니다.

우선순위Soft IRQ 서비스설명
0HI_SOFTIRQ가장 우선순위가 높으면 TASKLET_HI로 적용
1TIMER_SOFTIRQ동적 타이머로 사용
2NET_TX_SOFTIRQ네트워크 패킷 송신용으로 사용
3NET_RX_SOFTIRQ네트워크 패킷 수신용으로 사용
4BLOCK_SOFTIRQ블록 디바이스에서 사용
5IRQ_POLL_SOFTIRQIRQ_POLL 연관 동작
6TASKLET_SOFTIRQ일반 태스크릿으로 사용
7SCHED_SOFTIRQ스케줄러에서 주로 사용
8HRTIMER_SOFTIRQ현재 사용하지 않지만 하위 호환성을 위해 남겨둠
9RCU_SOFTIRQRCU 처리용으로 사용

이러한 내용은 링크에서 확인할 수 있으며, 코드 상으로 다음과 같이 구현되어 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
   frequency threaded job scheduling. For almost all the purposes
   tasklets are more than enough. F.e. all serial device BHs et
   al. should be converted to tasklets, not to softirqs.
 */

enum
{
	HI_SOFTIRQ=0,
	TIMER_SOFTIRQ,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	BLOCK_SOFTIRQ,
	IRQ_POLL_SOFTIRQ,
	TASKLET_SOFTIRQ,
	SCHED_SOFTIRQ,
	HRTIMER_SOFTIRQ,
	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

	NR_SOFTIRQS
};

그리고 해당 서비스의 문자열은 다음과 같이 배열로 선언되어 있는 것을 확인할 수 있습니다.

1
2
3
4
const char * const softirq_to_name[NR_SOFTIRQS] = {
        "HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "BLOCK_IOPOLL",
        "TASKLET", "SCHED", "HRTIMER", "RCU"
};

Softirq 동작 방식

Softirq의 동작 방식은 다음의 함수를 순서대로 호출하며 동작하게 됩니다.

  1. open_softirq
  2. raise_softirq
  3. __do_softirq

해당 함수들에 대해서 자세하게 설명드리겠습니다.

open_softirq

해당 함수를 통해 softirq를 등록하게 됩니다.

1
2
3
4
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}

여기서 softirq_action 구조체는 아래와 같습니다.

1
2
3
4
struct softirq_action
{
	void	(*action)(struct softirq_action *);
};

softirq_vec.actionsoftirq_action을 넣어줌으로써으로 softirq를 등록하게 됩니다.

raise_softirq

해당 함수를 통해 등록된 softirq가 활성화되게 됩니다. raise_softirq 함수는 3개의 함수를 호출하고, 인자로 nr 변수만 받고있습니다. 각각의 함수에 대해서 살펴보겠습니다.

1
2
3
4
5
6
7
8
void raise_softirq(unsigned int nr)
{
	unsigned long flags;

	local_irq_save(flags);
	raise_softirq_irqoff(nr);
	local_irq_restore(flags);
}
  • local_irq_save : eflags 레지스터의 IF 플래그의 상태를 저장하고 로컬 프로세서에서 인터럽트를 비활성화합니다.
  • local_irq_restore : local_irq_save 함수와 반대의 기능을 수행합니다.

raise_softirq_irqoff 함수의 구현은 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
 * This function must run with irqs disabled!
 */
inline void raise_softirq_irqoff(unsigned int nr)
{
	__raise_softirq_irqoff(nr);

	/*
	 * If we're in an interrupt or softirq, we're done
	 * (this also catches softirq-disabled code). We will
	 * actually run the softirq once we return from
	 * the irq or softirq.
	 *
	 * Otherwise we wake up ksoftirqd to make sure we
	 * schedule the softirq soon.
	 */
	if (!in_interrupt() && should_wake_ksoftirqd())
		wakeup_softirqd();
}

주석을 살펴보면 irqs가 비활성화 되어 있을때만 실행해야 된다고 설명되어있습니다. 여기서 __raise_softirq_irqoff 함수는 로컬 프로세서의 softirq 비트 마스크(__softirq_pending)에서 주어진 인덱스 nr에 해당하는 비트를 설정하여 softirq를 구분합니다. 이후, wakeup_softirqd 함수를 통해 ksoftirqd를 활성화하게 됩니다.

1
2
3
4
5
6
7
8
static void wakeup_softirqd(void)
{
	/* Interrupts are disabled: no need to stop preemption */
	struct task_struct *tsk = __this_cpu_read(ksoftirqd);

	if (tsk)
		wake_up_process(tsk);
}

활성화된 kosoftirqd는 요청된 서비스가 있는지 확인하고 __do_softirq 함수를 호출하여 인터럽트가 처리되게 됩니다.

__do_softirq

해당 함수의 구현은 링크에 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
asmlinkage __visible void __softirq_entry __do_softirq(void)
{
	unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
	unsigned long old_flags = current->flags;
	int max_restart = MAX_SOFTIRQ_RESTART;
	struct softirq_action *h;
	bool in_hardirq;
	__u32 pending;
	int softirq_bit;

	/*
	 * Mask out PF_MEMALLOC as the current task context is borrowed for the
	 * softirq. A softirq handled, such as network RX, might set PF_MEMALLOC
	 * again if the socket is related to swapping.
	 */
	current->flags &= ~PF_MEMALLOC;

	pending = local_softirq_pending();

	softirq_handle_begin();
	in_hardirq = lockdep_softirq_start();
	account_softirq_enter(current);

restart:
	/* Reset the pending bitmask before enabling irqs */
	set_softirq_pending(0);

	local_irq_enable();

	h = softirq_vec;

	while ((softirq_bit = ffs(pending))) {
		unsigned int vec_nr;
		int prev_count;

		h += softirq_bit - 1;

		vec_nr = h - softirq_vec;
		prev_count = preempt_count();

		kstat_incr_softirqs_this_cpu(vec_nr);

		trace_softirq_entry(vec_nr);
		h->action(h);
		trace_softirq_exit(vec_nr);
		if (unlikely(prev_count != preempt_count())) {
			pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
			       vec_nr, softirq_to_name[vec_nr], h->action,
			       prev_count, preempt_count());
			preempt_count_set(prev_count);
		}
		h++;
		pending >>= softirq_bit;
	}

	if (!IS_ENABLED(CONFIG_PREEMPT_RT) &&
	    __this_cpu_read(ksoftirqd) == current)
		rcu_softirq_qs();

	local_irq_disable();

	pending = local_softirq_pending();
	if (pending) {
		if (time_before(jiffies, end) && !need_resched() &&
		    --max_restart)
			goto restart;

		wakeup_softirqd();
	}

	account_softirq_exit(current);
	lockdep_softirq_end(in_hardirq);
	softirq_handle_end();
	current_restore_flags(old_flags, PF_MEMALLOC);
}

마무리

아직 softirq에 대한 이해가 완벽하지 않고 흐름만 이해했습니다. 추가적인 분석 이후 해당 포스터를 수정하겠습니다. 또한, 인터럽트 서비스 루틴(Interrupt Service Routine, ISR)은 softirq 말고도 2종류가 더 존재합니다. 해당 ISR에 대해서 공부하는대로 추가적으로 작성하겠습니다.

This post is licensed under CC BY 4.0 by the author.