Linux Kernel(4.14) Hacks

rousalome.egloos.com

포토로그 Kernel Crash




[라즈베리파이] 워크큐(Workqueue)의 특징 [라즈베리파이] 워크큐

다른 인터럽트 후반부 처리 기법과 비교하면서 알아봤으니 이제 워크큐의 특징에 대해서 알아봅니다.

첫째, 드라이버 레벨에서 워크큐는 쓰기 쉽습니다. 워크큐는 struct work_struct 구조체 변수만 설정하고 워크를 실행할 코드에 queue_work() 나 schedule_work() 함수만 추가하면 됩니다. 간단한 코드로 워크큐를 쓸 수 있다는 것은 워크큐 관련 커널 함수들이 예외 처리나 로컬리티 등등 많은 처리를 한다는 의미입니다.

둘째, 워크큐 실행 단위는 워크이며 워크는 워커 쓰레드에서 실행합니다. 워커 쓰레드는 프로세스 레벨로 실행되는데 워커 쓰레드 실행 여부는 스케쥴러가 결정합니다. 따라서 밀리 초 이내로 워커 쓰레드 실행을 제어하기는 어렵습니다. 실행 시각에 민감한 후반부 처리 용도로 워크큐는 적합하지 않습니다. 

셋째, 워크큐를 쓰면 드라이버를 조금 더 유연하게 설계할 수 있습니다. 후반부 처리 코드를 워크 핸들러에 위치시킨 다음 schedule_work()나 queue_work() 함수로 워크를 실행하면  됩니다. 워크큐에서는 딜레이 워크(struct delayed_work)를 제공합니다. jiffies(HZ) 단위로 워크를 특정 시각 이후 지연 후 실행할 수 있습니다. 

동적 타이머가 1초에 실행하는 횟수를 HZ라고 하며 이 값은 jiffies란 변수가 저장하고 있습니다. 라즈베리파이는 1초에 jiffies 변수가 100만큼 증감하므로 HZ는 100입니다.

여러 디바이스 드라이버 코드를 눈여겨보면 워크와 딜레이 워크를 후반부 처리 용도로 같이 쓰는 경우를 볼 수 있습니다. 딜레이 워크를 활용하면 워크큐로 후반부 처리를 더 유연하게 수행할 수 있습니다. 

동적 타이머가 1초에 실행하는 횟수를 HZ라고 하며 이 값은 jiffies란 변수가 저장하고 있습니다. 라즈베리파이는 1초에 jiffies 변수가 100만큼 증감하므로 HZ는 100입니다.

워크와 딜레이 워크로 후반부 처리를 어떻게 하는지 예를 들어볼까요? 인터럽트 후반부를 2단계로 나눠서 드라이버를 설계할 수 있습니다. 워크를 인터럽트 후반부 처리 용도로 쓰고 딜레이 워크는 워크가 제대로 실행됐는지 모니터링하는 용도로 쓸 수 있습니다. 워크와 딜레이 워크를 활용해서 2단계로 인터럽트 후반부 처리를 할 수 있는 실습할 수 있는 코드는 다음에 확인할 수 있습니다.

정리하면 시간에 민감하지 않은 커널 후반부 처리 기법으로 워크큐를 쓸 수 있습니다.



[라즈베리파이] 비트 처리 __test_and_set_bit() __test_and_clear_bit() 함수 동작 원리 Linux Kernel - Core Analysis

리눅스 커널과 드라이버에서 __test_and_set_bit()와 __test_and_clear_bit() 함수를 많이 씁니다.


두 함수 중 test_and_set_bit()를 써서 비트를 처리하는 코드를 보겠습니다.
다음은 워크를 워크큐에 큐잉하는 queue_work_on() 함수입니다.
[kernel/workqueue.c]
1 bool queue_work_on(int cpu, struct workqueue_struct *wq,
2    struct work_struct *work)
3 {
4 bool ret = false;
5 unsigned long flags;
6
7 local_irq_save(flags);
8
9 if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) {
10 __queue_work(cpu, wq, work);
11 ret = true;
12 }
13
14 local_irq_restore(flags);
15 return ret;
16}

9번째 줄 코드를 보겠습니다. 
9 if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) {
10 __queue_work(cpu, wq, work);
11 ret = true;
12 }

work_data_bits(work) 매크로 함수는 struct work_struct 구조체 주소에서 struct work_struct.data 멤버를 읽습니다. 이 값이 WORK_STRUCT_PENDING_BIT(1)이면 9~12줄 코드를 실행하지 않고 if 문을 빠져나옵니다.

test_and_set_bit() 함수는 리눅스 커널 자료구조 함수 중 하나입니다.
test_and_set_bit(A, B); 와 같이 호출하면 A와 B란 변수 비트를 AND 연산한 다음 결과가 1이면 1을 리턴하고 반대로 0이면 0을 리턴합니다. 연산 결과에 상관없이 B이란 변수에 A비트를 설정합니다.

이해를 돕기 위해 9~12줄 코드를 다른 코드로 쉽게 표현하면 다음과 같습니다.
1 if (work->data == WORK_STRUCT_PENDING_BIT) {
3 } else
4 work->data =| WORK_STRUCT_PENDING_BIT;
5 __queue_work(cpu, wq, work);
6 ret = true;
7 }

work->data가 WORK_STRUCT_PENDING_BIT이면 if 문을 만족하는데 2번 줄 코드와 같이 동작도 안 하고 if 문을 빠져나옵니다. 2번째 줄 코드와 같이 실행할 코드가 없기 때문입니다. 대신 work->data가 WORK_STRUCT_PENDING_BIT 가 아니면 else문을 실행합니다. 

if 문 조건을 만족하면 아무 동작을 안 하는 코드는 모양이 이상하니 if 문에 ! 조건으로 바꿔  코드 순서를 바꿔 보면 다음과 같습니다.
if ( !(work->data == WORK_STRUCT_PENDING_BIT)) {
work->data =| WORK_STRUCT_PENDING_BIT;
__queue_work(cpu, wq, work);
ret = true;
}

work->data 멤버가 WORK_STRUCT_PENDING_BIT 매크로가 아니면 work->data에 WORK_STRUCT_PENDING_BIT를 저장하고 __queue_work() 함수를 호출하는 겁니다.

test_and_set_bit() 함수를 다른 코드로 바꿔서 설명을 드렸습니다. test_and_set_bit() 함수는 위에 바꾼 코드와 같이 struct work_struct.data 멤버가 WORK_STRUCT_PENDING_BIT 매크로가 아니면 struct work_struct.data 멤버를 WORK_STRUCT_PENDING_BIT 매크로로 설정하고 0을 리턴합니다.

반대로 struct work_struct.data 멤버가 WORK_STRUCT_PENDING_BIT 매크로이면 struct work_struct.data 멤버를 WORK_STRUCT_PENDING_BIT 매크로로 설정한 후 1을 리턴합니다.

여기까지 test_and_set_bit() 함수를 어느 커널 코드에서 쓰는지 알아봤으니 이번에 이 함수가 어떻게 동작히는지 확인합시다.

해당 코드 구현부는 다음 코드에서 확인할 수 있습니다.
https://elixir.bootlin.com/linux/v4.14.43/source/include/asm-generic/bitops/non-atomic.h#L58
static inline int __test_and_set_bit(int nr, volatile unsigned long *addr)
{
unsigned long mask = BIT_MASK(nr);
unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
unsigned long old = *p;

*p = old | mask;
return (old & mask) != 0;
}

static inline int __test_and_clear_bit(int nr, volatile unsigned long *addr)
{
unsigned long mask = BIT_MASK(nr);
unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
unsigned long old = *p;

*p = old & ~mask;
return (old & mask) != 0;
}

__test_and_set_bit() 함수와 __test_and_clear_bit() 함수  구현부를 그대로 가져와 리눅스 시스템 프로그래밍으로 동작을 확인합니다.  먼저 소스 코드를 소개합니다.
240 #include <stdio.h>
241 enum {
242         CHAINIV_STATE_INUSE = 1
243         CHAINIV_STATE_DISUSE = 2,
244         CHAINIV_STATE_ONLINE = 3,
245         CHAINIV_STATE_OFFLINE = 4
246 };
247
248
249 int __test_and_clear_bit(int nr, volatile unsigned long *addr)
250 {
251          unsigned long mask = (1UL << ((nr) % 32));
252          unsigned long *p = ((unsigned long *)addr) + ((nr) / 32);
253          unsigned long old = *p;
254
255          *p = old & ~mask;
256          return (old & mask) != 0;
257 }
258
259 int __test_and_set_bit(int nr, volatile unsigned long *addr)
260 {
261         unsigned long mask = (1UL << ((nr) % 32));
262         unsigned long *p = ((unsigned long *)addr) + ((nr) / 32);
263         unsigned long old = *p;
264
265         *p = old | mask;
266         return (old & mask) != 0;
267 }
268
269 int main()
270 {
271         unsigned long state = 0;
272
273         printf(" state: %ld, line[%d] \n", state, __LINE__);
274
275         if(__test_and_clear_bit(CHAINIV_STATE_INUSE, &state)) {
276                 printf(" state: %ld, line[%d] \n", state, __LINE__);
277         } else {
278                 printf(" state: %ld, line[%d] \n", state, __LINE__);
279         }
280
281         if(__test_and_set_bit(CHAINIV_STATE_INUSE, &state)) {
282                 printf(" state: %ld, line[%d] \n", state, __LINE__);
283         } else {
284                 printf(" state: %ld, line[%d] \n", state, __LINE__);
285         }
286
287         if(__test_and_set_bit(CHAINIV_STATE_INUSE, &state)) {
288                 printf(" state: %ld, line[%d] \n", state, __LINE__);
289         } else {
290                 printf(" state: %ld, line[%d] \n", state, __LINE__);
291         }
292
293         if(__test_and_clear_bit(CHAINIV_STATE_INUSE, &state)) {
294                 printf(" state: %ld, line[%d] \n", state, __LINE__);
295         } else {
296                 printf(" state: %ld, line[%d] \n", state, __LINE__);
297         }
298
299         return 1;
300 }

리눅스 시스템 프로그램으로 작성하면 되는데 위 코드를 작성 후
main.c 파일로 저장한 후 다음 명령어로 컴파일을 합시다.
gcc -o test_main main.c

컴파일 후 test_main 어플리케이션을 실행하면 다음과 같은 결과를 볼 수 있습니다.
state: 0, line[273]
state: 0, line[278]
state: 1, line[284]
state: 1, line[288]
state: 0, line[294]

위 로그가 어떻게 출력됐는지 해당 코드를 자세히 살펴봅시다.

첫 로그 실행 코드를 봅시다.
271         unsigned long state = 0;
272
273         printf(" state: %ld, line[%d] \n", state, __LINE__);

state이란 변수가 0이니 0을 출력합니다.

이번에는 두 번째 로그입니다.
275         if(__test_and_clear_bit(CHAINIV_STATE_INUSE, &state)) {
276                 printf(" state: %ld, line[%d] \n", state, __LINE__);
277         } else {
278                 printf(" state: %ld, line[%d] \n", state, __LINE__);
279         }

CHAINIV_STATE_INUSE 값이 1이므로 state가 0이니 __test_and_clear_bit() 함수는 0을 반환합니다.
동시에 state 값인 0 에서 1을 Clear합니다. 결과는 0입니다.

따라서 다음과 같이 278줄 코드를 실행하는 로그를 출력합니다.
state: 0, line[278]

다음 세 번째 로그 실행 코드를 분석합시다.
281         if(__test_and_set_bit(CHAINIV_STATE_INUSE, &state)) {
282                 printf(" state: %ld, line[%d] \n", state, __LINE__);
283         } else {
284                 printf(" state: %ld, line[%d] \n", state, __LINE__);
285         }

state 값이 0이니 __test_and_set_bit() 함수는 0을 반환합니다.
따라서 else문인 284 코드를 실행합니다. 동시에 state를 1로 설정합니다.

해당 로그는 다음과 같습니다.
state: 1, line[284]

이번에 마지막 로그를 분석합니다.
293         if(__test_and_clear_bit(CHAINIV_STATE_INUSE, &state)) {
294                 printf(" state: %ld, line[%d] \n", state, __LINE__);
295         } else {
296                 printf(" state: %ld, line[%d] \n", state, __LINE__);
297         }

state가 1이니 __test_and_clear_bit() 함수는 1을 반환하며 1을 Clear합니다.
따라서 294줄 코드를 실행합니다.

결과 로그는 다음과 같습니다.
state: 0, line[294]

리눅스 커널에서 보이는 코드를 보면 너무 어렵게 생각말고 이렇게 코드를 분리해서 테스트 해 봅시다. 
바로 어떻게 동작하는지 확인할 수 있습니다.




[라즈베리파이] 워크큐(Workqueue) 소개 [라즈베리파이] 워크큐

워크큐는 인터럽트 후반부 처리 기법으로 많이 알려져 있는데 커널 전반에서 후반부를 처리하는 기법입니다. 인터럽트 후반부는 물론이고 프로세스 컨택스트 후반부 처리 용도로도 많이 씁니다. 즉, 어떤 코드에서도 워크큐를 써서 후반부 처리를 할 수 있다는 의미입니다.

그동안 배웠던 IRQ Thread와 Soft IRQ 와 같은 인터럽트 후반부 처리 기법과 워크큐를 비교하면서 워크큐의 특징을 알아보겠습니다.

IRQ Thread는 인터럽트 후반부 처리 전용 쓰레드이며 threaded IRQ 방식으로 인터럽트 후반부 처리를 할 수 있습니다. 마찬가지로 워크큐도 인터럽트 후반부 용도로 쓸 수 있습니다. 그런데 인터럽트 발생 빈도가 높거나 더 안정적인 코드 유지 보수를 위해서 IRQ Thread 기법 적용을 선호합니다. 그 이유는 IRQ Thread는 인터럽트 처리를 위한 전용 쓰레드이므로 이 기법은 소프트웨어적으로 복잡도가 워크큐에 비해 낮기 때문입니다. 

워크큐는 인터럽트 후반부만 아니라 커널 전체 후반부 처리를 하므로 예외 처리 코드가 많습니다. 예를 들어 워크큐에서는 하나의 워크가 2개 이상의 워커 쓰레드에서 실행하지 못하는 코드부터 워커를 관리하는 워크 풀은 percpu 타입 변수로 Migration과 같은 추가 동작을 합니다. 또한 시스템 부하에 따라 워커 쓰레드 개수가 많이 늘어나면 워크큐는 예외 처리를 수행합니다. IRQ Thread에 비해 처리 속도가 느려질 수도 있습니다.

IRQ Thread 기법은 워크큐에 비해 좋은 인터럽트 후반부 처리 방법일까요? 
IRQ Thread 기법이 항상 좋은 것은 아닙니다. 어떤 시스템에서 인터럽트 개수가 아주 많다고 가정합시다. 모든 인터럽트 후반부를 IRQ Thread 기법을 적용하면 각각 인터럽트를 처리하기 위한 전용 IRQ Thread를 생성합니다. 이때 인터럽트마다 IRQ Thread를 생성하면 메모리와 같은 시스템 리소스를 많이 차지하게 됩니다. 그래서 인터럽트 개수와 발생 빈도 그리고 인터럽트 후반부 처리 시나리오에 따라 IRQ Thread와 워크큐 기법을 선택하면 됩니다. 개발자분들이 시스템 설계에 따라 유연하게 인터럽트 후반부 기법을 결정해야 하는 겁니다.

이번에는 Soft IRQ 기법과 비교해 보겠습니다. Soft IRQ는 인터럽트 발생 빈도가 아주 높은 인터럽트 후반부를 처리할 때 씁니다. 하지만 워크큐는 커널 쓰레드 레벨에서 워크를 처리하기 때문에 Soft IRQ에 비해 처리 속도가 느립니다. 그 이유는 Soft IRQ는 인터럽트가 발생할때 마다 Soft IRQ 서비스 실행 여부를 점검하기 때문입니다. Soft IRQ 서비스 실행 빈도는 인터럽트 발생 횟수라 할 수 있습니다. 이에 비해 워크큐는 스케쥴링 정책에 따라 워커 쓰레드가 실행한 순서가 되야 실행됩니다. 

인터럽트 후반부 처리를 정해진 시간 내 빨리 처리해야 하는 경우 워크큐 방식은 바람직하지 않습니다. 다시 반복하지만, 어떤 후반부 기법을 쓸지는 시스템 설계와 인터럽트 후반부 처리 시나리오에 따라 결정해야 합니다. 



[라즈베리파이] 커널 타이머 -동적 타이머는 누가 언제 호출하나? (1) [라즈베리파이] 타이머관리

이전에 동적 타이머를 설정하는 코드 흐름까지 알아봤습니다. 이 과정을 요약하면 다음과 같습니다.  
1. 동적 타이머를 표현하는 자료 구조인 struct timer_list 구조체에 기본 정보를 채운 다음 add_time() 혹은 mod_time() 함수를 호출합니다. 동적 타이머 기본 정보는 타이머 만료 시각(HZ단위)과 동적 타이머 핸들러 함수와 매개 변수입니다.

2. 동적 타이머를 초기화했을 때 실행했던 CPU 번호 기준으로 per-cpu 오프셋을 적용해서 timer_base 주소를 읽습니다. 이 주소에는 struct timer_base 구조체 멤버가 있는데 타이머 벡터 해시 인덱스에 동적 타이머를 등록했습니다.

이어서 이번에는 동적 타이머를 누가 언제 처리하는지 살펴봅니다. 동적 타이머는 커널 시스템 타이머가 호출하며 커널 타이머는 Soft IRQ 서비스 중 하나로 처리합니다. 이전 절에서는 동적 타이머를 실행하는 코드만 분석했는데 이번에는 시스템 관점으로 만료되는 동적 타이머를 어떻게 처리하는지 점검합니다. 이번에는 조금 더 큰 그림을 그리면서 코드를 볼 필요가 있습니다.

리눅스 커널은 Soft IRQ 서비스 중 TIMER_SOFTIRQ이란 아이디로 타이머 처리를 합니다.
이제 코드를 보겠습니다.
void __init init_timers(void)
{
init_timer_cpus();
open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
}

위 코드는 TIMER_SOFTIRQ이란 아이디로 Soft IRQ 서비스 핸들러는 run_timer_softirq() 함수로 등록합니다. 이후 TIMER_SOFTIRQ이란 서비스 아이디로 Soft IRQ 서비스 요청을 하면 해당 핸들러인 run_timer_softirq() 함수를 호출합니다.

이 과정은 다음 그림으로 설명할 수 있습니다.


이제 커널 시스템 타이머가 어떤 순서로 동적 타이머를 처리하는지 단계별로 확인하겠습니다.

먼저 1단계입니다. 
타이머 인터럽트가 발생하면 화살표 흐름으로 인터럽트 벡터부터 run_local_timer() 함수까지 실행합니다. 이 run_local_timer() 함수에서 TIMER_SOFTIRQ이란 아이디로 Soft IRQ 서비스 요청을 합니다. 콜스택을 유심히 보면 __irq_svc 심볼이 보이니 이 함수는 인터럽트 컨택스트에서 실행됩니다.

다음 2단계를 알아봅시다. 인터럽트 처리가 끝나면 irq_exit() 함수를 호출해서 Soft IRQ 서비스 루틴을 실행합니다. 인터럽트 컨택스트가 끝나고 Soft IRQ 컨택스트가 시작하는 시점입니다.

이제 3단계입니다. irq_exit() 함수에서 Soft IRQ 서비스 요청 여부를 확인합니다. 1 단계에서 이미 TIMER_SOFTIRQ 아이디로 Soft IRQ 서비스 요청을 했으니 invoke_softirq() 함수를 호출합니다. 이후 __do_softirq() 함수에서 TIMER_SOFTIRQ 아이디 Soft IRQ 서비스 핸들러인 run_timer_softirq() 함수를 실행합니다.

마지막 4단계를 알아봅시다. __run_timers() 함수를 호출해서 per-cpu 타입 변수인 timers_base 전역 변수에 접근해 만료될 동적 타이머들을 로딩합니다. 이때 타이머 해시 테이블에 등록된 struct timer_list 주소에 접근해서 동적 타이머들을 해제하고 타이머 핸들러 함수를 호출합니다.

커널 타이머는 Soft IRQ 서비스 중 하나로 처리하므로 Soft IRQ 처리 흐름에 대해 잘 알아야 합니다.
다음 시간에 이제 위 그림에서 설명해 드린 단계별로 코드를 살펴보겠습니다.



[Kernel][Workqueue] flush_work(), 배리어 워크(barrier_work, wq_barrier) [라즈베리파이] 워크큐

barrier work에 대해서 알아보겠습니다. 
flush_work 함수는 두 가지 상황에서 쓰입니다. 두 가지 경우에 barrier work가 어떻게 쓰이는지 알아볼게요.

1. 현재 다른 워커 쓰레드에서 동일한 워크가 실행 중에 동일한 work을 flush한 경우
           <...>-386   [001] ...1   143.380287: workqueue_execute_start: work struct e880e910: function sdhci_pm_qos_cpu_unvote_work
//...           
     mmc-cmdqd/0-339   [000] ...1   143.381065: flush_work <-__cancel_work_timer
     mmc-cmdqd/0-339   [000] ...1   143.381084: <stack trace>
 => flush_work
 => __cancel_work_timer
 => cancel_delayed_work_sync
 => sdhci_pm_qos_cpu_vote
 => cmdq_request
 => mmc_cmdq_start_req
 => mmc_blk_cmdq_issue_rq
 => mmc_cmdq_thread
 => kthread
 => ret_from_fork
     mmc-cmdqd/0-339   [000] d..1   143.381089: flush_work: [+++][Bret] start  [F:start_flush_work, L:2671] caller(__cancel_work_timer+0x130/0x1bc)
     mmc-cmdqd/0-339   [000] d..1   143.381092: flush_work: [+][Bret] work_struct->func: sdhci_pm_qos_cpu_unvote_work+0x0/0x34   [F:insert_wq_barrier, L:2344] caller(__cancel_work_timer+0x130/0x1bc)
     mmc-cmdqd/0-339   [000] d..3   143.381127: sched_switch: prev_comm=mmc-cmdqd/0 prev_pid=339 prev_prio=120 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120
//...
           <...>-386   [001] ...1   143.382352: workqueue_execute_end: work struct e880e910
           <...>-386   [001] ...1   143.382356: workqueue_execute_start: work struct e8881cf8: function wq_barrier_func
           <...>-386   [001] ...1   143.382360: wq_barrier_func <-process_one_work
          <idle>-0     [000] d..3   143.382361: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=DispSync next_pid=596 next_prio=97
           <...>-386   [001] ...1   143.382380: <stack trace>
 => wq_barrier_func
 => process_one_work
 => process_scheduled_works
 => worker_thread
 => kthread
 => ret_from_fork
          <...>-386   [001] d..4   143.382400: sched_wakeup: comm=mmc-cmdqd/0 pid=339prio=120 success=1 target_cpu=000
          <...>-386   [001] ...1   143.382403: workqueue_execute_end: work struct e8881cf8
          <...>-596   [000] d..3   143.382468: sched_switch: prev_comm=DispSync prev_pid=596 prev_prio=97 prev_state=S ==> next_comm=mmc-cmdqd/0 next_pid=339 next_prio=120
     mmc-cmdqd/0-339   [000] ....   143.382476: flush_work.part.6: [-][Bret] barrier work wait complete  [F:flush_work, L:2716] caller(flush_work+0x1b8/0x1fc)

2. 다른 커널 쓰레드에서 워크를 실행하고 싶은 경우
       core_ctl/0-359   [006] ...1   148.104238: <stack trace>
  => flush_work
 => workqueue_cpu_down_callback
 => notifier_call_chain
 => __raw_notifier_call_chain
 => __cpu_notify
 => _cpu_down
 => cpu_down
 => cpu_subsys_offline
 => device_offline
 => core_ctl_offline_core
 => do_hotplug
 => try_hotplug
 => kthread
 => ret_from_fork

flush_work 함수 코드 리뷰
여기서 배리어 워크가 등장합니다. start_flush_work 함수 호출이 끝난 후 wait_for_completion(&barr.done); complete(&barr.done); 호출될 때 까지 기다립니다.
2843bool flush_work(struct work_struct *work)
2844{
2845 struct wq_barrier barr;
2846
2847 lock_map_acquire(&work->lockdep_map);
2848 lock_map_release(&work->lockdep_map);
2849
2850 if (start_flush_work(work, &barr)) {
2851  wait_for_completion(&barr.done);
2852  destroy_work_on_stack(&barr.work);
2853  return true;
2854 } else {
2855  return false;
2856 }
2857}
2858EXPORT_SYMBOL_GPL(flush_work);

start_flush_work 코드 리뷰
2781static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr)
2782{
2783 struct worker *worker = NULL;
2784 struct worker_pool *pool;
2785 struct pool_workqueue *pwq;
2786
2787 might_sleep();
2788
2789 local_irq_disable();
2790 pool = get_work_pool(work);
2791 if (!pool) {
2792  local_irq_enable();
2793  return false;
2794 }
2795
2796 spin_lock(&pool->lock);
2797 /* see the comment in try_to_grab_pending() with the same code */
2798 pwq = get_work_pwq(work);
2799 if (pwq) {
2800  if (unlikely(pwq->pool != pool))
2801   goto already_gone;
2802 } else {
2803  worker = find_worker_executing_work(pool, work);  //<<<--[1]
2804  if (!worker)
2805   goto already_gone;
2806  pwq = worker->current_pwq;
2807 }
2808
2809 check_flush_dependency(pwq->wq, work);
2810
2811 insert_wq_barrier(pwq, barr, work, worker);  //<<<--[2]
2812 spin_unlock_irq(&pool->lock);
2813
2814 /*
2815  * If @max_active is 1 or rescuer is in use, flushing another work
2816  * item on the same workqueue may lead to deadlock.  Make sure the
2817  * flusher is not running on the same workqueue by verifying write
2818  * access.
2819  */
2820 if (pwq->wq->saved_max_active == 1 || pwq->wq->rescuer)
2821  lock_map_acquire(&pwq->wq->lockdep_map);
2822 else
2823  lock_map_acquire_read(&pwq->wq->lockdep_map);
2824 lock_map_release(&pwq->wq->lockdep_map);
2825
2826 return true;
2827already_gone:
2828 spin_unlock_irq(&pool->lock);
2829 return false;
2830}

[1]: 워커 풀과 work로 현재 실행 중인 worker를 가져옵니다.
[2]: 베리어 워크를 추가합니다.

find_worker_executing_work 코드 리뷰
1000static struct worker *find_worker_executing_work(struct worker_pool *pool,
1001       struct work_struct *work)
1002{
1003 struct worker *worker;
1004
1005 hash_for_each_possible(pool->busy_hash, worker, hentry,  //<<--[1]
1006          (unsigned long)work)
1007  if (worker->current_work == work &&
1008      worker->current_func == work->func)
1009   return worker;
1010
1011 return NULL;
1012}

[1]: 워커는 struct worker_pool.busy_hash로 등록됩니다. 이 array을 순회하면서 워커를 뒤집니다.
그런데 work의 func과 워커가 같을 때 해당 워커를 리턴합니다.

다음 ftrace log를 보면 해당 디버깅 정보를 확인할 수 있습니다.
     kworker/1:3-387   [001] ...1   166.380674: flush_work <-__cancel_work_timer
     kworker/1:3-387   [001] ...1   166.380725: <stack trace>
 => flush_work
 => __cancel_work_timer
 => cancel_delayed_work_sync
 => sdhci_ppp_bus_cancel_work_and_set_vote
 => sdhci_ppp_bus_voting
 => sdhci_ppp_set_clock
 => sdhci_do_set_ios
 => sdhci_set_ios
 => mmc_set_ios
 => mmc_gate_clock
 => mmc_host_clk_gate_delayed
 => mmc_host_clk_gate_work
 => process_one_work
 => worker_thread
 => kthread
 => ret_from_fork
mmc-cmdqd/0-340   [006] d..1   166.380882: flush_work: [+][Bret] work_struct->func: mmc_host_clk_gate_work+0x0/0x20   [F:insert_wq_barrier, L:2344] caller(__cancel_work_timer+0x130/0x1bc)

mmc_host_clk_gate_work 워크가 kworker/1:3-387 워커 쓰레드에 의해 실행 중인데, 
mmc_host_clk_gate_work 워크를 flush_work(&mmc_host_clk_gate_work)로 호출하는 경우입니다.
static void insert_wq_barrier(struct pool_workqueue *pwq,
2350         struct wq_barrier *barr,
2351         struct work_struct *target, struct worker *worker)
2352{
2353 struct list_head *head;
2354 unsigned int linked = 0;
2355
2356 /*
2357  * debugobject calls are safe here even with pool->lock locked
2358  * as we know for sure that this will not trigger any of the
2359  * checks and call back into the fixup functions where we
2360  * might deadlock.
2361  */
2362 INIT_WORK_ONSTACK(&barr->work, wq_barrier_func);  //<<--[1]
2363 __set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(&barr->work));
2364 init_completion(&barr->done);
2365
2366 /*
2367  * If @target is currently being executed, schedule the
2368  * barrier to the worker; otherwise, put it after @target.
2369  */
2370 if (worker)
2371  head = worker->scheduled.next;  //<<--[2]
2372 else {
2373  unsigned long *bits = work_data_bits(target);
2374
2375  head = target->entry.next;  //<<--[3]
2376  /* there can already be other linked works, inherit and set */
2377  linked = *bits & WORK_STRUCT_LINKED;
2378  __set_bit(WORK_STRUCT_LINKED_BIT, bits);
2379 }
2380
2381 debug_work_activate(&barr->work);
2382 insert_work(pwq, &barr->work, head,  //<<--[4]
2383      work_color_to_flags(WORK_NO_COLOR) | linked);
2384}

[1]: 콜백 함수를 wq_barrier_func로 배리어 워크를 초기화합니다.
[2]: 만약 현재 구동 중인 워커 쓰레드에서 돌고 있는 워크을 flush_work로 호출한 경우 실행됩니다.
워커의 worker->scheduled.next; 주소를 head로 가져옵니다.
[3]: 현재 워커 쓰레드가 아닌 다른 커널 쓰레드에서 flush_work가 호출됐을 때 실행됩니다.
현재 워크(struct work_struct)의 entry 주소를 head로 가져옵니다.
[4]: insert_work를 호출해서 배리어 워크와 flush_work(&work_list)을 추가합니다.

[2],[3]번 코드 조건에 따라 다음과 같이 링크됩니다.
&barr->work->entry = worker->scheduled.next
&barr->work->entry = target->entry.next

이번에는 insert_work() 함수 코드를 분석하겠습니다.
1274static void insert_work(struct pool_workqueue *pwq, struct work_struct *work,
1275   struct list_head *head, unsigned int extra_flags)
1276{
1277 struct worker_pool *pool = pwq->pool;
1278
1279 /* we own @work, set data and link */
1280 set_work_pwq(work, pwq, extra_flags);  
1281 list_add_tail(&work->entry, head);   //<<--[1]
1282 get_pwq(pwq);
1283
1284 /*
1285  * Ensure either wq_worker_sleeping() sees the above
1286  * list_add_tail() or we see zero nr_running to avoid workers lying
1287  * around lazily while there are works to be processed.
1288  */
1289 smp_mb();
1290
1291 if (__need_more_worker(pool))
1292  wake_up_worker(pool); //<<--[2]
1293}

[1]: head를 &work->entry tail로 추가합니다.
struct worker_pool->worklist에도 함께 추가됩니다. 이 이유는 조금 더 상세히 다음에 상세히 알아 보겠습니다.
[2]: 워커를 깨웁니다.

start_flush_work 하부 루틴 코드 리뷰를 끝내고 flush_work 함수로 돌아왔습니다.
2843bool flush_work(struct work_struct *work)
2844{
2845 struct wq_barrier barr;
2846
2847 lock_map_acquire(&work->lockdep_map);
2848 lock_map_release(&work->lockdep_map);
2849
2850 if (start_flush_work(work, &barr)) {
2851  wait_for_completion(&barr.done);  //<<--[1]
2852  destroy_work_on_stack(&barr.work);
2853  return true;
2854 } else {
2855  return false;
2856 }
2857}
2858EXPORT_SYMBOL_GPL(flush_work);

[1]: flush_work의 가장 중요한 코드입니다. barrier work(배리어 워크)가 처리될 때 까지 기다립니다.
wait_for_completion는 complete 코드가 실행되면 빠져 나옵니다.

다음 워커 쓰레드가 깨어나서 배리어 워크가 실행되면 wq_barrier_func 함수가 호출됩니다.
complete(&barr->done); 코드를 눈여겨봅시다.
2319static void wq_barrier_func(struct work_struct *work)
2320{
2321 struct wq_barrier *barr = container_of(work, struct wq_barrier, work);
2322 complete(&barr->done);
2323}

다음 워커 쓰레드 동작입니다.
2119static int worker_thread(void *__worker)
2120{
2121 struct worker *worker = __worker;
2122 struct worker_pool *pool = worker->pool;
2123
2124 /* tell the scheduler that this is a workqueue worker */
2125 worker->task->flags |= PF_WQ_WORKER;
2126woke_up:
2127 spin_lock_irq(&pool->lock);
2128
//.....
2166 worker_clr_flags(worker, WORKER_PREP | WORKER_REBOUND);
2167
2168 do {
2169  struct work_struct *work =
2170   list_first_entry(&pool->worklist,
2171      struct work_struct, entry);  //<<--[0]
2172
2173  if (likely(!(*work_data_bits(work) & WORK_STRUCT_LINKED))) {   //<<--[1]
2174   /* optimization path, not strictly necessary */
2175   process_one_work(worker, work);  //<<--[2]
2176   if (unlikely(!list_empty(&worker->scheduled)))
2177    process_scheduled_works(worker); //<<--[3]
2178  } else {
2179   move_linked_works(work, &worker->scheduled, NULL);
2180   process_scheduled_works(worker);  //<<--[4]
2181  }
2182 } while (keep_working(pool));
2183
2184 worker_set_flags(worker, WORKER_PREP);
2185sleep:

[0]: 워커풀에 등록된 &pool->worklist를 가져와 워크 변수에 입력합니다.
[1]: 현재 실행 중인 워커 쓰레드와 동일한 워크를 flush한 경우 work_data_bits(work)가 WORK_STRUCT_LINKED이 아닙니다.
이 조건을 검사합니다.

[2]: 워크를 process_one_work에 대입하여 실행합니다.
[3]: &worker->scheduled 멤버가 비어 있지 않으면 배리어 워크가 등록됐다는 의미입니다.
process_scheduled_works(worker) 함수를 호출하여 배리어 워크를 실행합니다.
[4]: 배리어 워크를 실행합니다. 

현재 실행 중인 워커 쓰레드와 동일한 워크를 flush한 경우 ftrace log는 다음과 같습니다.
 => wq_barrier_func
 => process_one_work
 => process_scheduled_works
 => worker_thread
 => kthread
 => ret_from_fork

다음과 같은 패치를 반영하고 로그를 받아 봤습니다.
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index ba8285b..58cf99e 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -2340,8 +2340,10 @@ static void insert_wq_barrier(struct pool_workqueue *pwq,
         * If @target is currently being executed, schedule the
         * barrier to the worker; otherwise, put it after @target.
         */
-       if (worker)
+       if (worker) {
+               trace_printk("[+][Bret] work_struct->func: %pF   [F:%s, L:%d] caller(%pS)\n", target->func, __func__,__LINE__,  (void *)__builtin_return_address(0));
                head = worker->scheduled.next;
+       }
        else {
                unsigned long *bits = work_data_bits(target);

@@ -2349,6 +2351,7 @@ static void insert_wq_barrier(struct pool_workqueue *pwq,
                /* there can already be other linked works, inherit and set */
                linked = *bits & WORK_STRUCT_LINKED;
                __set_bit(WORK_STRUCT_LINKED_BIT, bits);
+               trace_printk("[++][Bret] work_struct->func: %pF   [F:%s, L:%d] caller(%pS)\n", target->func, __func__,__LINE__,  (void *)__builtin_return_address(0));
        }

        debug_work_activate(&barr->work);
@@ -2647,6 +2650,7 @@ static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr)
        pool = get_work_pool(work);
        if (!pool) {
                local_irq_enable();
+               trace_printk("[0][Bret] no pool  [F:%s, L:%d] caller(%pS)\n", __func__,__LINE__,  (void *)__builtin_return_address(0));
                return false;
        }

@@ -2654,12 +2658,17 @@ static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr)
        /* see the comment in try_to_grab_pending() with the same code */
        pwq = get_work_pwq(work);
        if (pwq) {
-               if (unlikely(pwq->pool != pool))
+               if (unlikely(pwq->pool != pool)) {
+                       trace_printk("[+][Bret] goto already_gone  [F:%s, L:%d] caller(%pS)\n", __func__,__LINE__,  (void *)__builtin_return_address(0));
                        goto already_gone;
+               }
        } else {
                worker = find_worker_executing_work(pool, work);
-               if (!worker)
+               if (!worker) {
+                       trace_printk("[++][Bret] goto already_gone  [F:%s, L:%d] caller(%pS)\n", __func__,__LINE__,  (void *)__builtin_return_address(0));
                        goto already_gone;
+               }
+               trace_printk("[+++][Bret] start  [F:%s, L:%d] caller(%pS)\n", __func__,__LINE__,  (void *)__builtin_return_address(0));
                pwq = worker->current_pwq;
        }

@@ -2704,6 +2713,7 @@ bool flush_work(struct work_struct *work)

        if (start_flush_work(work, &barr)) {
                wait_for_completion(&barr.done);
+               trace_printk("[-][Bret] barrier work wait complete  [F:%s, L:%d] caller(%pS)\n", __func__,__LINE__,  (void *)__builtin_return_address(0));
                destroy_work_on_stack(&barr.work);
                return true;
        } else {

ftrace 설정 
adb shell "echo  > d/tracing/set_event"
adb shell "sleep 1"

adb shell "echo 0 > d/tracing/tracing_on"
adb shell "sleep 1"

:adb shell " echo 50000 > /d/tracing/buffer_size_kb"

adb shell "echo function > d/tracing/current_tracer"
adb shell "sleep 1"

adb shell "echo 1 > /d/tracing/events/sched/sched_switch/enable
adb shell "echo 1 > /d/tracing/events/sched/sched_wakeup/enable
adb shell "sleep 1"

adb shell "echo 1 > /d/tracing/events/workqueue/enable"
adb shell "sleep 1"

adb shell "echo wq_barrier_func flush_work   > d/tracing/set_ftrace_filter"
adb shell "sleep 1"

adb shell "echo 1 > d/tracing/options/func_stack_trace"
adb shell "sleep 1"

adb shell "echo 1 > d/tracing/tracing_on"
adb shell "sleep 1"

1. 현재 실행 중인 워커 쓰레드와 동일한 워크를 flush한 경우 ftrace log입니다.
kworker/7:1-345   [007] ...1   144.460351: workqueue_execute_start: work struct c3ff29f4: function sdhci_msm_pm_qos_cpu_unvote_work
     kworker/7:1-345   [007] ...1   144.460353: flush_work <-__cancel_work_timer
     kworker/7:1-345   [007] ...1   144.460373: <stack trace>
 => flush_work
 => __cancel_work_timer
 => cancel_delayed_work_sync
 => pm_qos_update_request
 => sdhci_msm_pm_qos_cpu_unvote_work
 => process_one_work
 => worker_thread
 => kthread
 => ret_from_fork
     mmc-cmdqd/0-340   [004] d..1   144.460461: flush_work: [+][Bret] work_struct->func: sdhci_msm_pm_qos_cpu_unvote_work+0x0/0x34   [F:insert_wq_barrier, L:2344] caller(__cancel_work_timer+0x130/0x1bc)
     kworker/7:1-345   [007] ...1   144.460485: workqueue_execute_end: work struct c3ff29f4
     kworker/7:1-345   [007] ...1   144.460489: workqueue_execute_start: work struct c3d37d00: function wq_barrier_func
     kworker/7:1-345   [007] ...1   144.460491: wq_barrier_func <-process_one_work
     kworker/7:1-345   [007] ...1   144.460511: <stack trace>
 => wq_barrier_func
 => process_one_work
 => process_scheduled_works
 => worker_thread
 => kthread
 => ret_from_fork
     kworker/7:1-345   [007] ...1   144.460532: workqueue_execute_end: work struct c3d37d00
     mmc-cmdqd/0-340   [007] ....   144.460551: flush_work.part.6: [-][Bret] barrier work wait complete  [F:flush_work, L:2710] caller(flush_work+0x150/0x188)

2. 현재 flush하려는 work을 수행 중인 worker가 없는 경우
<...>-357 [005] .... 123.236104: flush_work: [0][Bret] no pool [F:start_flush_work, L:2653] caller(__cancel_work_timer+0x130/0x1bc)
<...>-357 [005] d..2 123.236119: workqueue_queue_work: work struct=e8405db8 function=wq_unbind_fn workqueue=eac0dcc0 req_cpu=1 cpu=1
<...>-357 [005] d..2 123.236122: workqueue_activate_work: work struct e8405db8
<...>-357 [005] d..4 123.236144: sched_wakeup: comm=kworker/1:1H pid=379 prio=100 success=1 target_cpu=001

           <...>-357   [005] ...1   123.236239: <stack trace>
 => flush_work
 => workqueue_cpu_down_callback
 => notifier_call_chain
 => __raw_notifier_call_chain
 => __cpu_notify
 => _cpu_down
 => cpu_down
 => cpu_subsys_offline
 => device_offline
 => core_ctl_offline_core
 => do_hotplug
 => try_hotplug
 => kthread
 => ret_from_fork
           <...>-357   [005] d..1   123.236246: flush_work: [++][Bret] work_struct->func: wq_unbind_fn+0x0/0xd8   [F:insert_wq_barrier, L:2354] caller(workqueue_cpu_down_callback+0x90/0xac)
           <...>-357   [005] d..3   123.236269: sched_switch: prev_comm=core_ctl/0 prev_pid=357 prev_prio=0 prev_state=D ==> next_comm=kworker/u16:8 next_pid=833 next_prio=120
          <idle>-0     [001] d..3   123.236281: sched_switch: prev_comm=swapper/1 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/1:1H next_pid=379 next_prio=100
           <...>-5001  [000] d..4   123.236295: sched_wakeup: comm=CAM_AFD pid=5019 prio=120 success=1 target_cpu=000
           <...>-379   [001] ...1   123.236295: workqueue_execute_start: work struct e8405db8: function wq_unbind_fn
           <...>-379   [001] d..4   123.236319: sched_wakeup: comm=kworker/1:3 pid=386 prio=120 success=1 target_cpu=001
           <...>-5001  [000] d..4   123.236325: sched_wakeup: comm=CAM_ASD pid=5020 prio=120 success=1 target_cpu=000
           <...>-379   [001] d..3   123.236330: sched_switch: prev_comm=kworker/1:1H prev_pid=379 prev_prio=100 prev_state=R ==> next_comm=kworker/1:3 next_pid=386 next_prio=120
           <...>-386   [001] d..3   123.236346: sched_switch: prev_comm=kworker/1:3 prev_pid=386 prev_prio=120 prev_state=S ==> next_comm=kworker/1:1H next_pid=379 next_prio=100
           <...>-379   [001] d..4   123.236359: sched_wakeup: comm=kworker/1:0H pid=17 prio=100 success=1 target_cpu=001
           <...>-379   [001] ...1   123.236362: workqueue_execute_end: work struct e8405db8
           <...>-379   [001] ...1   123.236366: workqueue_execute_start: work struct e8405d58: function wq_barrier_func
           <...>-5001  [000] d..4   123.236368: sched_wakeup: comm=CAM_iface_hw pid=5342 prio=120 success=1 target_cpu=004
           <...>-379   [001] ...1   123.236372: wq_barrier_func <-process_one_work
   kworker/u16:8-833   [005] d..2   123.236372: workqueue_queue_work: work struct=e46a0c68 function=do_read_data workqueue=e46a1f80 req_cpu=8 cpu=4294967295
   kworker/u16:8-833   [005] d..2   123.236375: workqueue_activate_work: work struct e46a0c68
           <...>-5001  [000] d..3   123.236389: sched_switch: prev_comm=CAM_isp_parser prev_pid=5001 prev_prio=120 prev_state=S ==> next_comm=CAM_AECAWB next_pid=5016 next_prio=120
           <...>-379   [001] ...1   123.236395: <stack trace>
 => wq_barrier_func
 => process_one_work
 => process_scheduled_works
 => worker_thread
 => kthread
 => ret_from_fork
  kworker/u16:8-833   [005] d..4   123.236409: sched_wakeup: comm=kworker/u16:11 pid=4830 prio=120 success=1 target_cpu=000
           <...>-379   [001] d..4   123.236422: sched_wakeup: comm=core_ctl/0 pid=357 prio=0 success=1 target_cpu=005
           <...>-379   [001] ...1   123.236426: workqueue_execute_end: work struct e8405d58
           <...>-379   [001] d..3   123.236438: sched_switch: prev_comm=kworker/1:1H prev_pid=379 prev_prio=100 prev_state=S ==> next_comm=kworker/1:0H next_pid=17 next_prio=100
   kworker/u16:8-833   [005] d..3   123.236441: sched_switch: prev_comm=kworker/u16:8 prev_pid=833 prev_prio=120 prev_state=R+ ==> next_comm=core_ctl/0 next_pid=357 next_prio=0
           <...>-357   [005] ....   123.236452: flush_work.part.6: [-][Bret] barrier work wait complete  [F:flush_work, L:2716] caller(flush_work+0x1b8/0x1fc)



.

[Linux][Kernel] list_for_each_entry_safe() vs list_for_each_entry() [Kernel] Data Structure

이번에는 list_for_each_entry_safe()와 list_for_each_entry() 매크로 함수 차이점에 대해서 살펴봅시다.
둘 다 리눅스 커널에서 굉장히 많이 쓰는 함수이니 동작을 잘 알아두면 좋습니다.

list_for_each_entry_safe() 함수 구현부는 다음과 같습니다.
#define list_for_each_entry_safe(pos, n, head, member)   \
 for (pos = list_first_entry(head, typeof(*pos), member), \
  n = list_next_entry(pos, member);   \
      &pos->member != (head);      \
      pos = n, n = list_next_entry(n, member))

다음은 list_for_each_entry() 매크로 함수 코드입니다.
#define list_for_each_entry(pos, head, member)    \
 for (pos = list_first_entry(head, typeof(*pos), member); \
      &pos->member != (head);     \
      pos = list_next_entry(pos, member))

list_for_each_entry() 함수는 리스트를 순위하다가 리스트를 지우면 리스트 순회를 멈춥니다.
대신 list_for_each_entry_safe() 함수는 리스트 멤버를 지워도 계속 순회합니다.

이 차이점 기억해둡시다.

[라즈베리파이] 커널타이머 - 동적 타이머 실행(__mod_timer) 코드 분석 [라즈베리파이] 타이머관리

이제 add_timer()와 mod_timer() 함수들의 구현부를 살펴 보겠습니다.

add_timer() 함수 구현부를 보면 mod_timer() 함수를 호출하니 기능적으로 add_timer() 함수와 mod_timer() 함수는 차이가 없습니다.
void add_timer(struct timer_list *timer)
{
BUG_ON(timer_pending(timer));
mod_timer(timer, timer->expires);
}

int mod_timer(struct timer_list *timer, unsigned long expires)
{
return __mod_timer(timer, expires, false);
}

mod_timer() 함수는 __mod_timer() 함수를 호출하니 동적 타이머를 실행 함수는 __mod_timer()임을 알 수 있습니다.

여기서 __mod_timer() 함수를 제대로 분석하지 않았는데 이 함수를 핵심 함수라고 유추했습니다. 그 이유는 이 함수 앞에 __이란 기호가 붙었기 때문입니다. __가 붙은 함수는 커널 함수 내에서만 호출하면 다른 커널 서브 시스템에서 호출하지 못합니다. 

리눅스 커널에서 가장 유명한 schedule() 함수를 예를 들어 봅시다.
asmlinkage __visible void __sched schedule(void)
{
struct task_struct *tsk = current;

sched_submit_work(tsk);
do {
preempt_disable();
__schedule(false);
sched_preempt_enable_no_resched();
} while (need_resched());
}

schedule() 함수에서 __schedule() 함수를 호출합니다. 실제 세부 스케쥴링을 관련 세부 동작은 __schedule() 함수에서 수행합니다.

다시 동적 타이머 실행 분석으로 돌아가서 __mod_timer() 함수를 봅시다. 주석문을 뺀 함수 코드는 다음과 같습니다.
1 static inline int
2 __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)
3 {
4 struct timer_base *base, *new_base;
5 unsigned int idx = UINT_MAX;
6 unsigned long clk = 0, flags;
7 int ret = 0;
8
9 BUG_ON(!timer->function);
10
11 if (timer_pending(timer)) {
12 if (timer->expires == expires)
13 return 1;
14 base = lock_timer_base(timer, &flags);
15 forward_timer_base(base);
16
17 clk = base->clk;
18 idx = calc_wheel_index(expires, clk);
19 if (idx == timer_get_idx(timer)) {
20 timer->expires = expires;
21 ret = 1;
22 goto out_unlock;
23 }
24 } else {
25 base = lock_timer_base(timer, &flags);
26 forward_timer_base(base);
27 }
28
29 ret = detach_if_pending(timer, base, false);
30 if (!ret && pending_only)
31 goto out_unlock;
32
33 new_base = get_target_base(base, timer->flags);
34
35 if (base != new_base) {
36 if (likely(base->running_timer != timer)) {
37 /* See the comment in lock_timer_base() */
38 timer->flags |= TIMER_MIGRATING;
39
40 spin_unlock(&base->lock);
41 base = new_base;
42 spin_lock(&base->lock);
43 WRITE_ONCE(timer->flags,
44    (timer->flags & ~TIMER_BASEMASK) | base->cpu);
45 forward_timer_base(base);
46 }
47 }
48
49 debug_activate(timer, expires);
50
51 timer->expires = expires;
52 if (idx != UINT_MAX && clk == base->clk) {
53 enqueue_timer(base, timer, idx);
54 trigger_dyntick_cpu(base, timer);
55 } else {
56 internal_add_timer(base, timer);
57 }

우선 __mod_timer() 함수에 전달하는 인자의 의미를 알아보겠습니다.
timer: struct timer_list 구조체 타입인 timer란 인자는 로컬 타이머에서 지정한 값들을 담고 있습니다.
expires: 만료될 HZ 단위 시간 정보를 저장하고 있습니다.
pending_only: __mode_timer에서 false로 지정하므로 0입니다.

해당 인자들의 타입은 다음 코드를 보면 알 수 있습니다.
1 static inline int
2 __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)

이제 코드 분석으로 들어갑니다. 먼저 9번째 줄 코드를 보겠습니다.
9 BUG_ON(!timer->function);

struct timer_list 멤버 변수에 타이머 핸들러 함수가 NULL이면 BUG_ON() 매크로 함수가 실행해서 커널 패닉을 유발합니다. 디바이스 드라이버에서 동적 타이머 핸들러를 struct timer_list.function에 지정하지 않을 경우 실행합니다. 이 코드로 디바이스 드라이버에서 동적 타이머 핸들러를 반드시 설정하고 타이머를 실행해야 한다는 점을 알 수 있습니다.

다음 11줄 코드를 보겠습니다.
11 if (timer_pending(timer)) {
12 if (timer->expires == expires)
13 return 1;

timer_pending() 함수는 struct timer_list 멤버 변수 중 entry.pprev 값이 NULL이 아니면 1을 리턴합니다.
static inline int timer_pending(const struct timer_list * timer)
{
return timer->entry.pprev != NULL;
}

커널 타이머 관점으로 이 코드의 의미를 분석하면 이미 동적 타이머를 실행한 경우 1을 리턴합니다. 동적 타이머를 실행하면 timer->entry.pprev 포인터는 per-cpu타입 timer_base 변수의 벡터 해시 테이블 주소를 가르키고 있기 때문입니다.

이제부터 11~23번 줄 코드를 봅시다. 
11 if (timer_pending(timer)) {
12 if (timer->expires == expires)
13 return 1;
14 base = lock_timer_base(timer, &flags);
15 forward_timer_base(base);
16
17 clk = base->clk;
18 idx = calc_wheel_index(expires, clk);
19 if (idx == timer_get_idx(timer)) {
20 timer->expires = expires;
21 ret = 1;
22 goto out_unlock;
23 }

11줄 코드는 timer_pending() 함수가 1을 리턴하는지 점검해서 동적 타이머를 이미 실행하는지 확인합니다.

여기서 잠깐 12~23번 줄 코드가 실행하는 시나리오를 그려 보겠습니다. 12~23줄 코드가 다양한 예외 조건을 만족하기 위한 코드이기 때문입니다.

현재 시각을 나타내는 jiffies 값이 1000이고 동적 타이머 만료 시각을 2초 뒤인 1200(1000 + 2*Hz)으로 설정했다고 가정합시다.

첫 번째 상황을 생각해봅시다. 1초 후 jiffies는 1100이 됩니다. 이때 만료 시각을 1200값으로 중복해서 다시 동적 타이머 시각을 설정 후 mod_timer()를 호출합니다. 이 경우 12~13줄 코드를 실행해서 함수를 빠져나옵니다. 12번 줄은 같은 만료 시각으로 중복해서 동적 타이머를 실행하는지 점검하기 때문입니다.

두 번째 상황입니다. 1초 후에 만료 시각을 3초 뒤로 설정하고 싶은 경우입니다. 지금 시각이 HZ 단위로 jiffies는 1100이므로 만료 시각은 1400(jiffies + 3*HZ)으로 설정해서 mod_timer()을 설정해야 합니다. 

다시 설정한 동적 타이머 만료 시각이 기존에 설정한 만료 시각보다 크면 __mod_timer()를 계속 실행해서 동적 타이머를 설정합니다. 반대로 재설정한 만료시각과 기존에 설정한 만료시각 차이가 크지 않으면 19~21번 줄 코드를 실행한 후 out_unlock 레이블로 점프해서 함수 실행을 종료합니다.

이제 14번 코드부터 분석하겠습니다.
14 base = lock_timer_base(timer, &flags);

14줄 코드는 lock_timer_base() 함수를 호출해서 struct timer_base 구조체 포인터 타입 변수인 base에 리턴값을 받습니다. 

lock_timer_base() 함수를 호출하면 get_timer_base() -> get_timer_cpu_base() 순서로 함수를 호출해서 per-cpu 오프셋을 적용한 timer_base 주소를 읽어 옵니다. 이때 처음 동적 타이머를 초기화할 때 실행한 CPU 번호인 struct timer_list.flags 값 기준으로 per-cpu timer_bases 전역 변수 주소를 base이란 지역 변수로 읽어 옵니다. 해당 코드는 다음과 같습니다.
1 static inline struct timer_base *get_timer_cpu_base(u32 tflags, u32 cpu)
2{
3 struct timer_base *base = per_cpu_ptr(&timer_bases[BASE_STD], cpu);

timer_bases이란 전역변수는 per-cpu 타입입니다. timer_bases 전역변수가 위치한 주소 기준으로 cpu 개수만큼 struct time_base 구조체 공간이 있습니다. 라즈베리파이는 CPU 개수가 4개이니 4개의 struct time_base 구조체 공간이 있습니다.

위 get_timer_cpu_base() 함수의 3줄 코드를 실행하면 현재 실행 중인 CPU번호에 해당하는 per-cpu timer_bases 주소 공간을 알 수 있습니다.

15줄 코드를 봅시다. 
15 forward_timer_base(base);

forward_timer_base() 함수를 호출해서 struct timer_base.clk 멤버를 현재 시각인 jiffies 값으로 저장합니다. struct timer_base.clk 멤버 변수는 커널 시스템 타이머 실행 여부를 판단하는 중요한 정보를 담고 있습니다.

17줄은 struct timer_base.clk 멤버 변수 값을 clk로 저장합니다.
17 clk = base->clk;

18줄은 calc_wheel_index() 함수를 호출해서 커널 타이머 벡터 인덱스를 읽습니다. 
18 idx = calc_wheel_index(expires, clk);

커널 타이머 해시 벡터는 동적 시간을 만료 시각 기준으로 동적 타이머 64(1 << 6) 개를 8개씩 묶어서 관리합니다. 이 커널 타이머 해시 벡터 인덱스의 범위는 0부터 512입니다.

다음은 timer_bases 전역 변수의 선언부입니다. 14줄 코드를 보면 WHEEL_SIZE(512) 크기 배열을 볼 수 있습니다. WHEEL_SIZE 매크로가 커널 타이머 해시 벡터 인덱스 크기입니다.
[kernel/time/timer.c]
1 static DEFINE_PER_CPU(struct timer_base, timer_bases[NR_BASES]);
3 struct timer_base {
4 raw_spinlock_t lock;
5 struct timer_list *running_timer;
6 unsigned long clk;
7 unsigned long next_expiry;
8 unsigned int cpu;
9 bool migration_enabled;
10 bool nohz_active;
11 bool is_idle;
12 bool must_forward_clk;
13 DECLARE_BITMAP(pending_map, WHEEL_SIZE);
14 struct hlist_head vectors[WHEEL_SIZE];
15} ____cacheline_aligned;

struct timer_base.vectors란 멤버는 512개의 배열임을 알 수 있습니다. 512개의 배열을 8개씩 나눠서 관리하는 겁니다.

이제 19줄을 봅시다.
19 if (idx == timer_get_idx(timer)) {
20 timer->expires = expires;
21 ret = 1;
22 goto out_unlock;
23 }

현재 시각 기준으로 계산한 커널 타이머 해시 벡터 인텍스인 idx와 이미 동적 타이머를 실행했을 때 설정했던 커널 타이머 해시 벡터 인텍스를 비교해서 같으면 20줄 코드를 실행합니다. 재설정하는 동적 타이머 만료 시각과 기존에 설정한 만료 시각 차이가 크지 않을 경우 19번 줄 코드 조건을 만족합니다. 동적 타이머 만료 시각을 다시 설정하고 out_unlock 레이블을 이동해서 __mod_timer() 함수를 빠져나옵니다.

다음 24줄 코드를 봅시다.
11 if (timer_pending(timer)) {
24 } else {
25 base = lock_timer_base(timer, &flags);
26 forward_timer_base(base);
27 }

11번 줄 코드와 같이 timer_pending()이란 함수가 0을 리턴했을 때 else문입니다. 동적 타이머를 처음 실행하거나 동적 타이머를 이미 취소한 후 다시 실행할 때 25~26줄 코드가 실행됩니다. 동적 타이머를 초기화했을 때 CPU 번호가 저장된 struct timer_list.flags 멤버 변수 기준으로 per-cpu 타입 timer_base 전역 변수 주소를 읽어 base이란 지역 변수로 가져옵니다.

26 줄 코드는 forward_timer_base() 함수를 호출해서 struct timer_base.clk 멤버를 현재 시각인 jiffies 값으로 저장합니다.

다음 29~33줄 코드를 봅시다.
29 ret = detach_if_pending(timer, base, false);
30 if (!ret && pending_only)
31 goto out_unlock;
32
33 new_base = get_target_base(base, timer->flags);


29줄 코드는 detach_if_pending() 함수를 실행해서 이미 timer_base 타이머 해시 벡터인 timer_base.vector[512]에 지정한 링크를 끊습니다. detach_if_pending() 함수는 이미 동적 타이머를 실행했을 경우 동작합니다. 참고로  timer_base 타이머 해시 벡터 자료 구조는 다음 구조체 멤버와 같습니다.
struct timer_base.vectors[WHEEL_SIZE];

 struct timer_base {
raw_spinlock_t lock;
struct timer_list *running_timer;
...
DECLARE_BITMAP(pending_map, WHEEL_SIZE);
struct hlist_head vectors[WHEEL_SIZE];
} ____cacheline_aligned;

get_target_base() 함수는 get_timer_this_cpu_base() 함수 혹은 get_timer_cpu_base() 함수에 접근합니다.
static inline struct timer_base *
get_target_base(struct timer_base *base, unsigned tflags)
{
#ifdef CONFIG_SMP
if ((tflags & TIMER_PINNED) || !base->migration_enabled)
return get_timer_this_cpu_base(tflags);
return get_timer_cpu_base(tflags, get_nohz_timer_target());
#else
return get_timer_this_cpu_base(tflags);
#endif
}

static inline struct timer_base *get_timer_this_cpu_base(u32 tflags)
{
struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);

코드 흐름을 살펴보면 get_timer_this_cpu_base() 함수를 통해 timer_bases이란 per-cpu 타입 변수를 가져옵니다. 참고로 this_cpu_ptr() 함수로 per-cpu 타입 변수에 접근하면 현재 실행 중인 CPU 번호에 해당하는 per-cpu 변수 포인터를 가져 옵니다.

다시 35줄 코드 분석으로 돌아오겠습니다.
35 new_base = get_target_base(base, timer->flags);

get_target_base() 함수를 호출하면 처음 동적 타이머를 설정했을 때 CPU 번호 기준으로  per-cpu 타입 timer_bases 변수의 주소를 읽어 옵니다. 여기서 동적 타이머를 초기화할 때 CPU 번호는 timer->flags 값으로 가져옵니다.

계속 struct timer_list 구조체인 timer->flags 기준으로 per-cpu timer_base 주소를 읽어오는 이유를 알아봅시다.

do_init_timer () 함수에서 로컬 타이머를 초기화할 때 timer->flags 멤버에 현재 코드가 돌고 있는 CPU번호를 다음 5줄 코드와 같이 지정했습니다.
1 static void do_init_timer(struct timer_list *timer, unsigned int flags,
2   const char *name, struct lock_class_key *key)
3 {
4 timer->entry.pprev = NULL;
5 timer->flags = flags | raw_smp_processor_id();
6 lockdep_init_map(&timer->lockdep_map, name, key, 0);
7}

이 값을 바탕으로 per-cpu 오프셋을 결정하는 것입니다. init_timer() 함수를 호출하는 코드가 CPU1에서 실행 중 이었다면 per-cpu1에 해당하는 per-cpu timer_base 변수의 CPU1 공간 주소를 포인터 변수로 가져옵니다. 처음 동적 타이머를 초기화할 때 별 의미 없는 코드로 보였지만 코드 전체를 읽고 나니 중요한 루틴임을 알 수 있습니다.

다음은 로컬 타이머를 타이머에 큐잉하는 코드입니다.
11 if (timer_pending(timer)) {
12 if (timer->expires == expires)
13 return 1;
14 base = lock_timer_base(timer, &flags);
15 forward_timer_base(base);
16
17 clk = base->clk;
18 idx = calc_wheel_index(expires, clk);
24 } else {
25 base = lock_timer_base(timer, &flags);
26 forward_timer_base(base);
27 }
51 timer->expires = expires;
52 if (idx != UINT_MAX && clk == base->clk) {
53 enqueue_timer(base, timer, idx);
54 trigger_dyntick_cpu(base, timer);
55 } else {
56 internal_add_timer(base, timer);
57 }

51~57 줄 코드 이해를 돕기 위해 11~27번 줄 코드를 같이 봅시다.

이미 동적 타이머를 실행한 상태에서 __mod_timer() 함수를 실행하면 11~23줄 코드를 실행합니다. 이후 53~54번 줄 코드를 실행합니다.

11~23번 코드에서 이미 커널 타이머 해시 벡터 인텍스를 계산과 예외 처리를 했기 때문입니다. enqueue_timer() 함수를 호출해서 동적 타이머를 struct timer_base.vector 배열에 등록하는 과정만 남았습니다.

처음 동적 타이머를 실행한 경우 56줄 코드를 실행해서 internal_add_timer() 함수를 호출합니다. 다음 코드를 보면 internal_add_timer() 함수는 __internal_add_timer() 함수를 호출합니다.
static void
internal_add_timer(struct timer_base *base, struct timer_list *timer)
{
__internal_add_timer(base, timer);
trigger_dyntick_cpu(base, timer);
}

__internal_add_timer() 함수를 보면 HZ 단위 타이머 만료 시간과 현재 시각 정보인 base->clk 기준으로 calc_wheel_index () 함수를 호출해서 타이머 해시 벡터 인덱스를 얻어 옵니다.
static void
__internal_add_timer(struct timer_base *base, struct timer_list *timer)
{
unsigned int idx;

idx = calc_wheel_index(timer->expires, base->clk);
enqueue_timer(base, timer, idx);
}

이후 enqueue_timer() 함수를 호출합니다.

다음은 if~else 문에서 공통으로 접근하는 enqueue_timer() 함수를 살펴볼 차례입니다. 
1 static void enqueue_timer(struct timer_base *base, struct timer_list *timer,
2   unsigned int idx)
3 {
4 hlist_add_head(&timer->entry, base->vectors + idx);
5 __set_bit(idx, base->pending_map);
6 timer_set_idx(timer, idx);
7}

이 함수에 전달되는 첫 번째 인자인 struct timer_base *base는 per-cpu 타입 timer_bases 전역 변수에서 현재 구동 중인 CPU에 해당하는 오프셋을 적용한 주소를 저장하고 있습니다.

두 번째 인자인 struct timer_list *timer는 이번에 설정한 로컬 타이머 포인터입니다. 

4번 줄 코드는 base->vectors 멤버인 타이머 해시 벡터 테이블 시작 주소에 타이머 해시 벡터 인덱스를 더합니다. struct timer_base 구조체 멤버인 vectors 시작 주소 기준으로 idx만큼 위치한 주소에 timer->entry 주소를 저장하는 겁니다. 또한 struct timer_base 구조체 pending_map 멤버에 pending 비트를 설정합니다.

여기까지 동적 타이머를 설정하면 처리하는 데이터와 관련 자료구조를 살펴봤습니다. 다음에 이어서 동적 타이머 만료 시간에 동적 타이머를 처리하는 동작을 살펴보겠습니다.




[라즈베리파이] 커널 타이머 - 동적 타이머 실행 코드 [라즈베리파이] 타이머관리

동적 타이머를 초기화만 하면 동적 타이머를 쓸 수 없습니다. 동적 타이머를 실행해야 합니다. 이를 위해 add_timer()/add_timer_on() 이나 mod_timer() 함수에 적절한 인자를 채워 호출해야 합니다.

보통 add_timer() 함수를 써서 동적 타이머를 실행하며 이후 동적 타이머 만료 시간을 다시 설정한 후 동적 타이머를 실행시킬 때 mod_timer() 함수를 씁니다.  add_timer()와 mod_timer() 함수를 써서 로컬 타이머를 설정하는 코드를 살펴보겠습니다.

먼저 라즈비안에서 로컬 타이머를 실행하는 코드를 봅시다.
1 bool bcm2835_sdhost_send_command(struct bcm2835_host *host,
2  struct mmc_command *cmd)
3{
...
4 timeout = jiffies;
5 if (!cmd->data && cmd->busy_timeout > 9000)
6 timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
7 else
8 timeout += 10 * HZ;
9 mod_timer(&host->timer, timeout);

코드 분석에 앞서 jiffies와 timeout 변수 단위는 HZ라는 점을 염두하시기 바랍니다.

4번 줄 코드를 먼저 봅시다.
4 timeout = jiffies

timeout 이란 변수에 현재 시각 정보를 담고 있는 HZ 단위인 jiffies 변수를 저장합니다.
5~8번 줄은 조건에 따라 timeout 변수에 시각 정보를 더합니다. timeout 변수는 타이머 만료 시각을 나타냅니다.
5 if (!cmd->data && cmd->busy_timeout > 9000)
6 timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
7 else
8 timeout += 10 * HZ;

만약 8번 줄 코드를 실행하면 HZ에 10을 곱한 값을 timeout 변수에 더합니다. timeout 지역 변수에 저장된 값은 현재 시각 기준으로 10초 후 HZ단위 시각 정보를 의미합니다.  10초 후에 동적 타이머가 만료하는 겁니다.

9번 줄 코드는 mod_timer() 함수를 써서 타이머를 시작합니다. 로컬 타이머 구조체인 struct timer_list 포인터 주소와 로컬 타이머 만료 시간을 전달합니다.

&host->timer 타이머 속성은 bcm2835_sdhost_add_host () 함수에서 확인할 수 있습니다.
1 int bcm2835_sdhost_add_host(struct bcm2835_host *host)
2 {
3 struct mmc_host *mmc;
...
4 setup_timer(&host->timer, bcm2835_sdhost_timeout,
5     (unsigned long)host);

&host->timer 타이머는 이미 bcm2835_sdhost_add_host() 함수에서 초기화를 했는데 타이머 핸들러 함수가 bcm2835_sdhost_timeout() 입니다. &host->timer 변수 구조체는 struct timer_list입니다.

정리하면 timeout 변수가 지정한 로컬 타이머 만료 시간에 bcm2835_sdhost_timeout() 타이머 핸들러 함수가 호출합니다.

다음 코드는 add_timer() 함수를 써서 로컬 타이머를 설정합니다. 조금 복잡해 보이는 코드 같지만 함수 원리를 알면 어렵지 않습니다. 
[https://elixir.bootlin.com/linux/v4.14.43/source/drivers/mmc/host/vub300.c]
1 static int vub300_probe(struct usb_interface *interface,
2 const struct usb_device_id *id)
3{
...
4 init_timer(&vub300->sg_transfer_timer);
5 vub300->sg_transfer_timer.data = (unsigned long)vub300;
6 vub300->sg_transfer_timer.function = vub300_sg_timed_out;
7 kref_get(&vub300->kref);
8 init_timer(&vub300->inactivity_timer);
9 vub300->inactivity_timer.data = (unsigned long)vub300;
10 vub300->inactivity_timer.function = vub300_inactivity_timer_expired;
11 vub300->inactivity_timer.expires = jiffies + HZ;
12 add_timer(&vub300->inactivity_timer);

4번째 줄 코드부터 봅시다. init_timer() 함수로 로컬 타이머를 초기화 합니다.
4 init_timer(&vub300->sg_transfer_timer);

만약 이 코드가 CPU2에서 실행 중이면 &vub300->sg_transfer_timer.flags는 2가 됩니다.
이제 9~12줄 코드를 분석하겠습니다.
9 vub300->inactivity_timer.data = (unsigned long)vub300;
10 vub300->inactivity_timer.function = vub300_inactivity_timer_expired;
11 vub300->inactivity_timer.expires = jiffies + HZ;
12 add_timer(&vub300->inactivity_timer); 

9번 줄은 타이머 핸들러에 전달하는 매개 변수를 vub300 변수로 지정합니다. 이 매개 변수는 타이머 핸들러인 vub300_inactivity_timer_expired() 함수에 전달합니다. 대부분 매개 변수는 다바이스 속성 정보가 모두 포함된 구조체 주소 값을 넘겨 줍니다. 따라서 vub300 변수는 포인터 타입 변수입니다.

10번 줄 코드는 vub300_inactivity_timer_expired() 함수를 동적 타이머 핸들러로 등록합니다. 

11번 줄은 로컬 타이머가 만료할 시각을 설정합니다.  현재 시각 정보인 jiffies에 HZ를 더하니 1초 후에 타이머가 만료합니다.

12번 줄 코드를 보면 add_timer() 함수를 써서 타이머를 설정합니다.

그러면 add_timer()와 mod_timer() 함수의 차이점은 뭘까요? add_timer() 함수는 로컬 타이머 속성을 설정하고 호출해야 하며, mod_timer() 함수는 단지 로컬 타이머가 종료될 시간을 jiffies 기준으로 설정합니다. 보통 add_timer() 함수를 써서 동적 타이머의 상세 정보를 설정하고 실행합니다. 이후 동적 타이머 만료 시간을 다시 설정한 후 동적 타이머를 실행할 때 mod_timer() 함수를 씁니다.





[라즈베리파이] 커널 타이머 - 동적 타이머 초기화 [라즈베리파이] 타이머관리

동적 타이머 초기화를 알아 보기 앞서 동적 타이머는 전체 동작 흐름에 대해 알아 봅시다.
동적 타이머 동작은 3단계로 나눌 수 있습니다. 
1. 동적 타이머 초기화
2. 동적 타이머 실행
3. 동적 타이머 만료
 3.1 동적 타이머 해제
 3.2 동적 타이머 핸들러 실행

첫 번째 초기화 단계입니다.
동적 타이머 초기화는 보통 드라이버 레벨에서 수행합니다. 동적 타이머는 struct timer_list 이란 자료구조로 표현할 수 있는데 이 멤버 중 flags만 업데이트 합니다.

두 번째 동적 타이머 실행 단계입니다.
동적 타이머 실행도 마찬가지로 드라이버 레벨에서 이루어집니다. 각자 드라이버 시나리오에 따라 동적 타이머 만료 시간을 HZ 단위로 지정한 다음에 add_timer() 함수를 호출합니다.

이제는 동적 타이머가 지정한 만료 시간이 되면 커널 시스템 타이머가 동적 타이머를 처리합니다.

3단계 중 첫 단계인 동적 타이머를 어떻게 초기화하는지 살펴봅시다. 이 과정에서 어떤 자료구조가 업데이트 되는지도 알아보겠습니다.

동적 타이머 초기화 과정을 살펴보기 전에 우선 동적 타이머를 표현하는 자료구조를 알아 봅시다.
동적 타이머를 표현하는 자료구조는 struct timer_list이며 코드는 다음과 같습니다.
[include/linux/timer.h]
1 struct timer_list {
2 struct hlist_node entry;
3 unsigned long expires;
4 void (*function)(unsigned long);
5 unsigned long data;
6 u32 flags;
#ifdef CONFIG_LOCKDEP
7 struct lockdep_map lockdep_map;
#endif
};

각 멤버들의 의미를 살펴보겠습니다.

entry
해시 링크드 리스트인데 이 멤버는 timer_bases 전역 변수에 동적 타이머를 등록할 때 쓰입니다.

expires
동적 타이머가 만료할 시각을 저장합니다. 이 시각에 커널 시스템 타이머가 동적 타이머의 핸들러 함수를 호출합니다. 이 값의 단위는 HZ입니다.

(*function)(unsigned long)
동적 타이머 핸들러 함수 주소를 저장하는 변수입니다. 커널 시스템 타이머는 call_timer_fn() 함수를 실행할 때 이 변수에 접근해서 동적 타이머 핸들러를 호출합니다.

data
동적 타이머 핸들러에 전달하는 매개변수입니다. 보통 디바이스 드라이버 정보를 모두 담긴 주소를 전달합니다.

flags
타이머 설정 필드입니다. 다음 매크로 중 하나입니다.
[include/linux/timer.h]
#define TIMER_CPUMASK 0x0003FFFF
#define TIMER_MIGRATING 0x00040000
#define TIMER_BASEMASK (TIMER_CPUMASK | TIMER_MIGRATING)
#define TIMER_DEFERRABLE 0x00080000
#define TIMER_PINNED 0x00100000
#define TIMER_IRQSAFE 0x00200000
#define TIMER_ARRAYSHIFT 22
#define TIMER_ARRAYMASK 0xFFC00000

라즈비안은 CONFIG_LOCKDEP 컨피그가 선언돼 있지 않기 때문에 lockdep_map 변수를 struct timer_list 구조체에 포함돼 있지 않습니다.

동적 타이머를 초기화하면 struct timer_list 중 어떤 멤버가 초기화될까요? flags만 업데이트됩니다. 리눅스 커널 버전에 따라 동적 타이머를 초기화하는 범위가 다른데 라즈비안에서 구동하는 커널 4.14.43 버전은 flags 멤버만 업데이트 하는 수준입니다.
 
이제 어떤 함수을 써야 동적 타이머를 초기화할 수 있는지 알아 봅시다.
드라이버에서 로컬 타이머를 초기화할 때 보통 init_timer() 함수 혹은 setup_timer() 함수를 씁니다. 

이해를 돕기 위해 먼저 두 함수들을 써서 동적 타이머를 초기화하는 코드를 보겠습니다. 다음 두 코드들은 라즈비안와 다른 리눅스 시스템에서 로컬 커널 타이머를 초기화 합니다.
먼저 라즈비안에서 구현된 드라이버 코드를 봅시다.
[drivers/mmc/host/bcm2835-sdhost.c]
1 int bcm2835_sdhost_add_host(struct bcm2835_host *host)
2 {
3 struct mmc_host *mmc;
...
4 setup_timer(&host->timer, bcm2835_sdhost_timeout,
5     (unsigned long)host);

첫 번째 bcm2835_sdhost_add_host() 함수 4번째 줄 코드를 보면 setup_timer() 함수를 써서 로컬 타이머를 초기화합니다. setup_timer() 함수로 3개 인자를 전달합니다. host->timer 는 struct timer_list 구조체 주소, bcm2835_sdhost_timeout() 함수는 동적 타이머 핸들러 그리고 host는 bcm2835_sdhost_timeout() 핸들러 함수로 전달하는 매개변수입니다.

이번에는 다른 리눅스 시스템에서 코드를 보겠습니다.
[https://elixir.bootlin.com/linux/v4.14.43/source/drivers/usb/phy/phy-isp1301-omap.c#L1491] 
static int
6 isp1301_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
7 {
...
8 init_timer(&isp->timer);
9 isp->timer.function = isp1301_timer;
10 isp->timer.data = (unsigned long) isp;

isp1301_probe() 함수에서는 8번 코드와 같이 init_timer() 함수로 struct timer_list 구조체인 &isp->timer 로 동적 타이머를 초기화합니다. 다음 9번 줄 코드에서 isp1301_timer () 함수를 핸들러 함수로 지정하고 10번 줄 코드에서 isp는 핸들러 함수 매개 변수로 지정합니다. 

서로 다른 함수를 써서 동적 타이머를 초기화합니다. 그럼 두 함수의 차이점은 뭘까요? 각각 함수를 분석하면서 두 함수의 차이점을 확인하겠습니다. 

먼저 init_timer() 함수 코드를 분석 합시다.
#define init_timer(timer) \
__init_timer((timer), 0)

#define __init_timer(_timer, _flags) \
do { \
static struct lock_class_key __key; \
init_timer_key((_timer), (_flags), #_timer, &__key); \
} while (0)

void init_timer_key(struct timer_list *timer, unsigned int flags,
    const char *name, struct lock_class_key *key)
{
debug_init(timer);
do_init_timer(timer, flags, name, key);
}

init_timer() 함수 구현부를 보니 여러 매크로 함수로 치환되고 있습니다. init_timer()  함수는 __init_timer() 함수로 치환되며 __init_timer 함수에서는 init_timer_key() 함수를 호출합니다. 

이번에는 setup_timer()  함수를 보겠습니다. setup_timer() 함수도 init_timer() 매크로 함수와 마찬가지로 init_timer_key 함수를 호출합니다.
1 #define setup_timer(timer, fn, data) \
2 __setup_timer((timer), (fn), (data), 0)
3
4#define __setup_timer(_timer, _fn, _data, _flags) \
5 do { \
6 __init_timer((_timer), (_flags)); \
7 (_timer)->function = (_fn); \
8 (_timer)->data = (_data); \
9 } while (0)
10
11#define __init_timer(_timer, _flags) \
12 do { \
13 static struct lock_class_key __key; \
14 init_timer_key((_timer), (_flags), #_timer, &__key); \
15 } while (0)

setup_timer() 함수를 호출하면 위 7~8번 줄 코드와 같이 struct timer_list란 구조체 멤버 중 function과 data를 setup_timer() 함수에서 전달된 인자로 초기화합니다. 여기 function은 동적 타이머 핸들러 함수의 위치이고 data는 동적 타이머 핸들러 함수에 전달하는 매개 인자를 의미합니다. 이점이 init_timer() 함수를 호출해서 로컬 타이머를 초기화할 때와 다른 점입니다. 정리하면 init_timer()과 setup_timer() 두 함수를 호출하면 init_timer_key() 이라는 함수가 실행합니다. 2번, 6번, 11번, 14번 줄 코드를 눈여겨 봅시다.

어느 함수를 써도 로컬 타이머를 초기화하는 동작은 같습니다. 이를 확인하기 위해 두 함수의 구현부 코드를 봅시다.
1 #define init_timer(timer) \
2 __init_timer((timer), 0)
3
4#define setup_timer(timer, fn, data) \
5 __setup_timer((timer), (fn), (data), 0)

1번 줄을 보면 init_timer이란 매크로에 struct timer_list 타입인 timer이란 인자를 전달합니다. 여기서 timer 인자는 struct timer_list 구조체 주소를 의미합니다.

이번에는 4번 줄을 보겠습니다.
setup_timer() 함수에는 3개 인자를 전달합니다. 입력인자 timer, fn, data에 struct timer_list 구조체의 포인터와 타이머 핸들러 함수 그리고 타이머 핸들러 핸들을 지정해줘야 합니다.

이제 두 매크로 함수에서 공통으로 호출하는 init_timer_key() 함수를 봅시다.
void init_timer_key(struct timer_list *timer, unsigned int flags,
    const char *name, struct lock_class_key *key)
{
debug_init(timer);
do_init_timer(timer, flags, name, key);
}

init_timer_key() 함수는 debug_init()와 do_init_timer() 함수를 호출하니 우선 debug_init() 함수부터 살펴봐야겠습니다. 
1 static inline void debug_init(struct timer_list *timer)
2 {
3 debug_timer_init(timer);
4 trace_timer_init(timer);
5}

debug_init() 함수를 보니 debug_timer_init() 함수와 trace_timer_init() 함수를 호출합니다.

3번째 줄 debug_timer_init() 함수는 CONFIG_DEBUG_OBJECTS_TIMERS란 디버그용 컨피그를 설정하면 실행하는 함수입니다. 동적 타이머를 중복해서 초기화를 할 경우 WARN 매크로를 실행해서 콜스택을 출력하는 동작을 수행합니다. 라즈베리안에서는 기본설정으로 이 컨피그는 꺼져 있습니다.

4번 줄 trace_timer_init 함수는 ftrace timer_init 이벤트 로그를 출력합니다. 다음 명령어로 ftrace의 timer_init 이벤트를 설정하면 trace_timer_init () 함수가 실행하면서 ftrace 로그를 출력합니다. 
“echo 1 > /sys/kernel/debug/tracing/event/timer/timer_init/enable"

위와 같이 timer_init 이벤트를 킨 상태에서 trace_timer_init() 함수가 실행하면 다음과 같은 ftrace 로그를 출력합니다.
rcu_sched-8     [002] ....  5181.011526: timer_init: timer=b9e7bed0

위 로그는 5181.011526 초에 동적 타이머를 초기화했다는 메시지입니다. 동적 타이머를 표현하는 struct timer_list 구조체가 위치한 주소는 b9e7bed0입니다.

정리하면 debug_init() 함수는 로컬 커널 타이머 디버깅 정보를 출력합니다.

이제, do_init_timer 함수를 호출하면 어떤 과정으로 해당 타이머를 초기화하는지 알아봅시다. 
1 static void do_init_timer(struct timer_list *timer, unsigned int flags,
2   const char *name, struct lock_class_key *key)
3 {
4 timer->entry.pprev = NULL;
5 timer->flags = flags | raw_smp_processor_id();
6 lockdep_init_map(&timer->lockdep_map, name, key, 0);
7}

5줄 코드부터 분석하겠습니다.
입력인자 flags와 현재 실행 중인 CPU번호를 OR 연산한 후 timer->flags 멤버에 저장합니다. raw_smp_processor_id() 함수는 현재 실행 중인 코드가 몇 번 째 CPU에서 수행 중인지 알려줍니다. 

만약 do_init_timer() 함수로 전달되는 flags 인자가 0x2000000이고 CPU3번에서 do_init_timer() 함수가 실행했다면 다음 공식으로 timer->flags는 0x2000003이 됩니다.
timer->flags= 0x2000003 = 0x2000000 + 0x3

이렇게 동적 타이머를 초기화하는 흐름을 코드 분석으로 알아봤습니다. 분석한 내용이 잘 이해가시나요? 소스 코드만 분석하면 배운 내용이 머릿속에 잘 남지 않을 수도 있습니다.

리눅스 커널 코드는 함수 흐름으로 개념을 이해할 수도 있지만 정밀히 데이터를 연산하거나 변환하는 코드는 자료구조를 직접 눈으로 확인하는 것이 이해가 빠릅니다. 이해를 돕기 위해 이번에는 ftrace와 Trace32 프로그램으로 코드로 분석한 동적 타이머를 초기화할 때 자료구조를 확인하겠습니다.

ftrace로 timer_init 이벤트를 키면 다음과 같은 ftrace 로그를 볼 수 있다고 했습니다.
 rcu_sched-8     [002] ....  5181.011526: timer_init: timer=b9e7bed0

위 코드에서 볼드체로 002라고 돼 있는 숫자는 이 코드를 실행하고 있는 CPU번호를 의미합니다. rcu_sched는 프로세스 이름이고 8을 pid를 의미합니다. CPU2에서 “rcu_sched” 이란 프로세스가 구동 중입니다. 만약 위 ftrace 로그가 실행됐을 때는 timer->flags 멤버는 2로 업데이트 됩니다. 이 함수는 CPU2에서 구동하고 있기 때문입니다. 

이번에 ftrace 로그가 출력할 때 struct timer_list 구조체를 Trace32로 확인해 봅시다.
view.var %type %symbol struct timer_list *)0xb9e7bed0
 (struct timer_list *) (struct timer_list *)0xb9e7bed0 = 0xB9E7BED0 -> (
    (struct hlist_node) entry = ((struct hlist_node *) next = 0x0, 
    (long unsigned int) expires = 0,
    (void (*)()) function = 0x0,
    (long unsigned int) data = 0,
    (u32) flags = 2)

각각 멤버들은 0x0인데 flags 멤버만 2입니다. CPU2에서 init_timer_key() 함수를 실행해기 때문입니다.

이번에 분석한 내용을 정리하면, init_timer() 함수를 호출하면 간단히 로컬 타이머를 초기화합니다. 현재 구동 중인 CPU 번호를 계산해서 timer->flags 변수에 저장할 뿐입니다.

그러면 현재 실행 중인 CPU번호로 timer->flags 변수를 저장하는 이유는 뭘까요? 동적 타이머를 실행할 때 이 값을 바탕으로 struct timer_base 구조체 per-cpu 타입 timer_bases 주소에 접근합니다. 조금 쉽게 설명을 드리면 로컬 타이머를 설정한 CPU번호가 1번이면 per-cpu1 timer_bases를 로딩해서 로컬 타이머를 저장합니다. 이 내용은 다음에 상세히 다룰 예정입니다.



.

[라즈베리파이] 커널 타이머 - timer_after/timer_before 함수 [라즈베리파이] 타이머관리

리눅스 커널 중요 시스템은 물론 여러 디바이스 드라이버에서 time_after()와 time_before() 함수를 써서 실행 시간과 타이밍을 제어합니다.

보통 함수 처리 시간과 실행 시간의 데드라인을 점검합니다. 그러니 이 함수들이 어떤 역할을 하는지 잘 알아둘 필요가 있습니다. 

먼저 time_after()와 time_before() 함수 구현부를 보겠습니다.
[include/linux/jiffies.h]
1 #define time_after(a,b) \
2 (typecheck(unsigned long, a) && \
3  typecheck(unsigned long, b) && \
4  ((long)((b) - (a)) < 0))
5 #define time_before(a,b) time_after(b,a)

time_before() 함수 구현부를 보면 인자 값을 서로 바꾼 다음 time_after() 함수를 호출하므로 time_after() 함수를 먼저 분석합시다.

1번 줄 코드를 보면 첫 번째 인자로 a, 두 번째로 b를 받습니다.
2~3번 줄에서 입력 인자가 정수 타입인지 점검하고, 4번 줄에서 b에서 a를 빼고 난 결과를 리턴합니다.

만약 a가 1000이고 b가 1100이면 어떤 결괏값일까요?
100 = (1100 - 1000) = (b - a)

결괏값이 100이므로 0을 리턴합니다.  a가 b보다 크면 1, 아니면 0을 리턴하는 것입니다.
정리하면, a가 b보다 큰지 물어보는 함수입니다.

반대로 time_before() 함수는 a가 b보다 작은지 알려주는 함수입니다. a가 b보다 크면 1, 아니면 0을 리턴합니다.

이제 time_after(a, b)와 time_before(a,b) 함수에 전달하는 인자들의 의미를 알아봅시다.
a에는 현재 시각 정보를 b에는 비교하려는 시간 정보를 입력합니다. 둘다 jiffies 기준값입니다. 

위 함수를 구현부를 분석하는 것보다 리눅스 커널 코드에서 이 함수들을 어떻게 쓰는지 알아보는게 이해가 더 빠르니 관련 코드를 열어 보겠습니다. 다음은 워크큐 와치독 점검 루틴인 wq_watchdog_timer_fn() 함수입니다.
[kernel/workqueue.c]
1 static void wq_watchdog_timer_fn(unsigned long data)
2 {
...
3 if (time_after(jiffies, ts + thresh)) {
4 lockup_detected = true;
5 pr_emerg("BUG: workqueue lockup - pool");
6 pr_cont_pool_info(pool);
7 pr_cont(" stuck for %us!\n",
8 jiffies_to_msecs(jiffies - pool_ts) / 1000);
9 }

이 함수의 역할은 각 per-cpu별로 구동하는 워커풀에서 타임스탬프 정보를 확인해서 워크를 큐 했는데 정해진 시간 내에 해당 워크를 실행했는지 점검합니다.

3번째 줄을 보면 jiffies와 (ts+thread)를 비교해서 jiffies가 더 크면 time_after() 함수는 1을 리턴합니다. 워크큐 락업이라고 판단하고 if 문 내에서 에러 메시지를 출력하는 코드를 실행합니다. 현재 시각이 지정한 마감 시각보다 큰지 점검하는 동작입니다.

이번에는 Soft IRQ 핵심 함수인 __do_softirq()를 확인해 봅시다.
[kernel/softirq.c]
1 asmlinkage __visible void __softirq_entry __do_softirq(void)
2 {
3 unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
...
4 pending = local_softirq_pending();
5 if (pending) {
6 if (time_before(jiffies, end) && !need_resched() &&
7     --max_restart)
8 goto restart;
9
10 wakeup_softirqd();
11 }
12
13 #define MAX_SOFTIRQ_TIME  msecs_to_jiffies(2)

위 코드는 __do_softirq() 함수 실행 시간이 2ms를 넘었는지 점검합니다. 이전에 msec_to_jiffies(m) 함수는 ((m + 9) / 10) 공식으로 밀리초를 HZ단위로 변환한다고 배웠습니다. 배운 내용을 바로 활용하면 수식의 결과는 (2 + 9)/10 = 1입니다. 3번 줄 코드가 실행할 때 jiffies가 100이면 end 지역 변수는 101입니다.  

3번 코드가 실행될 시점 jiffies에 msecs_to_jiffies(2) 함수 결괏값을 더해 end라는 지역 변수에 저장합니다. 이후 Soft IRQ 서비스 핸들러를 실행한 다음 4번 줄 코드를 실행합니다. 참고로 3~4줄 코드 사이에 while 문 전체를 생략했으니 __do_softirq() 코드 전체를 한번 열어서 읽어 주세요.

이때 jiffies 값이 end보다 작은지 점검합니다. 다른 조건도 있지만 timer_before() 함수 조건만 보겠습니다. 현재 시각인 jiffies가 end보다 작으면 if 문을 실행해서 restart 레이브를 실행하고, 아니면 wakeup_softirqd() 함수를 호출해서 ksoftirqd 쓰레드를 깨웁니다.

리눅스 커널 코드에 time_after()와 time_before() 함수는 모래알같이 자주 볼 수 있습니다.
함수 원리를 제대로 익혀 관련 코드를 보면 바로 이해하도록 평소 준비할 필요가 있습니다.

# Reference (커널 타이머관리)

BR,
Guillermo Austin Kim, 08/01/2018




리눅스 커널은 정말 오픈 소스 프로젝트일까(2)? 임베디드 에세이

경험은 실력을 낳고 실력은 경험으로 이어진다. 임베디드 리눅스 개발 경험은 그 만큼 중요하다. 데이터 시트에 맞게 디바이스 드라이버 구현을 하는 작업은 누구나 할 수 있다. 초보와 고수 차이는 디바이스 드라이버를 여러 다양한 환경에서 구동할 때 생기는 문제를 해결하는 능력이다. 임베디드 리눅스 개발 도중 여러 문제를 만난다. 그럼 이 문제를 해결하기 위해서 여러 단계를 밟아야 한다.

문제는 이런 문제 해결 분석과 같은 실무 경험이 리눅스 커널 개발자끼리도 공유되고 있지 않다는 것이다. 이 이유가 뭔 지 조금 더 살펴보자.

첫 번째는 실력 있는 임베디드 리눅스 개발자들은 대부분 바쁘다. 문제 하나를 해결하면 다른 문제를 봐야 할 경우가 많다. 내 주위에 밤 낮으로 문제를 해결해야 하는 고수 개발자를 보면 측은하기 까지 하다. 주말에 여유가 있으면 고갈된 체력을 비축하기 위해 쉬어야 한다. 이렇게 바쁜 개발자들이 어떻게 자신의 노하우를 정리해서 블로그나 리눅스 커뮤니티에 정리하겠는가? 

두 번째로 대부분 개발자들은 글쓰기를 싫어한다. 이게 가장 큰 이유인 것 같다. 이공계 계통 능력자들은 대부분 이런 캐릭터인 것 같다. 나도 개발하면서 개발자들과 이메일이나 메일링 리스트로 다른 개발자들이 쓴 메일을 읽으면 가끔 이해가 안 갈 때가 많다. 기승전결 없이 자신의 생각이나 의견을 토해내는 내용인 경우가 많다. 코드나 디버깅 하기 바쁘니 그러려니 할 때도 있다. 그런데 사실 바쁘지 않아도 이 계통 분들은 글 쓰길 싫어한다. 그 이유는 난 잘 모르겠다. 특히 임베디드 계통은 그 비율이 더 적은 것 같다.

세 번째로 그 수가 많은 지 모르겠지만 임베디드 개발자들이 자신의 개발 노하우를 공유하길 꺼려하는 경우다. 자신만의 영업 기밀이라 생각하는 것 같다. 리눅스 커널은 오픈 소스라 어느 회사나 리눅스 커널을 포팅해서 제품을 만들면 소스 코드를 오픈해야 한다. 하지만 문제 해결 방법을 오픈해야 하는 것은 아니다.

사실 리눅스 커널에 대한 노하우나 Q&A는 stack overflow 같은 여러 사이트에서 확인할 수 있고 코드를 열심히 분석하고 질문을 올리면 답을 받을 수 있는 공간도 있다. 문제는 이런 사이트에서 활동하려면 영어를 잘 해야 한다. 

네 번째로는 한국 임베디드 리눅스 개발자들은 영어를 잘하지 못한다. 한국 임베디드 업종에 종사하는 분들을 만나면 다들 영어 때문에 스트레스를 많이 받는 걸 많이 봤다.

이런 이유로 임베디드 리눅스 개발 노하우가 공유되지 않으니 한국 임베디드 리눅스 개발자들은 회사에서 고참 개발자들 어깨 너머로 개발 노하우를 배워야 하는 처지다.

임베디드 리눅스 개발자로 실력을 인정 받으려면 많은 지식을 쌓아야 한다. 아는 것은 물론이고 실전 지식으로 개발에 써 먹을 정도로 잘 알아야 한다. ARM 아키텍처, 익셉션 원리, 어셈블리 명령어, 리눅스 커널 자료 구조, 회로도와 데이터 시트 보는 방법, 고급 C 언어 및 알고리즘 분석 그리고 Trace32, Crash-Utility, ftrace, Java Script 등등… 한도 끝도 없다. 

체계적으로 셋업이 안된 인프라에서 이런 고급 지식을 익히는 개발자의 현실은 녹녹하지 않다. 정말 리눅스 커널이 오픈 소스인가 묻고 싶다. 

[라즈베리파이] 커널 타이머 - msecs_to_jiffies 함수란 [라즈베리파이] 타이머관리

커널은 타이머를 HZ 단위 상대 시간을 나타내는 jiffies값을 기준으로 관리 합니다. 그리고 커널 타이머 함수는 HZ 단위 시간 정보를 받아 처리합니다. 그래서 커널이 이해할 수 있는 단위로 시간 정보를 변환시켜 줘야 합니다. msecs_to_jiffies() 함수가 이 역할을 수행합니다. 이 함수는 가독성도 높습니다. 함수 이름만 봐도 무슨 뜻인지 알기 쉽습니다.

실제 리눅스 커널에서 msecs_to_jiffies() 함수를 쓰는 코드를 살펴보겠습니다. 

다음은 pstore 파일 시스템 코드 일부 조각입니다.
[fs/pstore/platform.c]
pstore_timer.expires = jiffies +
2 msecs_to_jiffies(pstore_update_ms);
3 add_timer(&pstore_timer);

1번 줄 코드를 보면 pstore_update이란 밀리초 단위 상수를 msecs_to_jiffies() 함수를 써서 HZ(jiffies) 단위로 변환 후 pstore_timer.expires에 저장합니다. 이렇게 동적 타이머 만료 시각을 설정한 후 3번 줄 코드와 같이 add_timer() 함수를 호출해서 동적 타이머를 실행합니다. 

다른 코드를 보겠습니다.
[arch/ia64/kernel/salinfo.c]
1 static int __init
2 salinfo_init(void)
3 {
4 init_timer(&salinfo_timer);
5 salinfo_timer.expires = jiffies + SALINFO_TIMER_DELAY;
6 salinfo_timer.function = &salinfo_timeout;
7 add_timer(&salinfo_timer);

8#define SALINFO_TIMER_DELAY (60*HZ)

5번 줄 코드를 보면 현재 시각인 jiffies에 SALINFO_TIMER_DELAY이란 매크로를 더해서 만료 시각으로 설정합니다. 8번 줄 코드와 같이 SALINFO_TIMER_DELAY는 (60 * HZ) 이므로 60초를 의미합니다. 7번 줄을 보면 이번에도 add_timer() 함수를 호출해서 60초 만료 시각으로 동적 타이머를 실행합니다.

msecs_to_jiffies() 함수는 커널 코드에서 많이 쓰므로 어떤 의미인지 잘 알아 둘 필요가 있습니다. 바로 msecs_to_jiffies() 함수 구현부를 보겠습니다.
1 static __always_inline unsigned long msecs_to_jiffies(const unsigned int m)
2 {
3 if (__builtin_constant_p(m)) {
4 if ((int)m < 0)
5 return MAX_JIFFY_OFFSET;
6 return _msecs_to_jiffies(m);
7 } else {
8 return __msecs_to_jiffies(m);
9 }
10}

3번 줄 코드는 msecs_to_jiffies() 함수 입력 인자인 m 값이 정수인지 점검하는 코드입니다. msecs_to_jiffies() 함수는 인라인 형태 함수입니다. 심볼 없이 함수에 복사되는 코드 블록입니다. 컴파일러는 m이 정수가 아닌 경우 0으로 리턴하고 정수인 경우 1로 리턴합니다.

4번 줄은 m이라는 인자가 0보다 작을 경우 MAX_JIFFY_OFFSET 매크로를 리턴합니다. msecs_to_jiffies() 함수 선언부를 보면 unsigned long 타입 변수를 리턴하므로 MAX_JIFFY_OFFSET 매크로도 unsigned long일 겁니다.

이제 MAX_JIFFY_OFFSET 매크로 선언부를 보겠습니다.
#define MAX_JIFFY_OFFSET ((LONG_MAX >> 1)-1)
#define LONG_MAX ((long)(~0UL>>1))

MAX_JIFFY_OFFSET 매크로는 LONG_MAX 매크로를 치환해서 비트 연산자를 구성합니다.
결국 LONG_MAX 매크로에 선언된 연산자를 MAX_JIFFY_OFFSET에 대입하면 다음과 같은 코드를 볼 수 있습니다.
#define MAX_JIFFY_OFFSET ((((long)(~0UL>>1)) >> 1)-1)

MAX_JIFFY_OFFSET 매크로에 선언된 연산자를 단계별로 풀어볼까요?
((((long)(~0UL>>1)) >> 1)-1)
((((long)(0xFFFFFFFF >> 1)) >> 1)-1)
((((long)(0x3FFFFFFF)) >> 1)-1)
((0x1FFFFFFF)-1)
0x1FFFFFFE (536870910)

단계별로 계산한 연산 결과는 0x1FFFFFFE (536870910)입니다. 라즈베리파이에서는 HZ가 100이니 연산 결과는 5368709초이고 89,478분입니다. 밀리초를 음수로 전달하면 엄청난 HZ 단위 jiffies 값을 리턴합니다.

3~5번 줄은 예외 처리 코드입니다. 제대로 m인자를 밀리 초 상수 단위로 전달하면 6번 코드가 실행합니다.

이번에 6줄 코드를 봅시다. msecs_to_jiffies() 함수에 밀리초 단위 숫자를 인자로 전달하면 _msecs_to_jiffies(m) 함수를 호출합니다.

이제 _msecs_to_jiffies() 함수를 봅시다. _msecs_to_jiffies() 함수는 매크로로 연산을 수행하니 C 코드와 전처리 코드로 함수를 같이 보면 빨리 코드를 읽을 수 있습니다.
static inline unsigned long _msecs_to_jiffies(const unsigned int m)
{
return (m + (MSEC_PER_SEC / HZ) - 1) / (MSEC_PER_SEC / HZ);
}

static inline  unsigned long _msecs_to_jiffies(const unsigned int m)
 {
  return (m + (1000L / 100) - 1) / (1000L / 100);
 }

MSEC_PER_SEC 매크로는 1000L, HZ는 100입니다. 각 CPU 별로 다른 HZ 설정값에 따라 다른 연산을 하도록 구현된 코드입니다. 

_msecs_to_jiffies() 함수는 (m + (1000L / 100) - 1) / (1000L / 100) 연산자 수식임을 알 수 있습니다.

이번에도 복잡해 보이는 연산자 수식을 단계별로 풀어 봅시다.
m + (1000L / 100) – 1)/(1000L / 100);
m + (10) – 1) / 10;
(m + 9) / 10;

위 연산자를 차례로 푸니 밀리 초는 (m + 9)/10 공식으로 HZ 단위로 변환한다는 사실을 알 수 있습니다.

만약 10ms이면 jiffies 값은 어떻게 변환할까요? 다음 공식으로 1이됩니다.
(10 + 9) / 10 = 19 / 10 = 1

이번에는 11ms부터 20ms를 jiffie로 변환한 결과입니다.
밀리초     jiffies
11 2
12 2
13 2
14 2
15 2
16 2
17 2
18 2
19 2
20 2
21 3

HZ가 100이면 -+5ms만큼 총 10ms 오차가 있음을 알 수 있습니다.

HZ 단위 값으로 실행 흐름을 제어하는 코드를 짤 때 10ms 정도 오차가 있음을 염두할 필요가 있습니다. 또한 msecs_to_jiffies() 함수를 보면 위에서 살펴본 바와 같이 (m + 9)/10 이라는 공식을 떠올려 수식을 계산해 봅시다. 바로 코드를 읽을 수 있으니 더 빨리 동작을 이해할 수 있습니다.

이번에 밀리 초를 jiffies(HZ) 단위로 바꾸는 msecs_to_jiffies() 함수를 알아봤습니다. 다음에 HZ단위인 jiffies로 커널 실행 흐름을 제어하는 방법을 알아보겠습니다.


BR,
Guillermo Austin Kim, 07/30/2018

[라즈베리파이] 커널 타이머 - jiffies란 값은 누가 언제 증가하나 [라즈베리파이] 타이머관리

jiffies에 대해 조금 더 깊이 알아보겠습니다. jiffies는 커널 시스템 타이머가 동적 타이머를 처리하는 횟수입니다. 그럼, jiffies란 값은 누가 언제 증가시킬까요? 이 변수는 타이머 인터럽트가 발생하고 난 후 do_timer() 함수에서 jiffies를 업데이트합니다. 

소스 코드를 보면 이 내용을 더 쉽게 이해할 수 있으니 바로 코드 분석으로 들어가겠습니다.
[kernel/time/timekeeping.c]
1 void do_timer(unsigned long ticks)
2 {
3 jiffies_64 += ticks;
4 calc_global_load(ticks);
5}

3번 줄 코드를 보면 ticks이란 인자를 jiffies_64이란 jiffies 변수에 증감합니다.

여기서 do_timer() 함수는 누가 호출할까요? do_timer() 함수는 tick_do_update_jiffies64 () 함수에서 tick이란 변수를 1만큼 증감시키고 do_timer() 함수 인자로 전달합니다.

이 내용은 다음 7번 줄 코드를 보면 알 수 있습니다.
1 static void tick_do_update_jiffies64(ktime_t now)
2 {
...
3 if (delta >= tick_period) {
4
5 delta = ktime_sub(delta, tick_period);
6 last_jiffies_update = ktime_add(last_jiffies_update,
...
7 do_timer(++ticks);

이론으로 라즈베리파이에서 HZ가 100이니 jiffies는 1초에 100번 증감합니다.

이번에는 jiffies란 값이 실제 1초 안에 얼마나 증감하는지 확인하겠습니다. 이를 위해 다음 패치를 적용할 필요가 있습니다.
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -2194,11 +2194,17 @@ struct timespec64 get_monotonic_coarse64(void)
 }
 EXPORT_SYMBOL(get_monotonic_coarse64);
 
/*
  * Must hold jiffies_lock
  */
 void do_timer(unsigned long ticks)
 {
+ trace_printk("[+]jiffies_64: %llu, caller:%pS \n", 
+ jiffies_64, (void *)__builtin_return_address(0)); 
+
  jiffies_64 += ticks;
  calc_global_load(ticks);
 }

위 코드에서 + 기호는 새롭게 추가하는 코드를 표현합니다. 그러니 do_timer() 함수 앞단에 코드 2줄을 입력하면 됩니다.

패치 코드는 trace_printk()이란 함수를 써서 do_timer() 함수가 호출될 때 ftrace 로그를 출력합니다. jiffies_64 변수를 출력하고 __builtin_return_address(0) 빌트인 매크로를 써서 자신을 호출한 함수 정보를 출력합니다.

위 코드를 컴파일해서 라즈베리파이에 설치 후 리부팅을 시켜봅시다. 기존에 했던 방식으로 ftrace 로그를 받아 봅시다. 

이제 ftrace 로그를 볼 시간입니다.
1 <idle>-0     [000] d...   228.989960: do_timer: [+]jiffies_64: 4294960195, caller:tick_do_update_jiffies64.part.0+0xa0/0x180 
2 <idle>-0     [000] d...   229.000074: do_timer: [+]jiffies_64: 4294960196, caller:tick_do_update_jiffies64.part.0+0xa0/0x180
...
3 ibus-x11-706   [003] d.h.   229.989991: do_timer: [+]jiffies_64: 4294960295, caller:tick_do_update_jiffies64.part.0+0xa0/0x180 
4 <idle>-0     [003] d.h.   230.000012: do_timer: [+]jiffies_64: 4294960296, caller:tick_do_update_jiffies64.part.0+0xa0/0x180 

사실 위 ftrace 로그는 104줄인데 초가 바뀌는 부분만 소개한 겁니다

2번 줄 로그를 보면 229.000074이라는 숫자가 보이죠? 228초에서 229초로 처음 바뀌는 첫 타임스탬프입니다. 이때 HZ단위의 jiffies는 4294960196입니다.

이번에는 4번 줄 로그를 봅시다. 230.000012 타임스탬프를 볼 수 있습니다. 229초에서 230초로 처음 바뀐 시각입니다. 오른쪽 로그를 보면 jiffies는 4294960296입니다. 정확히 100만큼 증가했음을 알 수 있습니다.

HZ가 100이면 1초 안에 jiffies가 100번 증감된다는 사실을 확인했습니다.

이번에 do_timer() 함수은 어떻게 호출하는지 알아봅시다.

do_timer() 함수에 필터를 걸고 ftrace 로그를 받아보면 콜스택이 다음과 같습니다.
<idle>-0     [000] d.h.   230.859933: irq_handler_entry: irq=17 name=arch_timer
<idle>-0     [001] d.h.   230.859933: irq_handler_entry: irq=17 name=arch_timer
<idle>-0     [000] d.h.   230.859943: do_timer <-tick_do_update_jiffies64.part.0
<idle>-0     [000] d.h.   230.859993: <stack trace>
 => tick_sched_timer
 => __hrtimer_run_queues
 => hrtimer_interrupt
 => arch_timer_handler_phys
 => handle_percpu_devid_irq
 => generic_handle_irq
 => __handle_domain_irq
 => bcm2836_arm_irqchip_handle_irq
 => __irq_svc
 => arch_cpu_idle
 => arch_cpu_idle
 => default_idle_call
 => do_idle
 => cpu_startup_entry
 => rest_init
 => start_kernel

17번 arch_timer란 인터럽트가 발생한 후 tick_sched_timer() -> tick_do_update_jiffies() 함수 순서로 호출됐음을 알 수 있습니다. tick_sched_timer() 함수는 1초당 HZ 개수만큼 jiffies를 증감하는 역할을 하는 틱 디바이스 함수입니다.  

이번에 jiffies가 1초에 얼만큼 증감하는지 알아봤습니다. 이론상으로 HZ가 100인데 실제 1초에 100번 증감하는 동작을 확인했습니다. 다음 절에서는 밀리초를 HZ(jiffies)단위로 변환하는 msecs_to_jiffies 함수에 대해 살펴보겠습니다.


BR,
Guillermo Austin Kim, 07/30/2018

[라즈베리파이] 커널 타이머 - jiffies와 jiffies_64 변수란 [라즈베리파이] 타이머관리

jiffies에 대해 알아보기 전에 jiffies와 jiffies_64 전역 변수에 대해 짚고 가겠습니다. 리눅스 커널 코드를 보면 두 개 변수를 많이 볼 수 있는데 어떤 변수가 jiffies인지 헷갈릴 때가 많습니다.

예를 하나 들어보기 위해 다음 코드를 봅시다.
1 void pm_wakeup_ws_event(struct wakeup_source *ws, unsigned int msec, bool hard)
2 {
3 unsigned long flags;
4 unsigned long expires;
....
5 expires = jiffies + msecs_to_jiffies(msec);

pm_wakeup_ws_events() 함수 5번 줄 코드를 보면 expires이란 지역 변수에 jiffies와 msecs_to_jiffies(msec) 함수 리턴값을 저장합니다. msec 밀리 초 단위 값을 msecs_to_jiffies() 함수를 써서 HZ단위로 바꾼 후 jiffies에 더합니다. 변수 이름으로 보아 실행 마감 시각을 현재 시각 기준으로 msec 후로 설정합니다.

이번에는 다른 코드를 보겠습니다.
1 void do_timer(unsigned long ticks)
2 {
3 jiffies_64 += ticks;
4 calc_global_load(ticks);
5}

jiffies_64이란 전역 변수에 ticks이란 인자를 더합니다. 여기서 헷갈립니다. pm_wakeup_ws_events() 함수에서 쓰는 jiffies와 do_timer() 함수에서 증감하는 jiffies_64 변수는 다른 값일까요? 정답은 같은 값입니다.

실제 두 변수가 어떤 주소에 있는지 점검해 보겠습니다. 이번에는 라즈비안 리눅스 이미지를 Trace32 프로그램으로 올려서 확인합니다. 이를 위해 다음 Trace32 명령어로 두 변수가 어느 주소에 위치했는지 보겠습니다. 
var.view %d  &jiffies &jiffies_64
&jiffies = 0x80C02D00
&jiffies_64 = 0x80C02D00

확인 결과 두 변수가 같은 0x80C02D00 주소에 있습니다.

이번에는 각 변수가 어떤 값을 저장하고 있는지 살펴봅니다.
  (static long unsigned int) jiffies = 4294937296 = 0xFFFF8AD0
  (static u64) jiffies_64 = 4294937296 = 0xFFFF8AD0

같은 4294937296 값을 저장하고 있습니다.

이번에는 다른 테스트를 해보겠습니다. 다음 명령어로 강제로 jiffies 변수를 4294937297 값으로 바꿔 보겠습니다.
V %t jiffies = 4294937297

결과 두 변수가 같은 4294937297 값으로 바뀝니다.
var.view %decimal %type %hex  jiffies jiffies_64
  (static long unsigned int) jiffies = 4294937297 = 0xFFFF8AD1
  (static u64) jiffies_64 = 4294937297 = 0xFFFF8AD1

다음 링커 스크립트인 vmlinux.lds.S 파일을 봐도 jiffies와 jiffies_64는 같은 변수라는 정보를 알 수 있습니다.
[arch/arm/kernel/vmlinux.lds.S]
jiffies = jiffies_64;

위 설정으로 커널 컴파일을 하면 jiffies와 jiffies_64는 같은 주소에 위치합니다.

앞으로 리눅스 커널 코드에서 이 두 전역 변수를 보면 같은 변수라고 판단하고 코드를 분석하면 됩니다.
 
그럼 리눅스 커널에서는 왜 jiffies와 jiffies_64이란 변수가 두 개가 있을까요?
처음 리눅스 커널 코드는 타이머를 관리하는 변수인 jiffies는 u32 크기였습니다. 이후 64비트 아키텍처를 도입하면서 더 큰 크기 변수를 저장하는 jiffies_64란 전역 변수를 쓰기 시작했습니다. 그런데 문제가 생겼습니다. 기존 jiffies라는 변수로 드라이버 코드를 작성한 개발자들이 자신들이 짠 코드를 모두 jiffies_64로 수정해야 했습니다. 또한 다른 리눅스 커널 개발자들은 실행 흐름을 jiffies 변수로 처리하는데 익숙했습니다. 결과 리눅스 커널 소스 관리가 어렵게 됐습니다. 그래서 같은 주소 공간에 있는 변수로 링커 스크립트를 변경한 겁니다.

겉으로 전혀 달리 보이는 변수인데 기존 jiffies와 jiffies_64 변수가 어떻게 같이 공존할 수 있을까요? 그 이유는 두 변수는 상대적인 시각을 표현하는 값이기 때문입니다. 그래서 링커 스크립트로 jiffies와 jiffies_64를 같은 주소에 위치시켜 이런 문제를 없앤 것이죠.

다음에는 jiffies란 값을 어느 코드에서 증감하는지 알아보겠습니다.



BR,
Guillermo Austin Kim, 07/23/2018

[라즈베리파이] 커널 타이머(Kernel Timer) - jiffies란 [라즈베리파이] 타이머관리

jiffies는 커널 타이머를 실행하는 시간 단위입니다. 공학 용어로 Resolution이라고 합니다. jiffies를 지피스라고도 하는데 리눅스 커널에서 쓰는 jiffies란 용어를 그대로 쓰겠습니다.

jiffies 개념을 이해하려면 HZ에 대해 배워야 합니다. 그럼 HZ는 뭘 의미할까요? HZ는 1초당 커널 타이머가 동적 타이머를 처리하는 횟수를 의미합니다. 그래서 커널 타이머의 실행 빈도는 HZ에 의해 정해집니다. 커널 타이머가 로컬 타이머를 처리하는 단위라고 말할 수 있습니다. 

HZ가 500이면 1초당 jiffies가 500번 +1만큼 증감하고 500번 커널 타이머가 실행합니다. 그럼 HZ가 크면 좋은 시스템일까요? 그렇지는 않습니다. HZ를 너무 큰 값으로 설정하면 시스템 과부하가 걸릴 수 있습니다. HZ값이 늘어나면 타이머를 처리하는 횟수도 증가하기 때문입니다. 반대로 HZ가 너무 적으면 동적 타이머를 처리하는 횟수가 적으므로 타이머 만료 시각 처리에 오차가 생깁니다. 

이미 수많은 커널과 CPU 개발자들이 여러 시행착오로 CPU 별로 HZ값을 이미 정해 놨습니다. 그럼 라즈베리파이에서 HZ는 얼마일까요? 라즈베리파이는 ARM CPU를 탑재하므로 HZ값이 100입니다. 참고로 ARM 계열 리눅스 커널은 HZ값이 100입니다. 이 값은 라즈비안 커널을 빌드하면 생성되는 .config 파일에서 다음과 같이 확인할 수 있습니다.
CONFIG_HZ=100

라즈비안에서 HZ는 100이니 앞으로 이 기준으로 jiffies값으로 상대 시간을 어떻게 처리하는지 알아봅시다. 

예를 들어 지금 현재 시각을 나타내는 jiffies가 1000이라고 가정합시다. 이때 어떤 함수가 2초 안에 실행하는지 점검하려고 합니다. 현재 시각 기준으로 2초 후는 어떻게 표현할까요? 현재 시각인 jiffies에 2*100(HZ)을 더하면 1200입니다. 이 1200은 현재 시각 정보를 표현하는 1000보다 2초 후 시각 정보입니다. 

현재 jiffies가 1000이면 초당 다음 값으로 업데이트됩니다.
1초 후: 1100
2초 후: 1200
3초 후: 1300

jiffies란 용어는 2가지 개념으로 생각할 수 있습니다.
현재 시각: “지금 jiffies가 304200입니다.”라는 문장은 jiffies 라는 변수가 현재 시각 정보를 나타낸다는 의미입니다.

상대 시각: “jiffie 단위로 200ms로 마감시각을 설정합니다.” 이 문장에서 jiffies는 어떤 의미일까요? jiffies 는 HZ 단위로 시간 정보를 표현하는 단위라 할 수 있습니다. 시간을 현재 시각 값인 jiffies HZ단위 기준으로 설정합니다.

이런 개념을 섞어서 jiffies라는 용어를 쓰니 약간 헷갈릴 수 있는데 다음 절에서 소개해는 ftrace 로그를 같이 분석하면 확실히 jiffies의 개념을 잡을 수 있습니다.

이번에는 Soft IRQ 관점으로 HZ의 의미를 알아봅시다.
커널 타이머는 Soft IRQ로 1초에 HZ만큼 TIMER_SOFTIRQ 아이디로 Soft IRQ 서비스를 요청하고 Soft IRQ 서비스를 실행하는 __do_softirq() 함수에서 로컬 타이머를 실행합니다. 커널 타이머를 실행하는 단위를 HZ라고 할 수 있습니다. 라즈베리파이는 HZ가 100이니 1초에 100번 커널 시스템 타이머가 실행합니다.

HZ 단위 값은 mod_timer(), add_timer() 함수 및 timer_after()와 timer_before()에 전달하는 인자입니다. 관련 코드를 보면서 HZ단위로 시각값을 어떻게 전달하는지 알아봅시다.

우선 mod_timer() 함수를 보겠습니다.
1       static timer_list dynamic_timer
2 int timeout = 0;
3 timeout = jiffies;
4 timeout += 2 * HZ;
5
6 mod_timer(&dynamic_timer, timeout);

먼저 3번 줄 코드를 봅시다.
timeout이라는 지역변수에 현재 시각을 표현하는 jiffies 값을 대입합니다. 

4번 줄 코드는 timeout이라는 지역변수에 (2 * HZ) 값을 더합니다. HZ는 1초 동안 jiffies가 증감하는 횟수를 표현하니 (2 * HZ) 값은 2초를 나타내는 HZ 단위 시간 정보입니다. 

6번 줄 코드는 mod_timer() 함수 두 번째 인자로 timeout을 전달합니다. 현재 시각 기준으로 2초후에 동적 타이머를 실행하라는 의미입니다.

이렇게 로컬 타이머가 2초 후에 만료하려면 다음과 같이 (jiffies + 2 * HZ) 인자를 mod_timer() 함수에 채워서 호출해야 합니다.

다음 mod_timer() 함수 선언부를 봅시다.
[/include/linux/timer.h]
extern int mod_timer(struct timer_list *timer, unsigned long expires);

두 번째 인자가 expires인데 여기에 HZ 단위 시간 정보를 전달해야 합니다.

이번에 커널 코드에서 아주 많이 볼 수 있는 timer_after() 함수 사용 예를 확인합시다.
int elapsed_time = jiffies + ( 3 * HZ )
//
// 특정 함수 실행 구간
//
if (timer_after(jiffies, elapsed_time) {
panic(“Current function should have been executed within elapsed_time\n”);
}

jiffies는 이 코드가 실행할 때 시각 정보이고 elapsed_time는 HZ단위 시각 정보입니다. 현재 시각인 jiffies 값이 elapsed_time보다 크면 panic()이란 함수를 호출해서 커널 크래시를 유발하는 코드입니다. 조금 더 구체적으로 이 코드는 함수 특정 구간 실행 시간이 3초를 지나면 커널 크래시를 일으킵니다.

이렇게 timer_after() 함수에 전달하는 두 인자도 HZ 기준 시간 값들입니다.

커널 타이머에서 HZ는 상대 시간을 표현하는 매우 중요한 단위입니다. 그래서 이 개념을 정확히 이해해야 코드를 제대로 읽을 수 있습니다. 혹시 HZ와 jiffie에 대한 개념이 이해가 안 가도 걱정할 필요는 없습니다. 라즈베리파이를 동작시켜 ftrace 로그를 받아 timer 로그를 분석하면 자연히 머릿속에 들어올 겁니다.

다음에 소스 코드에서 jiffies와 jiffies_64 변수의 의미를 알아보겠습니다.

BR,
Guillermo Austin Kim, 07/18/2018



[임베디드] 한국 IT 회사 관리자들은 왜 허접할까?[1] - 한물간 퇴물 개발자 임베디드 에세이

한물간 퇴물 개발자

직급은 차장인데 대리 초반 개발자와 비슷한 개발 업무를 맡는 경우 회사는 이 차장을 어떻게 생각할까? 월급은 대리보다 더 많이 받는데 일을 비슷하게 하니 가성비가 낮다고 생각할 것이다.

이럴 때 정상적인 소프트웨어 회사면 차장 직급에 맞게 난이도 있는 개발 업무를 맡으라고 권유할 것이다. 그런데 현실은 다르다. 한국 소프트웨어 회사는 이런 차장에게 개발을 하면서 관리를 시킨다. 그 이유는 퇴물 개발자들은 학습 능력이 거의 거세되어 있어 새로운 기술을 거의 습득하지 못하기 때문이다.

그러니 회사는 관리 업무 중 시간만 갈아 넣으면 할 수 있는 일을 시킨다. 예를 들면 문서 취합이나 코드 변경 내역 정리와 같이 엑셀만 알면 할 수 있는 일이다. 이런 관리 업무를 하다 보니 동시에 맡고 있는 개발 능력은 점점 떨어져 가다가 관리자의 길을 걷게 되는 것이다.
 
소프트웨어 관리도 전문 영역이다. 기술에 대한 폭 넓은 지식, 개발자의 업무 패턴과 심리를 캐치하는 능력 그리고 기술 의사소통 능력이 요구된다. 개발자들 보다 더 많은 노력을 해야 뛰어난 관리자가 될 수 있다. 이를 위해 누구보다 뛰어난 학습 능력이 요구된다.

차근차근 소프트웨어 관리자가 되기 위한 준비 과정 없이 한물간 개발 실력으로 등 터 밀리다 싶히 관리자 노릇을 시작하는 경우가 많다. 한 마디로 개발을 못하니 관리자가 되는 것이다. 이런 소프트웨어 관리자가 개발자를 잘 관리할 수 있을까? 현실적으로 그럴 가능성은 거의 없다고 봐야 한다.

퇴물 개발자 출신 관리자가 다른 회사로 갈 데가 없는 것을 개발 임원들은 참 잘 안다. 그래서 이런 퇴물 관리자들에게 더러운(?) 일을 시키는 경우도 있다. 팔짱에 투명 완장을 채워 주고 개발자 군기 반장을 시킨다. 또한 다른 개발자가 퇴근을 못하게 하는 경비원 역할도 맡긴다. (이런 개발 임원은 개발자들 앞에서 천사 같이 쇼를 한다. 왜 이렇게 늦게 가냐고 하면서..)

나중에 개발자가 퇴사를 하거나 갑질로 회사에 클래임을 걸면 이런 퇴물 개발자 출신 관리자에게 모든 책임을 떠 넘기고 개발 임원들은 나 몰라라한다.

포르노 배우 같이 몸으로 때우는 일만 하는 퇴물 개발자 출신 개발자들은 인간적으로 참 불쌍하기 까지 하다. 이렇게 공부를 안하는 개발자들의 말로는 참 비참하다.

BR,
Guillermo Kim, 07/18/2018

`


[라즈베리파이] Soft IRQ 서비스는 누가 언제 처리하나? [2] - __do_softirq() 분석 [라즈베리파이]인터럽트후반부

이제 Soft IRQ 의 핵심 코드인 __do_softirq 함수를 분석할 차례입니다. 전체 코드는 다음과 같습니다.
1 asmlinkage __visible void __softirq_entry __do_softirq(void)
2{
3 unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
4 unsigned long old_flags = current->flags;
5 int max_restart = MAX_SOFTIRQ_RESTART;
6 struct softirq_action *h;
7 bool in_hardirq;
8 __u32 pending;
9 int softirq_bit;
10
//...
11
12 pending = local_softirq_pending();
13 account_irq_enter_time(current);
14
15 __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
16 in_hardirq = lockdep_softirq_start();
17
18 restart:
19 /* Reset the pending bitmask before enabling irqs */
20 set_softirq_pending(0);
21
22 local_irq_enable();
23
24 h = softirq_vec;
25
26 while ((softirq_bit = ffs(pending))) {
27 unsigned int vec_nr;
28 int prev_count;
29
30 h += softirq_bit - 1;
31
32 vec_nr = h - softirq_vec;
33 prev_count = preempt_count();
34
35 kstat_incr_softirqs_this_cpu(vec_nr);
36
37 trace_softirq_entry(vec_nr);
38 h->action(h);
39 trace_softirq_exit(vec_nr);
//...
40 h++;
41 pending >>= softirq_bit;
42 }
50
43 rcu_bh_qs();
44 local_irq_disable();
45
46 pending = local_softirq_pending();
47 if (pending) {
48 if (time_before(jiffies, end) && !need_resched() &&
49     --max_restart)
50 goto restart;
51
52 wakeup_softirqd();
53 }

우선 __do_softirq 함수에 선언된 지역 변수부터 살펴봅니다.
1 asmlinkage __visible void __softirq_entry __do_softirq(void)
2{
3 unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
4 unsigned long old_flags = current->flags;
5 int max_restart = MAX_SOFTIRQ_RESTART;

#define MAX_SOFTIRQ_TIME  msecs_to_jiffies(2)
#define MAX_SOFTIRQ_RESTART 10

MAX_SOFTIRQ_TIME 매크로는 msecs_to_jiffies(2) 코드로 치환됩니다. msecs_to_jiffies 함수는 현재 시각 기준으로 2밀리 초를 jiffies 값으로 바꾸는 함수입니다. 이 값을 현재 시각을 표현하는 jiffies란 변수에 더해서 end란 지역변수에 저장합니다. 현재 시각에서 2밀리 초를 end에 저장합니다. end란 로컬 변수는 이 __do_softirq 함수 실행 시간을 제한하려는 용도로 씁니다.

다음은 MAX_SOFTIRQ_RESTART 을 max_restart 지역 변수에 설정합니다. 이 값은 10입니다.
__do_softirq 함수에서 max_restart 지역 변수를 10으로 설정해 놓고 이 함수 내 restart 레이블을 실행할 때마다 --max_restart; 연산으로 1만큼 뺍니다. 만약 10번 restart 레이블을 실행해서 max_restart 변수가 0이 되면 restart 레이블 실행을 못하고 __do_softriq 함수는 도중에 실행을 종료합니다.

end, max_restart 이 로컬 변수는 __do_softirq 함수 실행에 대한 중요한 제약 조건을 설정하는 코드이니 잘 기억해야 합니다.

다음은 Soft IRQ 서비스 요청을 점검하는 코드입니다.
12 pending = local_softirq_pending();
13 account_irq_enter_time(current);
14
15 __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
16 in_hardirq = lockdep_softirq_start();
17

12번 줄 코드를 전처리 코드로 보면 다음과 같이 irq_stat 전역 변수에서 __softirq_pending 값을 pending 지역 변수로 읽어 옵니다.
(irq_stat[(current_thread_info()->cpu)].__softirq_pending);

pending 지역 변수는 로컬 변수로 선언했으므로 __do_softirq 함수를 실행하는 스택 메모리 공간에 있습니다. 전역 변수에 있는 값을 지역 변수로 복사하는 이유는 __do_softirq 함수가 실행하는 동안 irq_stat이란 전역 변수가 업데이트될 수 있기 때문입니다. 그래서 될 수 있는 대로 전역 변수는 지역 변수에 저장해서 처리해야 동기화 문제를 피할 수 있습니다.

이제는 restart 레이블 코드를 볼 차례입니다.
18 restart:
19 /* Reset the pending bitmask before enabling irqs */
20 set_softirq_pending(0);
21
22 local_irq_enable();
23
24 h = softirq_vec;

이번에도 전처리 코드로 분석하겠습니다.
set_softirq_pending 함수는 다음과 같이 치환됩니다. 해당 CPU의 irq_stat 배열 멤버인 __softirq_pending 변수를 0으로 초기화하는 겁니다.
((irq_stat[(current_thread_info()->cpu)].__softirq_pending) = (0));

24번째 줄 코드에서는 Soft IRQ 서비스 핸들러 정보가 담긴 softirq_vec 전역 변수를 h란 지역 변수에 저장합니다.
이제 Soft IRQ 서비스 핸들러를 호출하는 코드입니다.
26 while ((softirq_bit = ffs(pending))) {
27 unsigned int vec_nr;
28 int prev_count;
29
30 h += softirq_bit - 1;
31
32 vec_nr = h - softirq_vec;
33 prev_count = preempt_count();
34
35 kstat_incr_softirqs_this_cpu(vec_nr);
36
37 trace_softirq_entry(vec_nr);
38 h->action(h);
39 trace_softirq_exit(vec_nr);
//...
40 h++;
41 pending >>= softirq_bit;
42 }

26번째 줄 코드는 while 문을 구동하는 조건이므로 가장 유심히 눈여겨봐야 합니다.
26 while ((softirq_bit = ffs(pending))) {
27 unsigned int vec_nr;
28 int prev_count;

리눅스 커널에서 제공하는 ffs란 라이브러리 함수를 써서 pending 이란 변수에서 Soft IRQ 서비스를 설정한 비트를 찾습니다. 만약 pending 지역변수가 20이면 2진수로는 10100입니다.

ffs(20)을 입력하면 가장 먼저 1이 세팅된 비트 위치인 2를 알려줍니다.
30번째 코드는 softirqbit에서 1만큼 뺍니다. 이 이유는 뭘까요?
30 h += softirq_bit - 1;

irq_stat[cpu].__softirq_pending 변수에 Soft IRQ 서비스 아이디별로 설정된 비트 값이 Soft IRQ 서비스 핸들러 함수를 저장한 softirq_vec 배열 위치보다 1만큼 크기 때문입니다. 1 << 0(HI_SOFTIRQ) 연산 결과가 1 입니다. 이 관계를 그림으로 정리하면 다음과 같습니다.


각 Soft IRQ 서비스 아이디를 설정하는 __softirq_pending 변수의 비트 필드와 이를 실행하는 Soft IRQ 서비스 핸들러를 저장하는 softirq_vec 배열은 아주 밀접한 관계입니다.

38번째 코드는 Soft IRQ 서비스 핸들러를 호출합니다. 위 그림에서 softirq_vec에 표시된 함수를 호출하는 겁니다.
37 trace_softirq_entry(vec_nr);
38 h->action(h);
39 trace_softirq_exit(vec_nr);

라즈베리안의 경우 softirq_vec 이란 Soft IRQ 벡터에 등록된 함수들은 다음과 같습니다. 아래 함수 중 하나를 호출하는 것입니다.
v.v %t %d %i %y %l softirq_vec 
  (static struct softirq_action [10]) [D:0x80C02080] softirq_vec = (
    [0] = ((void (*)()) [D:0x80C02080] action = 0x80122888 = tasklet_hi_action),
    [1] = ((void (*)()) [D:0x80C02084] action = 0x80181270 = run_timer_softirq),
    [2] = ((void (*)()) [D:0x80C02088] action = 0x80614684 = net_tx_action),
    [3] = ((void (*)()) [D:0x80C0208C] action = 0x80615AB0 = net_rx_action),
    [4] = ((void (*)()) [D:0x80C02090] action = 0x804279B0 = blk_done_softirq),
    [5] = ((void (*)()) [D:0x80C02094] action = 0x0 = ),
    [6] = ((void (*)()) [D:0x80C02098] action = 0x8012299C = tasklet_action),
    [7] = ((void (*)()) [D:0x80C0209C] action = 0x801588EC = run_rebalance_domains),
    [8] = ((void (*)()) [D:0x80C020A0] action = 0x0 = ),
    [9] = ((void (*)()) [D:0x80C020A4] action = 0x8017ABC4 = rcu_process_callbacks))

37번째와 39번째 코드는 ftrace 로그를 출력하는 코드입니다. 

다음 코드는 pending에서 1로 설정된 비트를 없애는 코드입니다. 그래야 다음에 1로 설정된 비트를 읽을 수 있습니다.
40 h++;
41 pending >>= softirq_bit;

이렇게 while loop을 실행해서 pending 된 Soft IRQ 서비스 핸들러 호출을 마치면 다음 코드를 실행합니다. Soft IRQ 에서 가장 중요한 코드이니 조금 더 집중하시고 봅시다.
46 pending = local_softirq_pending();
47 if (pending) {
48 if (time_before(jiffies, end) && !need_resched() &&
49     --max_restart)
50 goto restart;
51
52 wakeup_softirqd();
53 }

46번째 줄 코드를 보면 다시 Soft IRQ 서비스 요청이 있었는지 확인합니다. __do_softirq 함수 첫 부분에서 Soft IRQ 서비스 요청이 있는지 확인했는데 왜 다시 확인할까요? 그 이유는 이 함수가 irq_exit 함수에서 호출될 수도 있지만 ksoftirqd 프로세스가 실행하는 run_ksoftirqd() 함수에서도 __do_softirq() 함수를 호출할 수 있습니다. ksoftirqd 프로세스 입장에서는 __do_softirq 함수 실행 도중 인터럽트가 다시 발생해서 누군가 Soft IRQ 서비스를 요청했을 수 있기 때문에 이 조건을 다시 검사하는 것입니다.

만약 Soft IRQ 서비스 요청이 있으면 48번 코드를 실행합니다. 여기서 restart 레이블을 10번 이상을 실행했는지와 time_before 함수를 써서 __do_softirq 함수 실행 시간이 2ms을 넘어서지는 않았는지 조건을 검사합니다. 

time_before 함수에 전달하는 jiffies 값과 end의 의미는 뭘까요? jiffies는 48번째 코드를 실행할 때 시각을 의미하고 end란 지역 변수에는 __do_softirq 함수가 처음 실행할 때 시각 정보를 담고 있는 jiffies에 2밀리 초를 jiffies로 환산한 값을 더한 값이 있습니다. 해당 코드를 전처리 코드에서 보면 다음과 같습니다.
void  __do_softirq(void)
 {
  unsigned long end = jiffies + msecs_to_jiffies(2);
  unsigned long old_flags = (current_thread_info()->task)->flags;
  int max_restart = 10;

이 jiffies와 end인자를 time_before란 타이머 함수에 전달해서 호출하면 __do_softirq를 실행한 시간이 2밀리 초를 넘지 않았으면 1을 리턴하고 2밀리 초를 넘으면 0을 리턴합니다.

max_restart 지역 변수는 처음에 10으로 설정했고 restart 레이블을 실행할 때마다 1만큼 뺍니다. 그런데 이 값이 0이면 뭘 의미일까요? 10번 restart 레이블을 실행했다는 정보입니다. __do_softirq 함수 실행 시간이 오래 걸리는 것을 방지하기 위한 코드입니다.
 
이번에는 wakeup_softirqd 함수를 볼 차례입니다.
1 static void wakeup_softirqd(void)
2 {
3 /* Interrupts are disabled: no need to stop preemption */
4 struct task_struct *tsk = __this_cpu_read(ksoftirqd);
5
6 if (tsk && tsk->state != TASK_RUNNING)
7 wake_up_process(tsk);
8 }

4번째 줄 코드를 보면 CPU 별로 생성된 ksoftirqd 쓰레드의 태스크 디스크립터를 가져옵니다. 현재 ksoftirqd 프로세스 실행 중인지를 점검한 후 wake_up_process 함수를 호출해서 ksoftirqd 프로세스를 깨웁니다.

이렇게 __do_softirq 함수가 처리 도중 wakeup_softirqd 함수를 호출하면 ksoftirqd 쓰레드를 깨우는데요. 나중에 프로세스 레벨에서 스케줄링 되어 실행됩니다. 

ksoftirqd 쓰레드는 어떻게 실행하는지 점검해야겠죠? 해당 코드는 다음과 같습니다.
1 static void run_ksoftirqd(unsigned int cpu)
2 {
3 local_irq_disable();
4 if (local_softirq_pending()) {
5 __do_softirq();
6 local_irq_enable();
7 cond_resched_rcu_qs();
8 return;
9 }
10 local_irq_enable();
11}

4번째 줄 코드를 보면 역시 local_softirq_pending 함수를 호출해서 Soft IRQ 서비스 요청이 있었는지 점검 후 __do_softirq함수를 호출합니다. 참고로 smp 핫플러그인 쓰레드로 실행하는 ksoftirqd 쓰레드의 속성은 다음과 같습니다.
static struct smp_hotplug_thread softirq_threads = {
.store = &ksoftirqd,
.thread_should_run = ksoftirqd_should_run,
.thread_fn = run_ksoftirqd,
.thread_comm = "ksoftirqd/%u",
};

여기까지 Soft IRQ 서비스를 어떻게 처리하는지 살펴봤습니다. 여러 코드를 넘나들면서 분석을 했는데요. 이렇게 Soft IRQ는 IRQ Thread 기법에 비해 인터럽트 후반부 처리를 더 세밀하게 처리합니다. 하지만 인터럽트 후반부 관점으로 Soft IRQ 동작을 다음과 같이 정리할 수 있습니다.

인터럽트가 발생하면 인터럽트 핸들러에서 Soft IRQ 서비스 요청을 합니다. 요청한 Soft IRQ 서비스는 인터럽트 핸들러 수행이 끝나면 irq_exit -> invoke_softirq -> __do_softirq 흐름으로 __do_softirq 함수에서 Soft IRQ 서비스를 실행합니다. 그런데 __do_softirq 함수 실행 시간이 2ms이상 이거나 10번 이상 Soft IRQ 서비스 핸들러를 호출하면 ksoftirqd 쓰레드를 깨우고 __do_softirq 함수는 바로 실행을 마칩니다. 이후 ksoftirqd 쓰레드가 깨어나면 이전 __do_softirq 함수가 실행 제한 조건에 걸려 처리 못한 Soft IRQ 서비스를 실행합니다.



임베디드 개발자 양극화는 얼마나 심각할까? (07/15/18) 임베디드 에세이

양극화는 우리 사회가 겪고 있는 많은 문제 중 하나다. 

양극화를 아주 이해하기 쉽게 설명하면, 
잘사는 가정에서 태어난 아이들은 평생 부유한 환경과 유리한 조건에서 살고 못사는 집안의 아이는 평생 라면만 먹고 산다는 것이다. 

양극화의 가장 큰 원인은 교육 기회의 박탈이다. 못사는 집안의 아이들은 부유한 가정의 아이들에 비교해 교육 환경이 좋지 못하니 좋은 대학에 가지 못하고 이로 좋은 회사에 취업도 못해 가난의 대물림이 계속된다는 것이다. 

이 문장을 임베디드 개발 양극화 관점으로 바꿔 표현해보자. 
좋은 개발 부서에서 실무 프로젝트를 수행한 개발자는 계속 실력이 향상하며 좋은 대우를 받으나 허접한 개발 부서에서 개발을 시작한 개발자는 개발 능력이 업그레이드되지 않아 평생 라면만 먹으며 낮은 연봉을 받으며 일한다는 것이다. 

그러면 임베디드 개발자도 양극화가 있을까? 내가 겪어 본 바로는 개발자 양극화도 심하다고 본다. 
내가 말하는 양극화는 기회의 박탈이다. 즉 실무 경험을 통해 개발 능력을 키울 기회를 의미한다. 

임베디드 개발자 등급도 소고기 등급과 같이 특급부터 3등급까지 여러 등급으로 나눌 수 있는데 언제 등급이 매겨지는지 살펴보자. 

가장 크게, 임베디드 개발자 등급은 취업할 때 입사하는 회사에 따라 나눠진다. 
여기서 말하는 회사는 연봉과 이름값만을 의미하지 않는다. 역량 있는 개발자로 성장할 수 있는 실무 프로젝트를 할 수 있는 회사다. 

아래 조건에서 임베디드 시스템을 개발을 시작하는 개발자는 우선 3~4등급으로 분류될 수밖에 없다. 왜냐? 
제대로 개발을 안 하니 실무 경력이 쌓이지 않기 때문이다. 

1. 임베디드 장비로 디바이스 테스트만 하거나 시간을 많이 갈아 넣으면 할 수 있는 소모적인 일을 하는 개발 부서 
2. 코딩한 줄 하지 않고 다른 업체가 포팅한 드라이버를 올려 테스트를 하며 갑질을 하는 개발 부서 
3. 이미 안정화된 리눅스 커널에 드라이버 코드만 약간 수정하면서 프로젝트를 진행하는 개발 부서 
4. 남들이 해 놓은 드라이버를 컴파일이나 빌드를 빨리 해서 디바이스에 올리는 일을 하는 개발 부서 

회사의 이름 네임밸류가 높거나 연봉이 높다고 해도 이런 조건으로 일하는 임베디드 개발자의 역량을 인정해 줄까?

절~대 그렇지 않다. 

임베디드 개발자의 생명은 디버깅 능력이라 생각한다. 디버깅 능력은 문제 해결 능력과 직결되며 문제 해결 능력이 없는 개발자는 이 업계에서 인정해주지 않는다. 그런데 제대로 실무 프로젝트를 맡으며 만나는 여러 문제를 해결하는 과정이 필요한데 이런 **기회**를 박탈당한 개발자는 디버깅 기술 역량이 업그레이드되지 않는다. 

내가 여러 임베디드 모임이나 친구들과의 교류로 알게 된 사실은 많은 한국 임베디드 회사는 제대로 개발을 안 한다는 것이다. 
그 이유는 한국 임베디드 IT 업체들은 실리콘 밸리에 있는 개발자와 경쟁할 수 있는 원천 기술이 녹아 있는 소프트웨어를 개발하려는 의지가 보이지 않기 때문이다. 대부분 미국 등 해외 업체가 구현한 소프트웨어를 올려서 커스터마이즈하는 수준의 일을 하는 경우가 많다. 

그럼 이제 1차 관문에서 다른 임베디드 개발자 등급은 가려졌으니 이제 1등급부터 특급으로는 가는 길만 있지 않을까? 
하지만 현실은 그렇지 않다. 운 좋게 1차 관문을 통과해서 좋은 회사에 취업해도 3~4등급으로 떨어질 수 있다. 

제대로 실무 경험을 쌓을 수 있는 회사에 배치됐다고 해도 그 부서 배치나 그 부서의 개발자 육성 정책에 따라 개발자 등급이 또 나뉘기 때문이다. 

1. 회사의 운명을 결정 짓는 최신 임베디드 시스템을 개발하는 부서에 배치된 경우 
- 쟁쟁한 개발자 틈에서 맨날 테스트만 한다. 
- 핵심 디버깅 기법은 핵심 개발자들끼리 이야기하고 공유하지 않는다. 
- 맨날 이메일을 쓰면서 다른 업체 인터페이스 역할만 한다. 

2. 정치 싸움에 밀려 개별화만커스터마이즈만 하는 프로젝트를 하는 개발 부서에 배치 
- 빌드 머신이 되어 하루에 3~4개씩 이미지를 배포하고 테스트만 한다. 코드 분석할 시간은 물론 없다. 
- 커스터마이즈만 하는 일만 하는 부서이니 선배 개발자 개발 능력이 허접해 배울 것이 없다. 

이름 값하는 회사에서도 벌레처럼 기어다니듯 개발자도 수 없이 많다.

제대로 개발 능력을 키울 수 있는 프로젝트에 투입되는 것이 현실에서는 **정말** 쉽지 않다. 
1~2 관문을 통과하고 정말 실무 경험을 키울 수 있는 프로젝트에서 일하면 특급 개발자가 될 수 있나? 
그렇지 않다. 끊임없는 노력으로 학습을 해야 특급 개발자가 될 수 있는 것 같다.

`



[라즈베리파이] Soft IRQ 서비스는 누가 언제 처리하나?[1] [라즈베리파이]인터럽트후반부

이제 Soft IRQ 기법의 하이라이트인 Soft IRQ 서비스를 처리하는 흐름을 살펴볼 차례입니다. Soft IRQ 서비스는 언제 처리할까요? 아래 Soft IRQ 전체 흐름도와 같이 인터럽트 핸들러를 처리하는 인터럽트 서비스 루틴이 끝나는 시점에 Soft IRQ 서비스 처리를 시작합니다. 그래서 Soft IRQ 서비스 처리를 시작하는 코드를 점검하려면 인터럽트 서비스 루틴이 끝나는 코드부터 확인해야 합니다.

이번 절에서는 Soft IRQ 전체 흐름도에서 볼드체로 된 부분을 점검할 예정이니 눈여겨보세요.


인터럽트 서비스 루틴이 끝나는 코드가 __handle_domain_irq 함수에서 실행하니 우선 이 함수부터 분석해야 합니다. 같이 볼 코드는 다음과 같습니다. 
1 int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *regs)
3 {
4 struct pt_regs *old_regs = set_irq_regs(regs);
5 unsigned int irq = hwirq;
6 int ret = 0;
7
8 irq_enter();
9
10 #ifdef CONFIG_IRQ_DOMAIN
11 if (lookup)
12 irq = irq_find_mapping(domain, hwirq);
13 #endif
14
15 /*
16  * Some hardware gives randomly wrong interrupts.  Rather
17  * than crashing, do something sensible.
18  */
19 if (unlikely(!irq || irq >= nr_irqs)) {
20 ack_bad_irq(irq);
21 ret = -EINVAL;
22 } else {
23 generic_handle_irq(irq);
24 }
25
26 irq_exit();  //<<---

다음 23번째 줄 코드에서 호출하는generic_handle_irq 함수 서브 루틴에서 인터럽트 핸들러를 호출합니다.
22 } else {
23 generic_handle_irq(irq);
24 }

예전에 봤던 ftrace 로그를 보면 아래 generic_handle_irq 함수가 보이죠? 이 함수가 호출된 후 62번 인터럽트 핸들러인 dwc_otg_common_irq 함수가 호출됐습니다.
kworker/0:0-27338 [000] 6028.897809: dwc_otg_common_irq <-__handle_irq_event_percpu
kworker/0:0-27338 [000] 6028.897847: <stack trace>
 => handle_irq_event
 => handle_level_irq
 => generic_handle_irq
 => bcm2836_chained_handle_irq
 => generic_handle_irq
 => __handle_domain_irq
 => bcm2836_arm_irqchip_handle_irq
 => __irq_svc

이제 인터럽트 핸들러 처리를 마치고 irq_exit() 함수를 호출합니다. 이 irq_exit() 함수가 Soft IRQ 서비스를 처리하는 시작점입니다. 이제 irq_exit() 함수를 분석하겠습니다.
1 void irq_exit(void)
2 {
3 account_irq_exit_time(current);
4 preempt_count_sub(HARDIRQ_OFFSET);
5 if (!in_interrupt() && local_softirq_pending())
6 invoke_softirq();
7
8 tick_irq_exit();
9 rcu_irq_exit();
10 trace_hardirq_exit(); /* must be last! */
11}

4번째 줄 코드를 보면 preempt_count_sub 함수를 호출합니다. struct thread_info->preeempt_count 변수에서 HARDIRQ_OFFSET를 뺍니다. 이제는 인터럽트 컨택스트가 아니라는 표시를 하는 겁니다.
4 preempt_count_sub(HARDIRQ_OFFSET);
5 if (!in_interrupt() && local_softirq_pending())
6 invoke_softirq();

그다음 5번째 줄 코드에서는 두 가지 조건을 점검합니다.

in_interrupt() 매크로를 호출해서 인터럽트 컨택스트가 아닌지 확인합니다. 4번째 줄 코드인 preempt_count_sub 함수가 처리된 직후 다른 인터럽트가 발생해서 다음 코드와 같이 preempt_count_add(HARDIRQ_OFFSET)를 실행해서 in_interrupt함수를 활성화할 수도 있기 때문입니다. 
#define __irq_enter()     \
  do {      \
   account_irq_enter_time(current); \
   preempt_count_add(HARDIRQ_OFFSET); \   
   trace_hardirq_enter();   \
  } while (0)

인터럽트는 언제 어디든 발생할 수 있으니 예외 처리 조건을 추가한 것입니다.

다음은 local_softirq_pending() 함수를 호출해서 Soft IRQ 서비스를 요청했는지 점검합니다. 전처리 코드로 irq_exit 함수를 확인하면 local_softirq_pending() 함수는 (irq_stat[(current_thread_info()->cpu)].__softirq_pending) 코드로 치환됩니다. 
void irq_exit(void)
{
...
__preempt_count_sub((1UL << ((0 + 8) + 8)));
if (!((preempt_count() & ((((1UL << (4))-1) << ((0 + 8) + 8)) | (((1UL << (8))-1) << (0 + 8)) | (((1UL << (1))-1) << (((0 + 8) + 8) + 4))))) && (irq_stat[(current_thread_info()->cpu)].__softirq_pending))
invoke_softirq();

이제 Soft IRQ 서비스를 실행하는 invoke_softirq() 함수를 점검해 봅시다.
1 static inline void invoke_softirq(void)
2 {
3
4 if (!force_irqthreads) {
5 #ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK
6 __do_softirq();
7  #else
8 do_softirq_own_stack();
9 #endif
10 } else {
11 wakeup_softirqd();
12 }
13 }

위 코드에서 CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK 컨피그에 따라 8번째 줄 혹은 10번째 줄 코드가 컴파일되는 것 같습니다. 이럴 때 라즈비안에서 리눅스를 빌드할 때 생성되는 .config 파일을 열어서 CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK 가 있는지 확인해야 할까요? 그럴 필요 없이 다음과 같이 전처리 코드를 열어보면 실제 컴파일하는 코드를 보여줍니다.
1 static inline void invoke_softirq(void)
2 {
3 if (!force_irqthreads) {
 # 361 "/home/pi/dev_raspberrian/src/rasp_kernel/linux/kernel/softirq.c"
4   do_softirq_own_stack();
5  } else {
6   wakeup_softirqd();
7  }

3번째 줄 코드부터 볼까요? force_irqthreads 변수를 1로 설정했으면 wakeup_softirqd 함수를 호출해서 ksofrirqd 쓰레드를 깨웁니다. 이 변수는 리눅스 커널을 부팅하기 전 부트로더에서 threadirqs를 커맨드라인으로 전달하면 설정하는데 라즈베리안에서는 0으로 설정돼 있습니다.
[kernel/irq/manage.c]
__read_mostly bool force_irqthreads;
static int __init setup_forced_irqthreads(char *arg)
{
force_irqthreads = true;
return 0;
}
early_param("threadirqs", setup_forced_irqthreads);

커맨드 라인이란 부트로더에서 리눅스 커널을 부팅시킬 때 전달하는 아규먼트와 같은 역할을 합니다. 리눅스 커널이 부팅할 때 이 커맨드 라인을 읽어서 커널 시스템 설정을 합니다. 

이번에는 6번째 줄 코드입니다. do_softirq_own_stack 함수는 바로 __do_softirq 함수를 호출합니다.
static void do_softirq_own_stack(void)
{
__do_softirq();
}

invoke_softirq 함수는 복잡해 보였는데 코드를 분석하니 바로 __do_softirq 함수를 호출하는 간단한 코드였습니다. 




리눅스 커널은 정말 오픈 소스 프로젝트일까(1)? 임베디드 에세이

리눅스 커널은 오픈 소소다. 모든 개발자나 학생들이 소스 코드를 들여다볼 수 있어 많은 정보를 알 수 있다. 함수 이름과 자료 구조를 보면서 리눅스 커널을 익힐 수 있다. 

난 운영체제를 소스 코드를 보면서 익힐 수 있는 리누즈 토발즈 및 오픈 소스 정신을 부르짖는 분들께 감사를 드리고 싶다. 리눅스 커널이 오픈 소스 프로젝트가 아니였다면 난 아마 RTOS 운영체제로 임베디드 디바이스 개발을 했거나 아예 다른 오픈 소스 프로젝트에 뛰어들었을 지도 모른다.
RTOS 회사가 알려주는 운영체제 개념도를 보면서 **그려러니** 하며 머릿속 개념으로 남아 있을 것이다.  

오픈 소스인 리눅스 커널로 누리는 혜택(?)이 있지만 만만치 않은 걸림돌이 있다.
리눅스 커널 소스 코드를 이해하면서 읽기가 너무 어렵다는 것이다. 구조체와 자료 구조가 너무 복잡하다. 

또한 리눅스 커널 소스에서 매크로를 정말 많이 쓴다. 그런데 매크로가 매크로를 5중으로 치환하면서 함수에서 쓰는 매크로 실제 값을 알기도 어렵다.

한 가지 예를 들어보자. 다음은 워크 멤버 중 data 필드로 워커 풀을 읽는 함수다.
static struct worker_pool *get_work_pool(struct work_struct *work)
{
unsigned long data = atomic_long_read(&work->data);
int pool_id;

assert_rcu_or_pool_mutex();

if (data & WORK_STRUCT_PWQ)
return ((struct pool_workqueue *)
(data & WORK_STRUCT_WQ_DATA_MASK))->pool;

pool_id = data >> WORK_OFFQ_POOL_SHIFT;
if (pool_id == WORK_OFFQ_POOL_NONE)
return NULL;

return idr_find(&worker_pool_idr, pool_id);
}

WORK_STRUCT_WQ_DATA_MASK이란 매크로 변수가 어떤 값인지 바로 이해할 수 있나?

다음 코드 선언부와 같이 WORK_STRUCT_FLAG_MASK 매크로를 알려면 WORK_STRUCT_FLAG_MASK, WORK_STRUCT_FLAG_BITS, WORK_STRUCT_COLOR_SHIFT, WORK_STRUCT_COLOR_BITS 매크로를 알아야 한다.
WORK_STRUCT_FLAG_MASK = (1UL << WORK_STRUCT_FLAG_BITS) - 1,
WORK_STRUCT_WQ_DATA_MASK = ~WORK_STRUCT_FLAG_MASK,
WORK_STRUCT_FLAG_BITS = WORK_STRUCT_COLOR_SHIFT +  WORK_STRUCT_COLOR_BITS,

WORK_STRUCT_COLOR_SHIFT 매크로를 분석하려고 하는데 어라? CONFIG_DEBUG_OBJECTS_WORK이란 컨피그에 따라  WORK_STRUCT_COLOR_SHIFT값이 달라진다. 
enum {
WORK_STRUCT_PENDING_BIT = 0, /* work item is pending execution */
WORK_STRUCT_DELAYED_BIT = 1, /* work item is delayed */
WORK_STRUCT_PWQ_BIT = 2, /* data points to pwq */
WORK_STRUCT_LINKED_BIT = 3, /* next work is linked to this one */
#ifdef CONFIG_DEBUG_OBJECTS_WORK
WORK_STRUCT_STATIC_BIT = 4, /* static initializer (debugobjects) */
WORK_STRUCT_COLOR_SHIFT = 5, /* color for workqueue flushing */
#else
WORK_STRUCT_COLOR_SHIFT = 4, /* color for workqueue flushing */
#endif 

최소 3~4년 리눅스 디바이스 드라이버를 만지면서 실무 개발에 뛰어든 개발자들을 제외하고 리눅스 커널을 처음 소스 코드로 공부한다는 것은 불가능하다. 

다른 이유는 뭘까? 
분석 중인 소스 코드가 실제 리눅스 시스템에서 어떻게 실행하는지 모르기 때문이다. 좀 유식한 말로 콜스택을 알 수 없기 때문이다.  
또한 분석 중인 코드가 컴파일되는 코드인지 모를 때도 있다. 
아예 컴파일 되지 않는 코드를 깊게 분석을 하는 가련한 개발자들을 보면 참 측은한 마음이 생긴다. 말 그대로 개삽질을 하고 있기 때문이다.

리눅스 커널을 익히기 어려운 또 다른 이유는 뭘까?
리눅스 커널을 쉽게 익힐 수 있는 툴이 부족하다. C# 프로그래밍을 하려면 C#용 Studio 프로그램을 설치하고 코딩을 하면 된다. 
그런데 리눅스 커널을 쉽게 설치하고 코딩할 수 있는 시스템이 있나? 사실 없다고 봐야 한다. 컴퓨터에 Ubuntu를 직접 설치하고 크로스 컴파일러 환경을 설정한 후 일일이 명령어를 입력해야 한다. 시스템에서 구동시키려면 엄청 손이 많이 간다. 

마지막으로 리눅스 커널을 쉽게 설명한 책이나 블로그도 없다. 
너무 이론 위주로 설명된 책이 대부분이라 신입 개발자들은 무슨 소리인지 알기 어렵고 블로그도 대부분 소스 코드 몇 줄 복사해 놓고 간단한 분석 내용을 요약해 놓은 수준에 불과하다. 

해외 신입 리눅스 개발자들은 한국과 상황이 약간 다르다. 해외 사이트의 경우 Stack Overflow나 영어로 된 리눅스 커널 입문자들을 위한 사이트가 많다. 영어만 제대로 해도 많은 정보를 얻을 수 있다.

리눅스 커널은 오픈 소스 프로젝트이지만 한국 신입 개발자들에겐 사실상 오픈 프로젝트가 아니다. 리눅스 커널 앞에 거대한 기술 장벽이 있다는 생각이 든다.
하지만 한국의 경우 이런 리눅스 커널을 익히기 위한 인프라가 절대적으로 열악하다.

4차 산업 혁명 시대가 열리면서 리눅스 디바이스 수요도 증가하고 있다. 수요 맞게 리눅스 커널 인프라를 한국에도 깔 필요가 있다. 


[라즈베리파이] Soft IRQ 서비스는 언제 요청하나? [라즈베리파이]인터럽트후반부

이번 시간에는 Soft IRQ 서비스를 어떻게 요청하는지 배워 보겠습니다. 우리는 이전에 Soft IRQ를 등록하는 과정을 배웠습니다. 다음 코드와 같이 open_softirq 함수를 써서 TIMER_SOFTIRQ 타입의 Soft IRQ 서비스를 등록했습니다.
1 void __init init_timers(void)
2 {
3 init_timer_cpus();
4 open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
5 }

그럼 TIMER_SOFTIRQ 타입의 Soft IRQ 서비스를 등록만 하면 핸들러 함수인 run_timer_softirq() 함수가 호출될까요? 그렇지 않습니다. 따로 Soft IRQ 서비스를 요청해야 합니다. 인터럽트 핸들러를 등록하는 과정보다 약간 복잡합니다.

Soft IRQ 서비스를 요청하면 커널은 이 정보를 어디에 저장할까요? 정답은 irq_stat란 전역 변수입니다. irq_stat 전역 변수는 아래와 같이 정의합니다. CPU 개수만큼 배열을 잡죠.
extern irq_cpustat_t irq_stat[]; /* defined in asm/hardirq.h */
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)

라즈베리파이는 4개의 CPU를 쓰므로 irq_stat 변수를 Trace32 프로그램으로 보면 다음과 같습니다.
v.v %t %d %h %i irq_stat
(static irq_cpustat_t [4]) irq_stat = (
    [0] = (
      (unsigned int) __softirq_pending = 0 = 0x1,
    [1] = (
      (unsigned int) __softirq_pending = 0 = 0x3,
    [2] = (
      (unsigned int) __softirq_pending = 0 = 0x0,
    [3] = (
      (unsigned int) __softirq_pending = 0 = 0x6,

커널은 CPU 별로 지정한 __softirq_pending 변수에 (1 << Soft IRQ 서비스 아이디) 비트 연산 결과를 저장합니다. Soft IRQ 서비스 아이디를 왼쪽 시프트 한 결괏값입니다. 각 CPU 별로 저장한 __softirq_pending 변수를 해석하면 다음과 같습니다. 

각 Soft IRQ 서비스 아이디 값은 다음과 같이 선언돼있습니다. HI_SOFTIRQ 아이디가 0이고, RCU_SOFTIRQ가 9입니다.
(where)
[include/linux/interrupt.h]
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,  
NR_SOFTIRQS
};

Trace32 프로그램으로 확인한 irq_stat 값과 위에서 연산한 결과를 주석문으로 같이 확인해보겠습니다.    
(static irq_cpustat_t [4]) irq_stat = (
    [0] = (
      (unsigned int) __softirq_pending = 0 = 0x1, // HI_SOFTIRQ
    [1] = (
      (unsigned int) __softirq_pending = 0 = 0x2, // HI_SOFTIRQ | TIMER_SOFTIRQ
    [2] = (
      (unsigned int) __softirq_pending = 0 = 0x0,
    [3] = (
      (unsigned int) __softirq_pending = 0 = 0x6, // NET_TX_SOFTIRQ | TIMER_SOFTIRQ

CPU0에는 HI_SOFTIRQ이란 Soft IRQ 서비스를 요청했고, CPU1는 HI_SOFTIRQ와 TIMER_SOFTIRQ Soft IRQ 서비스를 요청한 상태입니다. CPU3에서 요청된 Soft IRQ 서비스는 NET_TX_SOFTIRQ와 TIMER_SOFTIRQ입니다.

그런데 커널에서 Soft IRQ 서비스를 요청하려면 커널이 제공한 함수인 raise_softirq() 혹은 __raise_softirq_irqoff() 를 호출해야 합니다. 위와 같이 (static irq_cpustat_t [4]) irq_stat 전역 변수에 직접 접근해서 값을 쓸 수는 없습니다. 

관련 코드를 함께 분석해 보겠습니다. raise_softirq 함수를 호출하면 raise_softirq_irqoff 함수를 거쳐 __raise_softirq_irqoff 함수를 호출합니다.
[kernel/softirq.c]
void raise_softirq(unsigned int nr)
{
unsigned long flags;

local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}

Soft IRQ 서비스를 요청하려면 raise_softirq 혹은 __raise_softirq_irqoff 함수를 호출해야 하는데 두 함수의 차이점은 뭘까요?

raise_softirq 함수는 local_irq_save와 local_irq_store 함수를 써서 raise_softirq_irqoff 함수 실행 도중 인터럽트 동기화를 수행합니다. 그래서 대부분 Soft IRQ 서비스를 요청할 때 raise_softirq 함수를 씁니다. 다만 이미 인터럽트 발생에 대한 동기화가 진행 중인 상태면 __raise_softirq_irqoff 함수를 써도 상관은 없습니다.

이번에는 __raise_softirq_irqoff 함수를 점검할 차례입니다.
void __raise_softirq_irqoff(unsigned int nr)
{
trace_softirq_raise(nr);
or_softirq_pending(1UL << nr);
}

입력 인자인 nr을 왼쪽으로 쉬프트한 값을 or_softirq_pending 함수에 전달합니다.

or_softirq_pending 함수 구현부를 보면 매크로이고 irq_stat[4].__softirq_pending 변수에 접근한다는 정보를 알 수 있습니다. 원래 __softirq_pending 변수가 저장한 값과 “|=” 연산을 하니 __softirq_pending 멤버가 저장한 값은 그대로 두는 것이죠. 
#define or_softirq_pending(x)  (local_softirq_pending() |= (x))

#define local_softirq_pending() \
__IRQ_STAT(smp_processor_id(), __softirq_pending)

extern irq_cpustat_t irq_stat[]; /* defined in asm/hardirq.h */
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)

매크로 함수 중에 smp_processor_id 함수가 보이는데 이 함수는 현재 프로세스가 돌고 있는 CPU 번호를 알려 줍니다. 코드를 잠깐 보면 smp_processor_id 함수는 current_thread_info()->cpu로 치환됩니다.  in_interrupt 매크로와 비슷한 원리로 구현한 매크로죠.

or_softirq_pending 매크로 함수를 쓰면 다른 코드가 실행된다는 점을 알 수 있습니다.
(irq_stat[cpu].__softirq_pending) |= (1UL << x)

분석한 코드 내용을 종합하면 위 코드 연산 결과는 다음과 같이 알기 쉽게 표현할 수 있습니다.
#define or_softirq_pending(x)  (local_softirq_pending() |= (x))

irq_stat[cpu]__softirq_pending |=  (1UL << nr)
irq_stat[cpu]__softirq_pending =  irq_stat[cpu]__softirq_pending + (1UL << nr)

위 코드에서 nr는 Soft IRQ 서비스 아이디이고 cpu는 __raise_softirq_irqoff() 함수를 실행하는 CPU번호를 의미합니다. 

이렇게 or_softirq_pending란 함수를 3단계로 매크로가 매크로를 호출하니 소스를 따라가기 어렵습니다. 이럴 때는 3장에서 소개했듯이 전처리 코드를 보면 효율적으로 커널 코드를 읽을 수 있습니다. 전처리 코드는 매크로를 모두 푼 정보를 담고 있거든요. 전처리 코드를 볼까요? 
1 void __raise_softirq_irqoff(unsigned int nr)
2 {
3  trace_softirq_raise(nr);
4  ((irq_stat[(current_thread_info()->cpu)].__softirq_pending) |= (1UL << nr));
5 }

전처리 코드를 보니 or_softirq_pending(1UL << nr); 코드는 위 4번째 줄 코드으로 치환됩니다. irq_stat[cpu갯수].__softirq_pending 멤버에 Soft IRQ enum 값을 왼쪽으로 쉬프트한 값을 OR 연산으로 저장합니다.

여기까지 Soft IRQ 서비스를 요청하는 코드를 알아 봤습니다. 
Soft IRQ 서비스를 요청했으면 어디선가 이 서비스 요청 알아보고 처리하는 코드가 있을 겁니다. 이제 Soft IRQ 서비스를 요청했는지는 누가 어떻게 점검할까요? 

Soft IRQ 서비스 요청 여부는 인터럽트 핸들러 수행이 끝나면 호출하는 irq_exit()과 ksoftirqd() 쓰레드 핸들 함수인 run_ksoftirqd에서 점검합니다. 이 때 커널에서 제공하는 local_softirq_pending 함수를 호출합니다.

실제 소스 코드를 함께 살펴보겠습니다. 다음 코드를 보면 irq_exit 함수에서 local_softirq_pending 함수를 호출하는 루틴을 알 수 있습니다.
void irq_exit(void)
{
....
if (!in_interrupt() && local_softirq_pending())
  invoke_softirq();

irq_exit 함수에서 2가지 조건을 점검한 후 Soft IRQ 서비스를 실행할지 결정합니다. 첫 번째는 현재 실행 중인 코드가 인터럽트 컨택스트 상태인지와 두 번째로 Soft IRQ 서비스 요청이 있었는지를 점검하는 것이죠.

irq_exit 함수를 전처리 코드에서 보면 local_softirq_pending 함수는 irq_stat[(current_thread_info()->cpu)].__softirq_pending)로 치환됨을 알 수 있습니다.
void irq_exit(void)
{
...
if (!((preempt_count() & ((((1UL << (4))-1) << ((0 + 8) + 8)) | (((1UL << (8))-1) << (0 + 8)) | (((1UL << (1))-1) << (((0 + 8) + 8) + 4))))) && (irq_stat[(current_thread_info()->cpu)].__softirq_pending))

ksoftirqd 쓰레드도 마찬가지입니다. 다음 4번째 줄 코드에서 local_softirq_pending 함수를 호출해서 Soft IRQ 서비스 요청이 있었는지 점검합니다.
1 static void run_ksoftirqd(unsigned int cpu)
2 {
3 local_irq_disable();
4 if (local_softirq_pending()) {
5 __do_softirq();

run_ksoftirqd 함수를 전처리 코드로 열어봐도 (irq_stat[(current_thread_info()->cpu)].__softirq_pending) 코드가 실행함을 알 수 있습니다.
[./kernel/.tmp_softirq.i]
static void run_ksoftirqd(unsigned int cpu)
{
  do { arch_local_irq_disable(); trace_hardirqs_off(); } while (0);
  if ((irq_stat[(current_thread_info()->cpu)].__softirq_pending)) {
   __do_softirq();

이렇게 __raise_softirq_irqoff 함수를 써서 irq_stat[(current_thread_info()->cpu)].__softirq_pending)에 값을 써주면 나중에 이 변수를 읽어서 확인하는 것입니다.

전체 Soft IRQ 구조에서 Soft IRQ 서비스를 요청하는 흐름도는 다음과 같습니다.

위 그림과 같이 __softirq_pending 변수의 비트 필드 순서는 Soft IRQ 벡터에 대응합니다. 즉, __softirq_pending 변수의 두 번째 비트 필드가 1이면 run_timer_softirq 함수가 호출되는 것입니다.

이번 절에 배운 내용을 간단히 정리해 보겠습니다.
인터럽트 핸들러에서 or_softirq_pending()이란 함수를 호출해서 Soft IRQ 서비스를 요청하면, irq_exit()과 run_softirqd() 함수에서 local_softirq_pending이란 함수를 써서 Soft IRQ 요청이 있었는지 점검합니다.

나중에 __do_softirq() 함수가 실행할 때 각 CPU 별로 irq_stat[cpu].__softirq_pending 변수에 접근해서 비트 필드를 점검합니다. 이 정보로 softirq_vec에 접근해서 Soft IRQ 서비스 핸들러 함수를 호출합니다.


[라즈베리파이] Soft IRQ 서비스 및 서비스 핸들러 등록(디버깅) [라즈베리파이]인터럽트후반부

라즈베리파이에서 동작 확인
라즈베리안에서 Soft IRQ 등록하는지 알려면 어떻게 해야 할까요? 다음 패치를 적용 후 라즈베리파이를 실행하면 됩니다.
diff --git a/kernel/softirq.c b/kernel/softirq.c
index 3f3fbc230..b0e75e8d0 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -433,6 +439,8 @@ void __raise_softirq_irqoff(unsigned int nr)
 void open_softirq(int nr, void (*action)(struct softirq_action *))
 {
  softirq_vec[nr].action = action;
+
+ dump_stack();
 }

dump_stack() 이란 함수는 자신을 호출한 콜스택 정보를 알려 줍니다.

위 패치를 적용하고 커널 빌드를 한 다음 라즈베리안에 설치합니다. 라즈베리파이를 재부팅하고 커널 로그를 받으면 부팅할 때 커널 로그에서 Soft IRQ 서비스를 등록하는 흐름을 확인할 수 있습니다.

커널 로그를 열어보면 가장 먼저 init_sched_fair_class 함수에서 SCHED_SOFTIRQ Soft IRQ 서비스 아이디로 run_rebalance_domains란 Soft IRQ 서비스 핸들러 함수를 등록한다는 정보를 알 수 있습니다.
[0.000000] CPU: 0 PID: 0 Comm: swapper/0 Not tainted 4.14.39-v7+ #16
[0.000000] Hardware name: BCM2835
[0.000000] (unwind_backtrace) from (show_stack+0x20/0x24)
[0.000000] (show_stack) from (dump_stack+0xc8/0x10c)
[0.000000] (dump_stack) from (open_softirq+0x24/0x28)
[0.000000] (open_softirq) from (init_sched_fair_class+0x24/0x48)
[0.000000] (init_sched_fair_class) from (sched_init+0x3c8/0x41c)
[0.000000] (sched_init) from (start_kernel+0x21c/0x3e0)
[0.000000] (start_kernel) from  (0x807c)

위 로그에서 open_softirq() 함수를 호출하는 코드는 다음과 같습니다.
[kernel/sched/fair.c]
__init void init_sched_fair_class(void)
{
#ifdef CONFIG_SMP
open_softirq(SCHED_SOFTIRQ, run_rebalance_domains);

이후 Soft IRQ 서비스 아이디 별로 커널 로그의 콜스택과 이를 실행한 커널 코드를 같이 확인해볼까요?

RCU_SOFTIRQ: rcu_process_callbacks
로그 
[0.000000] (open_softirq) from (rcu_init+0x300/0x374)
[0.000000] (rcu_init) from (start_kernel+0x254/0x3e0)
[0.000000] (start_kernel) from [<0000807c>] (0x807c)

코드: [kernel/rcu/tree.c]
void __init rcu_init(void)
{
...
__rcu_init_preempt();
open_softirq(RCU_SOFTIRQ, rcu_process_callbacks); 

TIMER_SOFTIRQ: run_timer_softirq
로그 
[0.000000] (open_softirq) from (init_timers+0xa0/0xa4)
[0.000000] (init_timers) from (start_kernel+0x268/0x3e0)
[0.000000] (start_kernel) from [<0000807c>] (0x807c)

코드: [kernel/time/timer.c]
void __init init_timers(void)
{
...
open_softirq(TIMER_SOFTIRQ, run_timer_softirq);

TASKLET_SOFTIRQ, HI_SOFTIRQ Soft IRQ 서비스는 softirq_init 함수에서 초기화합니다
TASKLET_SOFTIRQ: tasklet_action
HI_SOFTIRQ: tasklet_hi_action
로그 
[0.000000] (softirq_init) from [<80b00cc4>] (start_kernel+0x270/0x3e0)
[0.000000] (start_kernel) from [<0000807c>] (0x807c)

[0.000000] (softirq_init) from (start_kernel+0x270/0x3e0)
[0.000000] (start_kernel) from (0x807c)

코드 [kernel/softirq.c]
void __init softirq_init(void)
{
...
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

BLOCK_SOFTIRQ: blk_done_softirq
로그 
[0.059227] (open_softirq) from (blk_softirq_init+0x78/0xac)
[0.059245] (blk_softirq_init) from (do_one_initcall+0x54/0x17c)
[0.059263] (do_one_initcall) from (kernel_init_freeable+0x224/0x2b8)

코드 [block/blk-softirq.c]
static __init int blk_softirq_init(void)
{
...
open_softirq(BLOCK_SOFTIRQ, blk_done_softirq);

NET_TX_SOFTIRQ: net_tx_action
NET_RX_SOFTIRQ: net_rx_action
로그 
[0.070773] (open_softirq) from (net_dev_init+0x1ac/0x214)
[0.070790] (net_dev_init) from (do_one_initcall+0x54/0x17c)
[0.070807] (do_one_initcall) from (kernel_init_freeable+0x224/0x2b8)
[0.070824] (kernel_init_freeable) from (kernel_init+0x18/0x124)
[0.070840] [<8079bfd0>] (kernel_init) from (ret_from_fork+0x14/0x28)

[0.070911] (open_softirq) from (net_dev_init+0x1bc/0x214)
[0.070925] (net_dev_init) from (do_one_initcall+0x54/0x17c)
[0.070940] (do_one_initcall) from (kernel_init_freeable+0x224/0x2b8)
[0.070954] (kernel_init_freeable) from (kernel_init+0x18/0x124)
[0.070968]  (kernel_init) from (ret_from_fork+0x14/0x28)

코드: [net/core/dev.c]
static int __init net_dev_init(void)
{
...
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);

커널 로그에 담겨 있는 콜스택 정보로 라즈베리안이 부팅하는 과정에서 다음 순서로 Soft IRQ 서비스들을 등록한다는 사실을 알게 됐습니다.
Soft IRQ 서비스 아이디  Soft IRQ 서비스 핸들러
SCHED_SOFTIRQ run_rebalance_domains
RCU_SOFTIRQ rcu_process_callbacks
TIMER_SOFTIRQ run_timer_softirq
TASKLET_SOFTIRQ tasklet_action
HI_SOFTIRQ tasklet_hi_action
BLOCK_SOFTIRQ blk_done_softirq
NET_TX_SOFTIRQ net_tx_action
NET_RX_SOFTIRQ net_rx_action

이렇게 라즈베리안이 부팅하고 난 다음 Soft IRQ 벡터인 softirq_vec 전역 변수는 어떤 모습일까요? Trace32 프로그램으로 라즈베리안의 Soft IRQ 벡터를 확인하니 다음과 같네요.
v.v %t %d %i %y %l softirq_vec 
  (static struct softirq_action [10]) [D:0x80C02080] softirq_vec = (
    [0] = ((void (*)()) [D:0x80C02080] action = 0x80122888 = tasklet_hi_action),
    [1] = ((void (*)()) [D:0x80C02084] action = 0x80181270 = run_timer_softirq),
    [2] = ((void (*)()) [D:0x80C02088] action = 0x80614684 = net_tx_action),
    [3] = ((void (*)()) [D:0x80C0208C] action = 0x80615AB0 = net_rx_action),
    [4] = ((void (*)()) [D:0x80C02090] action = 0x804279B0 = blk_done_softirq),
    [5] = ((void (*)()) [D:0x80C02094] action = 0x0 = ),
    [6] = ((void (*)()) [D:0x80C02098] action = 0x8012299C = tasklet_action),
    [7] = ((void (*)()) [D:0x80C0209C] action = 0x801588EC = run_rebalance_domains),
    [8] = ((void (*)()) [D:0x80C020A0] action = 0x0 = ),
    [9] = ((void (*)()) [D:0x80C020A4] action = 0x8017ABC4 = rcu_process_callbacks))

이번에 라즈베리안에서 Soft IRQ 서비스를 어떻게 등록하는지 커널 로그를 통해 확인했습니다. 그리고 실제로 8개의 Soft IRQ 서비스를 등록하고 부팅 후 Soft IRQ 벡터에 등록된 함수 목록도 봤습니다.



[라즈베리파이] Soft IRQ 서비스 및 서비스 핸들러 등록(코드 분석) [라즈베리파이]인터럽트후반부

Soft IRQ를 설명하면서 낯선 용어를 설명했는데, 이제부터 그 의미를 하나하나씩 살펴 보겠습니다. Soft IRQ 서비스란 용어부터 배워볼까요?

Soft IRQ 서비스를 빨리 이해하려면 코드를 먼저 봐야 합니다. Soft IRQ 서비스는 아래 enum으로 정의한 코드와 같습니다.
[include/linux/interrupt.h]
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,  
NR_SOFTIRQS
};

const char * const softirq_to_name[NR_SOFTIRQS] = {
"HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "IRQ_POLL",
"TASKLET", "SCHED", "HRTIMER", "RCU"
};

리눅스 커널에서는 Soft IRQ 서비스 종류를 지정해 놨습니다. 위 코드를 보면 10개나 되네요. enum 타입으로 정의된 이름으로 타이머, 네트워크 그리고 블록 디바이스별로 Soft IRQ 서비스가 있습니다.

그럼 Soft IRQ 서비스는 어떻게 실행할까요? 각 Soft IRQ 서비스별로 등록된 서비스 핸들러 함수가 실행할 때 Soft IRQ 서비스를 실행하는 겁니다. Soft IRQ 서비스 핸들러는 어디서 확인할 수 있을까요? softirq_vec 변수가 Soft IRQ 서비스 핸들러 정보를 저장하고 있습니다.

다음은 ARM64 아키텍처 기반의 다른 리눅스 시스템에서 확인한 softirq_vec 전역 변수입니다.
Trace32 프로그램으로 확인한 결과인데, softirq_vec 변수는 Soft IRQ 서비스 핸들러 함수 주소를 저장하고 있습니다.
v.v %t %y %i %d softirq_vec
  (static struct softirq_action [10]) softirq_vec = (
    [0] = ((void (*)()) action = 0xFFFFFFC0000AB7F0 = tasklet_hi_action),
    [1] = ((void (*)()) action = 0xFFFFFFC00010DA9C = run_timer_softirq),
    [2] = ((void (*)()) action = 0xFFFFFFC000CFF084 = net_tx_action),
    [3] = ((void (*)()) action = 0xFFFFFFC000D022D8 = net_rx_action),
    [4] = ((void (*)()) action = 0xFFFFFFC0003190B8 = blk_done_softirq),
    [5] = ((void (*)()) action = 0xFFFFFFC0003197EC = blk_iopoll_softirq),
    [6] = ((void (*)()) action = 0xFFFFFFC0000AB938 = tasklet_action),
    [7] = ((void (*)()) action = 0xFFFFFFC0000E6CEC = run_rebalance_domains),
    [8] = ((void (*)()) action = 0xFFFFFFC0001104D8 = run_hrtimer_softirq),
    [9] = ((void (*)()) action = 0xFFFFFFC000106FB0 = rcu_process_callbacks))

Soft IRQ가 실행될 때 __do_softirq 함수에서 위에서 보이는 함수들 하나가 실행됩니다.
다음에 라즈베리안에서는 Soft IRQ 서비스는 어떤 코드에서 등록하는지 알아볼까요?

Soft IRQ 서비스 핸들러는 언제 등록할까?
Soft IRQ 서비스 핸들러 등록과 Soft IRQ 서비스 등록은 같은 의미입니다. 왜냐면 Soft IRQ 서비스 등록을 할 때 Soft IRQ 핸들러 함수를 등록해야 하기 때문입니다.

Soft IRQ 서비스 핸들러를 등록하려면 다음 규칙에 따라 open_softirq 함수를 호출해야 합니다. 
open_softirq(Soft IRQ 서비스 아이디, Soft IRQ 서비스 핸들러);

우선 TIMER_SOFTIRQ이란 Soft IRQ 서비스 아이디로 Soft IRQ 서비스 핸들러를 등록하는 코드를 살펴 보겠습니다.
1 void __init init_timers(void)
2 {
3 init_timer_cpus();
4 open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
5 }

위 코드를 보면 init_timer 함수에서 open_softirq 함수를 호출하는데, 첫 번째 인자는 Soft IRQ 서비스 아이디, 두 번째는 run_timer_softirq 함수를 지정합니다. 참고로, init_timers 함수 선언부를 보면 __init 매크로가 붙어 있으니 부팅 도중 한번 실행됩니다.

open_softirq 함수를 보면 예상한 대로 Soft IRQ 서비스 핸들러 정보를 저장하는 softirq_vec에 함수 주소를 지정합니다.
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}

리눅스 커널에서는 softirq_vec 전역 변수를 다른 말로 Soft IRQ 벡터라 말하는데 이 전역 변수는 Soft IRQ 서비스 핸들러 함수 포인터를 담고 있습니다. softirq_vec 전역 변수의 선언부를 보면 NR_SOFTIRQS 크기만큼 배열이란 정보를 알 수 있습니다.
[/kernel/softirq.c]
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

이렇게 각 Soft IRQ 서비스 아이디 별로 Soft IRQ 서비스를 등록하면 softirq_vec[nr].action 멤버에 Soft IRQ 서비스 핸들러를 볼 수 있습니다.

조금 후 살펴볼 예정이지만, Soft IRQ 서비스 핸들러는 다음 코드와 같이 __do_softirq 함수에서 호출합니다. TIMER_SOFTIRQ Soft IRQ 서비스 아이디인 경우 __do_softirq 함수에서 run_timer_softirq 함수를 호출합니다.
1 asmlinkage __visible void __softirq_entry __do_softirq(void) {
2 struct softirq_action *h;
3 h = softirq_vec;
4 h->action(h);

위에 보이는 코드는 __do_softirq 함수에서 Soft IRQ 벡터인 softirq_vec 변수를 읽어서 Soft IRQ 핸들러 함수를 호출하는 코드만 가져온 것입니다. 위 4번째 줄 코드에서 Soft IRQ 핸들러 함수를 호출합니다.

여기까지 Soft IRQ 서비스를 등록하는 방법을 알아봤습니다. Soft IRQ 서비스 아이디와 Soft IRQ 서비스 핸들러를 open_softirq 함수 인자로 호출하면 되죠.

라즈베리안에서는 Soft IRQ 서비스를 어떻게 등록하는지 알아볼까요? 코드만 봐서 Soft IRQ 서비스 전체 개수를 파악하기는 어렵습니다. 직접 라즈베리파이에서 Soft IRQ 서비스를 어떻게 등록하는지 알아 보겠습니다.



[라즈베리파이]인터럽트 후반부 처리(Bottom Half) 소개 #CS [라즈베리파이]인터럽트후반부

이전에 리눅스 커널이 인터럽트를 어떻게 처리하는지 배웠습니다. 배운 내용을 잠깐 복습해볼까요? 핵심 개념은 다음과 같습니다.
첫째, 인터럽트가 발생하면 커널은 실행 중인 프로세스를 멈추고 인터럽트 벡터를 실행해서 인터럽트 핸들러를 실행합니다.
둘째, 인터럽트 핸들러는 짧고 빨리 실행해야 합니다.
셋째, 인터럽트를 처리하는 구간이 인터럽트 컨택스트인데 이를 in_interrupt 매크로가 알려줍니다.

그런데 욕심이 지나쳐 인터럽트 핸들러에서 많은 일을 하고 싶을 때가 있습니다. 유저 공간에 인터럽트가 발생한 사실을 알리거나 다른 프로세스에게 일을 시키고 싶은 경우입니다.  인터럽트 핸들러에서 이런 동작을 실행하면 어떻게 될까요? 시스템이 아주 느려지거나 커널이 오동작 할 수 있습니다. 그래서 인터럽트 컨택스트에서 많은 일을 하는 함수를 호출하면 커널은 이를 감지하고 커널 패닉을 유발합니다. 

여기서 인터럽트 핸들러에서 많은 일을 하다가 커널 패닉이 발생하는 예를 들어 보겠습니다.  다음 로그는 인터럽트 핸들러 실행 도중 발생한 커널 패닉 로그입니다. 함수가 일렬로 정렬해 있습니다.
[21.719319] [callstack mxt_interrupt,2449] task[InputReader]========= 
[21.719382] BUG: scheduling while atomic: InputReader/1039/0x00010001
[21.719417] (unwind_backtrace+0x0/0x144) from (dump_stack+0x20/0x24)
[21.719432] (dump_stack+0x20/0x24) from (__schedule_bug+0x50/0x5c)
[21.719444] (__schedule_bug+0x50/0x5c) from (__schedule+0x7c4/0x890)
[21.719455] (__schedule+0x7c4/0x890) from [<c0845d70>] (schedule+0x40/0x80)
[21.719468] (schedule+0x40/0x80) from [<c0843bc0>] (schedule_timeout+0x190/0x33c)
[21.719480] (schedule_timeout+0x190/0x33c) from (wait_for_common+0xb8/0x15c)
[21.719491] (wait_for_common+0xb8/0x15c) from (wait_for_completion_timeout+0x1c/0x20)
[21.719504] (wait_for_completion_timeout+0x1c/0x20) from (tegra_i2c_xfer_msg+0x380/0x958)
[21.719517] (tegra_i2c_xfer_msg+0x380/0x958) from (tegra_i2c_xfer+0x314/0x438)
[21.719531] (tegra_i2c_xfer+0x314/0x438) from (i2c_transfer+0xc4/0x128)
[21.719546] (i2c_transfer+0xc4/0x128) from (__mxt_read_reg+0x70/0xc8)
[21.719560] (__mxt_read_reg+0x70/0xc8) from (mxt_read_and_process_messages+0x58/0x1648)
[21.719572] (mxt_read_and_process_messages+0x58/0x1648) from (mxt_interrupt+0x78/0x144)
[21.719588] (mxt_interrupt+0x78/0x144) from (handle_irq_event_percpu+0x88/0x2ec)
[21.719601] (handle_irq_event_percpu+0x88/0x2ec) from (handle_irq_event+0x4c/0x6c)
[21.719614] (handle_irq_event+0x4c/0x6c) from (handle_level_irq+0xbc/0x118)
[21.719626] (handle_level_irq+0xbc/0x118) from (generic_handle_irq+0x3c/0x50)
[21.719642] (generic_handle_irq+0x3c/0x50) from (tegra_gpio_irq_handler+0xa8/0x124)
[21.719655] (tegra_gpio_irq_handler+0xa8/0x124) from (generic_handle_irq+0x3c/0x50)
[21.719669] (generic_handle_irq+0x3c/0x50) from (handle_IRQ+0x5c/0xbc)
[21.719682] (handle_IRQ+0x5c/0xbc) from (gic_handle_irq+0x34/0x68)
[21.719694] (gic_handle_irq+0x34/0x68) from (__irq_svc+0x40/0x70)

참고로 위 로그가 동작한 시스템은 엔비디아 Tegra4i SoC 디바이스입니다. 그래서 tegra가 붙은 함수들이 보입니다. 라즈베리파이 이외에 다른 리눅스 시스템에서 인터럽트를 어떻게 처리하는지 알면 좋으니 리눅스 시스템에서 발생한 문제를 소개합니다.

로그를 꼼꼼히 분석하겠습니다. 함수들이 줄 서 있는데 어느 부분 로그부터 읽어봐야 할까요? 함수들이 가장 먼저 실행된 순서로 정렬돼 있으니 가장 아랫부분 로그부터 봐야 합니다. 이제부터 5장에서 배운 내용을 떠 올리면서 로그 분석을 시작합니다.
 
가장 처음 실행된 함수 로그부터 봅시다. 인터럽트가 발생하면 인터럽트 벡터인 __irq_svc가 실행하니 인터럽트가 발생했습니다. 인터럽트 벡터인 __irq_svc 함수부터 실행된 콜스택(함수 흐름)이니 인터럽트 컨택스트입니다.  
[21.719682] (handle_IRQ+0x5c/0xbc) from (gic_handle_irq+0x34/0x68)
[21.719694] (gic_handle_irq+0x34/0x68) from (__irq_svc+0x40/0x70)

아래 로그로 인터럽트 핸들러로 mxt_interrupt 함수가 호출됐다는 사실을 알 수 있습니다. 
[21.719572] (mxt_read_and_process_messages+0x58/0x1648) from (mxt_interrupt+0x78/0x144)
[21.719588] (mxt_interrupt+0x78/0x144) from (handle_irq_event_percpu+0x88/0x2ec)
[21.719601] (handle_irq_event_percpu+0x88/0x2ec) from (handle_irq_event+0x4c/0x6c)

우리는 인터럽트 핸들러는 __handle_irq_event_percpu 함수에서 호출한다고 배웠습니다. 그런데 위 로그에서는 handle_irq_event_percpu 함수에서 인터럽트 핸들러를 호출합니다.

그 이유는 이 로그를 출력한 시스템의 리눅스 커널 버전이 3.10.77 버전이기 때문입니다. 다음 코드를 보면 5번째 줄 코드에서 인터럽트 핸들러를 호출합니다.
[https://elixir.bootlin.com/linux/v3.10.77/source/kernel/irq/handle.c#L133]
1 irqreturn_t
2 handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
3 {
...
4 do {
...
5 res = action->handler(irq, action->dev_id);

다음은 커널 패닉이 발생하는 이유를 알려주는 로그입니다. 함수 흐름으로 보아 wait_for_common() 함수를 호출해서 complete() 함수가 수행되기를 기다리는 상황입니다. 그런데 complete() 함수 호출을 안 하니 schedule_timeout() 함수를 호출합니다.
[21.719444] (__schedule_bug+0x50/0x5c) from (__schedule+0x7c4/0x890)
[21.719455] (__schedule+0x7c4/0x890) from [<c0845d70>] (schedule+0x40/0x80)
[21.719468] (schedule+0x40/0x80) from [<c0843bc0>] (schedule_timeout+0x190/0x33c)
[21.719480] (schedule_timeout+0x190/0x33c) from (wait_for_common+0xb8/0x15c)
[21.719491] (wait_for_common+0xb8/0x15c) from (wait_for_completion_timeout+0x1c/0x20)
[21.719504] (wait_for_completion_timeout+0x1c/0x20) from (tegra_i2c_xfer_msg+0x380/0x958)
[21.719517] (tegra_i2c_xfer_msg+0x380/0x958) from (tegra_i2c_xfer+0x314/0x438)

이후 __schedule() -> __schedule_bug() 순서로 함수를 호출합니다.

이제 커널 패닉이 발생하는 이유를 알려주는 로그를 보겠습니다. 인터럽트 컨택스트에서 스케줄링을 하니 커널은 이를 감지하고 커널 패닉을 유발합니다.
1 [21.719319] [callstack mxt_interrupt,2449] task[InputReader]========= 
2 [21.719382] BUG: scheduling while atomic: InputReader/1039/0x00010001

두 번째 줄 로그를 보면 InputReader는 프로세스 이름, 1039는 pid 그리고 0x00010001는 struct thread_info의 preempt_count 멤버 변수입니다. 0x00010001 값과 HARDIRQ_OFFSET(0x10000)란 매크로를 AND 연산하면 1이므로 인터럽트 컨택스트입니다.

위 로그에서 “scheduling while atomic”란 메시지가 보입니다. 여기서 atomic이란 무슨 의미일까요? 커널에서는 어떤 코드나 루틴이 실행 도중 스케줄링 되면 안 될 때가 있습니다. 이를 유식한 말로 원자적 처리라고 하며 원어 그대로 atomic operation이라고 합니다. 커널에서는 인터럽트 컨택스트도 실행 도중 스케쥴링하면 안되는 atomic operation이라고 판단합니다. 그래서 이런 경고 메시지를 출력하는 겁니다. (atomic operation은 커널 동기화 장에서 자세히 다루니 이 점 참고하세요.)

정리하면 인터럽트 컨택스트에서 스케쥴링을 시도하니 커널은 이를 감지하고 커널 패닉을 유발하는 겁니다. 이 내용이 잘 이해가 안 가는 분은 5장 인터럽트의 in_interrupt 절을 잠깐 읽고 다시 오시길 바래요.

이렇게 인터럽트가 발생하면 처리할 일이 많을 때는 어떻게 해야 할까요? 인터럽트 컨택스트에서는 빨리 일을 끝내야 하는데 말이죠. 이럴 때는 해야 할 일을 두 개로 나누면 됩니다. 빨리 처리해야 하는 일과 조금 있다가 처리해도 되는 일입니다. 임베디드 세상에선 인터럽트가 발생 후 빨리 처리해야 하는 일은 Top Half, 조금 있다가 처리해도 되는 일은 Bottom Half라고 합니다. 좀 낯선 용어입니다. 그런데 사실 용어만 낯설지 그 내용은 별 게 아닙니다. 우선 인터럽트 핸들러가 하는 일은 Top Half라고 할 수 있습니다. Bottom Half는  인터럽트에 대한 처리를 프로세스 레벨에서 수행하는 방식입니다. 이를 인터럽트 후반부 처리라고도 합니다.

리눅스 커널에서 Bottom Half는 어떻게 구현할까요? 보통 IRQ Thread, Soft IRQ와 워크큐 기법으로 구현합니다. 인터럽트 핸들러는 일하고 있던 프로세스를 멈춘 시점인 인터럽트 컨택스트에서 실행하는데 IRQ Thread, Soft IRQ와 워크큐는 커널 쓰레드 레벨에서 실행합니다. 급하게 처리해야 할 일은 인터럽트 컨택스트 조금 후 실행해도 되는 일은 커널 쓰레드 레벨에서 처리하는 것입니다. 

인터럽트 컨택스트와 커널 쓰레드 레벨에서 어떤 코드를 동작할 때 어떤 차이점이 있을까요?
우선 인터럽트 컨택스트에서는 호출할 수 있는 함수가 제한돼 있습니다. 리눅스 커널에서는 인터럽트 컨택스트에서 많은 일을 하는 함수를 호출할 때 경고 메시지를 출력하거나 커널 패닉을 유발해서 시스템 실행을 중단시킵니다. 예를 들어 스케쥴링과 연관된 뮤텍스나 schedule 함수를 쓰면 커널은 강제로 커널 패닉을 유발합니다.

그런데 인터럽트 컨택스트에 비해 커널 쓰레드에서는 커널이 제공하는 스케쥴링을 포함한 모든 함수를 쓸 수 있습니다. 그래서 시나리오에 따라 유연하게 코드를 설계할 수 있습니다.

예를 들어 인터럽트가 발생했을 때 이를 유저 공간에 알리고 싶을 경우가 있습니다. 안드로이드  디바이스 같은 경우에 터치를 입력하면 발생하는 인터럽트를 uevent로 유저 공간에 알릴 수 있죠. 이런 동작은 IRQ Thread에서 동작하도록 코드를 구현해야 합니다.

이번에 리눅스 커널이 Bottom Half을 처리하는 대표적인 기법인 IRQ Thread와 Soft IRQ에 대해 살펴봅니다. 워크큐는 워크큐를 다루는 장에서 살펴볼 예정입니다. 세 가지 기법은 인터럽트가 발생하고 나면 처리하는 방식이 조금씩 다르지만 인터럽트 핸들러에서 해야 할 일을 나눈다는 점은 같습니다. 
이 세 가지 기법의 특징이 뭔지 알아보겠습니다. 
IRQ Thread: 인터럽트를 처리하는 전용 IRQ Thread에서 인터럽트 후속 처리를 합니다. 만약 rasp란 24번 인터럽트가 있으면 “irq/24-rasp”란 IRQ Thread가 24번 인터럽트를 전담해서 처리합니다.

Soft IRQ: 인터럽트 핸들러가 실행이 끝나면 이때 일을 시작합니다. 인터럽트 핸들러에서 하지 못한 처리를 하다가 실행 시간이 오래 걸리면 ksoftirqd란 프로세스로 스케쥴링되고 이 ksoftirqd란 프로세스에서 나머지 인터럽트 후반부 잔업을 처리하는 구조입니다.  

워크큐: 인터럽트 핸들러가 실행될 때 워크를 워크큐에 큐잉하고 프로세스 레벨의 워커 쓰레드에서 인터럽트 후속처리를 하는 방식입니다.

인터럽트를 처리하는 드라이버를 작성할 때 위 세 가지 중 어느 기법을 선택할지는 드라이버 담당자의 몫입니다. 인터럽트 발생 빈도와 이를 처리하는 시나리오에 따라 위 세 가지 기법을 적절히 조합해서 드라이버 코드를 작성해야 합니다. 이를 위해서 인터럽트를 시스템에서 처리하는 방식과 인터럽트 발생 빈도를 알아야 합니다. 사실, 디바이스 드라이버나 시스템 전반을 설계하는 개발자는 인터럽트가 발생하면 소프트웨어적으로 이를 어떻게 설계할지 많은 고민을 합니다. 이런 설계를 잘하려면 우선 이 기법들은 잘 알고 있어야 합니다.

예를 들어 유저가 터치 화면을 입력하면 발생하는 인터럽트는 IRQ Thread나 워크큐를 써서 처리하면 되고, 1초에 수백 번 이상 발생하는 인터럽트를 예정된 시간 내에 처리해야 하는 실행 속도에 민감한 시나리오에서는 Soft IRQ 기법을 써야 합니다. 

IRQ Thread부터 배워볼까요?




[라즈베리파이] Soft IRQ 소개 [라즈베리파이]인터럽트후반부

Soft IRQ는 리눅스 커널 시스템 핵심 기능 중 하나입니다. 리눅스 커널의 타이머, 스케쥴링은 물론 네트워크 시스템과 연관돼 있죠. 그만큼 Soft IRQ는 구조가 복잡해서 전체적인 큰 그림을 그리며 개념을 익히기 힘듭니다.

하지만 Soft IRQ 핵심 개념은 뭐니해도 인터럽트 후반부 처리라고 할 수 있습니다. 그래서 이번 절에서는 Soft IRQ를 인터럽트 후반부 처리 중심으로 알아볼 예정입니다. 이 개념을 정확히 익힌 다음 Soft IRQ에서 프로세스, 타이머 혹은 네트워크 시스템을 어떻게 처리하는지 알아보는 것이 좋습니다.  

Soft IRQ에 대해 알아보기 전에 우선 Soft IRQ를 왜 알아야 하는지 생각해 봅시다.
1. 리눅스 커널 입문자를 벗어나 중급 수준 개발자가 되려면 Soft IRQ가 뭔지 알아야 합니다. 반응 속도에 민감한 네트워크 패킷 처리나 고속 그래픽 처리 및 스토리지(UFS) 드라이버들은 Soft IRQ 핸들러를 이용해서 구현됐기 때문입니다.

2. 우리는 인터럽트가 발생하면 일하던 프로세스를 멈추고 인터럽트 핸들러를 실행한다고 알고 있습니다. 그런데 Soft IRQ는 인터럽트 핸들러가 수행하면 일하던 프로세스로 돌아가지 않고 바로 Soft IRQ 실행을 시작합니다. Soft IRQ 에서 실행 속도가 늦으면 시스템 반응 속도가 늦어집니다. 그래서 시스템 전반을 책임지는 개발자는 Soft IRQ를 잘 알아야 합니다.

3. 커널 타이머를 제대로 이해하려면 Soft IRQ 구조를 알아야 합니다. 드라이버에서 요청한 로컬 타이머들은 타이머 인터럽트가 발생한 다음 Soft IRQ 서비스로 실행하기 때문입니다.

Soft IRQ를 IRQ Thread 기법과 비교하면 어떤 차이점이 있을까요? IRQ Thread 기법은 인터럽트 후반부 처리를 해야 할 때 IRQ Thread를 깨우고 조금 후 프로세스 레벨에서 IRQ Thread 핸들러에서 남은 인터럽트에 대한 처리를 하는 구조입니다. 그래서 인터럽트 후반부를 처리하는 코드 설계를 할 때 아주 많은 고민을 할 필요는 없습니다. 다음 그림은 IRQ Thread 전체 흐름도인데 “irq=92” mmc1인터럽트가 발생했을 때 이 인터럽트의 후반부를 처리하는 “irq/92-mmc1” IRQ Thread가 실행하는 과정을 나타냅니다.


Soft IRQ는 IRQ Thread 기법보다 구조가 더 복잡합니다. 또한 Soft IRQ는 IRQ Thread에 비해 훨씬 반응 속도에 민감한 리눅스 커널 시스템에서 사용합니다. 10개 Soft IRQ를 서비스를 인터럽트 핸들러에서 요청하면 프로세스 레벨에서 Soft IRQ 서비스에서만 실행하는 것이 아닙니다. Soft IRQ 전체 구조를 알아보기 전에 잠깐 Soft IRQ 관련 용어를 소개할게요.

Soft IRQ 서비스
우선 Soft IRQ 서비스란 용어를 소개합니다. 리눅스 커널에서는 다음과 같이 10가지 Soft IRQ 서비스를 정의해 놨는데, 부팅할 때 open_softirq 란 함수를 써서 등록합니다.
[include/linux/interrupt.h]
const char * const softirq_to_name[NR_SOFTIRQS] = {
"HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "IRQ_POLL",
"TASKLET", "SCHED", "HRTIMER", "RCU"
};

Soft IRQ 서비스 아이디
그리고 Soft IRQ 서비스 아이디란 용어는 다음 코드에서 enum 타입으로 정의한 값들을 의미합니다.
[include/linux/interrupt.h]
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,  
NR_SOFTIRQS
};

Soft IRQ 서비스 핸들러
이번에는 Soft IRQ 서비스 핸들러입니다. 각 Soft IRQ 서비스를 실행할 함수입니다. 부팅 과정에서 open_softirq 함수로 Soft IRQ 서비스를 등록할 때 softirq_vec이란 전역 변수에 등록합니다. 다음 softirq_vec 전역 변수는 Trace32로 라즈베리안에 등록된 Soft IRQ 서비스 핸들러 정보를 확인한 것입니다. 이 함수들은 __do_softirq 함수에서 호출됩니다. 
(static struct softirq_action [10]) [D:0x80C02080] softirq_vec = (
    [0] = ((void (*)()) [D:0x80C02080] action = 0x80122888 = tasklet_hi_action),
    [1] = ((void (*)()) [D:0x80C02084] action = 0x80181270 = run_timer_softirq),
    [2] = ((void (*)()) [D:0x80C02088] action = 0x80614684 = net_tx_action),
    [3] = ((void (*)()) [D:0x80C0208C] action = 0x80615AB0 = net_rx_action),
    [4] = ((void (*)()) [D:0x80C02090] action = 0x804279B0 = blk_done_softirq),
    [5] = ((void (*)()) [D:0x80C02094] action = 0x0 = ),
    [6] = ((void (*)()) [D:0x80C02098] action = 0x8012299C = tasklet_action),
    [7] = ((void (*)()) [D:0x80C0209C] action = 0x801588EC = run_rebalance_domains),
    [8] = ((void (*)()) [D:0x80C020A0] action = 0x0 = ),
    [9] = ((void (*)()) [D:0x80C020A4] action = 0x8017ABC4 = rcu_process_callbacks))

Soft IRQ 서비스 요청
Soft IRQ 서비스 요청은 raise_softirq나 raise_softirq_irqoff 함수로 Soft IRQ 서비스 요청을 합니다. 이 때 요청할 Soft IRQ 서비스 아이디를 지정해야 합니다.
이제 Soft IRQ 전체 구조를 확인해 볼까요?

위 그림과 같이 Soft IRQ 후반부 처리는 4단계로 분류할 수 있습니다. 각 단계별로 어떤 동작을 하는지 확인해 봅시다.

[1] 단계
인터럽트가 발생하면 해당 인터럽트 핸들러에서 Soft IRQ 서비스를 요청합니다. 이를 위해  raise_softirq_irqoff 함수를 호출해야 합니다. 이 동작은 인터럽트 핸들러에서 IRQ_WAKE_THREAD를 리턴하는 동작과 유사합니다. 각 함수 호출 순서는 번호 순서이니 참고하세요.
11 raise_softirq_irqoff
10 Interrupt handler
9 __handle_irq_event_percpu
8 handle_irq_event
7 handle_level_irq
6 generic_handle_irq
5 bcm2836_chained_handle_irq
4 generic_handle_irq
3 __handle_domain_irq
2 bcm2836_arm_irqchip_handle_irq
1 __irq_svc

[2] 단계
인터럽트 서비스 루틴 동작이 끝나면 irq_exit이란 함수를 호출합니다. 여기서 Soft IRQ 서비스 요청 여부를 점검하고 요청한 Soft IRQ 서비스가 있으면 __do_softirq 함수에서 해당 Soft IRQ 핸들러를 호출합니다. 만약 Soft IRQ 서비스 요청이 없으면 irq_exit는 일을 끝냅니다. 
4 Soft IRQ Handler
3 __do_softirq
2 invoke_softirq
1 irq_exit

[3] 단계
__do_softirq 함수에서 Soft IRQ 서비스 핸들러를 호출하다가 2ms 이상 시간이 걸리거나 10번 이상 Soft IRQ 핸들러를 호출하면Soft IRQ 서비스 수행을 도중에 마치고 wakeup_softirqd 함수를 호출해서 ksoftirqd 프로세스를 깨웁니다. 프로세스 레벨로 인터럽트 후반부 처리를 하기 위해서 입니다. __do_softirq 함수 실행 시간에 제약을 건 이유는 __do_softirq 함수를 호출하는 irq_exit 함수가 프로세스가 일을 멈춘 상태에서 실행하기 때문입니다.

[4] 단계
ksoftirqd 프로세스가 깨어나 [3] 단계에서 마무리 못 한 Soft IRQ 서비스를 실행합니다.
6 Soft IRQ Handler
5 __do_softirq
4 run_ksoftirqd
3 smpboot_thread_fn
2 kthread
1 ret_from_fork

Soft IRQ 처리 과정을 알아보니 IRQ Thread 구조보다 인터럽트 후반부를 처리하는 과정이 더 복잡합니다. IRQ Thread는 인터럽트 핸들러에서 못한 일을 프로세스 레벨에서 실행합니다. 그런데 Soft IRQ서비스는 인터럽트 핸들러가 수행된 후 바로 일을 시작합니다. 그러니 Soft IRQ 서비스 핸들러는 일을 빨리 끝내야겠죠.

조금 어려운 개념이지만 코드 한줄 한줄 분석하며 라즈베리파이에서 Soft IRQ가 어떻게 동작하는지 확인하면 더 쉽게 이해할 수 있습니다. 이제 Soft IRQ 서비스를 어떻게 등록하는지 알아볼까요?



[리눅스커널][Trace32] wakelock 디버깅 - container_of [Debugging] Tips


이번 시간에는 wakeup_sources이란 링크드 리스트를 통해 wakelock 디버깅을 합시다.
이전에는 crash-utility를 썻는데 이번에는 Trace32를 쓰겠습니다.

개발자는 다양한 툴을 써야 한 가지 툴에 종속된 노예 개발자가 되는 것을 피할 수 있습니다.
또한 각 툴의 장점을 잘 활용할 수도 있습니다.

먼저, 다음 T32 명령어를 입력해서 offsetof와 container_of 매크로를 생성합시다.
sYmbol.NEW.MACRO offsetof(type,member) ((int)(&((type*)0)->member))
sYmbol.NEW.MACRO container_of(ptr,type,member) ((type *)((char *)(ptr)-offsetof(type,member)))

wakelock을 잡은 모듈들은 wakeup_sources 이란 전역 변수에 등록돼 있습니다.
우선 wakeup_sources 변수를 봅시다.
v.v %h %t wakeup_sources
  (static struct list_head) wakeup_sources = (
    (struct list_head *) next = 0xFFFFFFC5FD92EA88 -> (
      (struct list_head *) next = 0xFFFFFFC5FD92FC88 -> (
        (struct list_head *) next = 0xFFFFFFC541226488,
        (struct list_head *) prev = 0xFFFFFFC5FD92EA88),
      (struct list_head *) prev = 0xFFFFFF97D92B4D00),
    (struct list_head *) prev = 0xFFFFFFC62D3EDB88)

계속 struct list_head->next로 리스트들이 연결돼 있습니다.

이제 앞에서 생성한 container_of 매크로를 쓸 순간입니다. 다음과 같이 명령어를 입력합시다.
v.v %h %t %s  container_of(0xFFFFFFC5FD92EA88,struct wakeup_source,entry)
  (struct wakeup_source *) container_of(0xFFFFFFC5FD92EA88,struct wakeup_source,entry) = 0xFFFFFFC5FD92EA80 -> (
    (char *) name = 0xFFFFFFC55DEE3280 -> "ipc00001269_1988_HwBinder:682_1",
    (struct list_head) entry = ((struct list_head *) next = 0xFFFFFFC5FD92FC88, (struct list_head *) prev = 0xFFF
    (spinlock_t) lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u16) owner = 0x298F, (u16)
    (struct wake_irq *) wakeirq = 0x0,
    (struct timer_list) timer = ((struct hlist_node) entry = ((struct hlist_node *) next = 0x0, (struct hlist_nod
    (long unsigned int) timer_expires = 0x0,
    (ktime_t) total_time = ((s64) tv64 = 0x0),
    (ktime_t) max_time = ((s64) tv64 = 0x0),
    (ktime_t) last_time = ((s64) tv64 = 0x000040E1FC6B0DF2),
    (ktime_t) start_prevent_time = ((s64) tv64 = 0x0),
    (ktime_t) prevent_sleep_time = ((s64) tv64 = 0x0),
    (long unsigned int) event_count = 0x0,
    (long unsigned int) active_count = 0x0,
    (long unsigned int) relax_count = 0x0,
    (long unsigned int) expire_count = 0x0,
    (long unsigned int) wakeup_count = 0x0,
    (long unsigned int) pending_count = 0x0,
    (bool:1) active = FALSE,
    (bool:1) autosleep_enabled = FALSE)

위 멤버 변수 중 active 변수가 FALSE이니 "ipc00001269_1988_HwBinder:682_1"이란 wakeup source가 wakelock을 잡고 있지 않습니다.

v.v %h %t %s  container_of(0xFFFFFFC5FD92EA88,struct wakeup_source,entry)

위와 같은 명령어를 쓴 이유는, struct wakeup_source이란 구조체에 entry이란 struct list_head 타입 멤버 변수가
wakeup_sources이란 링크드 리스트 전역 변수 wakeup_sources->next로 등록됐기 때문입니다.

해당 구조체를 봅시다.
struct wakeup_source {
const char *name;
struct list_head entry;
spinlock_t lock;
struct wake_irq *wakeirq;
struct timer_list timer;
unsigned long timer_expires;
ktime_t total_time;
ktime_t max_time;
ktime_t last_time;
ktime_t start_prevent_time;
ktime_t prevent_sleep_time;
unsigned long event_count;
unsigned long active_count;
unsigned long relax_count;
unsigned long expire_count;
unsigned long wakeup_count;
bool active:1;
bool autosleep_enabled:1;
};

쉽게 설명하면 아래 방식으로 wakeup_source가 wakeup_sources 리스트에 등록합니다.
1st wakeup source 등록
wakeup_sources->next = struct wakeup_source->entry

2nd wakeup source등록
wakeup_sources->next->next = struct wakeup_source->entry

개발자분들이여, 임베디드 동네의 최강의 툴인 Trace32를 잘 활용해서 일찍 퇴근합시다.


[리눅스] printk 아규먼트 포멧 Linux Kernel - Core Analysis

printk에 %로 어떤 형식을 지정할 지 헷갈릴 때가 많습니다.
이럴 때는 다음 형식을 참고하면 됩니다.
If variable is of Type, use printk format specifier:
------------------------------------------------------------
int %d or %x
unsigned int %u or %x
long %ld or %lx
unsigned long %lu or %lx
long long %lld or %llx
unsigned long long %llu or %llx
size_t %zu or %zx
ssize_t %zd or %zx
s32 %d or %x
u32 %u or %x
s64 %lld or %llx
u64 %llu or %llx


예를 들면 다음 코드와 같이,
timer->expires 멤버 변수를 (unsigned long long) 타입으로 캐스팅해서 %llu 형식으로 출력합니다.
diff --git a/kernel/time/timer.c b/kernel/time/timer.c
--- a/kernel/time/timer.c
+++ b/kernel/time/timer.c
@@ -1008,6 +1010,21 @@ __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)
  debug_activate(timer, expires);
  timer->expires = expires;
+
+ if (raspbian_debug_state == 5060) {
+ void *stack = NULL;
+ struct thread_info *current_thread;
+
+ stack = current->stack;
+ current_thread = (struct thread_info*)stack;
+
+ trace_printk("[-][%s]jiffies_64: %llu, timer->expires: %llu  caller:%pS \n",
+ current->comm, jiffies_64, (unsigned long long)timer->expires, (void *)__builtin_return_address(0));
+
+ trace_printk("[-] in_softirq(): 0x%08x,preempt_count = 0x%08x \n",
+ (unsigned int)in_softirq(), (unsigned int)current_thread->preempt_count);
+ }

출처

[라즈베리파이] ksoftirqd 쓰레드란 [라즈베리파이]인터럽트후반부

ksoftirqd이란 per-cpu 타입 프로세스입니다. 즉 CPU 개수만큼 생성해서 정해진 CPU 내에서만 실행합니다. ksoftirqd 프로세스는 커널 쓰레드로 Soft IRQ 서비스를 쓰레드 레벨에서 처리합니다. 

리눅스 커널을 탑재한 어떤 시스템에서도 볼 수 있는 친근한 프로세스입니다. 먼저 ksoftirqd 쓰레드를 같이 확인하겠습니다.

다음 사이트를 방문하면 다른 리눅스 시스템에서 ksoftirqd 쓰레드를 확인할 수 있습니다.
[출처: https://zetawiki.com/wiki/Ksoftirqd]
[root@zetawiki ~]# ps -ef | grep ksoftirqd | grep -v grep
root         3     2  0 Jan08 ?        00:00:07 [ksoftirqd/0]
root        13     2  0 Jan08 ?        00:00:10 [ksoftirqd/1]
root        18     2  0 Jan08 ?        00:00:08 [ksoftirqd/2]
root        23     2  0 Jan08 ?        00:00:07 [ksoftirqd/3]
root        28     2  0 Jan08 ?        00:00:07 [ksoftirqd/4]
root        33     2  0 Jan08 ?        00:00:06 [ksoftirqd/5]
root        38     2  0 Jan08 ?        00:00:06 [ksoftirqd/6]
root        43     2  0 Jan08 ?        00:00:07 [ksoftirqd/7]

“ps –ef” 명령어와 grep 명령어를 조합해서 프로세스 목록 중 ksoftirqd 쓰레드만 출력했습니다. Ksoftirqd란 쓰레드 이름 뒤에 숫자가 보이죠? 각각 ksoftirqd 쓰레드가 실행 중인 CPU번호입니다.

이번에는 라즈비안에서 ksoftirqd 쓰레드를 확인해 보겠습니다.
root@raspberrypi:/home/pi# ps axl | grep ksoftirq
1     0     7     2  20   0      0     0 smpboo S    ?          0:00 [ksoftirqd/0]
1     0    14     2  20   0      0     0 smpboo S    ?          0:00 [ksoftirqd/1]
1     0    19     2  20   0      0     0 smpboo S    ?          0:00 [ksoftirqd/2]
1     0    24     2  20   0      0     0 smpboo S    ?          0:00 [ksoftirqd/3]

라즈비안은 CPU4개인 쿼드코어 시스템이므로 CPU번호는 0~3번까지 보입니다.
ksoftirqd 는 시스템 CPU 개수만큼 생성되는데 커널은 다음 규칙으로 ksoftirqd 프로세스 이름을 짓습니다.
"ksoftirqd/[CPU 번호]"

ksoftirqd/0 쓰레드는 CPU0에서만 돌고 ksoftirqd/1, ksoftirqd/2 그리고 ksoftirqd/3 쓰레드도 마찬가지로 해당 CPU(CPU1/CPU2/CPU3)에서 수행합니다. 조금 전문적인 용어로 이런 프로세스를 per-cpu 쓰레드라고 합니다. 라즈베리안에서는 4개 ksoftirqd 프로세스가 보이니 CPU가 4개인 쿼드코어 시스템이라는 사실을 유추할 수 있습니다.

그럼 ksoftirqd 프로세스는 언제 생성할까요? 다음과 같이 spawn_ksoftirqd 함수에서 생성합니다. spawn_ksoftirqd 함수 앞에 __init란 매크로가 보이니 커널이 부팅할 때 실행합니다.
static __init int spawn_ksoftirqd(void)
{
cpuhp_setup_state_nocalls(CPUHP_SOFTIRQ_DEAD, "softirq:dead", NULL,
  takeover_tasklets);
BUG_ON(smpboot_register_percpu_thread(&softirq_threads));

return 0;
}

ksoftirqd 프로세스 선언부를 보면 ksoftirqd 프로세스가 실행하면 run_ksoftirqd 함수가 수행한다는 사실을 알 수 있습니다.
static struct smp_hotplug_thread softirq_threads = {
.store = &ksoftirqd,
.thread_should_run ksoftirqd_should_run,
.thread_fn run_ksoftirqd,
.thread_comm = "ksoftirqd/%u",
};

ksoftirqd와 같은 per-cpu 타입 쓰레드는 smp 핫플러그 쓰레드로 등록해서 실행합니다. 

간단히 설명을 드리면, 리눅스 커널에선 시스템 부하가 떨어졌을 때는 여러 개의 CPU가 실행할 필요가 없습니다. 예를 들어 라즈베리파이에서 음악이나 동영상 재생을 안 하고 아무 프로그램도 실행을 안 한 상태로 두면 1개 CPU만 실행합니다. 시스템 부하에 따라 CPU를 끄고 키는 동작을 하는데 이때 smp_boot 쓰레드가 동작합니다.

ksoftirqd 쓰레드는 각 CPU마다 생성된 프로세스입니다. 예를 들면 "ksoftirqd/2" 쓰레드는 CPU2에서만 일을 합니다.  그런데 CPU2가 꺼져 있으면 안 되겠죠. CPU2가 꺼지는 동작을 유식하게 CPU Hot-plugout이라고 합니다. 

만약 smp_boot는 "ksoftirqd/2"에서 더 처리해야 할 Soft IRQ 서비스가 있는데 CPU2가 Hotplug-out될 상황이면 이 Soft IRQ 서비스를 "ksoftirqd/3”와 같이 깨어 있는 다른 ksoftirqd 쓰레드가 실행하게 처리합니다.

smpboot를 관리하는 함수는 smpboot_thread_fn() 이며 다음 코드와 같습니다.
1 static int smpboot_thread_fn(void *data)
2 {
3 struct smpboot_thread_data *td = data;
....
4 if (!ht->thread_should_run(td->cpu)) {
5 preempt_enable_no_resched();
6 schedule();
7 } else {
8 __set_current_state(TASK_RUNNING);
9 preempt_enable();
10 ht->thread_fn(td->cpu);
11 }

smpboot 에 핫플러그인으로 등록된 ksoftirqd 쓰레드의 thread_fn 멤버로 등록한 run_ksoftirqd() 함수는 smpboot_thread_fn() 함수의 다음 10번 줄 코드에서 실행합니다.

다음에 볼 코드는 ksoftirqd 프로세스가 실행될 때 호출되는 run_ksoftirqd 함수입니다
1 static void run_ksoftirqd(unsigned int cpu)
2 {
3 local_irq_disable();
4 if (local_softirq_pending()) {
5 __do_softirq();
6 local_irq_enable(); 
7 cond_resched_rcu_qs();
8 return;
9 }
10 local_irq_enable();
11 }

4 번째 줄 코드를 보면 local_softirq_pending 함수를 호출해서 요청한 Soft IRQ 서비스가 있는지 점검합니다. 만약에 Soft IRQ 서비스 요청이 있으면 __do_softirq() 함수를 호출해서 Soft IRQ 서비스 핸들러를 실행합니다.

이번에는 ksoftirqd 쓰레드를 제어하는 ksoftirqd_should_run 함수를 살펴보겠습니다.
static int ksoftirqd_should_run(unsigned int cpu)
{
return local_softirq_pending();
}

함수 이름과 같이 ksoftirqd 쓰레드를 실행 여부를 알려주는 임무를 수행합니다.
이번에도 local_softirq_pending 함수를 호출해서 Soft IRQ 서비스 요청이 있으면 ksoftirqd 쓰레드를 실행 여부를 알려줍니다.

전체 Soft IRQ 구조에서 ksoftirqd 쓰레드는 어떤 역할을 할까요? 다음 그림에서 검은색으로 된 블록을 보시면 됩니다.

인터럽트가 발생한 후 irq_exit 함수로 시작해서 __do_softirq 함수에서 Soft IRQ 서비스 핸들러를 실행합니다. __do_softirq 함수에서 Soft IRQ 서비스 핸들러 실행 시간이 2ms 초를 넘어서면 하던 일을 멈추고 ksoftirqd 프로세스를 깨웁니다. Soft IRQ 서비스는 프로세스 실행을 멈춘 상태에서 동작하므로 실행 시간이 길면 시스템 응답 속도가 느려지거나 오동할 수 있기 때문입니다.

여기까지 ksoftirqd 쓰레드가 어떻게 생성되고 Soft IRQ 구조에서 어떤 역할을 수행하는지 알아봤습니다. ksoftirqd 쓰레드는 IRQ Thread와 비슷한 역할을 합니다. ksoftirqd 쓰레드는 프로세스 레벨에서 Soft IRQ 서비스를 처리하는 임무를 수행합니다. 다른 관점으로 Soft IRQ가 인터럽트 후반부 처리를 할 때의 주인공이 ksoftirqd 쓰레드인 것입니다.


[라즈베리파이][리눅스커널] IRQ Thread는 언제 생성할까?[2] #CS [라즈베리파이]인터럽트후반부

여기까지 request_threaded_irq 함수를 호출하면 결국 kthread_create 함수가 호출되어 IRQ Thread를 생성하는 코드를 짚어 봤습니다. 이제 분석한 대로 라즈베리파이에서 동작하는지 알아봐야겠죠. 이를 알아 보기 위한 코드를 소개할게요.
diff --git a/kernel/kthread.c b/kernel/kthread.c
index 1c19edf82..0bc8a0037 100644
--- a/kernel/kthread.c
+++ b/kernel/kthread.c
@@ -275,13 +275,25 @@ struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),
1   struct task_struct *task;
struct kthread_create_info *create = kmalloc(sizeof(*create),
     GFP_KERNEL);
4 +
5 + int irq_thread_enable = !strncmp(namefmt, "irq", 3); 
6 if (!create)
7 return ERR_PTR(-ENOMEM);
create->threadfn = threadfn;
create->data = data;
10  create->node = node;
11  create->done = &done;
12+
13+    if (irq_thread_enable) {
14+        void *irq_threadfn = (void*)threadfn;
15+        
16+        printk("[+] irq_thread handler: %pS caller:(%pS) \n", 
17+ irq_threadfn, (void *)__builtin_return_address(0)); 
18+        dump_stack();
19+ }
20 
  spin_lock(&kthread_create_lock);
  list_add_tail(&create->list, &kthread_create_list);

이 패치 코드에 대한 이해를 돕기 위해 패치 적용 전__kthread_create_on_node 함수의 원래 코드를 보여드릴게요. 아래 화살표를 친 부분에 코드가 추가된 것이지요.
struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),
    void *data, int node,
    const char namefmt[],
    va_list args)
{
DECLARE_COMPLETION_ONSTACK(done);
struct task_struct *task;
struct kthread_create_info *create = kmalloc(sizeof(*create),
     GFP_KERNEL);
// <<-- 첫 번째 코드: irq_thread_enable = !strncmp(namefmt, "irq", 3);

if (!create)
return ERR_PTR(-ENOMEM);
create->threadfn = threadfn;
create->data = data;
create->node = node;
create->done = &done;

// <<-- 두 번째 코드: if (irq_thread_enable) {

이 점 참고하시고 패치 코드를 작성하시길 바랍니다.

그런데, 그 동안 한 번도 분석 안 한 __kthread_create_on_node 함수에 IRQ Thread를 생성하는 콜스택을 커널 로그로 저장하는 코드를 작성한 이유가 궁금하지는 않나요?
static int
setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
//...
if (!secondary) {
t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
   new->name);

우리는 set_up_irqthread 함수에서 kthread_create 함수를 호출하는 코드를 봤습니다. 그런데 kthread_create 함수를 호출하면 __kthread_create_on_node 함수가 호출됩니다. 그래서 이 코드를 작성한 것이죠. 다음 코드에서 볼드체로 된 부분을 눈여겨보세요.
#define kthread_create(threadfn, data, namefmt, arg...) \
kthread_create_on_node(threadfn, data, NUMA_NO_NODE, namefmt, ##arg)

struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
   void *data, int node,
   const char namefmt[],
   ...)
{
struct task_struct *task;
va_list args;

va_start(args, namefmt);
task = __kthread_create_on_node(threadfn, data, node, namefmt, args);

우리는 kthread_create 함수를 호출하면 커널 쓰레드를 생성한다고 알고 있습니다. 그런데 kthread_create는 구현부를 보면 kthread_create는 kthread_create_on_node로 치환되는 매크로 함수라는 사실을 알 수 있습니다. 그런데 kthread_create_on_node 함수를 열어보면 __kthread_create_on_node 함수를 호출합니다.

이제 패치 코드를 분석할 차례입니다. 우선 먼저 수정한 4번째 줄 코드부터 보겠습니다.
struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),
    void *data, int node,
    const char namefmt[],
    va_list args)
4 +
5 + int irq_thread_enable = !strncmp(namefmt, "irq", 3); 
6 +      char *process_name = &namefmt[0];

__kthread_create_on_node 함수로 전달되는 namefmt란 파라미터를 “irq” 문자열과 비교합니다. strncmp는 지정한 문자열 개수만큼만 스트링을 비교하는 라이브러리 함수입니다. 이때 namefmt 변수 첫 번째 주소 기준으로 세 개 스트링이 “irq” 이면 irq_thread_enable 변수가 1로 설정되겠죠. 

IRQ Thread 이름은 setup_irq_thread 함수에서 "irq/%d-%s" 인자로 kthread_create 함수를 호출할 때 지정한다고 배웠죠? 다음 5번째 줄 코드를 봐주세요. setup_irq_thread 함수에서 지정한 "irq/%d-%s" 가 __kthread_create_on_node 함수의 namefmt 인자로 전달되는 것입니다. 그래서 이 앞 세 개의 스트링이 “irq”인지 비교하는 코드를 작성한 것입니다.  
1 static int
2 setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
3 {
//...
4 if (!secondary) {
5 t = kthread_create(irq_thread, new, "irq/%d-%s", irq,  // <<-[1]
6    new->name);

이제 디버깅 정보를 출력하는 13번째 줄 코드를 볼 차례입니다.
13+    if (irq_thread_enable) {
14+        void *irq_threadfn = (void*)threadfn;
15+        
16+        printk("[+] irq_thread handler: %pS caller:(%pS) \n", 
17+ irq_threadfn, (void *)__builtin_return_address(0)); 
18+        dump_stack();
19+ }

13번째 줄부터 19번째 줄까지 코드는irq_thread_enable 지역변수가 1이면 수행합니다. irq_thread_enable 변수가 1이면 뭘 의미할까요? 쓰레드 이름이 “irq”로 시작하니 IRQ Thread를 생성하려는 조건입니다. 14번째 줄 코드에서는 IRQ Thread 쓰레드 핸들 함수 포인터를 저장하고 있는 threadfn을 irq_threadfn 포인터에 캐스팅합니다. threadfn에는 irq_thread 함수 주소를 지정하고 있습니다. 16번째 줄 코드는 IRQ Thread 핸들 함수와 자신을 호출한 함수 정보를 출력합니다. 18번째 줄 코드는 콜스택을 출력하는 dump_stack 함수를 호출합니다.

이렇게 코드를 입력하고 커널 빌드를 완료한 후 라즈베리안에 커널 이미지를 설치해야겠죠? 이후 라즈베리파이를 리부팅 시키고 커널 로그(/var/log/kern.log)을 열어보면 다음과 같은 정보를 확인할 수 있습니다.

아래 로그를 차근차근 분석해볼까요?
1  [0.722882] mmc-bcm2835 3f300000.mmc: mmc_debug:0 mmc_debug2:0
2  [0.722892] mmc-bcm2835 3f300000.mmc: DMA channel allocated
3  [0.722933] [+] process_name: irq/%d-%s caller:(kthread_create_on_node+0x30/0x38) 
4  [0.722947] [+] irq_thread handler: irq_thread+0x0/0x20c
 caller:(kthread_create_on_node+0x30/0x38) 
5  [0.722958] CPU: 0 PID: 31 Comm: kworker/0:1 Not tainted 4.14.39-v7+ #15
6  [0.722962] Hardware name: BCM2835
7  [0.722980] Workqueue: events deferred_probe_work_func
8  [0.723006] (unwind_backtrace) from [<8010c21c>] (show_stack+0x20/0x24)
9  [0.723022] (show_stack) from (dump_stack+0xc8/0x10c)
10 [0.723039] (dump_stack) from (__kthread_create_on_node+0x1c4/0x1e0)
11 [0.723055] (__kthread_create_on_node) from (kthread_create_on_node+0x30/0x38)
12 [0.723070] (kthread_create_on_node) from (setup_irq_thread+0x54/0xe4)
13 [0.723086] (setup_irq_thread) from (__setup_irq+0x260/0x730)
14 [0.723101] (__setup_irq) from (request_threaded_irq+0xec/0x160)
15 [0.723118] (request_threaded_irq) from (devm_request_threaded_irq+0x78/0xcc)
16 [0.723140] (devm_request_threaded_irq) from (bcm2835_mmc_probe+0x514/0x644)
17 [0.723159] (bcm2835_mmc_probe) from (platform_drv_probe+0x60/0xc0)
18 [0.723176] (platform_drv_probe) from (driver_probe_device+0x244/0x2f8)
19 [0.723193] (driver_probe_device) from (__device_attach_driver+0xa8/0xdc)
20 [0.723210] (__device_attach_driver) from (bus_for_each_drv+0x70/0xa4)
21 [0.723228] (bus_for_each_drv) from (__device_attach+0xc0/0x124)
22 [0.723245] (__device_attach) from (device_initial_probe+0x1c/0x20)
23 [0.723261] (device_initial_probe) from (bus_probe_device+0x94/0x9c)
24 [0.723278] (bus_probe_device) from (deferred_probe_work_func+0x68/0x150)
25 [0.723296] (deferred_probe_work_func) from (process_one_work+0x224/0x518)
26 [0.723317] (process_one_work) from (worker_thread+0x60/0x5f0)
27 [0.723333] (worker_thread) from (kthread+0x144/0x174)
28 [0.723348] (kthread) from (ret_from_fork+0x14/0x28)

먼저 커널 쓰레드 이름과 쓰레드 핸들 함수를 분석하겠습니다.
3  [0.722933] [+] process_name: irq/%d-%s caller:(kthread_create_on_node+0x30/0x38) 
4  [0.722947] [+] irq_thread handler: irq_thread+0x0/0x20c  caller:(kthread_create_on_node+0x30/0x38) 

쓰레드 이름은 irq/%d-%s 이고 자신을 호출한 함수가 kthread_create_on_node+0x30이네요. IRQ Thread 쓰레드 핸들 함수는 irq_thread란 정보를 알 수 있습니다.

다음엔 이 함수가 어느 프로세스에서 실행됐는지 확인해 봐야겠죠. PID가 31인 kworker/0:1란 워커쓰레드가 CPU0에서 수행됐음을 알 수 있습니다.
5  [0.722958] CPU: 0 PID: 31 Comm: kworker/0:1 Not tainted 4.14.39-v7+ #15
6  [0.722962] Hardware name: BCM2835

이번에는 콜스택을 살펴볼 차례입니다. platform_drv_probe 함수에서 bcm2835_mmc_probe 함수를 호출하고 있습니다. 이 정보로 부팅 도중에 IRQ Thread를 설정한다고 유추할 수 있겠네요. 보통 드라이버에서 짠 코드의 함수 이름에 probe가 보이면 부팅 도중 한 번 실행합니다.
11 [0.723055] (__kthread_create_on_node) from (kthread_create_on_node+0x30/0x38)
12 [0.723070] (kthread_create_on_node) from (setup_irq_thread+0x54/0xe4)
13 [0.723086] (setup_irq_thread) from (__setup_irq+0x260/0x730)
14 [0.723101] (__setup_irq) from (request_threaded_irq+0xec/0x160)
15 [0.723118] (request_threaded_irq) from (devm_request_threaded_irq+0x78/0xcc)
16 [0.723140] (devm_request_threaded_irq) from (bcm2835_mmc_probe+0x514/0x644)
17 [0.723159] (bcm2835_mmc_probe) from (platform_drv_probe+0x60/0xc0)
18 [0.723176] (platform_drv_probe) from (driver_probe_device+0x244/0x2f8)

이후 그동안 우리가 열심히 분석한 함수들이 보입니다. request_threaded_irq 함수로 출발해서 __kthread_create_on_node 함수까지 실행하고 있습니다. kthread_create 매크로 함수 대신 실제 kthread_create_on_node 함수가 실행한다는 점도 확인할 수 있습니다.

여기까지 IRQ Thread를 생성하는 흐름을 점검했습니다. request_threaded_irq 함수를 호출하면 __kthread_create_on_node까지 호출돼서 IRQ Thread를 생성하는 것이죠. 이제 여러분이 어느 리눅스 시스템에서도 만나는 “irq/92-mmc1”과 같은 IRQ Thread가 어떤 방식으로 생성되는지 배웠습니다. 다음 절에서는 IRQ Thread가 어떻게 실행하는지 점검해보겠습니다.



[라즈베리파이][리눅스커널] IRQ Thread는 언제 생성할까?[1] #CS [라즈베리파이]인터럽트후반부

IRQ Thread를 생성하기 위해서는 request_threaded_irq 을 호출하면 IRQ Thread가 생성된다고 설명해 드렸습니다. 사실 request_threaded_irq을 호출할 때 IRQ Thread가 생성되는 것은 아니고 다음 흐름에서  __kthread_create_on_node 함수가 실행할 때 생성됩니다.









우리는 커널 쓰레드를 생성할 때 kthread_create 함수를 호출한다고 배웠죠. IRQ Thread도 이 kthread_create 함수를 호출해서 생성합니다. 이로 IRQ Thread도 커널 쓰레드의 한 종류라고 유추할 수 있겠네요.

request_threaded_irq 부터 __kthread_create_on_node 함수까지 IRQ Thread를 어떻게 생성하는지 코드를 함께 살펴볼까요? 이를 위해 우선 인터럽트 핸들러를 설정하는 코드를 확인할 필요가 있습니다. 어떤 인터럽트 핸들러를 설정하는 코드를 봐야 할까요? 당연히 IRQ Thread를 생성하는 인터럽트 핸들러 코드를 봐야겠죠.

이전에 확인했다 싶히 라즈베리안에서는 "irq/92-mmc1" IRQ Thread를 확인할 수 있습니다. 
root@raspberrypi:/home/pi/dev_raspberrian# ps –ely | grep irq
S   UID   PID  PPID  C PRI  NI   RSS    SZ WCHAN  TTY          TIME CMD
S     0    65     2  0   9   -     0     0 irq_th ?        00:00:00 irq/92-mmc1

92번 인터럽트 핸들러와 해당 IRQ Thread를 설정하는 코드를 함께 분석하겠습니다. 
[drivers/mmc/host/bcm2835-mmc.c]
1 static int bcm2835_mmc_add_host(struct bcm2835_host *host)
2 {
3 struct mmc_host *mmc = host->mmc;
4 struct device *dev = mmc->parent;
//...
5 bcm2835_mmc_init(host, 0);
6 ret = devm_request_threaded_irq(dev, host->irq, bcm2835_mmc_irq,
7 bcm2835_mmc_thread_irq, IRQF_SHARED,
8 mmc_hostname(mmc), host); 

위 코드를 보면 request_threaded_irq 함수 대신 낯설게 보이는 devm_request_threaded_irq 함수로 IRQ Thread를 설정합니다. 다른 설정을 하는 함수 같아 보이기도 하는데요. 다음 코드와 같이 devm_request_threaded_irq 함수에서 request_threaded_irq 함수를 호출합니다.
1 int devm_request_threaded_irq(struct device *dev, unsigned int irq,
2       irq_handler_t handler, irq_handler_t thread_fn,
3       unsigned long irqflags, const char *devname,
4       void *dev_id)
5 {
6 struct irq_devres *dr;
7 int rc;
8
//...
9
10 rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
11   dev_id);

devm_request_threaded_irq 함수는 인터럽트 설정 정보를 디바이스 드라이버에서 체계적으로 관리하는 코드 빼고는 request_threaded_irq 함수와 같은 역할을 수행합니다. IRQ Thread 관점으로 devm_request_threaded_irq 함수를 호출하면 request_threaded_irq 함수가 실행된다고 봐도 무방합니다.

아래 코드에서 devm_request_threaded_irq 에서 호출하는request_threaded_irq 함수는 어디서 많이 본 함수 같지 않나요? 맞습니다. 인터럽트 핸들러를 등록할 때 호출하는 request_irq 함수에서 request_threaded_irq 함수를 호출했죠.  
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
    const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

이번에 request_threaded_irq 함수에 전달하는 인자가 약간 다른 것 같습니다.
6 ret = devm_request_threaded_irq(dev, host->irq, bcm2835_mmc_irq,
7 bcm2835_mmc_thread_irq, IRQF_SHARED,
8 mmc_hostname(mmc), host); 

6번째 줄을 보면 bcm2835_mmc_irq 함수를 인터럽트 핸들러로 등록합니다. 이 함수는 92번 “mmc1” 인터럽트가 발생하면 호출되는 함수죠. 

7번째 줄 코드를 보면 request_threaded_irq 함수 세 번째 인자로 bcm2835_mmc_thread_irq 함수를 전달합니다. 이 함수를 IRQ Thread 핸들러라고 합니다. IRQ Thread가 실행될 때 호출되는 핸들러 함수입니다.  

조금 더 이해를 돕기 위해 request_threaded_irq 함수의 선언부를 보면 세 번째 인자로 irq_handler_t thread_fn가 선언돼 있습니다. 두 번째 인자는 인터럽트 핸들러 함수입니다.
[include/linux/interrupt.h]
extern int __must_check
request_threaded_irq(unsigned int irq, irq_handler_t handler,
     irq_handler_t thread_fn,
     unsigned long flags, const char *name, void *dev);

thread_fn 이란 함수 포인터에 bcm2835_mmc_thread_irq 이란 IRQ Thread 핸들러 함수를 등록하는 것입니다.
이를 알기 쉬운 코드 형식으로 표현하면 아래와 같이 각각 인자를 다음과 같이 등록합니다.
인터럽트 번호    irq    host->irq
인터럽트 핸들러    handler    bcm2835_mmc_irq
IRQ Thread 핸들러 thread_fn  bcm2835_mmc_thread_irq

인터럽트가 발생했을 때 인터럽트 컨택스트에서 수행하는 인터럽트 핸들러는 bcm2835_mmc_irq 이고 “irq/92-mmc1” IRQ Thread에서 실행하는 핸들러 함수는 bcm2835_mmc_thread_irq 라고 볼 수 있습니다. 

이제 bcm2835_mmc_thread_irq 핸들러를 등록하고 “irq/92-mmc1”이란 IRQ Thread를 생성하는 흐름을 살펴보겠습니다. 우선 request_threaded_irq 함수부터 살펴보겠습니다.
1 int request_threaded_irq(unsigned int irq, irq_handler_t handler,
 irq_handler_t thread_fn, unsigned long irqflags,
 const char *devname, void *dev_id)
4 {
struct irqaction *action;
struct irq_desc *desc;
int retval;
//…
8 action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
//…
action->handler = handler;
10  action->thread_fn = thread_fn; // <<--[1]
11  action->flags = irqflags;
12  action->name = devname;
13  action->dev_id = dev_id;
14 
15  chip_bus_lock(desc);
16  retval = __setup_irq(irq, desc, action); // <--[2]

10번째 줄 [1] 코드를 보면 thread_fn 포인터를 action->thread_fn에 등록합니다. bcm2835_mmc_thread_irq 함수가 irq_handler_t thread_fn란 파라미터로 전달됩니다.
10  action->thread_fn = thread_fn; 
11  action->flags = irqflags;
12  action->name = devname;
13  action->dev_id = dev_id;

action란 포인터 타입 지역 변수는 struct irqaction 구조체로 선언됐습니다.
이 변수는 8번째 줄 코드와 같이 메모리 할당을 받은 다음에 9번째와 13번째 줄 코드와 같이 인터럽트 핸들러와 IRQ Thread 핸들러 정보를 저장하죠.

이 변수는 나중에 인터럽트 디스크립터의 action이란 멤버에 저장됩니다. 다음 struct irq_desc 구조체를 참고하세요.
struct irq_desc {
struct irq_common_data irq_common_data;
//...
struct irqaction *action; /* IRQ action list */

여기까지 설정한 인자들을 Trace32로 확인하면 다음과 같이 확인할 수 있습니다. 각 멤버들에 대한 설명은 주석문을 참고하세요.
(struct irq_desc *) (struct irq_desc*)0xB008B300
...
    (struct irqaction *) action = 0xBB4E6E40  
      (irq_handler_t) handler = 0x8061EC00 = bcm2835_mmc_irq, // 인터럽트 핸들러
      (void *) dev_id = 0xBB47B010  // 인터럽트 핸들러 핸들
      (void *) percpu_dev_id = 0x0 = ,
      (struct irqaction *) next = 0x0 = ,
      (irq_handler_t) thread_fn = 0x8061DCC4 = bcm2835_mmc_thread_irq, // IRQ Thread 핸들러
      (struct task_struct *) thread = 0xBB516CC0 // “irq/92-mmc1” IRQ Thread의 태스크 디스크립터
      (struct irqaction *) secondary = 0x0 = ,
      (unsigned int) irq = 92, // 인터럽트 번호

16번째 줄 [2] 번 코드를 보면 __setup_irq 함수를 호출합니다.
16  retval = __setup_irq(irq, desc, action);

여기까지는 인터럽트 핸들러를 등록하는 동작과 똑같습니다. 그런데 __setup_irq 함수 코드를 조금 더 살펴보면 Thread IRQ로 설정할 때만 동작하는 코드를 볼 수 있습니다. __setup_irq 코드를 같이 분석해볼까요?
1 static int
2 __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
3 {
4 struct irqaction *old, **old_ptr;
5 unsigned long flags, thread_mask = 0;
//...
6 nested = irq_settings_is_nested_thread(desc);
//...
7 if (new->thread_fn && !nested) {  
8 ret = setup_irq_thread(new, irq, false);
9 if (ret)
10 goto out_mput;

우선 __setup_irq 함수에 전달되는 파라미터는 아래와 같습니다.
irq 인터럽트 번호
desc 인터럽트 디스크립터
new 인터럽트 디스크립터의 action 멤버(struct irq_desc->action)
 
여섯 번째 줄 코드를 보면 두 가지 조건을 점검합니다. (struct irqaction *) 타입 구조체인 new->thead_fn 멤버에 함수 포인터가 등록됐거나 nested 변수가 0일 경우에 setup_irq_thread 함수를 호출합니다. 
6 if (new->thread_fn && !nested) {  
7 ret = setup_irq_thread(new, irq, false);

nested 변수는 현재 설정하는 IRQ Thread가 nested 타입인지 점검합니다. 이 기능을 쓸 경우 nested 변수가 1이 되거든요. nested 변수를 읽어 오는 다음 코드를 눈여겨보세요.
6 nested = irq_settings_is_nested_thread(desc);
여기서 new->thead_fn 로 bcm2835_mmc_thread_irq 함수가 IRQ_Thread 핸들러로 등록됐고 nested 변수가 0이면 7번째 줄 코드가 실행되게 되겠죠.

이번에는 IRQ Thread를 생성하는 setup_irq_thread 함수를 분석할 차례입니다.
1 static int
2 setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
3 {
4 struct task_struct *t;
5 struct sched_param param = {
6 .sched_priority = MAX_USER_RT_PRIO/2,
7 };
8
9 if (!secondary) {
10 t = kthread_create(irq_thread, new, "irq/%d-%s", irq,  // <<-[1]
11    new->name);
12 } else {
13 t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
14    new->name);
15 param.sched_priority -= 1;
16 }

위 코드를 보면 별 특이한 점은 없고 kthread_create 함수를 호출해서 커널 쓰레드를 생성할 뿐이죠. 

먼저 10번째 줄 [1] 코드를 볼까요? 코드를 보니irq_thread란 IRQ Thread를 제어하는 함수와 함께 "irq/%d-%s"란 이름으로 IRQ Thread를 생성합니다. 
9 t = kthread_create(irq_thread, new, "irq/%d-%s", irq,   
10    new->name);

여기서 irq는 인터럽트 번호, new->name은 인터럽트 이름이 되겠죠. 그런데 ftrace 로그를 봐도 92번 인터럽트는 이름이 mmc1이란 사실을 알 수 있습니다.
kworker/u8:2-1310  [000] d.h.  2208.183670: irq_handler_entry: irq=92 name=mmc1
kworker/u8:2-1310  [000] d.h.  2208.183673: bcm2835_mmc_irq <-__handle_irq_event_percpu
kworker/u8:2-1310  [000] d.h.  2208.183714: irq_handler_exit: irq=92 ret=handled

이 정보를 참고하면 “irq=92 name=mmc1” 인터럽트의 IRQ Thread 이름은 “irq/92-mmc1”라는 점을 유추할 수 있습니다. 

여기서 kthread_create 함수에 전달하는 두 번째 파라미터는 (struct irqaction *)new입니다. 이는 커널 쓰레드가 생성되면 쓰레드 핸들 함수로 전달됩니다. IRQ Thread인 경우 irq_thread 함수가 되겠죠. 이때 커널 쓰레드 핸들을 (struct irqaction *)new 변수로 등록한다는 점 기억하세요.
1 static int irq_thread(void *data)
2 {
3 struct callback_head on_exit_work;
4 struct irqaction *action = data;

결국 IRQ Thread가 수행할 때 irq_thread 함수가 실행하는데 이 함수 인자로 void 타입 data 포인터를 전달합니다. 이 포인터를 struct irqaction * 타입으로 캐스팅하는 것이지요.

이 과정을 정리하면 다음 다이어그램으로 설명할 수 있겠네요.

 







“irq/92-mmc1” IRQ Thread는 언제 실행될까요? 92번 인터럽트가 발생하면 해당 인터럽트 핸들러가 IRQ Thread를 실행할 지 결정합니다. 만약 아예 92번 인터럽트가 발생하지 않으면 IRQ Thread는 실행할 필요가 없는 것이지요.




1 2 3 4 5 6 7 8 9 10 다음