Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

30137
1186
128675


[리눅스커널] 커널 디버깅과 코드 학습: 디버깅과 코드 학습 능력 3. 커널 디버깅과 코드 학습

디버깅과 코드 학습 능력

디버깅하면서 리눅스 커널 코드를 함께 분석하면 다음과 같은 정보를 더 얻을 수 있습니다. 

분석 대상 코드가 동작하는 콜스택
함수가 실행될 때 변경되는 자료구조
함수가 실행되는 빈도와 실행 시각
분석 대상 코드를 실행하는 프로세스

대부분 리눅스 커널을 공부할 때는 커널 소스코드를 열어 봅니다. 코드를 이해하는 능력은 리눅스 개발자의 기본 소양이므로 소스를 이해하는 능력은 중요합니다. 하지만 소스코드를 분석하면 실행 흐름을 보는 시야가 좁아집니다. 그래서 코드 분석과 함께 함수 실행 흐름과 실행 빈도를 알 필요가 있습니다.

커널 소스코드만 분석한 분이 있다고 가정해 봅시다. 또 다른 분은 커널 디버깅과 함께 커널 코드를 함께 분석했습니다. 둘 중에서 누가 더 빨리 커널을 익힐 수 있을까요? 필자는 디버깅과 함께 커널 소스를 분석하면 소스만 분석할 때보다 5배 정도의 학습 효과가 있다고 생각합니다. 디버깅을 하면 5배 정도 많은 정보를 얻기 때문입니다. 그만큼 커널 디버깅이 중요합니다. 

그런데 여러분은 필자의 의견을 읽고 조금 수긍이 가지만 조금은 막연한 느낌이 들 수 있습니다. 그렇다면 어떻게 커널 디버깅을 어떻게 해야 할까요? 사실, 필자는 '이 책에서 소개한 실습 과정을 확인해보세요.'라고 말씀드리고 싶습니다. 한 가지 쉬운 예시를 들면서 커널 디버깅 과정을 설명하겠습니다.

5장 '인터럽트'에서 다룬 내용을 읽으면서 커널이 인터럽트를 처리하는 과정을 배우고 있다고 가정하겠습니다. 여기서 한 가지 흥미로운 사실을 알게 됐습니다. 바로 "cat /proc/interrupts" 명령어를 입력하면 인터럽트 세부 속성을 알 수 있다는 것입니다.

그래서 라즈베리 파이에서 터미널에서 다음과 같은 명령어를 입력해서 인터럽트 속성을 알게 됐습니다.

root@raspberrypi:/home/pi# cat /proc/interrupts 
           CPU0       CPU1       CPU2       CPU3       
 17:       6624          0          0          0  ARMCTRL-level   1 Edge      3f00b880.mailbox
 18:         34          0          0          0  ARMCTRL-level   2 Edge      VCHIQ doorbell
...
IPI5:       8411       8223       5705       8429  IRQ work interrupts
IPI6:          0          0          0          0  completion interrupts
Err:          0

그런데 리눅스 터미널에서 인터럽트 속성 정보를 보고 나니 커널의 어느 코드에서 이를 출력하는지 궁금해졌습니다. 어렵게 코드를 검색하다 보니 show_interrupts() 함수가 이 정보를 터미널로 출력한다는 사실을 알게 됐습니다.

또한 분석을 하고 나니 다음과 같은 내용이 궁금해지기 시작했습니다. 
인터럽트 디스크립터인 irq_desc 구조체의 action 필드에 저장된 인터럽트 속성 정보를 점검하고 싶다.
'cat /proc/interrupts' 명령어를 입력하면 show_interrupts() 함수가 호출되는지 확인하고 싶다.
show_interrupts() 함수를 호출할 때 프로세스 정보를 보고 싶다.

여기서 중요한 질문을 하나 하겠습니다. 이 정보를 파악하려면 어떻게 해야 할까요?

이를 위해서는 커널 코드를 수정할 필요가 있습니다. 2장에서 라즈비안 커널 소스를 빌드하는 방법을 알았으니 커널 코드를 수정해볼까요? 패치 코드의 내용을 먼저 소개합니다.

01 diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c
02 --- a/kernel/irq/proc.c
03 +++ b/kernel/irq/proc.c
@@ -449,6 +449,21 @@ int __weak arch_show_interrupts(struct seq_file *p, int prec)
04 # define ACTUAL_NR_IRQS nr_irqs
05 #endif
06
07 +void rpi_get_interrupt_info(struct irqaction *action_p)
08 +{
09 +       unsigned int irq_num = action_p->irq;
10 +       void *irq_handler = NULL;
11 +
12 +       if (action_p->handler) {
13 +               irq_handler = (void*)action_p->handler;
14 +       }
15 +
16 +       if (irq_handler) {
17 +               trace_printk("[%s] %d: %s, irq_handler: %pS \n",
18 +                               current->comm, irq_num, action_p->name, irq_handler);
19 +       }
20 +}
21 +
22 int show_interrupts(struct seq_file *p, void *v)
23 {
24        static int prec;
@@ -514,6 +529,10 @@ int show_interrupts(struct seq_file *p, void *v)
25                seq_printf(p, "-%-8s", desc->name);
26
27        action = desc->action;
28 +
29 +       if (action)
30 +               rpi_get_interrupt_info(action);
31 +
32        if (action) {
33                seq_printf(p, "  %s", action->name);
34                while ((action = action->next) != NULL)

먼저 패치 코드를 입력하는 방법을 설명하겠습니다.

다음 코드를 보면 커널의 원본 show_interrupts() 함수와 패치 코드를 입력할 위치를 알 수 있습니다.

int show_interrupts(struct seq_file *p, void *v)
{
static int prec;

unsigned long flags, any_count = 0;
...
if (desc->name)
seq_printf(p, "-%-8s", desc->name);

action = desc->action;
/* 이 부분에 1번째 패치 코드를 입력하세요 */
if (action) {
seq_printf(p, "  %s", action->name);
while ((action = action->next) != NULL)
seq_printf(p, ", %s", action->name);
}

seq_putc(p, '\n');

“/* 이 부분에 1번째 패치 코드를 입력하세요 */”라는 주석으로 표시된 부분에 다음 코드를 입력합니다.

29 +       if (action)
30 +               rpi_get_interrupt_info(action);

irqaction 구조체 타입인 action 포인터형 변수가 NULL이 아닐 때 action 인자와 함께 rpi_get_interrupt_info() 함수를 호출하는 코드입니다.

이어서 show_interrupts() 함수의 윗부분에 다음 rpi_get_interrupt_info() 함수의 코드를 작성합니다.

07 +void rpi_get_interrupt_info(struct irqaction *action_p)
08 +{
09 +       unsigned int irq_num = action_p->irq;
10 +       void *irq_handler = NULL;
11 +
12 +       if (action_p->handler) {
13 +               irq_handler = (void*)action_p->handler;
14 +       }
15 +
16 +       if (irq_handler) {
17 +               trace_printk("[%s] %d: %s, irq_handler: %pS \n",
18 +                               current->comm, irq_num, action_p->name, irq_handler);
19 +       }
20 +}

rpi_get_interrupt_info() 함수는 ftrace로 다음 정보를 출력하는 기능입니다.

프로세스 이름
인터럽트 번호
인터럽트 이름
인터럽트 핸들러 함수 이름 

여기서 한 가지 의문이 생깁니다. 위와 같은 패치 코드를 입력한 이유가 무엇일까요? 필자는 여러분이 인터럽트에 대해 공부하는 중이라고 가정했습니다.

인터럽트 속성 정보를 담고 있는 자료구조는 인터럽트 디스크립터를 나타내는 irq_desc 구조체입니다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/irqdesc.h
01 struct irq_desc {
02 struct irq_common_data irq_common_data;
03 struct irq_data irq_data;
04 unsigned int __percpu *kstat_irqs;
...
05 struct irqaction *action; /* IRQ action list */

그런데 코드를 읽다 보니 인터럽트 속성 정보는 위 05번째 줄과 같이 action 필드에 저장된다는 사실을 알게 됐습니다.

이어서 irqaction 구조체 내 필드를 보면서 인터럽트 속성 정보를 확인해봅시다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/interrupt.h
01 struct irqaction {
02 irq_handler_t handler;  /* 인터럽트 핸들러 함수 */ 
03 void *dev_id;
...
04 unsigned int irq;       /* 인터럽트 번호 */
05 unsigned int flags;
06 unsigned long thread_flags;
07 unsigned long thread_mask;
08 const char *name;    /* 문자열 타입의 인터럽트 이름 */
09 struct proc_dir_entry *dir;
10 } ____cacheline_internodealigned_in_smp;

irqaction 구조체 오른쪽 부분에 표시된 주석이 인터럽트 속성 정보입니다. 이 정보를 라즈비안에서 확인하려고 패치 코드를 입력한 것입니다.

이제 코드를 입력하고 라즈비안 커널 빌드를 시작합시다. 커널 빌드가 끝난 후 커널 이미지를 라즈베리 파이에 설치합니다.

ftrace를 설정하기 위해 일일이 명령어를 입력할 수 있습니다만ftrace를 설정할 때마다 비슷한 패턴의 명령어를 입력하면 불편합니다. 다음에 소개할 내용은 ftrace를 설정하는 명령어로 작성한 셸 스크립트 코드입니다.

01 #!/bin/bash
02 
03 echo 0 > /sys/kernel/debug/tracing/tracing_on
04 sleep 1
05 echo "tracing_off"
06 
07 echo 0 > /sys/kernel/debug/tracing/events/enable
08 sleep 1
09 echo "events disabled"
10
11 echo  secondary_start_kernel  > /sys/kernel/debug/tracing/set_ftrace_filter
12 sleep 1
13 echo "set_ftrace_filter init"
14
15 echo function > /sys/kernel/debug/tracing/current_tracer
16 sleep 1
17 echo "function tracer enabled"
18
19 echo rpi_get_interrupt_info > /sys/kernel/debug/tracing/set_ftrace_filter
20 sleep 1
21 echo "set_ftrace_filter enabled"
22
23 echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_entry/enable
24 echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_exit/enable
25 echo "event enabled"
26
27 echo 1 > /sys/kernel/debug/tracing/options/func_stack_trace
28 echo "function stack trace enabled"
29
30 echo 1 > /sys/kernel/debug/tracing/options/func_stack_trace
31 echo 1 > /sys/kernel/debug/tracing/options/sym-offset
32 echo "function stack trace enabled"
33
34 echo 1 > /sys/kernel/debug/tracing/tracing_on
35 echo "tracing_on"

위 코드를 입력한 다음 irq_trace_ftrace.sh 파일로 저장해 실행하면 손쉽게 ftrace를 세팅할 수 있습니다. 

위에서 소개한 ftrace 설정 명령어 중 가장 중요한 내용은 다음과 같습니다. 

19 echo rpi_get_interrupt_info > /sys/kernel/debug/tracing/set_ftrace_filter 

패치 코드에서 작성한 rpi_get_interrupt_info()라는 함수명을 /sys/kernel/debug/tracing/set_ftrace_filter 파일에 지정하는 명령어입니다.
이 같은 방식으로 함수 이름을 설정하면 rpi_get_interrupt_info() 함수를 누가 호출했는지 알 수 있습니다.

이번에는 다음 명령어를 입력해 irq_trace_ftrace.sh 셸 스크립트를 실행합니다.

root@raspberrypi:/home/pi# ./irq_trace_ftrace.sh 
tracing_off
events disabled
set_ftrace_filter init
function tracer enabled
event enabled
set_ftrace_filter enabled
function stack trace enabled
tracing_on

이번에는 'cat /proc/interrupts' 명령어를 입력합니다.

root@raspberrypi:/home/pi# cat /proc/interrupts 
           CPU0       CPU1       CPU2       CPU3       
 17:        282          0          0          0  ARMCTRL-level   1 Edge      3f00b880.mailbox
 18:         34          0          0          0  ARMCTRL-level   2 Edge      VCHIQ doorbell
 40:         36          0          0          0  ARMCTRL-level  48 Edge      bcm2708_fb dma
 42:          0          0          0          0  ARMCTRL-level  50 Edge      DMA IRQ
...
IPI5:        806        522        230        775  IRQ work interrupts
IPI6:          0          0          0          0  completion interrupts
Err:          0

여기서 한 가지 의문이 생깁니다. 'cat /proc/interrupts' 명령어를 입력하는 이유는 무엇일까요? 이 질문에는 다음과 같이 답할 수 있습니다.

rpi_get_interrupt_info() 함수가 호출되도록 커널을 동작시킨다.
   
이미 우리는 'cat /proc/interrupts'를 입력하면 show_interrupts() 함수가 호출된다고 분석한 바 있습니다. 그런데 이번에 소개한 패치 코드는 show_interrupts() 함수 내에서 rpi_get_interrupt_info() 함수를 호출합니다. 따라서 rpi_get_interrupt_info() 함수가 호출되도록 'cat /proc/interrupts' 명령어를 입력하는 것입니다.

이어서 다음 명령어를 입력해 get_ftrace.sh 셸 스크립트를 실행합니다. 

root@raspberrypi:/home/pi# ./get_ftrace.sh 
ftrace off

이 셸 스크립트를 실행하면 ftrace 로그를 현재 디렉터리에 복사합니다.

이번에는 분석할 ftrace 로그를 소개합니다.

01 cat-884 [002] d... 333.875303: rpi_get_interrupt_info+0x14/0x6c <-show_interrupts+0x2dc/0x3e0
02 cat-884 [002] d... 333.875350: <stack trace>
03 => rpi_get_interrupt_info+0x18/0x6c
04 => show_interrupts+0x2dc/0x3e0
05 => seq_read+0x3d0/0x4b4
06 => proc_reg_read+0x70/0x98
07 => __vfs_read+0x48/0x168
08 => vfs_read+0x9c/0x164
09 => ksys_read+0x5c/0xbc
10 => sys_read+0x18/0x1c
11 => ret_fast_syscall+0x0/0x28 

위와 같은 콜스택을 해석하는 방법을 알아봅시다. 위 로그에 나타난 함수의 흐름은 11번째 줄에서 03번째 줄 방향으로 이어집니다. 따라서 맨 먼저 실행된 함수는 ret_fast_syscall() 함수이고, 그다음은 sys_read() 함수입니다.

각 메시지에서 다음과 같은 정보를 알 수 있습니다.
01번째 줄: 'cat-884' 메시지로서 pid가 884인 cat 프로세스가 rpi_get_interrupt_info() 함수를 호출한다.
05번째 줄: seq_read() 함수에서 show_interrupts() 함수를 호출했다.
10번째 줄: sys_read() 함수가 호출됐으니 유저 공간에서 read 시스템 콜을 실행했다.

이번에는 rpi_get_interrupt_info() 함수에서 출력하는 인터럽트 속성 정보를 파악해보겠습니다. 이를 위해 다음과 같은 명령어를 입력합니다.

root@raspberrypi:/home/pi/# egrep -nr irq_handler  *
cat-884 [002] d... 333.875361: rpi_get_interrupt_info+0x28/0x6c: [cat] 17: 3f00b880.mailbox, irq_handler: bcm2835_mbox_irq+0x0/0x94
cat-884 [002] d... 333.875397: rpi_get_interrupt_info+0x28/0x6c: [cat] 18: VCHIQ doorbell, irq_handler: vchiq_doorbell_irq+0x0/0x48
cat-884 [002] d... 333.875451: rpi_get_interrupt_info+0x28/0x6c: [cat] 40: bcm2708_fb dma, irq_handler: bcm2708_fb_dma_irq+0x0/0x44
cat-884 [002] d... 333.875487: rpi_get_interrupt_info+0x28/0x6c: [cat] 42: DMA IRQ, irq_handler: bcm2835_dma_callback+0x0/0x140
cat-884 [002] d... 333.875521: rpi_get_interrupt_info+0x28/0x6c: [cat] 44: DMA IRQ, irq_handler: bcm2835_dma_callback+0x0/0x140
cat-884 [002] d... 333.875555: rpi_get_interrupt_info+0x28/0x6c: [cat] 45: DMA IRQ, irq_handler: bcm2835_dma_callback+0x0/0x140
cat-884 [002] d... 333.875597: rpi_get_interrupt_info+0x28/0x6c: [cat] 56: dwc_otg, irq_handler: dwc_otg_common_irq+0x0/0x28
cat-884 [002] d... 333.875656: rpi_get_interrupt_info+0x28/0x6c: [cat] 80: mmc0, irq_handler: bcm2835_sdhost_irq+0x0/0x3e8
cat-884 [002] d... 333.875691: rpi_get_interrupt_info+0x28/0x6c: [cat] 81: uart-pl011, irq_handler: pl011_int+0x0/0x474
cat-884 [002] d... 333.875730: rpi_get_interrupt_info+0x28/0x6c: [cat] 86: mmc1, irq_handler: bcm2835_mmc_irq+0x0/0x754
cat-884 [002] d... 333.875815: rpi_get_interrupt_info+0x28/0x6c: [cat] 161: arch_timer, irq_handler: arch_timer_handler_phys+0x0/0x48
cat-884 [002] d... 333.875849: rpi_get_interrupt_info+0x28/0x6c: [cat] 162: arch_timer, irq_handler: arch_timer_handler_phys+0x0/0x48
cat-884 [002] d... 333.875887: rpi_get_interrupt_info+0x28/0x6c: [cat] 165: arm-pmu, irq_handler: armpmu_dispatch_irq+0x0/0x88
 
'egrep -nr irq_handler *' 명령어는 현재 디렉터리에 있는 파일에서 “irq_handler”라는 문자열을 검색한 결과를 출력합니다. 출력 결과의 각 라인에는 다음과 같은 정보가 출력됩니다.

인터럽트 번호 
인터럽트 이름 
인터럽트 핸들러 이름 

예를 들어, 다음 로그를 해석해볼까요?

cat-884 [002] d... 333.875730: rpi_get_interrupt_info+0x28/0x6c: [cat] 86: mmc1, irq_handler: bcm2835_mmc_irq+0x0/0x754
 
위 로그를 읽으면 다음과 같은 내용을 알 수 있습니다.
인터럽트 번호: 86
인터럽트 이름: mmc1 
인터럽트 핸들러 이름: bcm2835_mmc_irq

그런데 여기서 궁금증이 생깁니다. 86번 인터럽트 핸들러인 bcm2835_mmc_irq() 함수는 언제 어떻게 호출될까요?

이번 절에서 소개한 irq_trace_ftrace.sh 셸 스크립트의 23번째 줄을 다음과 같이 바꿔볼까요?

23 echo bcm2835_mmc_irq > /sys/kernel/debug/tracing/set_ftrace_filter

이것은 bcm2835_mmc_irq() 함수의 콜스택을 ftrace에 설정하는 명령어입니다. 코드를 이와 같이 코드를 고친 후 irq_trace_ftrace.sh 셸 스크립트를 실행합니다. 

root@raspberrypi:/home/pi# ./irq_trace_ftrace.sh 
tracing_off
events disabled
set_ftrace_filter init
function tracer enabled
event enabled
set_ftrace_filter enabled
function stack trace enabled
tracing_on

3초 후 get_ftrace.sh 스크립트를 실행해 ftrace 로그를 받습니다.

root@raspberrypi:/home/pi# ./get_ftrace.sh 
ftrace off

이번에는 ftrace에서 bcm2835_mmc_irq() 함수의 콜스택을 볼 수 있습니다. 

<idle>-0 [000] d.h. 826.627311: irq_handler_entry: irq=86 name=mmc1
<idle>-0 [000] d.h. 826.627313: bcm2835_mmc_irq+0x14/0x754 <-__handle_irq_event_percpu+0xbc/0x224
<idle>-0 [000] d.h. 826.627350: <stack trace>
 => bcm2835_mmc_irq+0x18/0x754
 => __handle_irq_event_percpu+0xbc/0x224
 => handle_irq_event_percpu+0x3c/0x8c
 => handle_irq_event+0x54/0x78
 => handle_level_irq+0xc0/0x16c
 => generic_handle_irq+0x34/0x44
 => bcm2836_chained_handle_irq+0x38/0x50
 => generic_handle_irq+0x34/0x44
 => __handle_domain_irq+0x6c/0xc4
 => bcm2836_arm_irqchip_handle_irq+0x60/0xa8
 => __irq_svc+0x5c/0x7c
 => finish_task_switch+0xa8/0x230
 => finish_task_switch+0xa8/0x230
 => __schedule+0x328/0x9b0
 => schedule_idle+0x44/0x84
 => do_idle+0x120/0x17c
 => cpu_startup_entry+0x28/0x2c
 => rest_init+0xc0/0xc4
 => start_kernel+0x490/0x4b8
 <idle>-0     [000] d.h.   826.627355: irq_handler_exit: irq=86 ret=handled

ftrace 로그에 보이는 함수에 대해 더 자세히 설명하고 싶지만 이번 절은 커널을 디버깅하는 방법을 소개하는 절이므로 이 정도로 마무리하겠습니다. 

지금까지 커널 코드를 수정해 ftrace 로그를 분석하는 커널 디버깅 방법을 알아봤는데, show_interrupts() 함수를 눈으로만 읽어 분석할 때보다 어떤 내용을 더 알게 됐나요? 함수 호출 흐름과 해당 코드를 실행하는 프로세스 이름, 자료구조까지 알게 됐습니다.

이처럼 코드를 분석하는 것보다 리눅스 시스템에서 디버깅하면 더 유익한 정보를 알 수 있습니다. 이 내용을 익힌 다음, 나중에 참고할 수 있게 차곡차곡 정리해서 블로그에 올려보면 어떨까요? 친구들에게 자랑도 하고 세미나로 배운 내용을 공유해도 좋습니다. 이러한 자료와 경험이 쌓여서 커널 디버깅 및 프로그래밍 실력을 높일 수 있습니다.

리눅스 개발자뿐만 아니라 다른 분야에 몸담고 있는 소프트웨어 개발자를 만나면 주로 이야기하는 주제가 디버깅입니다. 그중에서 게임계의 레전드 개발자이자 엔지니어인 김포프 님께서 언급하신 '디버깅에 대한 생각'을 공유합니다 (괄호 안의 내용은 발언 시점).

프로그래머의 자질은 코딩을 해서 제품을 만들 수 있는 능력이다(1:15).  
프로그래머가 갖춰야 할 가장 중요한 능력은 디버깅 스킬이다(2:45). 
디버깅을 잘한다는 것은 남의 코드를 잘 읽고 그 코드 속의 로직을 따질 수 있고 그 로직을 단계별로 나눌 수 있는 것이다(3:22). 
디버깅을 잘하면 남의 코드를 보는 것이 두렵지 않고 이 과정으로 배우는 것이 정말 많다(4:52).  
디버깅을 잘하는 프로그래머를 보면 엄청나게 성장할 것이란 것을 안다(5:01). 
정말 코딩을 잘하는 사람 중에 디버깅을 못하는 사람을 본 적이 없다. 디버깅을 잘하는 사람 중에 코딩을 못하는 사람을 본 적이 없다. 디버깅을 못하는 사람 중에 코딩을 잘하는 사람은 (거의) 본 적이 없다. 디버깅을 못하는 사람 중에 설계를 잘하는 사람은 (거의) 본 적이 없다(7:35).

소프트웨어 개발자로서 너무 공감이 되고 깊이 새겨야 할 명언인 것 같습니다.

이번 절에서는 디버깅의 중요성과 코드 학습에 대해 살펴봤습니다. 다음 절에서 커널 디버깅 툴을 활용하는 방법을 소개합니다.



[라즈베리 파이] 라즈비안 리눅스 커널 빌드하고 설치하기 2. 라즈베리 파이 설정

라즈비안 리눅스 커널 빌드

라즈비안의 커널 소스를 내려받았으니 이제 소스를 빌드하는 방법을 알아볼 차례입니다. 참고로 아래의 라즈베리 파이 홈페이지에 가면 커널을 빌드하는 방법을 확인할 수 있습니다.

https://www.raspberrypi.org/documentation/linux/kernel/building.md

이 페이지에서 ‘Raspberry Pi 2, Pi 3, Pi 3+, and Compute Module 3 default build configuration’라고 적힌 부분을 아래 명령어를 차례로 입력하면 커널을 빌드하기 위한 설정을 진행할 수 있습니다.

cd linux
KERNEL=kernel7
make bcm2709_defconfig

이어서 그 아래의 ‘Building’ 부분에 나온 아래 명령어를 통해 본격적인 커널 빌드를 진행합니다.

make -j4 zImage modules dtbs
sudo make modules_install
sudo cp arch/arm/boot/dts/*.dtb /boot/
sudo cp arch/arm/boot/dts/overlays/*.dtb* /boot/overlays/
sudo cp arch/arm/boot/dts/overlays/README /boot/overlays/
sudo cp arch/arm/boot/zImage /boot/$KERNEL.img

그런데 커널 빌드를 할 때마다 위와 같은 명령어를 입력하면 어떨까요? 그럼 빌드하는 데 시간이 오래 걸릴 것입니다. 그래서 위와 같은 커널 빌드 명령어를 모아 하나의 파일로 만들 수 있습니다. 이 파일을 커널 빌드를 할 때 실행하면 일일이 빌드 명령어를 입력할 필요가 없습니다. 이를 빌드 셸 스크립트라고 부르며 대부분의 임베디드 리눅스 개발에서 활용합니다.

다음은 라즈베리 파이에서 커널 빌드를 수행하는 빌드 스크립트입니다.

01 #!/bin/bash
02
03 echo "configure build output path"
04 
05 KERNEL_TOP_PATH="$( cd "$(dirname "$0")" ; pwd -P )"
06 OUTPUT="$KERNEL_TOP_PATH/out"
07 echo "$OUTPUT"
08 
09 KERNEL=kernel7
10 BUILD_LOG="$KERNEL_TOP_PATH/rpi_build_log.txt"
11 
12 echo "move kernel source"
13 cd linux
14
15 echo "make defconfig"
16 make O=$OUTPUT bcm2709_defconfig
17
18 echo "kernel build"
19 make O=$OUTPUT zImage modules dtbs -j4 2>&1 | tee $BUILD_LOG

위와 같은 입력한 다음 build_rpi_kernel.sh라는 이름으로 저장합니다(참고로 맨 왼쪽에 있는 번호는 입력하면 안 됩니다).

파일을 저장한 다음의 chmod 명령어를 입력해 파일에 실행 권한을 부여합니다.
root@raspberrypi:/home/pi/rpi_kernel_src# chmod +x build_rpi_kernel.sh

빌드 스크립트의 내용을 조금 더 살펴볼까요?

05 KERNEL_TOP_PATH="$( cd "$(dirname "$0")" ; pwd -P )"
06 OUTPUT="$KERNEL_TOP_PATH/out"
07 echo "$OUTPUT"

05번째 줄에서는 현재 작업 디렉터리를 KERNEL_TOP_PATH에 저장합니다. 

06 번째 줄에서는 KERNEL_TOP_PATH 경로에 out 폴더를 추가해 OUTPUT이라는 셸 스크립트 변수에 저장합니다.

05~06 번째 줄 코드를 실행하면 OUTPUT 변수는 다음과 같이 '/home/pi/rpi_kernel_src/out'으로 변경됩니다.

OUTPUT=/home/pi/rpi_kernel_src/out

여기서 설정한 OUTPUT은 16 번째와 19번째 줄과 같이 커널 컨피그와 커널 빌드 명령어에서 "O=$OUTPUT" 형식으로 사용됩니다.

16 make O=$OUTPUT bcm2709_defconfig
19 make O=$OUTPUT zImage modules dtbs -j4 2>&1 | tee $BUILD_LOG

위 코드가 실행되면 커널을 빌드했을 때 만들어지는 오브젝트와 커널 파일이 out 폴더에 생성됩니다.

16번째 줄은 커널 컨피그 파일을 생성하는 코드입니다.

16 make O=$OUTPUT bcm2709_defconfig

이 명령어는 다음 경로에 있는 bcm2709_defconfig 파일에 선언된 컨피그 파일을 참고해 .config 파일을 생성합니다.

root@raspberrypi:/home/pi/rpi_kernel_src/linux/arch/arm/configs/bcm2709_defconfig

make의 옵션으로 "O=$OUTPUT"을 추가했으므로 다음과 같이 out 폴더에 .config가 생성됩니다.

root@raspberrypi:/home/pi/rpi_kernel_src/out/.config

다음은 19 번째 줄입니다.

19 make O=$OUTPUT zImage modules dtbs -j4 2>&1 | tee $BUILD_LOG

이 명령어는 리눅스 커널 소스를 빌드하는 명령어입니다.

다음으로 커널 빌드 로그를 저장하는 코드를 함께 보겠습니다.

10 BUILD_LOG="$KERNEL_TOP_PATH/rpi_build_log.txt"
...
19 make O=$OUTPUT zImage modules dtbs -j4 2>&1 | tee $BUILD_LOG

10 번째 줄에서는 $KERNEL_TOP_PATH 디렉터리 안의 rpi_build_log.txt라는 파일을 지정해 BUILD_LOG에 저장합니다. 

19 번째 줄은 커널 빌드 명령어입니다. 맨 오른쪽에 "2>&1 | tee $BUILD_LOG"가 추가됐는데, 이는 커널을 빌드할 때 출력되는 메시지를 $BUILD_LOG 파일에 저장한다는 뜻입니다.

커널을 빌드하는 도중 컴파일 에러가 발생하면 rpi_build_log.txt 파일을 열어 어디서 문제가 생겼는지 확인할 수 있습니다. 실전 개발에서 리눅스 커널을 빌드하는 도중 문제가 발생하면 이 같은 방법으로 커널 빌드 로그를 다른 개발자에게 전달합니다. 커널 로그를 통해 커널 빌드 옵션 등을 볼 수 있기 때문입니다.

빌드 스크립트 코드를 설명했으니 이번에는 빌드 스크립트를 실행하는 방법을 알아보겠습니다. 먼저 라즈비안 커널 소스로 이동합니다. 소스로 이동한 상태는 다음과 같습니다.

root@raspberrypi:/home/pi/rpi_kernel_src# ls
build_rpi_kernel.sh linux  

다음으로 ./build_rpi_kernel.sh 명령어를 입력해 커널 빌드를 시작합니다. 

root@raspberrypi:/home/pi/rpi_kernel_src# ./build_rpi_kernel.sh
configure build output path
make[1]: Entering directory ' /home/pi/rpi_kernel_src/out'
  GEN     ./Makefile
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/kconfig/conf.o
  SHIPPED scripts/kconfig/zconf.tab.c
  SHIPPED scripts/kconfig/zconf.lex.c
  HOSTCC  scripts/kconfig/zconf.tab.o
  HOSTLD  scripts/kconfig/conf
...
  CC [M]  net/bridge/netfilter/ebt_nflog.o
  AR      net/bluetooth/bnep/built-in.o
  CC [M]  net/bluetooth/bnep/core.o
  CC [M]  fs/dlm/recover.o
  CC [M]  sound/soc/codecs/cs42xx8-i2c.o
  AR      drivers/i2c/busses/built-in.o
  CC [M]  drivers/i2c/busses/i2c-bcm2708.o
  CC [M]  fs/dlm/recoverd.o
...
  LD [M]  sound/soc/snd-soc-core.ko
  LD [M]  sound/usb/6fire/snd-usb-6fire.ko
  LD [M]  sound/usb/caiaq/snd-usb-caiaq.ko
  LD [M]  sound/usb/hiface/snd-usb-hiface.ko
  LD [M]  sound/usb/misc/snd-ua101.ko
  LD [M]  sound/usb/snd-usb-audio.ko
  LD [M]  sound/usb/snd-usbmidi-lib.ko
make[1]: Leaving directory '/home/pi/rpi_kernel_src/out'

만약 컴파일 에러가 발생하면 다음과 같은 에러 메시지와 함께 빌드가 중단됩니다.

root@raspberrypi:/home/pi/rpi_kernel_src# ./build_rpi_kernel.sh
configure build output path
make[1]: Entering directory '/home/pi/rpi_kernel_src/out'
  GEN     ./Makefile
#
# configuration written to .config
#
make[1]: Leaving directory '/home/pi/rpi_kernel_src/out'
make[1]: Entering directory '/home/pi/rpi_kernel_src/out'
  GEN     ./Makefile
scripts/kconfig/conf  --silentoldconfig Kconfig
...
  CHK     include/generated/compile.h
  CC      kernel/sched/core.o
/home/pi/rpi_kernel_src/linux/kernel/sched/core.c:3302:6: error: #error "invoke compile error inside __schdedule"
     #error "invoke compile error inside __schdedule"
      ^
/home/pi/rpi_kernel_src/linux/scripts/Makefile.build:328: recipe for target 'kernel/sched/core.o' failed
make[3]: *** [kernel/sched/core.o] Error 1
/home/pi/rpi_kernel_src/linux/scripts/Makefile.build:587: recipe for target 'kernel/sched' failed
make[2]: *** [kernel/sched] Error 2
/home/pi/rpi_kernel_src/linux/Makefile:1040: recipe for target 'kernel' failed
make[1]: *** [kernel] Error 2
make[1]: *** Waiting for unfinished jobs....
make[1]: Leaving directory '/home/pi/rpi_kernel_src/out'
Makefile:146: recipe for target 'sub-make' failed
make: *** [sub-make] Error 2

사실 이 에러는 다음과 같이 컴파일 에러를 일으키는 코드(#error)를 일부러 작성해서 발생시킨 것입니다.

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 4e89ed8..5c46f29 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -3299,7 +3299,7 @@ static void __sched notrace __schedule(bool preempt)
        struct rq_flags rf;
        struct rq *rq;
        int cpu;
-
+    #error "invoke compile error inside __schdedule"
        cpu = smp_processor_id();
        rq = cpu_rq(cpu);
        prev = rq->curr;

#error는 gcc 컴파일러가 제공하는 매크로인데, 코드를 컴파일할 때 해당 코드를 실행하면 무조건 컴파일 에러를 유발합니다. 여기서 한 가지 주의해야 할 점이 있는데, 컴파일 에러를 만나면 반드시 리눅스 커널 코드를 수정한 다음 다시 커널 빌드를 해야 합니다. 만약 컴파일 에러를 제대로 수정하지 않고 커널 이미지를 설치하면 수정한 코드가 제대로 동작하지 않습니다.

라즈비안 리눅스 커널 설치하기

커널 코드를 빌드만 해서는 수정한 코드가 라즈베리 파이에서 실행되지 않습니다. 컴파일해 생성된 이미지를 라즈베리 파이에 설치해야 합니다. 라즈비안 리눅스 커널을 빌드했으니 이제 빌드한 커널 이미지를 설치해 봅시다. 다음은 라즈비안 이미지를 라즈베리 파이에 설치하는 셸 스크립트입니다.

install_rpi_kernel_img.sh
01 #!/bin/bash
02
03 KERNEL_TOP_PATH="$( cd "$(dirname "$0")" ; pwd -P )"
04 OUTPUT="$KERNEL_TOP_PATH/out"
05 echo "$OUTPUT"
06
07 cd linux
08
09 make O=$OUTPUT modules_install
10 cp $OUTPUT/arch/arm/boot/dts/*.dtb /boot/
11 cp $OUTPUT/arch/arm/boot/dts/overlays/*.dtb* /boot/overlays/
12 cp $OUTPUT/arch/arm/boot/dts/overlays/README /boot/overlays/
13 cp $OUTPUT/arch/arm/boot/zImage /boot/kernel7.img

위와 같은 코드를 입력한 후 다음 명령어로 install_rpi_kernel_img.sh 셸 스크립트를 실행합시다.

root@raspberrypi:/home/pi/rpi_kernel_src# ./install_RPi_kernel_img.sh

다시 한 번 반복하지만 install_rpi_kernel_img.sh 스크립트를 실행하기 전에 먼저 커널 빌드 시 발생한 에러는 반드시 수정해야 합니다. 이 과정을 건너뛰고 커널 이미지를 설치하면 수정한 커널 이미지가 제대로 설치되지 않습니다. 

라즈베리 파이 설정 

라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)




[SW 에세이] 생각이 낡은 SW 개발자들(1) - 노력할 필요가 없다 임베디드 에세이

이번 시간에는 "생각이 낡은 SW 개발자"란 주제로 포스팅을 해보려 합니다. "생각이 낡은 SW 개발자"는 무엇을 의미할까요? 다음과 같은 부류의 개발자를 뜻합니다.

   * "SW 개발자로써 자기 개발을 하지 않거나 자기 개발을 할 필요가 없다고 확신하는 개발자"
   * "열심히 해도 현실은 나아지지 않는다고 확신하는 개발자"

이런 부류의 개발자가 어떤 생각을 하고 행동하는지 조금 더 알아볼까요?

SW 개발은 제품의 일부분이라는 점을 강조하며 배울 필요가 없다고 생각한다

제품을 보는 관점에 따라 SW 개발은 제품 개발의 일부분으로 볼 수 있습니다. 물론 이 관점이 틀렸다고 말하려는 게 아닙니다. 기획, 영업, 생산부터 품질 그리고 하드웨어 부서까지 다양한 부서들이 힘을 모아 출시하는 것이 제품(Product)입니다. 따라서 SW 개발이 전부는 아니죠. 아무리 SW 개발을 잘해도 제품의 컨셉이 좋아야 하고 소비자가 만족하는 스팩으로 구성돼 있어야 합니다. 

그래서 "다양한 관점으로 제품을 바라볼 필요가 있고 다룬 분야의 부서의 관점이나 의견을 존중해야 한다"라고 생각할 수 있지 않나요? 이건 제 생각이지만 여러분은 다른 생각을 할 수도 있겠죠.

하지만, 생각이 낡은 SW 개발자는 다음과 같은 결론을 내립니다.

   * SW는 제품의 일부분이니 그리 중요하지 않다. 따라서 SW 개발을 잘하기 위한 기법들은 잘 배울 필요가 없다!"

어라! 조금 그럴싸하게 말하자면 "SW 개발의 허무론"이란 이론을 생각해냈군요! 제 생각에 삼천포로 빠지신 것 같지만요.

어느 누구나 자신만의 관점과 시선이 있기 마련이라 이런 의견에 대해 뭐라고 말할 수는 없습니다. '생각이 낡은' 개발자들이 이런 생각을 일기장에 생각을 적으면 누가 뭐라고 하겠습니까? 문제는 이런 "SW 개발의 허무론"을 주위 개발자들에게 설파를 한다는 점입니다. 

SW 개발자로써 전문성을 키우려고 노력하는 후배 개발자들에게도 이런 "SW 개발의 허무론"을 설파하면 그리 반응이 좋지 않습니다. 대신 자신과 비슷한 생각을 하는 "생각이 낡은" 주위 개발자들은 이런 이야기를 듣곤 다음과 같이 맞장구를 치며 공감해줍니다. 

   * "맞아, SW 개발이 뭐가 중요해! 그게 뭐 대단해!" 

이런 위로와 공감의 말을 들으면 "SW 개발의 허무론"을 설파한 "생각이 낡은" 개발자의 마음에는 평온과 안정이 찾아옵니다. 속으로 다음과 같이 독백을 하죠.

   * "그래! 나와 비슷한 생각을 하는 동료들이 있네"
   * "맞아, SW 개발은 제품의 극히 일부분에 지나지 않아!"

하지만 이런 생각을 더 많이 할수록 지속적으로 긍정적인 에너지는 계속 부식되고 맙니다. 달리 말하자면 "생각이 계속 낡아"지는 거죠. 스스로 아무런 노력을 하지 않고 자신을 위로하려는 핑계만 대니 현실이 나아지겠습니까? 아무리 스스로 위로해도 현실은 그대로입니다. 가만히 앉아서 "노력을 해도 안된다"라고 생각하니 말이죠.

"SW 개발의 허무론"은 개발자로써 아무런 자기 개발을 하지 않는 자신을 위한 핑계에 지나지 않을 뿐입니다. 

나이가 들어서 SW 개발을 잘할 수 없다!

생각이 낡은 40대 중후반의 개발자들은 가끔 "나이가 많아" 개발을 잘 따라갈 수 없다라고 말합니다. 이런 이야기를 듣고 근거가 뭐냐고 물으면 "프로 스포츠 선수가 30대 중반이 지나면 신체 능력이 떨어져 은퇴의 길에 접어든다"라는 근거를 제시합니다. 물론 틀린 이야기는 아닌 것 같습니다만, 역시나 삼천포로 빠집니다.

   * 나이가 많아서 SW 개발을 잘할 수 없고, 
     나이가 들어서 SW 개발 능력을 키우기 위한 노력을 해도 별 효과가 없을 것이야!

이런 이야기를 들으면 "나이가 들어서 생체적인 능력이 떨어졌다면 더 노력을 해야 하지 않을까?"란 생각이 들면서 전혀 납득이 되지 않습니다. 이 또한 스스로를 위로하기 위한 궁색한 변명으로 들립니다.

시선을 돌려 여의도에서 맹활약 중인 국회의원님을 떠올려 봅시다. 국회에서 활동하는 분들, 기력이 쇠퇴해 보이나요? 생체적인 능력이 감퇴돼 보이나요? 그렇게 보이진 않습니다. 국회 의원님들, 얼마나 에너지가 넘쳐 보입니까! 

   * 아마존에 있는 식인 물고기인 피라니아와 같아 보입니다.

그런데 국회의원님들 나이대가 50대 중후반이 대부분이고 60~70대도 보입니다. "피라니아"와 같이 역동적으로 다른 정당의 국회 의원을 물어뜯는 의원님들의 나이대가 50대 중후반인데 이제 고작 40대 중후반을 넘어선 개발자님들은 "우리는 나이가 많아 개발을 제대로 할 수 없어"라고 말할 수 있나요?  


(To be continued...)

.

[리눅스커널] 새로운 ftrace 이벤트 생성하기: TRACE_SYSTEM 매크로 설정 [Kernel] Debug Feature

해더 파일의 이름과 TRACE_SYSTEM 매크로가 일치해야 함

아래와 같이 매크로를 지정했다면, 'include/trace/events/hvc.h' 해더 파일을 찾는다.
#define TRACE_SYSTEM hvc

아래는 TRACE_SYSTEM 매크로 이름에 해당하는 헤더 파일이 없을 때 컴파일하면 출력되는 메시지이다.

/home001/austin.kim/src/rpi_dev_code/last_rpi_src/linux/include/trace/define_trace.h:89:43: fatal error: trace/events/hvc.h: No such file or directory
 #include TRACE_INCLUDE(TRACE_INCLUDE_FILE)
                                           ^
compilation terminated.

[리눅스커널] 크래시 유틸리티(Crash Utility)로 시스템 콜 확인하기 11. 시스템 콜

크래시 유틸리티 프로그램으로 'sys -c" 명령어를 입력하면 시스템 콜 번호와 시스템 콜 핸들러를 알 수 있습니다.

먼저 ARM64 아키텍처에서 시스템 콜 정보를 확인해볼까요? 
다음은 'sys -c" 명령어를 입력한 다음에 출력되는 결과입니다.

crash64> sys -c
NUM  SYSTEM CALL                FILE AND LINE NUMBER
  0  __arm64_sys_io_setup       ../kernel/fs/aio.c: 1303
  1  __arm64_sys_io_destroy     ../kernel/fs/aio.c: 1372
  2  __arm64_sys_io_submit      ../kernel/fs/aio.c: 1900
  3  __arm64_sys_io_cancel      ../kernel/fs/aio.c: 1990
  4  __arm64_sys_io_getevents   ../kernel/fs/aio.c: 2064
  5  __arm64_sys_setxattr       ../kernel/fs/xattr.c: 489
  6  __arm64_sys_lsetxattr      ../kernel/fs/xattr.c: 496
  7  __arm64_sys_fsetxattr      ../kernel/fs/xattr.c: 503
  8  __arm64_sys_getxattr       ../kernel/fs/xattr.c: 583
  9  __arm64_sys_lgetxattr      ../kernel/fs/xattr.c: 589
 10  __arm64_sys_fgetxattr      ../kernel/fs/xattr.c: 595
 11  __arm64_sys_listxattr      ../kernel/fs/xattr.c: 659
 12  __arm64_sys_llistxattr     ../kernel/fs/xattr.c: 665
 13  __arm64_sys_flistxattr     ../kernel/fs/xattr.c: 671
 14  __arm64_sys_removexattr    ../kernel/fs/xattr.c: 724
 15  __arm64_sys_lremovexattr   ../kernel/fs/xattr.c: 730
 16  __arm64_sys_fremovexattr   ../kernel/fs/xattr.c: 736
 17  __arm64_sys_getcwd         ../kernel/fs/d_path.c: 425
 18  __arm64_sys_lookup_dcookie ../kernel/fs/dcookies.c: 206
 19  __arm64_sys_eventfd2       ../kernel/fs/eventfd.c: 412
 20  __arm64_sys_epoll_create1  ../kernel/fs/eventpoll.c: 1991
 21  __arm64_sys_epoll_ctl      ../kernel/fs/eventpoll.c: 2009
 22  __arm64_sys_epoll_pwait    ../kernel/fs/eventpoll.c: 2217
 23  __arm64_sys_dup            ../kernel/fs/file.c: 944
 24  __arm64_sys_dup3           ../kernel/fs/file.c: 909
 25  __arm64_sys_fcntl          ../kernel/fs/fcntl.c: 448

이어서 ARM32 아키텍처에서 시스템 콜 정보를 확인해볼까요? 
다음은 'sys -c" 명령어를 입력한 다음에 출력되는 결과입니다.

crash> sys -c
NUM  SYSTEM CALL                FILE AND LINE NUMBER
  0  sys_restart_syscall        ../kernel/signal.c: 2462
  1  sys_exit
  2  sys_fork                   ../kernel/fork.c: 2074
  3  sys_read
  4  sys_write
  5  sys_open
  6  sys_close
  7  sys_ni_syscall
  8  sys_creat
  9  sys_link
 10  sys_unlink
 11  sys_execve
 12  sys_chdir
 13  sys_ni_syscall
 14  sys_mknod
 15  sys_chmod
 16  sys_lchown16
 17  sys_ni_syscall
 18  sys_ni_syscall
 19  sys_lseek
 20  sys_getpid                 ../kernel/sys.c: 833
 21  sys_mount
 22  sys_ni_syscall
 23  sys_setuid16
 24  sys_getuid16               ..kernel/uid16.c: 201
 25  sys_ni_syscall
 26  sys_ptrace

이처럼 크래시 유틸리티를 통해 시스템 콜 번호와 시스템 콜 핸들러의 이름을 알 수 있습니다.

[리눅스커널] preempt_disable() 함수: 스케줄링을 지원하는 함수 실행 금지 10. 프로세스 스케줄링

preempt_disable()/preempt_enable() 함수를 사용할 때 주의해야 할 점이 있습니다. 그것은;

   * preempt_disable()/preempt_enable() 함수를 사용하는 구간에서 스케줄링을 지원하는 함수가 호출되면 안된다.

라는 점입니다.
다들 아시다시피, 커널 드라이버에서 preempt_disable() 함수를 호출하면 프로세스 struct thread_info 의 preempt_count를 +1만큼 증가시켜 Preemption을 비활성화합니다. 다음 예제 코드를 보면서 설명을 시작 하겠습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/module.c
01 bool try_module_get(struct module *module)
02 {
03 bool ret = true;
04
05 if (module) {
06 + preempt_disable();
07 /* Note: here, we can fail to get a reference */
08 if (likely(module_is_live(module) &&
09    atomic_inc_not_zero(&module->refcnt) != 0))
10 trace_module_get(module, _RET_IP_);
11 else
12 ret = false;
13
14 + preempt_enable();
15 }
16 return ret;
17 }

8~12번째 줄이 실행될 때 선점 스케줄링이 일어나면 안 되는 조건이라고 가정해 봅시다. 이 경우에
어떻게 하면 선점 스케줄링이 일어나지 않게 할 수 있을까요? 아래의 7번째와 14번째 코드와 같이
preempt_disable() 함수와 preempt_enable() 함수를 호출하면 됩니다.

이 코드를 커널 관점으로 해석하면 8~12번째 줄이 실행하는 프로세스의 struct thread_info 구조체의 preempt_count가 1이나 2라는 것입니다.

그런데 preempt_disable() 함수를 호출할 때 한 가지 주의해야 할 점이 있습니다. preempt_disable()/preempt_enable() 함수로 Preemption을 비활성화하는 코드 구간에  스케줄링을 지원하는 커널 함수가 호출되면 안된다는 것입니다. 

그 이유는 preempt_disable() 함수를 호출한 후 Preemption으로 스케줄링 된 다음 다시 Preemption으로 실행할 수 없기 때문입니다.
이해를 돕기 위해 코드를 보면서 이 내용을 설명하겠습니다.

32비트 기반 ARM 아키텍처에서 커널 스레드나 커널의 함수가 실행하는 동안에 __irq_svc 레이블에서 Preemption이 됩니다.
다음은  __irq_svc 레이블의 코드와 주석을 보면서 관련 동작을 더 알아봅시다.

   NSR:C010F1E0|__irq_svc:sub     r13,r13,#0x4C    ; r13,r13,#76
   NSR:C010F1E4|          tst     r13,#0x4         ; r13,#4
   NSR:C010F1E8|          subeq   r13,r13,#0x4     ; r13,r13,#4
   NSR:C010F1EC|          stm     r13,{r1-r12}
   NSR:C010F1F0|          ldm     r0,{r3-r5}
   NSR:C010F1F4|          add     r7,r13,#0x30     ; r7,r13,#48
   NSR:C010F1F8|          mvn     r6,#0x0          ; r6,#0
   NSR:C010F1FC|          add     r2,r13,#0x4C     ; r2,r13,#76
   NSR:C010F200|          addeq   r2,r2,#0x4       ; r2,r2,#4
   NSR:C010F204|          push    {r3}
   NSR:C010F208|          cpy     r3,r14
   NSR:C010F20C|          stm     r7,{r2-r6}
   NSR:C010F210|          lsr     r9,r13,#0x0D // 프로세스의 스택 주소 접근
   NSR:C010F214|          lsl     r9,r9,#0x0D   //  프로세스의 스택 최상단 주소 접근
...
   NSR:C010F24C|          ldr     r8,[r9,#0x4] // r8 = struct thread_info 구조체의 preempt_count
   NSR:C010F250|          ldr     r0,[r9] // r0 = struct thread_info 구조체의 flags
   NSR:C010F254|          teq     r8,#0x0          ; r8,#0 // thread_info 구조체의 preempt_count가 0인지 체크
   NSR:C010F258|          movne   r0,#0x0          ; r0,#0 // preempt_count가 0보다 크면  thread_info 구조체의 flags를 0으로 변경
   NSR:C010F25C|          tst     r0,#0x2          ; r0,#2 //  thread_info 구조체의 flags가 _TIF_NEED_RESCHED 를 포함하는지 체크
   NSR:C010F260|          blne    0xC010F288       ; svc_preempt // 포함하면 svc_preempt 레이블로 브랜치

주석의 내용을 정리하면, "preempt_disable() 함수가 호출돼 thread_info 구조체의 preempt_count가 0보다 크면 thread_info 구조체의 flags를 0으로 변경한다"라고 이야기할 수 있습니다. 다른 프로세스가 wake_up_process() 함수를 호출해 어떤 프로세스를 깨우거나 어떤 프로세스의 타임 슬라이스가 소진되면  thread_info 구조체의 flags를 _TIF_NEED_RESCHED 플래그로 설정합니다. 그런데 thread_info 구조체의 preempt_count가 0보다 크면 thread_info 구조체의 flags를 무시합니다.

이번에는 64비트 기반 ARM 아키텍처에서는 Preemption은 el1_irq 레이블에서 실행됩니다.

    MX:FFFFFF8008083180|A90007E0 el1_irq:  stp     x0,x1,[SP]
    MX:FFFFFF8008083184|A9010FE2           stp     x2,x3,[SP,#0x10]   ; x2,x3,[SP,#16]
    MX:FFFFFF8008083188|A90217E4           stp     x4,x5,[SP,#0x20]   ; x4,x5,[SP,#32]
...
    MX:FFFFFF8008083234|B9401398           ldr     w24,[x28,#0x10]   ; w24,[x28,#16] // w24 = current->thread_info.preempt_count
    MX:FFFFFF8008083238|35000098           cbnz    w24,0xFFFFFF8008083248 // current->thread_info.preempt_count가 0인지 체크?
                                                                                                                  0이면 FFFFFF800808323C 주소로 브랜치 아니면  FFFFFF8008083248 브랜치
    MX:FFFFFF800808323C|F9400380           ldr     x0,[x28] //  w0 = current->thread_info.flags     
    MX:FFFFFF8008083240|36080040           tbz     x0,#0x1,0xFFFFFF8008083248   ; x0,#1,0xFFFFFF8008083248 // current->thread_info.flags & (_TIF_NEED_RESCHED | _TIF_SIGPENDING) 
    MX:FFFFFF8008083244|94000018           bl      0xFFFFFF80080832A4   ; el1_preempt // current->thread_info.flags가 _TIF_NEED_RESCHED나 _TIF_SIGPENDING 플래그를 포함하면 el1_preempt 레이블로 브랜치
    MX:FFFFFF8008083248|F94093F4           ldr     x20,[SP,#0x120] ;x20,[SP,#288]  

주석의 내용을 보면 __irq_svc 레이블과 동작 원리는 같습니다.

정리하면 프로세스의 preempt_count를 1만큼 증가하면 Preemption으로 다시 실행되지 않거나 다른 프로세스가 깨워도 깨어나지 않습니다.

그래서 스케줄링을 지원하는 함수에서 cond_resched() 혹은 cond_resched_lock() 함수를 통해 ___might_sleep() 함수를 호출하게 됩니다.

다음은 cond_resched() 혹은 cond_resched_lock() 매크로 함수의 구현부입니다.
https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/sched.h
#define cond_resched() ({ \
___might_sleep(__FILE__, __LINE__, 0); \
_cond_resched(); \
})

extern int __cond_resched_lock(spinlock_t *lock);

#define cond_resched_lock(lock) ({ \
___might_sleep(__FILE__, __LINE__, PREEMPT_LOCK_OFFSET);\
__cond_resched_lock(lock); \
})

보시다시피 둘다 ___might_sleep() 함수를 호출합니다.

이번에는 뮤텍스를 획득하는 mutex_lock() 함수의 구현부입니다. 
프로세스는 뮤텍스 획득을 시도하다가 다른 프로세스가 뮤텍스를 획득하면 휴면에 진입하므로 뮤텍스 관련 함수는 스케줄링을 지원하는 함수를 호출합니다. 

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/locking/mutex.c
void __sched mutex_lock(struct mutex *lock)
{
might_sleep();

if (!__mutex_trylock_fast(lock))
__mutex_lock_slowpath(lock);
}

might_sleep() 함수를 호출하는데, might_sleep() 함수의 구현부를 보면 __might_sleep() 함수로 치환됩니다.

https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/kernel.h
# define might_sleep() \
do { __might_sleep(__FILE__, __LINE__, 0); might_resched(); } while (0)


___might_sleep() 함수의 구현부는 다음과 같습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/sched/core.c
01 void ___might_sleep(const char *file, int line, int preempt_offset)
02 {
03 /* Ratelimiting timestamp: */
04 static unsigned long prev_jiffy;
05
06 unsigned long preempt_disable_ip;
07
08 /* WARN_ON_ONCE() by default, no rate limit required: */
09 rcu_sleep_check();
10
11 if ((preempt_count_equals(preempt_offset) && !irqs_disabled() &&
12      !is_idle_task(current)) ||
13     system_state == SYSTEM_BOOTING || system_state > SYSTEM_RUNNING ||
14     oops_in_progress)
15 return;
16
17 if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy)
18 return;
19 prev_jiffy = jiffies;
20
21 /* Save this before calling printk(), since that will clobber it: */
22 preempt_disable_ip = get_preempt_disable_ip(current);
23
24 printk(KERN_ERR
25 "BUG: sleeping function called from invalid context at %s:%d\n",
26 file, line);
27 printk(KERN_ERR
28 "in_atomic(): %d, irqs_disabled(): %d, pid: %d, name: %s\n",
29 in_atomic(), irqs_disabled(),
30 current->pid, current->comm);

위 함수의 핵심 코드는 11번째 즐과 같이  'preempt_count_equals()' 구문인데 프로세스의 preempt_count가 0보다 큰지 체크합니다.
이 조건을 만족하면 preempt_disable() 함수를 이미 호출해 Preemption을 비활성화한 상태이므로 17~30번째 줄이 실행되 에러 메시지를 출력하거나 커널 패닉을 유발합니다.

[리눅스커널] 프로세스 별로 파일 디스크립터(파일 객체)는 어떻게 관리하나? 13. 가상 파일 시스템

아래 포스팅을 읽은 방문자님께서 댓글로 남겨 주신 질문에 대답하려고 합니다.

  [리눅스커널][가상파일시스템] 파일 객체: write 연산 세부 동작 분석

질문

서로 다른 프로세스에서 f1파일을 열었으니 fd가 가르키는 시스템 파일 테이블의 파일이 다를텐데
어떻게 내용이 삭제되거나 섞이지도 않는지 알수 있을까요?

답신

사실 이 부분은 커널을 디버깅해봐야 알 수 있는 내용입니다. 
커널 디버깅 툴 중 많이 사용되는 크래시 유틸리티로 이 내용을 확인해보겠습니다.

다음은 프로세스가 저장하고 있는 파일 디스크립터 목록을 출력해주는 'files 1' 명령어를 입력한 결과입니다.

crash> files 1
PID: 1      TASK: cf930f40  CPU: 0   COMMAND: "systemd"
ROOT: /    CWD: /
 FD    FILE     DENTRY    INODE    TYPE  PATH
  0  cac6b900  cc610008  cce10a90  CHR   /dev/null
  1  cac6b900  cc610008  cce10a90  CHR   /dev/null
  2  cac6b900  cc610008  cce10a90  CHR   /dev/null
  3  cac5b3c0  cc610bd8  cca9c2e0  CHR   /dev/kmsg

위에서 보면 FD 열에 3(볼드체)이 보이고 struct file 구조체의 주소가 cac5b3c0 임을 알 수 있습니다.

이번에는 PID가 619인 다른 프로세스가 저장하고 있는 파일 디스크립터의 목록을 볼까요?
아래는 'files 619' 명령어를 입력한 출력 결과입니다.

crash> files 619
PID: 619    TASK: c87247c0  CPU: 0   COMMAND: "(agetty)"
ROOT: /    CWD: /
 FD    FILE     DENTRY    INODE    TYPE  PATH
  0  cac6b900  cc610008  cce10a90  CHR   /dev/null
  1  cac6b900  cc610008  cce10a90  CHR   /dev/null
  2  cac6b900  cc610008  cce10a90  CHR   /dev/null
  3  cac5b3c0  cc610bd8  cca9c2e0  CHR   /dev/kmsg

이번에도 FD 열에 3(볼드체)이 보이고 struct file 구조체의 주소가 cac5b3c0 임을 알 수 있습니다.

정리하면 PID가 1인 systemd 프로세스와 PID가 619인 "(agetty)"가 각각 3이란 FD로 '/dev/kmsg' 파일을 관리한다는 사실을 알 수 있습니다.  

이번에는 cac5b3c0 주소를 struct file로 캐스팅해 보겠습니다.
다음은 'struct file cac5b3c0' 명령어를 입력한 결과입니다.

crash> struct file cac5b3c0
struct file {
  f_u = {
    fu_llist = {
      next = 0x0
    },
    fu_rcuhead = {
      next = 0x0,
      func = 0x0
    }
  },
  f_path = {
    mnt = 0xcb65e850,
    dentry = 0xcc610bd8
  },
  f_inode = 0xcca9c2e0,
  f_op = 0xc0d02b38 <kmsg_fops>,
  f_lock = {
    {
      rlock = {
        raw_lock = {
          slock = 1
        },
        magic = 3735899821,
        owner_cpu = 4294967295,
        owner = 0xffffffff
      }
    }
  },
  f_write_hint = WRITE_LIFE_NOT_SET,
...

커널에서 관리하는 파일 객체의 필드들이 어떤 값을 저장하는지 알 수 있습니다.


"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)



[리눅스커널] 스케줄링: ret_to_user_from_irq/ret_fast_syscall/__irq_svc 레이블 분석 10. 프로세스 스케줄링

선점 스케줄링과 시그널을 처리하는 3군데 레이블을 TRACE32로 점검해보자.

ret_to_user_from_irq 레이블

ret_to_user_from_irq 레이블의 어셈블리 코드는 다음과 같다.

01   NSR:8010104C|E5992008  ret_to_user_from_irq:    ldr     r2,[r9,#0x8]
02   NSR:80101050|E352047F                           cmp     r2,#0x7F000000   ; r2,#2130706432
03   NSR:80101054|1B002C7E                           blne    0x8010C254       ; addr_limit_check_failed
04   NSR:80101058|E5991000                           ldr     r1,[r9]
05   NSR:8010105C|E311000F                           tst     r1,#0x0F         ; r1,#15
06   NSR:80101060|1AFFFFF0                           bne     0x80101028       ; slow_work_pending

05번째 줄에서 r1 레지스터는 thread_info 구조체의 flags 필드에 해당한다.

1> r1 레지스터가 0x2(_TIF_NEED_RESCHED)이거나 0x1(_TIF_SIGPENDING)인 경우

아래와 같이 slow_work_pending 레이블로 브랜치한다.

   NSR:80101028|E1A0000D__slow_work_pending:___cpy_____r0,r13
   NSR:8010102C|E1A02008                       cpy     r2,r8
   NSR:80101030|EB002C27                       bl      0x8010C0D4       ; do_work_pending

2> r1 레지스터가 0x0인 경우(0xf과 AND 연산한 결과가 false인 경우)

예상한데로 no_work_pending 레이블로 브랜치한다.

   NSR:8010105C|E311000F                       tst     r1,#0x0F         ; r1,#15
   NSR:80101060|1AFFFFF0                       bne     0x80101028       ; slow_work_pending
   NSR:80101064|EB03C818__no_work_pending:_____bl______0x801F30CC_______;_trace_hardirqs_on

ret_fast_syscall 레이블

다음은 ret_fast_syscall 레이블에서 선점 스케줄링을 할 조건인지 혹은 시그널을 받았는지 점검하는 분기점이다.
 
   NSR:80101000|E5AD0008  ret_fast_syscall:  str     r0,[r13,#0x8]!
   NSR:80101004|F10C0080                       cpsid   i
   NSR:80101008|E5992008                       ldr     r2,[r9,#0x8]
   NSR:8010100C|E352047F                       cmp     r2,#0x7F000000   ; r2,#2130706432
   NSR:80101010|1B002C8F                       blne    0x8010C254       ; addr_limit_check_failed
   NSR:80101014|E5991000                       ldr     r1,[r9]
   NSR:80101018|E31100FF_______________________tst_____r1,#0xFF_________;_r1,#255
   NSR:8010101C|0A000010                       beq     0x80101064       ; no_work_pending
   NSR:80101020|E31100F0                       tst     r1,#0xF0         ; r1,#240
   NSR:80101024|1A00005F                       bne     0x801011A8       ; __sys_trace_return_nosave

1> r1 레지스터가 0x2(_TIF_NEED_RESCHED)이거나 0x1(_TIF_SIGPENDING)인 경우

아래와 같이 no_work_pending 레이블로 브랜치하지 않고 0x80101020 주소에 브래이크 포인트가 걸려 있다.

   NSR:80101018|E31100FF                       tst     r1,#0xFF         ; r1,#255
   NSR:8010101C|0A000010                       beq     0x80101064       ; no_work_pending
   NSR:80101020|E31100F0_______________________tst_____r1,#0xF0_________;_r1,#240
   NSR:80101024|1A00005F                       bne     0x801011A8       ; __sys_trace_return_nosave
 
이번에는 위 조건에서 어떤 방식으로 어셈블리 코드가 동작하는지 체크하자.

2> r1 레지스터가 0xf인 경우

다음과 같이 __sys_trace_return_nosave 레이블로 브랜치를 한다.

   NSR:801011A8|F1080080____sys_trace_return_nosave:___cpsie___i
   NSR:801011AC|E1A0000D                               cpy     r0,r13
   NSR:801011B0|EB00272A                               bl      0x8010AE60       ; syscall_trace_exit
   NSR:801011B4|EAFFFFA3                               b       0x80101048       ; ret_to_user
 
위 코드에서 syscall_trace_exit 레이블(시스템 콜 트레이싱)을 처리한 다음에 마지막 줄과 같이 ret_to_user 레이블로 브랜치를 한다.

ret_to_user 레이블의 코드를 보면 아랫 부분에 ret_to_user_from_irq 레이블을 실행히 다시 선점 스케줄링될 조건과 프로세스에게 시그널이 전달됐는지 체크한 후 둘 중 하나라도 만족하면 slow_work_pending 레이블로 브랜치를 한다.

   NSR:80101048|F10C0080  ret_to_user:               cpsid   i
   NSR:8010104C|E5992008  ret_to_user_from_irq:      ldr     r2,[r9,#0x8]
   NSR:80101050|E352047F                             cmp     r2,#0x7F000000   ; r2,#2130706432
   NSR:80101054|1B002C7E                             blne    0x8010C254       ; addr_limit_check_failed
   NSR:80101058|E5991000                             ldr     r1,[r9]
   NSR:8010105C|E311000F                             tst     r1,#0x0F         ; r1,#15
   NSR:80101060|1AFFFFF0                             bne     0x80101028       ; slow_work_pending
 
다시 ret_fast_syscall 레이블의 어셈블리 코드로 되돌아 가자.

3> r1 레지스터가 0x2(_TIF_NEED_RESCHED)이거나 0x1(_TIF_SIGPENDING)인 경우

아래와 같이 slow_work_pending 레이블로 브랜치를 한다(브레이크 포인트가 걸려 있다). 

   NSR:80101020|E31100F0                               tst     r1,#0xF0         ; r1,#240
   NSR:80101024|1A00005F                               bne     0x801011A8       ; __sys_trace_return_nosave
   NSR:80101028|E1A0000D__slow_work_pending:___________cpy_____r0,r13
   NSR:8010102C|E1A02008                               cpy     r2,r8
   NSR:80101030|EB002C27                               bl      0x8010C0D4       ; do_work_pending
 
__irq_svc 레이블

__irq_svc 레이블의 코드는 다음과 같다. 추가 설명은 주석문을 참고하자.

   NSR:C010B180|E24DD04C  __irq_svc:sub     r13,r13,#0x4C    ; r13,r13,#76
   NSR:C010B184|E31D0004            tst     r13,#0x4         ; r13,#4
   NSR:C010B188|024DD004            subeq   r13,r13,#0x4     ; r13,r13,#4
   NSR:C010B18C|E88D1FFE            stm     r13,{r1-r12}
   NSR:C010B190|E8900038            ldm     r0,{r3-r5}
   NSR:C010B194|E28D7030            add     r7,r13,#0x30     ; r7,r13,#48
   NSR:C010B198|E3E06000            mvn     r6,#0x0          ; r6,#0
   NSR:C010B19C|E28D204C            add     r2,r13,#0x4C     ; r2,r13,#76
   NSR:C010B1A0|02822004            addeq   r2,r2,#0x4       ; r2,r2,#4
   NSR:C010B1A4|E52D3004            str     r3,[r13,#-0x4]!
   NSR:C010B1A8|E1A0300E            cpy     r3,r14
   NSR:C010B1AC|E887007C            stm     r7,{r2-r6}
   NSR:C010B1B0|EE130F10            mrc     p15,0x0,r0,c3,c0,0x0   ; p15,0,r0,c3,c0,0 (domain access control)
   NSR:C010B1B4|E58D0048            str     r0,[r13,#0x48]
   NSR:C010B1B8|E3A00051            mov     r0,#0x51         ; r0,#81
   NSR:C010B1BC|EE030F10            mcr     p15,0x0,r0,c3,c0,0x0   ; p15,0,r0,c3,c0,0 (domain access control)
   NSR:C010B1C0|F57FF06F            isb     sy
   NSR:C010B1C4|E59F1040            ldr     r1,0xC010B20C
   NSR:C010B1C8|E1A0000D            cpy     r0,r13
   NSR:C010B1CC|E28FE000            adr     r14,0xC010B1D4
   NSR:C010B1D0|E591F000            ldr     pc,[r1] // IRQ 서브 시스템을 구성하는 커널 함수 호출로 인터럽트 핸들러 처리
   NSR:C010B1D4|E1A096AD            lsr     r9,r13,#0x0D
   NSR:C010B1DC|E5998004            ldr     r8,[r9,#0x4] // r8 레지스터에 thread_info의 preempt_count 필드 로딩
   NSR:C010B1E0|E5990000            ldr     r0,[r9]  // r0 레지스터에 thread_info의 flags 필드 로딩
   NSR:C010B1E4|E3380000____________teq_____r8,#0x0__________;_r8,#0
   NSR:C010B1E8|13A00000            movne   r0,#0x0          ; r0,#0
   NSR:C010B1F0|1B000006            blne    0xC010B210       ; svc_preempt
   NSR:C010B1F4|E59D0048            ldr     r0,[r13,#0x48]
 
1> r8 레지스터(preempt_count)가 0x2인 경우

다음과 같이 실행되며 r0 레지스터(flags)를 0으로 변경한다.

   NSR:C010B1E0|E5990000                               ldr     r0,[r9]
   NSR:C010B1E4|E3380000                               teq     r8,#0x0          ; r8,#0
   NSR:C010B1E8|13A00000                               movne   r0,#0x0          ; r0,#0
   NSR:C010B1EC|E3100002_______________________________tst_____r0,#0x2__________;_r0,#2

2> r8 레지스터(preempt_count)가 0x2인 경우

다음과 같이 실행되며 r0 레지스터(flags)는 0으로 변경되지 않는다.

   NSR:C010B1E4|E3380000                               teq     r8,#0x0          ; r8,#0
   NSR:C010B1E8|13A00000                               movne   r0,#0x0          ; r0,#0
   NSR:C010B1EC|E3100002_______________________________tst_____r0,#0x2__________;_r0,#2
   NSR:C010B1F0|1B000006                               blne    0xC010B210       ; svc_preempt
 
위 조건에서 이어서 디버깅을 해보자.

2.1> r0 레지스터(flags)가 0x2인 경우

다음과 같이 svc_preempt 레이블로 브랜치를 해 선점 스케줄링을 시작한다.

   NSR:C010B210|E1A0800E__svc_preempt:_________________cpy_____r8,r14
   NSR:C010B214|EB3734D1                               bl      0xC0ED8560       ; preempt_schedule_irq
   NSR:C010B218|E5990000                               ldr     r0,[r9]
 
2.2> r0 레지스터(flags)가 0x0인 경우

아래와 같이 svc_preempt 레이블로 브랜치를 하지 않고 C010B1F4 주소를 실행한다.

   NSR:C010B1EC|E3100002                               tst     r0,#0x2          ; r0,#2
   NSR:C010B1F0|1B000006                               blne    0xC010B210       ; svc_preempt
   NSR:C010B1F4|E59D0048_______________________________ldr_____r0,[r13,#0x48]

정리하면, thread_info의 preempt_count(r8 레지스터)가 0이고 flags(r0 레지스터)가 2(_TIF_NEED_RESCHED)인 경우에만 
svc_preempt 레이블로 브랜치를 해 선점 스케줄링을 시작한다.

[리눅스커널] softirq: SOFTIRQ_MASK의 정체 6. 인터럽트 후반부 처리

SOFTIRQ_MASK 분석하기 

SOFTIRQ_MASK 가 어떤 값인지 알아보기 위해 이 플래그의 선언부를 확인해봤습니다.
다음은 SOFTIRQ_MASK의 선언부입니다.

https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/preempt.h
#define SOFTIRQ_MASK (__IRQ_MASK(SOFTIRQ_BITS) << SOFTIRQ_SHIFT)

#define SOFTIRQ_SHIFT (PREEMPT_SHIFT + PREEMPT_BITS)

보시다시피 SOFTIRQ_MASK 매크로는 __IRQ_MASK, SOFTIRQ_BITS, SOFTIRQ_SHIFT와 같이 생소한 매크로로 구성돼 있어 바로 어떤 값인지 확인하기 어렵습니다.
그래서 각각의 매크로의 선언부를 따라 가다 보면 시간이 오래 걸리는 경우가 많습니다.

SOFTIRQ_MASK 매크로를 확인하기 위한 패치 코드

그래서 SOFTIRQ_MASK 매크로의 정체를 빨리 확인하기 위해 다음과 같은 패치 코드를 생성해봤습니다.

diff --git a/kernel/softirq.c b/kernel/softirq.c
index 6f58486..acde648 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -246,6 +246,15 @@ static inline bool lockdep_softirq_start(void) { return false; }
 static inline void lockdep_softirq_end(bool in_hardirq) { }
 #endif

+int __find_softirq_mask(int param)
+{
+       int result = 0;
+
+       result = param & SOFTIRQ_MASK;
+
+       return result;
+}
+
 asmlinkage __visible void __softirq_entry __do_softirq(void)
 {
        unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
@@ -256,6 +265,9 @@ asmlinkage __visible void __softirq_entry __do_softirq(void)
        __u32 pending;
        int softirq_bit;

+       if (__find_softirq_mask(max_restart))
+                       return;
+
        /*
         * Mask out PF_MEMALLOC s current task context is borrowed for the
         * softirq. A softirq handled such as network RX might set PF_MEMALLOC

여기서 주의 사항은 소스를 빌드한 후 리눅스 시스템에는 절대 설치하면 안됩니다.  
 
SOFTIRQ_MASK 매크로를 전처리 코드에서 확인하기 
 
위에서 보이는 패치를 반영한 후 커널을 빌드해서 전처리 파일을 확인해 봤습니다. 
다음 코드를 같이 볼까요?

out/kernel/.tmp_softirq.i
int __find_softirq_mask(int param)
{
 int result = 0;

 result = param & (((1UL << (8))-1) << (0 + 8));

 return result;
}

위 코드에서 보이듯 SOFTIRQ_MASK의 정체는 '(((1UL << (8))-1) << (0 + 8))' 임을 알 수 있습니다.
(((1UL << (8))-1) << (0 + 8)) 구문을 계산하려고 하니 짜증이 나는군요.

SOFTIRQ_MASK 매크로를 어셈블리 코드로 확인하기 

이번에는 __find_softirq_mask() 함수를 어셈블리 코드로 분석해보겠습니다. 다음 코드를 같이 보겠습니다. 

______addr/line|code______|label_________________|mnemonic________________|comment
01   NSR:801244AC|E1A0C00D   __find_softirq_mask:   cpy     r12,r13
02   NSR:801244B0|E92DD800                          push    {r11-r12,r14,pc}
03   NSR:801244B4|E24CB004                          sub     r11,r12,#0x4     ; r11,r12,#4
04   NSR:801244B8|E52DE004                          push    {r14}
05   NSR:801244BC|EBFFAE15                          bl      0x8010FD18       ; __gnu_mcount_nc
06   NSR:801244C0|E2000CFF                          and     r0,r0,#0xFF00    ; r0,r0,#65280
07   NSR:801244C4|E89DA800                          ldm     r13,{r11,r13,pc}

06번째 줄을 보면 'and     r0,r0,#0xFF00' 구문이 보입니다. 즉, SOFTIRQ_MASK의 정체는 0xFF00입니다.
'param & SOFTIRQ_MASK' 구문은 어셈블리 코드 포멧으로 'and     r0,r0,#0xFF00' 명령어이기 때문입니다.

결론 

C 포멧의 소스 코드보다 어셈블리 코드를 분석할 때 매크로의 정체를 빨리 파악할 수 있습니다.
이처럼 어셈블리 코드를 읽으면 더 빨리 코드를 읽을 때도 있습니다.


[리눅스커널] 커널 크래시: Data Abort @usb_copy_descriptors [Crash]Troubleshooting!!

소개

이번 시간에는 커널 패닉을 디버깅하는 과정을 소개합니다.

1차 분석

콜스택을 TRACE32로 잡아 보니 Data abort로 커널 패닉이 발생했습니다.

-000|el1_da(asm)
 -->|exception
-001|usb_copy_descriptors(src = 0xFFFFFFF50D806080)
-002|configfs_composite_bind(gadget = 0xFFFFFFF4C4F16298, gdriver = 0xFFFFFFF5B2943B08)
-003|udc_bind_to_driver(udc = 0xFFFFFFF5B67BD000, driver = 0xFFFFFFF5B2943B08)
-004|usb_udc_attach_driver(name = 0xFFFFFFF57B6C3F80, driver = 0xFFFFFFF5B2943B08)
-005|schedule_work(inline)
-005|gadget_dev_desc_UDC_store(item = 0xFFFFFFF5B2943800, ?, len = 12)
-006|configfs_write_file(file = 0xFFFFFFF4D93A7F00, buf = 0x0000007F961D31D1, ?, ppos = 0xFFFFFFF5B9363EB
-007|__vfs_write(file = 0xFFFFFFF4D93A7F00, ?, ?, pos = 0xFFFFFFF5B9363EB0)
-008|vfs_write(file = 0xFFFFFFF4D93A7F00, buf = 0x0000007F961D31D1, ?, pos = 0xFFFFFFF5B9363EB0)
-009|sys_write(?, ?, ?)
-010|el0_svc_naked(asm)
 -->|exception
-011|NUX:0x7F9741D688(asm)
 ---|end of frame
 
Data Abort가 발생한 인스트럭션은 'ldrb w2,[x2]' 인데 x2는 0x736563697665642F입니다.
'0x736563697665642F'이라? 주소의 형태만 봐도 MMU가 처리할 수 없는 주소로 보입니다.

 ______________addr/line|code___________|label____|mnemonic________________|comment
   NSX:FFFFFF84CDC306B8|91000421                  add     x1,x1,#0x1       ; x1,x1,#1
   NSX:FFFFFF84CDC306BC|8B010EA2                  add     x2,x21,x1,lsl #0x3   ; x2,src,x1,lsl #3
   NSX:FFFFFF84CDC306C0|F85F8042                  ldur    x2,[x2,#-0x8]    ; x2,[x2,#-8]
   NSX:FFFFFF84CDC306C4|B4000082                  cbz     x2,0xFFFFFF84CDC306D4
___NSX:FFFFFF84CDC306C8|39400042__________________ldrb____w2,[x2]

Data Abort가 발생한 소스 코드의 위치는 어딜까요?

y.l.line 0xFFFFFF84CDC306C8

linux-next\drivers\usb\gadget\config.c|\136--136   

이번에는 'v.l %l %t' 명령어로 지역 변수를 어느 레지스터가 저장했는지 확인해볼까요?

usb_copy_descriptors(
    (register struct usb_descriptor_header * *) [X21] src = 0xFFFFFFF50D806080)
  (register unsigned int) [X19] n_desc = 0
  
x21은 0xFFFFFFF50D806080입니다.

0xFFFFFFF50D806080의 출처는 0xFFFFFFF5551DD800입니다.  

  (struct usb_function *) [-] (struct usb_function*)0xFFFFFFF5551DD800 = 0xFFFFFFF5551DD800 -> (
    (char *) [D:0xFFFFFFF5551DD800] name = 0xFFFFFF84CEF20533 -> "mtp",
    (int) [D:0xFFFFFFF5551DD808] intf_id = 0,
    (struct usb_gadget_strings * *) [D:0xFFFFFFF5551DD810] strings = 0xFFFFFF84CFEE3138,
    (struct usb_descriptor_header * *) [D:0xFFFFFFF5551DD818] fs_descriptors = 0xFFFFFFF50D806080 -> 0x1 -> (  //<<--
      (__u8) [D:0x1] bLength = 0,
      (__u8) [D:0x2] bDescriptorType = 0),
    (struct usb_descriptor_header * *) [D:0xFFFFFFF5551DD820] hs_descriptors = 0xFFFFFFF50D806280 -> 0x1 -> ( //<<--
      (__u8) [D:0x1] bLength = 0,
      (__u8) [D:0x2] bDescriptorType = 0),
    (struct usb_descriptor_header * *) [D:0xFFFFFFF5551DD828] ss_descriptors = 0xFFFFFFF50D806F00 -> 0x0 -> NULL //<<--

그런데 코어 덤프에서는 otg_desc[0]이 NULL입니다.

  (static struct usb_descriptor_header * [2]) otg_desc = (
    [0] = 0x0 =  -> NULL,
    [1] = 0x0 =  -> NULL)
(where)
1764static void configfs_composite_unbind(struct usb_gadget *gadget)
1765{
1766 struct usb_composite_dev *cdev;
1767 struct gadget_info *gi;
1768
1769 /* the gi->lock is hold by the caller */
1770
1771 cdev = get_gadget_data(gadget);
1772 gi = container_of(cdev, struct gadget_info, cdev);
1773
1774 kfree(otg_desc[0]);
1775 otg_desc[0] = NULL; //<<--

Solution Patch

아래와 같이 코드를 수정하면 Data Abort를 방지할 수 있습니다.

diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
index ec94fbb..be06b39 100644
--- a/drivers/usb/gadget/configfs.c
+++ b/drivers/usb/gadget/configfs.c
@@ -1570,7 +1570,7 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
                                goto err_purge_funcs;
                        }
-                       if (f->multi_config_support) {
+                       if (f->multi_config_support && !otg_desc[0]) {
                                if (f->fs_descriptors)
                                        f->fs_descriptors =
                                                usb_copy_descriptors(f->fs_descriptors);

[라즈베리파이] 라즈베리 파이 사용 시 주의사항 2. 라즈베리 파이 설정

이번에는 라즈베리 파이를 쓰면서 주의해야 할 사항 몇 가지를 정리합니다. 이 내용을 숙지하면 조금 더 오랫동안 라즈베리 파이를 쓸 수 있습니다.

1. 라즈베리 파이의 전원을 끌 때는 반드시 셧다운 메뉴를 선택합시다. 컴퓨터의 전원을 끌 때처럼 하면 됩니다. 바로 전원 케이블을 빼버리면 라즈베리 파이가 다시 부팅을 못할 수 있습니다. 마이크로 SD 카드가 제대로 마운트를 해제하지 않은 채로 전원이 끊기면 파일 시스템이 손상될 수 있기 때문입니다.

2. 라즈베리 파이는 주머니에 들어갈 만한 크기입니다. 그렇다고 정말 주머니에 그대로 넣고 다니면 안 됩니다. 라즈베리 파이를 가지고 다니다 떨어뜨리면 못 쓸 수 있습니다. 라즈베리 파이는 꼭 보호 케이스를 써서 충격으로부터 보호합시다.

3. 겨울철에 정전기가 일어날 수 있는 환경에서 라즈베리 파이를 구동하면 갑자기 실행을 멈출 수 있습니다. EMP라는 정전기 쇼크를 받으면 라즈베리 파이가 손상될 수도 있습니다.

이는 라즈베리 파이에 문제가 있는 것이 아닙니다. 라즈베리 파이처럼 보드 형태로 제작된 디바이스는 외부의 하드웨어적인 노이즈에 취약할 수밖에 없습니다.


라즈베리 파이 설정 

라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)



[리눅스커널] 스케줄링: __schedule() 함수와 'bool preempt' 인자 10. 프로세스 스케줄링

다음과 같이 __schedule() 함수의 구현부를 보면 'bool preempt' 인자를 전달한다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/sched/core.c 
static void __sched notrace __schedule(bool preempt)
{
    struct task_struct *prev, *next;
    unsigned long *switch_count;
    struct rq_flags rf;

이번 시간에는 이 함수에 'bool preempt' 인자가 추가된 이력을 확인해보자.

__schedule() 함수에 'bool preempt' 인자가 추가된 패치

출처는 다음과 같다.
https://lkml.org/lkml/2015/9/30/100

패치 코드의 내용은 다음과 같다.

From fc13aebab7d8f0d19d557c721a0f25cdf7ae905c Mon Sep 17 00:00:00 2001
From: Peter Zijlstra <peterz@infradead.org>
Date: Mon, 28 Sep 2015 18:05:34 +0200
Subject: [PATCH 479/867] sched/core: Add preempt argument to __schedule()

There is only a single PREEMPT_ACTIVE use in the regular __schedule()
path and that is to circumvent the task->state check. Since the code
setting PREEMPT_ACTIVE is the immediate caller of __schedule() we can
replace this with a function argument.

Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Frederic Weisbecker <fweisbec@gmail.com>
Reviewed-by: Steven Rostedt <rostedt@goodmis.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>
---
 kernel/sched/core.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 8d8722b..0a71f89 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -3056,7 +3056,7 @@ pick_next_task(struct rq *rq, struct task_struct *prev)
  *
  * WARNING: must be called with preemption disabled!
  */
-static void __sched __schedule(void)
+static void __sched __schedule(bool preempt)
 {
    struct task_struct *prev, *next;
    unsigned long *switch_count;
@@ -3096,7 +3096,7 @@ static void __sched __schedule(void)
    rq->clock_skip_update <<= 1; /* promote REQ to ACT */

    switch_count = &prev->nivcsw;
-   if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
+   if (!preempt && prev->state) {
        if (unlikely(signal_pending_state(prev->state, prev))) {
            prev->state = TASK_RUNNING;
        } else {
@@ -3161,7 +3161,7 @@ asmlinkage __visible void __sched schedule(void)
    sched_submit_work(tsk);
    do {
        preempt_disable();
-       __schedule();
+       __schedule(false);
        sched_preempt_enable_no_resched();
    } while (need_resched());
 }
@@ -3202,7 +3202,7 @@ static void __sched notrace preempt_schedule_common(void)
 {
    do {
        preempt_active_enter();
-       __schedule();
+       __schedule(true);
        preempt_active_exit();

        /*
@@ -3267,7 +3267,7 @@ asmlinkage __visible void __sched notrace preempt_schedule_notrace(void)
         * an infinite recursion.
         */
        prev_ctx = exception_enter();
-       __schedule();
+       __schedule(true);
        exception_exit(prev_ctx);

        barrier();
@@ -3296,7 +3296,7 @@ asmlinkage __visible void __sched preempt_schedule_irq(void)
    do {
        preempt_active_enter();
        local_irq_enable();
-       __schedule();
+       __schedule(true);
        local_irq_disable();
        preempt_active_exit();
    } while (need_resched());
--
2.6.2

코드를 보면 알 수 있듯이 원래 __schedule() 함수는 인자가 없었다.
이 패치에서는  __schedule() 함수에 bool preempt 이란 인자를 추가하고 다음과 같은 패턴으로 함수를 호출한다.

   * __schedule(true);
   * __schedule(false);


패치 코드를 유심히 보면 __schedule(true) 함수를 호출하기 전에는 preempt_active_enter() 함수를 호출한다.
다음은 preempt_schedule_common() 함수이다.

@@ -3202,7 +3202,7 @@ static void __sched notrace preempt_schedule_common(void)
 {
    do {
        preempt_active_enter(); //<<-- 여기
-       __schedule();
+       __schedule(true);
        preempt_active_exit();

이번에는 preempt_schedule_irq() 함수이다. 역시 preempt_active_enter() 함수를 호출한다.

@@ -3296,7 +3296,7 @@ asmlinkage __visible void __sched preempt_schedule_irq(void)
    do {
        preempt_active_enter();  //<<-- 여기
        local_irq_enable();
-       __schedule();
+       __schedule(true);


__schedule() 함수에서 가장 먼저 변경된 코드는 다음과 같다.

+static void __sched __schedule(bool preempt)
 {
    struct task_struct *prev, *next;
    unsigned long *switch_count;
@@ -3096,7 +3096,7 @@ static void __sched __schedule(void)
    rq->clock_skip_update <<= 1; /* promote REQ to ACT */

    switch_count = &prev->nivcsw;
-   if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
+   if (!preempt && prev->state) {

if문에서 'preempt_count() & PREEMPT_ACTIVE' 구문을 삭제한 것이다.
preempt_count() 함수는 프로세스의 thread_info 구조체의 preempt_count 필드를 나타내는데 이 값을 PREEMPT_ACTIVE 플래그와 AND 비트 연산을 하는 코드였다.

이 사실로 preempt_active_enter() 함수는 프로세스의 thread_info 구조체의 preempt_count 필드에  PREEMPT_ACTIVE 플래그를 설정한다고 유추할 수 있다.
그런데 지금  preempt_active_enter() 함수는 소스 트리에서 볼 수 없다.

이력을 조금 조사해볼까?

이번에도 'Peter Zijlstra' 님이 삭제했다. preempt_active_enter() 함수를 쓰기 싫었나 보다.
preempt_active_enter() 함수가 반환하는 값을 테스트하기 싫었나? '테스트를 하기 싫어서 함수 자체를 없애 버린다!' 

From 3d8f74dd4ca1da8a1a464bbafcf679e40c2fc10f Mon Sep 17 00:00:00 2001
From: Peter Zijlstra <peterz@infradead.org>
Date: Mon, 28 Sep 2015 18:09:19 +0200
Subject: [PATCH 481/867] sched/core: Stop setting PREEMPT_ACTIVE

Now that nothing tests for PREEMPT_ACTIVE anymore, stop setting it.

Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Steven Rostedt <rostedt@goodmis.org>
Reviewed-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>
---
 kernel/sched/core.c | 19 ++++++-------------
 1 file changed, 6 insertions(+), 13 deletions(-)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index cfad7f5..6344d82 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -3201,9 +3201,9 @@ void __sched schedule_preempt_disabled(void)
 static void __sched notrace preempt_schedule_common(void)
 {
    do {
-       preempt_active_enter();
+       preempt_disable();
        __schedule(true);
-       preempt_active_exit();
+       sched_preempt_enable_no_resched();

        /*
         * Check again in case we missed a preemption opportunity
@@ -3254,13 +3254,7 @@ asmlinkage __visible void __sched notrace preempt_schedule_notrace(void)
        return;

    do {
-       /*
-        * Use raw __prempt_count() ops that don't call function.
-        * We can't call functions before disabling preemption which
-        * disarm preemption tracing recursions.
-        */
-       __preempt_count_add(PREEMPT_ACTIVE + PREEMPT_DISABLE_OFFSET);
-       barrier();
+       preempt_disable_notrace();
        /*
         * Needs preempt disabled in case user_exit() is traced
         * and the tracer calls preempt_enable_notrace() causing
@@ -3270,8 +3264,7 @@ asmlinkage __visible void __sched notrace preempt_schedule_notrace(void)
        __schedule(true);
        exception_exit(prev_ctx);

-       barrier();
-       __preempt_count_sub(PREEMPT_ACTIVE + PREEMPT_DISABLE_OFFSET);
+       preempt_enable_no_resched_notrace();
    } while (need_resched());
 }
 EXPORT_SYMBOL_GPL(preempt_schedule_notrace);
@@ -3294,11 +3287,11 @@ asmlinkage __visible void __sched preempt_schedule_irq(void)
    prev_state = exception_enter();

    do {
-       preempt_active_enter();
+       preempt_disable();
        local_irq_enable();
        __schedule(true);
        local_irq_disable();
-       preempt_active_exit();
+       sched_preempt_enable_no_resched();
    } while (need_resched());

    exception_exit(prev_state);
--
2.6.2

preempt_active_enter() 함수는 뭔가 선점을 활성화하는 플래그를 설정했던 것 같다.

preempt_count_add() 매크로 함수의 구현부

이번에는 preempt_active_enter() 함수의 구현부를 볼까? 2015년도에 존재했던 함수이다.
https://lkml.org/lkml/2015/5/11/519

+#define preempt_active_enter() \
+do { \
+ preempt_count_add(PREEMPT_ACTIVE + PREEMPT_DISABLE_OFFSET); \
+ barrier(); \
+} while (0) 

내 예상이 맞았다. preempt_count_add() 함수를 사용해 thread_info 구조체의 preempt_count에 'PREEMPT_ACTIVE + PREEMPT_DISABLE_OFFSET' 를 더한다.

정리

음 쓸때 없는 코드를 분석하는 것 같은데, 좀 정리를 좀 하자.

   * 선점 스케줄링으로 프로세스를 CPU에서 빼버릴 때는 __schedule(true); 함수를 호출한다. 
   * __schedule(true) 함수를 호출해 선점 스케줄링을 하면 대상 프로세스는 런큐에서 제거되지는 않는다.
   * 원래 preempt_active_enter() 매크로 함수를 사용해 해당 프로세스가 선점될 대상이라고 지정했다.
   * preempt_active_enter() 함수를 제거하고 __schedule() 함수에 'bool preempt' 이란 인자를 추가했다.
   
가끔 가만히 있는 소스 코드를 분석하는 것 보다 코드가 수정된 이력을 보면 덜 지루한 것 같다.

[리눅스커널][디버깅] ftrace: ftrace를 비활성화하기 [Debugging] Tips

보통 프로덕션 빌드로 이미지를 생성하려고 할 때 커널에서 ftrace를 아예 꺼버리고 싶을 때가 있다. 
이 때 다음 패치를 적용해보자.

diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 40817e4..2cf0d86 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -273,6 +273,7 @@ unsigned long long ns2usecs(u64 nsec)
  */
 static struct trace_array global_trace = {
        .trace_flags = TRACE_DEFAULT_FLAGS,
+       .buffer_disabled = 1,
 };

 LIST_HEAD(ftrace_trace_arrays);

[리눅스커널] 시그널: ERESTARTSYS 매크로와 signal_pending() 에 대해서 12. 시그널

-ERESTARTSYS 는 리눅스 커널의 '시그널' 서브 시스템과 연관된 매크로로 시스템 콜을 다시 실행시키려고 할 때 반환하는 매크로이다. 

ERESTARTSYS 매크로의 의미

보통 커널이 다시 시스템 콜을 재실행을 시키려는 이유는 '어떤 액션의 동기화'를 맞추기 위해서이다. 음, 내가 써도 무슨 소리인지 모르겠네. 나중에 이 글을 읽을 '나 자신'을 위해 이해하기 쉽도록 비유를 하나 들자.

* 침대에서 잠을 든 상태이고 난 침대에서 잘 들어 있어야 한다.
* 그런데 갑자기 택배가 와서 초인종이 울린다.
* 일어나서 택배를 받고 다시 침대에 들어가 잠든 상태에 있어야 한다.

여기서 중요한 사실은 '난 침대에 잠 들어 있어야 한다.'라는 사실이다. 자, 위에서 든 예시를 조금 어려운 용어로 바꿔보자.

* 프로세스는 휴면 상태에 있는 상태이고 프로세스는 휴면 상태를 유지해야 한다.
* 그런데 갑자기 시그널이 전달돼 깨어나 시그널을 처리한다.
* 시스템 콜을 다시 재실행하도록 설정한다.
* 다시 시스템 콜이 재실행이 되서 프로세스는 이전과 같이 휴면 상태를 유지한다.

이번에는 다른 예시를 들어보자.

* 시스템 콜 컨텍스트로 실행이 된 프로세스는 I/O 상태라 휴면 상태에 있는 상태이고 프로세스는 휴면 상태를 유지해야 한다.
   왜냐면, 블록 디바이스와 같은 저장 매체로 I/O 할 때 프로세스는 휴면 상태에서 I/O가 끝나기를 기다려야 한다.
* 그런데 갑자기 시그널이 전달돼 I/O Wait 상태의 프로세스를 깨우면 프로세스는 깨어나 시그널을 처리한다.
* 시스템 콜을 다시 재실행하도록 설정한다.
* 다시 시스템 콜이 재실행이 되서 프로세스는 이전과 같이 휴면 상태를 유지하면서 I/O wait 를 한다.

ERESTARTSYS 매크로를 사용하는 예제 코드 확인하기

그런데 ERESTARTSYS 매크로를 사용하는 패턴은 정해져 있다. 즉, 프로세스에게 시그널이 전달됐다는 정보와 함께 사용한다는 점이다.

먼저 pipe_write() 함수를 보자.

https://elixir.bootlin.com/linux/v4.19.30/source/fs/pipe.c
static ssize_t
pipe_write(struct kiocb *iocb, struct iov_iter *from)
{
    struct file *filp = iocb->ki_filp;
...
        if (signal_pending(current)) {
            if (!ret)
                ret = -ERESTARTSYS;
            break;
        }

signal_pending() 함수를 호출해 현재 실행 중인 프로세스에게 시그널이 전달이 됐다면,
-ERESTARTSYS 매크로를 반환한다.

이어서 do_wait_intr_irq() 함수를 보자.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/sched/wait.c
int do_wait_intr_irq(wait_queue_head_t *wq, wait_queue_entry_t *wait)
{
if (likely(list_empty(&wait->entry)))
__add_wait_queue_entry_tail(wq, wait);

set_current_state(TASK_INTERRUPTIBLE);
if (signal_pending(current))
return -ERESTARTSYS;

signal_pending() 함수가 true를 반환해 프로세스 자신에게 시그널이 전달됐다는 사실을 파악하면,
'-ERESTARTSYS' 매크로를 반환한다.

위와 같은 코드를 커널에서 보면 다음과 같이 머릿 속으로 그리며 해석하며 된다.

   * 이 함수는 시스템 콜을 통해 실행됐다.
   * 시그널을 받은 후 시그널을 처리한 후 시스템 콜이 다시 재실행된다.
   * 그 다음에 위에서 본 함수로 다시 되돌아 올 것이다.

반환된 ERESTARTSYS 매크로를 처리하는 시그널 처리 함수

프로세스가 시그널을 받은 후 호출되는 do_signal() 함수를 보자.

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/kernel/signal.c
static int do_signal(struct pt_regs *regs, int syscall)
{
unsigned int retval = 0, continue_addr = 0, restart_addr = 0;
struct ksignal ksig;
int restart = 0;

/*
* If we were from a system call, check for system call restarting...
*/
if (syscall) {
continue_addr = regs->ARM_pc;
restart_addr = continue_addr - (thumb_mode(regs) ? 2 : 4);
retval = regs->ARM_r0;

/*
* Prepare for system call restart.  We do this here so that a
* debugger will see the already changed PSW.
*/
switch (retval) {
case -ERESTART_RESTARTBLOCK:
restart -= 2;
case -ERESTARTNOHAND:
case -ERESTARTSYS:
case -ERESTARTNOINTR:
restart++;
regs->ARM_r0 = regs->ARM_ORIG_r0;
regs->ARM_pc = restart_addr;
break;
}
}

위 코드에서 다음 주석을 감상하자.

/*
* If we were from a system call, check for system call restarting...
*/

역시 시스템 콜을 리스타트 한다는 내용이다.

프로세스가 시그널을 받아 처리하는 do_signal() 함수가 호출되는 경로(패스)는 다음과 같다.

   * ret_fast_syscall 레이블: 시스템 콜을 처리한 후 유저 공간으로 되돌아가기 직전
   * __irq_usr 레이블: 유저 프로세스가 유저 공간에서 실행하는 도중 인터럽트가 발생. 이후 인터럽트 핸들러를 처리한 다음 유저 공간으로 되돌아가기 직전

관련 커널 뉴스 레터


조금 더 자세한 내용은 다음 링크의 '커널 뉴스 레터'를 참고하자.  

제목: A new system call restart mechanism
https://lwn.net/Articles/17744/

System calls often have to wait for things - I/O completion, availability of a resource, or simply for a timeout to expire, for example. Normally the process making the system call becomes unblocked at the appropriate time, and the call completes its work and returns to user space. What happens, though, if a signal is queued for the process while it is waiting? In that case, the system call needs to abort its work and allow the actual delivery of the signal. For this reason, kernel code which sleeps tends to follow the sleep with a test like:
    if (signal_pending(current))
return -ERESTARTSYS;
...

아, 리눅스 커널의 '시그널' 서브 시스템은 정말 어려운 것 같다.


[리눅스커널] 시스템 콜: _TIF_SYSCALL_WORK 매크로의 정체 11. 시스템 콜

_TIF_SYSCALL_WORK 매크로의 정체는 다음 코드와 같다.

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/include/asm/thread_info.h 
#define _TIF_SYSCALL_WORK (_TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT | \
   _TIF_SYSCALL_TRACEPOINT | _TIF_SECCOMP)

_TIF_SYSCALL_WORK 매크로는 4개의 매크로를 'OR 비트'를 연산한 결과로 치환된다.

_TIF_SYSCALL_TRACE~_TIF_SECCOMP 매크로의 정체를 확인해볼까?

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/include/asm/thread_info.h 
#define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE)
#define _TIF_SYSCALL_AUDIT (1 << TIF_SYSCALL_AUDIT)
#define _TIF_SYSCALL_TRACEPOINT (1 << TIF_SYSCALL_TRACEPOINT)
#define _TIF_SECCOMP (1 << TIF_SECCOMP)

각각 매크로는 TIF_SYSCALL_TRACE부터 TIF_SECCOMP 매크로를 왼쪽 비트 시프트를 연산한 결괏값이다.

이어서 각 매크로의 선언부를 보자.

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/include/asm/thread_info.h 
#define TIF_SYSCALL_TRACE 4 /* syscall trace active */
#define TIF_SYSCALL_AUDIT 5 /* syscall auditing active */
#define TIF_SYSCALL_TRACEPOINT 6 /* syscall tracepoint instrumentation */
#define TIF_SECCOMP 7 /* seccomp syscall filtering active */

위에 정의된 매크로의 정수값을 참고하면, 다음 매크로는 주석과 같이 해석할 수 있다.

#define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE) // (1 << 4) = 0x10
#define _TIF_SYSCALL_AUDIT (1 << TIF_SYSCALL_AUDIT) // (1 << 5) = 0x20
#define _TIF_SYSCALL_TRACEPOINT (1 << TIF_SYSCALL_TRACEPOINT) // (1 << 6) = 0x40
#define _TIF_SECCOMP (1 << TIF_SECCOMP) // (1 << 7) = 0x80

위 결괏값을 통해 _TIF_SYSCALL_WORK 매크로는 다음과 같이 계산할 수 있다.

#define _TIF_SYSCALL_WORK (_TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT | \
   _TIF_SYSCALL_TRACEPOINT | _TIF_SECCOMP)

_TIF_SYSCALL_WORK = 0x10 | 0x20 | 0x40 | 0x80 = 0xF0

_TIF_SYSCALL_WORK 매크로의 정체는 16진수로 0xF0 이고 2진수로는 1111_0000‬ 이다.

이번에는 ret_fast_syscall 레이블의 어셈블리 코드에서 _TIF_SYSCALL_WORK 매크로가 어떻게 동작하는지 확인해보자.

아래는 ret_fast_syscall 레이블의 코드이다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/arch/arm/kernel/entry-common.S
01 ret_fast_syscall:
02 __ret_fast_syscall:
03 UNWIND(.fnstart )
04 UNWIND(.cantunwind )
...
05 tst r1, #_TIF_SYSCALL_WORK
06 bne __sys_trace_return_nosave
07 slow_work_pending:
08 mov r0, sp @ 'regs'
09 mov r2, why @ 'syscall'

아래는 TRACE32로 본 어셈블리 명령어 포멧의 코드이다.

______addr/line|code____|label_____________|mnemonic________________|comment
01 NSR:80101010|1B002C8F                    blne    0x8010C254       ; addr_limit_check_failed
02 NSR:80101014|E5991000                    ldr     r1,[r9]
03 NSR:80101018|E31100FF                    tst     r1,#0xFF         ; r1,#255
04 NSR:8010101C|0A000010                    beq     0x80101064       ; no_work_pending
05 NSR:80101020|E31100F0                    tst     r1,#0xF0         ; r1,#240
06 NSR:80101024|1A00005F                    bne     0x801011A8       ; __sys_trace_return_nosave
07 NSR:80101028|E1A0000D slow_work_pending: cpy     r0,r13
08 NSR:8010102C|E1A02008                    cpy     r2,r8
09 NSR:80101030|EB002C27                    bl      0x8010C0D4       ; do_work_pending

5번째 줄을 보면 'tst     r1,#0xF0' 명령어가 보인다. _TIF_SYSCALL_WORK 매크로의 정체는 계산식과 같이 0xF0인 것이다.

다음 시간에는 tst 명령어의 동작 원리를 살펴보자.

______addr/line|code____|label_____________|mnemonic________________|comment
01 NSR:80101010|1B002C8F                    blne    0x8010C254       ; addr_limit_check_failed
02 NSR:80101014|E5991000                    ldr     r1,[r9]
03 NSR:80101018|E31100FF                    tst     r1,#0xFF         ; r1,#255
04 NSR:8010101C|0A000010                    beq     0x80101064       ; no_work_pending
05 NSR:80101020|E31100F0____________________tst_____r1,#0xF0_________;_r1,#240
06 NSR:80101024|1A00005F                    bne     0x801011A8       ; __sys_trace_return_nosave
07 NSR:80101028|E1A0000D slow_work_pending: cpy     r0,r13
08 NSR:8010102C|E1A02008                    cpy     r2,r8
09 NSR:80101030|EB002C27                    bl      0x8010C0D4       ; do_work_pending

[라즈베리파이] 라즈비안: objdump 바이너리 유틸리티 2. 라즈베리 파이 설정

바이너리 유틸리티는 오브젝트 포맷의 파일을 조작할 수 있는 프로그램입니다. 다음은 대표적인 바이너리 유틸리티를 정리한 표입니다.

objdump: 라이브러리나 ELF(Executable and Linkable Format) 형식의 파일을 어셈블리어로 출력
as: 어셈블러
ld: 링커
addr2line: 주소를 파일과 라인으로 출력
nm: 오브젝트 파일의 심벌을 출력
readelf ELF 파일의 내용을 출력

이 중에서 리눅스 커널 어셈블리 코드와 섹션 정보를 볼 수 있는 objdump라는 바이너리 유틸리티 사용법을 소개합니다. 오브젝트 파일로는 리눅스 커널을 빌드하면 생성되는 vmlinux를 활용합니다.

다음 명령어를 입력하면 objdump를 실행할 때 지정 가능한 옵션 정보를 확인할 수 있습니다.

root@raspberrypi:/home/pi/kernel_obj# objdump
Usage: objdump <option(s)> <file(s)>
 Display information from object <file(s)>.
 At least one of the following switches must be given:
  -a, --archive-headers    Display archive header information
  -f, --file-headers       Display the contents of the overall file header
  -p, --private-headers    Display object format specific file header contents
  -P, --private=OPT,OPT... Display object format specific contents
  -h, --[section-]headers  Display the contents of the section headers
  -x, --all-headers        Display the contents of all headers
  -d, --disassemble        Display assembler contents of executable sections
  -D, --disassemble-all    Display assembler contents of all sections
  -S, --source             Intermix source code with disassembly
  -s, --full-contents      Display the full contents of all sections requested


라즈비안에서는 기본적으로 바이너리 유틸리티를 사용할 수 있어서 바이너리 유틸리티를 따로 설치할 필요가 없습니다.


먼저 /home/pi/kernel_obj라는 디렉터리를 만들고 리눅스 커널 이미지 생성 폴더에 있는 vmlinux 파일을 복사합니다.
root@raspberrypi:/home/pi# mkdir kernel_obj
root@raspberrypi:/home/pi# cd kernel_obj/
root@raspberrypi:/home/pi/kernel_obj# cp ../rpi_kernel_src/out/vmlinux  .

objdump -x vmlinux 명령어를 입력해 vmlinux의 헤더 정보를 확인합니다.

01 root@raspberrypi:/home/pi/kernel_obj# objdump -x vmlinux | more
02 vmlinux:     file format elf32-littlearm
03 vmlinux
04 architecture: arm, flags 0x00000112:
05 EXEC_P, HAS_SYMS, D_PAGED
06 start address 0x80008000
07 
08 Program Header:
09    LOAD off    0x00000000 vaddr 0x80000000 paddr 0x80000000 align 2**16
10         filesz 0x0000826c memsz 0x0000826c flags r-x
11    LOAD off    0x00010000 vaddr 0x80100000 paddr 0x80100000 align 2**16
12         filesz 0x006a873c memsz 0x006a873c flags r-x
...
13 Sections:
14 Idx Name          Size      VMA       LMA       File off  Algn
15  0 .head.text    0000026c  80008000  80008000  00008000  2**2
16                  CONTENTS, ALLOC, LOAD, READONLY, CODE
17  1 .text         006a8720  80100000  80100000  00010000  2**6
18                  CONTENTS, ALLOC, LOAD, READONLY, CODE
19  2 .fixup        0000001c  807a8720  807a8720  006b8720  2**2
20                  CONTENTS, ALLOC, LOAD, READONLY, CODE
21  3 .rodata       001d0890  80800000  80800000  006c0000  2**12
22                  CONTENTS, ALLOC, LOAD, DATA
23  4 __ksymtab     00007f58  809d0890  809d0890  00890890  2**2
24                  CONTENTS, ALLOC, LOAD, READONLY, DATA  
 
vmlinux 헤더 출력 내용을 좀 더 자세히 살펴보겠습니다. 

04~06번째 줄에서는 아키텍처 이름과 스타트업 코드의 위치를 표시합니다.

04 architecture: arm, flags 0x00000112:
05 EXEC_P, HAS_SYMS, D_PAGED
06 start address 0x80008000

보다시피 스타트업 코드의 주소는 0x80008000입니다.


스타트업 코드는 이미지가 처음 실행될 때 동작하며, 어셈블리 코드로 구성돼 있습니다. 보통 시스템 초기 설정을 수행하고 arm 모드별로 스택 주소를 설정합니다.


13~24번째 줄은 섹션 정보입니다.
13 Sections:
14 Idx Name          Size      VMA       LMA       File off  Algn
15  0 .head.text    0000026c  80008000  80008000  00008000  2**2
16                  CONTENTS, ALLOC, LOAD, READONLY, CODE
17  1 .text         006a8720  80100000  80100000  00010000  2**6
18                  CONTENTS, ALLOC, LOAD, READONLY, CODE

objdump -d vmlinux 명령어를 입력하면 vmlinux에서 어셈블리 코드를 출력할 수 있습니다.

root@raspberrypi:/home/pi/kernel_obj# objdump -d vmlinux
vmlinux:     file format elf32-littlearm
80008000 <stext>:
80008000: eb0430de  bl 80114380 <__hyp_stub_install>
80008004: e10f9000  mrs r9, CPSR
80008008: e229901a  eor r9, r9, #26
8000800c: e319001f  tst r9, #31
80008010: e3c9901f  bic r9, r9, #31
80008014: e38990d3  orr r9, r9, #211 ; 0xd3

그런데 너무 많은 어셈블리 코드가 출력돼 어셈블리 코드를 보기 어렵습니다. 그래서 이번에는 옵션을 지정해서 특정 함수 어셈블리 코드를 보는 방법을 소개하겠습니다.

먼저 커널 이미지를 빌드하면 함께 생성되는 System.map 파일을 열어 보겠습니다.

root@raspberrypi:/home/pi# mkdir kernel_obj
root@raspberrypi:/home/pi# cd kernel_obj/
root@raspberrypi:/home/pi/kernel_obj# cp ../rpi_kernel_src/out/System.map  .

System.map 파일을 열어보면 다음과 같이 심벌별 주소를 확인할 수 있습니다.

01 80004000 A swapper_pg_dir
02 80008000 T _text
03 80008000 T stext
04 8000808c t __create_page_tables
05 80008138 t __turn_mmu_on_loc
...
06 807a0208 t __schedule
07 807a0b6c T schedule
08 807a0c14 T yield_to

함수 목록 중에서 리눅스 커널에서 가장 유명한 schedule() 함수의 주소 범위가 0x807a0b6c ~ 0x807a0c14임을 유추할 수 있습니다. 참고로 주소 출력 결과는 16진수 형식입니다.

이번에는 다음과 같은 형식으로 시작 주소와 끝 주소를 지정하면 해당 주소에 대한 어셈블리 코드를 출력합니다.

objdump --start-address=[시작주소] --stop-address=[끝주소] -d vmlinux

라즈베리 파이에서 다음 명령어를 입력하면 schedule() 함수의 어셈블리 코드만 볼 수 있습니다. 

root@raspberrypi:/home/pi/kernel_obj# objdump --start-address=0x807a0b6c --stop-address=0x807a0c14 -d vmlinux 
vmlinux:     file format elf32-littlearm


Disassembly of section .text:

807a0b6c <schedule>:
807a0b6c: e1a0c00d  mov ip, sp
807a0b70: e92dd830  push {r4, r5, fp, ip, lr, pc}
807a0b74: e24cb004  sub fp, ip, #4
807a0b78: e52de004  push {lr} ; (str lr, [sp, #-4]!)
807a0b7c: ebe5b8e2  bl 8010ef0c <__gnu_mcount_nc>  
 
이번 절에서 소개한 방법을 활용하면 커널을 빌드한 후 생성되는 vmlinux 파일로 커널 어셈블리 코드를 분석할 수 있습니다. 참고로 이 책의 다음 장에서는 이번 장에서 소개한 방식으로 어셈블리 코드를 확인하고 다음과 같은 요소를 분석합니다. 

인터럽트
커널 스케줄링
시스템 콜


라즈베리 파이 설정 

라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)




[라즈베리파이] 라즈비안: 리눅스 커널 소스의 구조 2. 라즈베리 파이 설정

지금까지 라즈비안 리눅스의 커널 코드를 내려받고 빌드하는 방법을 알아봤습니다. 리눅스 커널 코드를 수정해서 실습 코드를 빌드할 수 있는 준비를 끝낸 것입니다. 이번에는 리눅스 커널 코드의 디렉터리 구조를 살펴보겠습니다. 

참고로 저도 처음으로 리눅스 커널 코드를 내려받고 디렉터리를 봤을 때 어떤 코드를 먼저 봐야 할지 감이 오지 않았습니다. 10여년 동안 커널을 빌드하면서 커널 소스는 다음과 같은 구조로 디렉터리가 구성돼 있다는 것을 알게 됐습니다.

arch

arch 하부 디렉터리에는 아키텍처별로 동작하는 커널 코드가 있습니다.
arm: 32비트 계열 ARM 아키텍처 코드가 있으며, 라즈비안도 이 하부 디렉터리 코드를 실행합니다.
arm64: 64비트 계열 ARM 아키텍처 코드가 있습니다.
x86: 폴더 이름과 같이 인텔 x86 아키텍처 코드가 있습니다.

include 

include에는 커널 코드 빌드에 필요한 헤더 파일이 있습니다.

Documentation

커널 기술 문서가 있는 폴더로, 커널 시스템에 대한 기본 동작을 설명하는 문서를 찾을 수 있습니다. 커널 개발자를 대상으로 작성된 문서이기에 커널에 대한 기본 지식이 없으면 이해하기가 조금 어렵습니다.

kernel

커널의 핵심 코드가 있는 디렉터리로, 다음과 같은 하위 디렉터리를 확인할 수 있습니다.
irq: 인터럽트 관련 코드
sched: 스케줄링 코드
power: 커널 파워 매니지먼트 코드 
locking: 커널 동기화 관련 코드 
printk: 커널 콘솔 관련 코드 
trace: frace 관련 코드 

위 디렉터리에는 아키텍처와 무관한 커널 공통 코드가 있으며, 아키텍처별로 동작하는 커널 코드는 arch/*/kernel/에 있습니다. 라즈비안의 경우 ARMv7 아키텍처 관련 코드를 arch/arm/kernel/에서 확인할 수 있습니다.

mm 

Memory Management의 약자로 가상 메모리 및 페이징 관련 코드가 들어 있습니다.
아키텍처별로 동작하는 메모리 관리 코드는 arch/*/mm/ 아래에 있습니다. 라즈비안의 경우 ARMv7 아키텍처 관련 코드를 arch/arm/mm/에서 확인할 수 있습니다.

drivers

모든 시스템의 디바이스 드라이버 코드가 있습니다. 하부 디렉터리에 드라이버 종류별 소스가 들어 있습니다.

fs 

모든 파일 시스템 코드가 담긴 폴더입니다. fs 폴더에 있는 파일에는 파일시스템 공통 함수가 들어 있고 파일 시스템별로 하나씩 세분화된 폴더를 볼 수 있습니다.

lib 
lib 디렉터리에는 커널에서 제공하는 라이브러리 코드가 있습니다. 아키텍처에 종속적인 라이브러리 코드는 arch/*/lib/에 있습니다.

라즈베리 파이 설정 

라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)



[라즈베리파이] 라즈비안 버전과 커널 소스 버전 2. 라즈베리 파이 설정

이번 절에서는 라즈베리 파이에서 라즈비안 리눅스 커널 소스코드를 내려받고 빌드하는 방법을 알아보겠습니다. 참고로 필자는 불필요한 권한 설정을 피하기 위해 터미널에서 다음 명령어를 입력해 root 권한을 획득했습니다.

$ sudo su

실제 라즈베리 파이의 터미널에서 sudo su 명령어를 입력할 때의 모습은 다음과 같습니다.

 
그림 2.37  터미널에서 sudo su 명령어를 입력한 모습

라즈비안 버전과 커널 소스 버전

라즈비안 커널 이미지를 내려받는 방법을 설명하기에 앞서 라즈비안과 커널 버전에 대해 알아둘 필요가 있습니다. 

이 책에서 다루는 커널 디버깅과 관련된 내용은 2019년 7월 10일에 라즈베리 파이 커뮤니티에서 배포한 다음 이미지를 기준으로 테스트했습니다.

라즈비안 이미지 파일명: 2019-07-10-raspbian-buster-full.zip 
라즈비안 커널 브랜치: rpi-4.19.y
리눅스 커널 버전: 4.19.60

그런데 라즈비안 이미지는 1년에 2회 이상 업그레이드되며, 라즈비안 커널 버전도 함께 올라갑니다. 따라서 가급적 아래 URL을 방문하셔서 2019-07-10-raspbian-buster-full.zip 이미지를 내려받아 후 라즈비안을 설치하시길 바랍니다. 

https://downloads.raspberrypi.org/raspbian/images/

 
그림 2.38 기존 라즈비안 이미지 파일 내려받기

그림 2.38의 왼쪽 아래 부분에 있는 raspbian-2019-07-12/를 선택한 후 오른쪽 그림에 있는 2019-07-10-raspbian-buster.zip을 선택하면 이 책에서 테스트한 환경에 맞출 수 있습니다.


라즈베리 파이 설정 

라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)



[TRACE32] gcore 크래시 유틸리티로 유저 프로세스의 콜스택 보기 [Debugging] Tips

이번 포스팅에서는 크래시 유틸리티의 extensions인 gcore를 빌드하는 방법과 이 기능을 활용해 유저 프로세스의 스택을 추출하는 방법을 소개한다.

* gcore 소스 코드를 내려받기

'http://people.redhat.com/~anderson/extensions.html' url에 액세스한 다음에, 
crash-gcore-command-1.5.1.tar.gz 파일을 내려받는다.

url: http://people.redhat.com/~anderson/extensions.html
crash-gcore-command-1.5.1.tar.gz

crash-gcore-command-1.5.1.tar.gz 파일을 받은 다음에 압축을 푼다.

* gcore 소스 코드를 빌드하기

먼저 크래시 유틸리티의 소스 코드를 빌드한다. (ARM64 아키텍처를 기준)
$ make target=ARM64

크래시 유틸리티의 소스에서 extensions 디렉토리로 이동한다.

austindh.kim:~/bin/crash_src/crash/extensions$ ls
COPYING  defs.h  dminfo.c  echo.c  eppic  eppic.c  eppic.mk  libgcore  Makefile  snap.c  snap.mk  trace.c

gcore 유틸리티의 소스 코드를 extensions 폴더로 복사한다.

austindh.kim:~/bin/crash_src/crash/extensions$ cp -r ~austindh.kim/bin/gcore-command-1.5.1/crash-gcore-command-1.5.1/* .
austindh.kim:~/bin/crash_src/crash/extensions$ ls
COPYING  defs.h  dminfo.c  echo.c  eppic  eppic.c  eppic.mk  gcore.c  gcore.mk  libgcore  Makefile  snap.c  snap.mk  trace.c

gcore의 소스 코드를 복사한 후 gcore.c와 gcore.mk 파일이 보일 것이다. 그럼 제대로 복사가 된 것이다.

크래시 유틸리티의 최상단 디렉토리로 이동한 후 'make target=ARM64 extensions' 명령어를 입력해 extensions 기능을 빌드한다. 

austindh.kim:~/bin/crash_src/crash$ make target=ARM64 extensions
gcc -Wall -g -shared -rdynamic -o echo.so echo.c -fPIC -DARM64  -DGDB_7_6
cd eppic/libeppic && make

extensions 디렉터리로 이동하면 gcore.so이란 라이브러리 파일이 제대로 생성됐음을 확인할 수 있다.

austindh.kim:~/bin/crash_src/crash/extensions$ ls
COPYING  dminfo.c   echo.c   eppic    eppic.mk  gcore.c   gcore.so  Makefile  snap.mk  trace.so
defs.h   dminfo.so  echo.so  eppic.c  eppic.so  gcore.mk  libgcore  snap.c    trace.c

크래시 유틸리티를 실행한 다음에 gcore.so 라이브러리 파일을 extension 명령어로 로딩한다.

crash64> extend /home007/austindh.kim/bin/Crash64Tool/extensions/gcore.so
/home007/austindh.kim/bin/Crash64Tool/extensions/gcore.so: shared object loaded

'shared object loaded' 메시지가 보이니 제대로 gcore.so 라이브러리 파일이 로딩됐음을 알 수 있다.

* 크래시 유틸리티에서 gcore.so 파일을 로딩하기

이제 PID가 1인 init 프로세스의 유저 공간을 덤프해보자.
이를 위해 'gcore [pid]' 명령어를 입력해야 한다.

crash64> gcore 1
gcore: WARNING: page fault at 64a000
gcore: WARNING: page fault at 64b000
gcore: WARNING: page fault at 64e000
gcore: WARNING: page fault at 651000
gcore: WARNING: page fault at 652000
gcore: WARNING: page fault at 659000
gcore: WARNING: page fault at 7f78601000
gcore: WARNING: page fault at 7f78602000
...
gcore: WARNING: page fault at 7fc2740000
gcore: WARNING: page fault at 7fc2741000
Saved core.1.init

'WARNING: page fault' 메시지는 False Positive 이니 너무 겁먹지 말자.

* TRACE32로 gcore 덤프 파일을 로딩하기

이제 gcore로 추출한 덤프 파일을 TRACE32 프로그램으로 로딩하자.

Data.LOAD.binary core.1.init 0x7FC20FD000
Data.LOAD.elf init 

참고로 0x7FC20FD000
 오프셋은 gcore 메타 해더 오프셋을 고려해 계산했다.
0x7FC20FD000 = 0x7FC2742000 - 0x645000

커널 스택 주소의 스택 최하단에 위치한 '유저 프로세스의 레지스터 세트'정보를 참고해 레지스터 세트를 설정한다.
TRACE32 명령어는 다음과 같다.
 
$ r.s X29   0x7FC2744110
$ r.s X30                  0
$ r.s PC            0x419344
$ r.s SP    0x7FC27440D0
$ v.f

이제 유저 공간에서 실행 중인 init 프로세스의 콜스택을 볼 수 있다.

-000|std::__1::__compressed_pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allo
-000|std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string(
-000|android::init::SocketConnection::source_context(?)
-001|std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string(
-001|android::init::handle_property_set_fd()
-002|android::init::main(?, ?)
-003|main(?, ?)
-004|__libc_init(?, ?, slingshot = 0x00403698, structors = 0x0000007FC2744600)
 ---|end of frame


[라즈베리파이] 라즈베리 파이에서 언어 설정 따라해보기 2. 라즈베리 파이 설정

대부분 리눅스 개발을 할 때는 터미널로 리눅스 명령어를 입력합니다. 이 정도로 라즈비안을 설정해도 개발하는 데 문제는 없지만 라즈비안에 설치된 크롬 브라우저를 쓰려면 언어를 설정해야 합니다.

라즈비안 메뉴를 실행해 언어(Locale) 설정을 하겠습니다. 그림 2.30과 같이 터미널에서 raspi-config 명령어를 입력합니다.

 
그림 2.30 터미널에서 raspi-config 명령어 입력  

그럼 다음과 같은 화면이 나타납니다.

 
그림 2.31 라즈베리 파이 소프트웨어 설정 도구

여기서 ‘4 Localisation Options’ 메뉴로 이동해 키보드로 엔터를 입력합니다.

 
그림 2.32 언어 설정 메뉴로 이동

화면이 바뀌면서 설정 가능한 언어 목록이 나타납니다. 

 
그림 2.33 언어 설정 화면

이 목록에서 스페이스바를 이용해 en_GB.UTF-8 UTF-8, en_US.UTF-8 UTF-8, ko_kr.UTF-8 UTF-8을 체크합니다.

 
그림 2.34 en_GB.UTF-8 UTF-8 체크(en_US.UTF-8 UTF-8은 생략)

 
그림 2.35 ko_kr.UTF-8 UTF-8 체크

다시 터미널을 열고 'cd /home/pi' 명령어를 입력해 다음 디렉터리로 이동합니다.

root@raspberrypi:/home/pi#

이후 다음 명령어를 입력하면 라즈비안 설치 프로그램이 업데이트됩니다.

# apt-get update
# apt-get upgrade

이어서 다음 명령어를 입력해 폰트 프로그램을 설치합시다.

# apt-get install ibus
# apt-get install ibus-hangul
# fonts-unfonts-core

설치가 끝나면 라즈베리 파이를 재부팅합니다.

다음은 한글 설정을 적용한 후 웹 브라우저로 필자의 블로그를 열어본 모습입니다.

 
그림 2.36 언어 설정을 적용한 후 한글 웹 페이지를 확인한 모습

보다시피 한글이 제대로 출력되는 것을 확인할 수 있습니다.

지금까지 라즈비안을 실행하기 위한 기본 설정을 마쳤습니다. 이어서 리즈비안에서 리눅스 커널 소스코드를 빌드해 보겠습니다. 


라즈베리 파이 설정 

라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)


[라즈베리파이] 라즈베리 파이 초기 설정하기 2. 라즈베리 파이 설정

라즈베리 파이는 버전이 업그레이드될수록 초기 설정을 쉽게 할 수 있게 진화하고 있습니다. 마우스 클릭만 몇 번 하면 기본 설정을 마칠 수 있으니 긴장하지 말고 따라 해 봅시다.

다음 화면에서 [Next] 버튼을 클릭합니다.

 
그림 2.21 라즈베리 파이 설정 대화상자

[Set Country] 대화상자가 나타나면 [Use US Keyboard]를 체크박스를 체크한 다음 [Next] 버튼을 클릭합니다.



그림 2.22 국가 및 언어 설정

다음 화면은 패스워드를 설정하는 대화상자인데, 여기서는 일단 [Next] 버튼을 클릭해 넘어갑니다.

  
그림 2.23 패스워드 설정

이어서 나오는 [Set Up Screen]에서는 별도로 중요한 설정이 없으므로 [Next] 버튼을 클릭해 넘어갑니다.

 
그림 2.24 화면 설정

이어지는 [Select WiFi Network] 화면에서는 접속할 와이파이 네트워크를 선택한 다음 [Next] 버튼을 클릭합니다. 이 책에서 접속하는 와이파이 네트워크의 이름은 ’iptime’이고, 이 이름은 접속 환경마다 다릅니다.

 
그림 2.25 와이파이 네트워크 연결

와이파이 비밀번호를 요구하는 경우 비밀번호를 입력하고 [Next] 버튼을 클릭합니다.

 
그림 2.26 와이파이 비밀번호 입력

다음 [Update Software] 화면에서 소프트웨어 업그레이드가 끝나면 [Next] 버튼을 클릭합니다.


 
그림 2.27 소프트웨어 업데이트

마지막으로 설정이 완료됐다는 대화상자가 나타나고, [Restart] 버튼을 클릭해 라즈베리 파이를 재부팅합니다.
 
 
그림 2.28 라즈베리 파이 설정 완료

터미널 실행

라즈베리 파이가 재부팅되고 나면 리즈비안에서 제공하는 프로그램을 사용할 수 있습니다. 이번에는 라즈베리 파이에서 터미널을 여는 방법을 소개합니다.

그림 2.29에서 화면 상단에 표시된 부분을 마우스로 클릭하면 터미널을 실행할 수 있습니다. 여기서는 터미널을 연 후 'sudo su' 명령어를 입력해 루트로 권한을 바꿨습니다.

 
그림 2.29 라즈베리 파이에서 터미널 실행


라즈베리 파이 설정 

라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)





[라즈베리 파이] 라즈베리 파이를 부팅시키기 2. 라즈베리 파이 설정

다음으로 라즈베리 파이를 설치하는 과정에서 가장 중요한 단계를 설명하겠습니다. 마이크로 SD 카드를 라즈베리 파이 SD 카드 슬롯에 삽입합니다.
 
            [라즈베리 파이 앞면] [라즈베리 파이 뒷면]
그림 2.19 마이크로 SD 카드를 라즈베리 파이에 삽입한 모습

각각 왼쪽에 표시한 부분을 보면 마이크로 SD 카드 슬롯을 삽입한 모습을 볼 수 있습니다.

이제 라즈베리 파이를 실행할 수 있는 준비가 끝났습니다. 이제 그림 2.20에서 볼 수 있듯이 주변 장치를 라즈베리 파이에 연결합니다.

① 전원 케이블
② HDMI 케이블
③ 키보드 연결 단자
④ UBS 연결 단자


 
그림 2.20 라즈베리 파이에 주변 기기를 연결한 모습

라즈베리 파이에 전원 케이블을 연결하면 전원이 들어와 켜집니다. 참고로 라즈베리 파이에는 별도의 전원 버튼은 없습니다.   

다음은 라즈베리 파이를 실행하고 난 후에 나오는 첫 화면입니다.
 
그림 2.21 라즈베리 파이(라즈비안)를 부팅한 후 나오는 첫 화면 

드디어 라즈비안 이미지를 마이크로 SD 카드에 구워서 라즈베리 파이를 부팅시켰습니다. 이어서 라즈비안을 설정하는 단계를 시작하겠습니다. 

라즈베리 파이 설정 

라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)




[임베디드 에세이] '문c 블로그'의 운영자를 만나다!!! 임베디드 에세이

12/27/2019, 리얼 리눅스가 주최하는 '리눅스 모임'에서 탑 리눅스 커널 블로그인 '문c 블로그'의 문영일 선배님을 만났다.

문c 블로그란

'문c 블로그'의 링크

'문c 블로그'란 단지 블로그는 아니다. 많은 리눅스 개발자들에게 어둠 속의 불빛이었다고 할까?
나에게 있어 '문c 블로그'는 구도자와 같은 존재였다.  내가 모르는 대부분의 커널의 함수들에 대한 설명은 이 블로그에서 찾을 수 있었다. 이 분을 드디어 만나게 된 것이었다.

문c 블로그 운영자님과의 만남


'리눅스 모임'에서 내가 간단한 발표(아래 링크)를 마친 후에 자리에 왔을 때 문영일 선배님은 나에게 먼저 다가와 인사를 하셨다. 

'제가 문c 블로그를 운영하는 문영일입니다.' 

   * 아우, 난 정말 놀랬다. 마치 리누스 토발즈를 만났다면 이런 느낌일까? 

그 동안 '문c 블로그'를 운영하시는 분이 누군지 정말 궁금했는데. 이 모임에서 이 분을 만나게 된 것이다. 문영일 선배님은 놀랍게도 나에게 '내가 운영하는 블로그'에 자주 오신다고 하셨다. 그래서 내 블로그에서 가장 기억이 남는 글이 무엇인지 여쭈어 봤는데, '[임베디드] 꼰대 개발자가 되는 방법(1) '이라고 말씀하셨다.
http://rousalome.egloos.com/10007727

(이 꼰대 개발자는 그 모임에서 정말 유명했다.)

문영일 선배님은 나보다 10+ 정도 연차가 많으신 분이었다. 30분 동안 대화를 나웠는데 참 배울 점이 많았다.

   * 끊임없이 공부면서 자기 개발을 하겠다는 의지
   * 리눅스 커널의 소스 코드를 분석하는 방법과 노하우
   * IAMROOT란 리눅스 스터디 모임에 대한 정보
   * 굉장히 openmind하심 

무엇보다, 리눅스 개발자로써 대단한 열정을 느낄 수 있었다. 8~10개월 동안 매주 토요일에 7시간을 반드시 참석해야 하는 IAMROOT란 스터디 모임을 2~3번씩 완수하셨다니? 개발자로써 정말 단단히 결심하지 않으면 절대로 다다를 수 없는 길이었다.

문영일 선배님과 같은 개발자를 본 받아 나도 끊임없이 공부해서 개발자로써의 역량을 키워야 겠다는 결심을 했다.

 
"이 포스팅이 유익하다고 생각되시면 공감 혹은 댓글로 응원해주시면 감사하겠습니다.
"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!"

Thanks,
Austin Kim(austindh.kim@gmail.com)

.

[라즈베리 파이] 설치 - 라즈비안 이미지를 SD카드에 굽기 2. 라즈베리 파이 설정

2. 마이크로 SD 카드에 라즈비안 이미지 쓰기

다음으로 마이크로 SD 카드에 라즈비안 이미지를 굽는 프로그램인 Win32 Disk Imager를 내려받기 위해 다음 URL로 접속합시다.

https://sourceforge.net/projects/win32diskimager/

다음과 같은 화면이 나타나면 [Download] 버튼을 클릭합니다.

 

그림 2.11 Win32 Disk Imager 다운로드 페이지

다음과 같이 페이지가 바뀌고 프로그램 다운로드가 시작됩니다.

 
그림 2.12 Win32 Disk Imager 다운로드

Win32 Disk Imager의 다운로드가 끝나면 프로그램을 설치한 후 실행합니다.

 
그림 2.13 Win32 Disk Imager를 실행한 모습

[Image File] 섹션 우측의 파일 불러오기 아이콘( )을 클릭해 다음 그림과 같이 앞에서 다운로드한 라즈비안 이미지 파일을 선택합니다.

 
그림 2.14 라즈비안 이미지 선택

라즈비안 이미지를 선택하고 나면 하단의 [Write] 버튼을 클릭합니다.

 
그림 2.15 [Write] 버튼을 클릭해 이미지 굽기를 시작

그럼 계속 진행할지 여부를 묻는 대화상자가 나타나는데, [Yes]를 선택합니다.

 
그림 2.16 경고 대화상자

그러면 SD 카드에 앞서 선택한 라즈비안 이미지를 쓰기 시작합니다.

 
그림 2.17 SD 카드에 이미지 쓰기 작업을 진행

이미지 쓰기가 끝나면 다음 그림과 같이 쓰기 작업을 완료했다는 메시지가 나타나고 [OK]를 클릭해 대화상자를 닫습니다. 

 
그림 2.18 라즈비안 이미지 굽기 완료


라즈베리 파이 설정 

라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)


[임베디드] 소프트웨어의 지배자 - 오픈 소스 프로젝트 임베디드 에세이

제가 개발자로 첫 걸음을 내딛으면서 플래시 엔진을 포팅하는 일을 맡았어요. 조금 전문적으로 말하자면 미들 웨어 계층의 코드를 짰다고 해야 할까요? 플레시 엔진을 포팅하는 소스 코드는 물론 회사의 소유였어요. 물론 제가 작성한 소스 코드는 외부에 공개할 생각도 없었고, 만약 공개한다면 처벌을 받을 것 같았어요. 왜냐면 소스 코드는 회사의 소유물이기 때문이죠. 요즘도 회사의 기밀을 누출했다는 개발자의 뉴스를 보면 '소스 코드를 몰래 들고 튀었다.'란 이야기를 듣곤 하죠.

한 4~5년 개발을 하면서 소프트웨어 개발의 세계에서는 독특한 문화가 있다는 사실을 알게 됐어요.

   * 가장 먼저 희한하다고 느꼈던 것은 커뮤니티였어요.

웹에는 정말 수많은 개발 관련 커뮤니티가 있어요. 누군가 프로그래밍에 대해 질문을 올리면 이를 대답해주는 사이트죠. 한 가지 예를 들어 볼까요? 스택 오버플로우(Stack overflow)란 커뮤니티에 가본 적이 있나요?

개발 초보자들에겐 마치 구원자와 같은 사이트에요. 학생이나 개발자가 질문을 올리면 다른 개발자는 자신의 시간을 투자해서 수준 높은 대답을 해주거든요.  프로그래밍을 하다 걸림돌을 만나면 대부분의 해결책은 스택 오버플로우에 있다고 해도 과언은 아닌 꺼에요. 이렇게 축적된 질문과 대답들은 소스 코드와 함께 누구나 접근할 수 있게 오픈돼 있어요. 그래서 새로 시작하는 프로그래머들도 이 내용을 참고해 시행착오를 크게 줄일 수 있어요. 이를 소프트웨어의 공유 문화라고 부를 수 있을까요?

이런 공유 문화의 끝판왕은 바로 '오픈 소스 프로젝트'이에요. 자, 그럼 오픈 소스란 무엇일까요?
오픈 소스란 오픈과 소스의 합성어로 소스 코드(Source)가 공개(Open)된 프로젝트나 소프트웨어를 뜻해요. 대부분의 오픈 소스 프로젝트는 무료로 쓸 수 있어요. 무료료 사용할 수 있다는 점에서 오픈 소스는 프리웨어(freeware)와 약간 헷갈릴 수 있는데요. 프리웨어는 소프트웨어를 무료로 배포해서 누구나 사용할 수 있다는 의미이고 오픈 소스 프로젝트는 소스가 공개돼 있다는 뜻이므로 완전히 다른 개념이에요. 따라서 프리웨어는 보통 상업적으로 사용이 불가능하지만 오픈 소스는 상용 프로젝트에서도 많이 활용돼요.

한 가지 예를 들어 볼까요? 오픈 소스 프로젝트 중 하나인 안드로이드는 삼성, 샤오미 같은 개발 업체들이 소스 코드를 내려받아 자신의 입맛이나 시나리오에 맞게 수정해서 상용 제품에 탑재할 수 있어요. 

[라즈베리 파이] 설치 - 라즈비안 이미지를 다운로드하고 SD카드를 포멧하기 2. 라즈베리 파이 설정

라즈베리 파이를 설치하려면 먼저 이미지 파일을 내려받아야 합니다. 이 책에서는 리눅스 커널을 라즈베리 파이로 공부해야 하므로 라즈비안 이미지를 설치하겠습니다. 여기서 라즈비안은 라즈베리 파이에서 실행할 수 있는 리눅스 배포판을 뜻합니다.

라즈비안 이미지 다운로드

브라우저에서 다음 URL로 접속해 라즈베리 파이 커뮤니티 내 다운로드 사이트로 이동합니다.

https://www.raspberrypi.org/downloads/

그러면 다음 화면이 보일 것입니다.

  
그림 라즈베리 파이 다운로드 페이지

여기서 우측의 'Raspbian’을 마우스로 클릭하면 다음 화면으로 바뀔 것입니다.

  
그림 라즈비안 이미지를 다운로드

여기서 "Raspbian Buster with desktop and recommended software"라고 적힌 이미지 하단의 [Download ZIP] 버튼을 클릭하면 이미지 다운로드를 시작합니다. 다운로드하는 이미지 파일의 이름은 다음과 같습니다. 
   
2019-07-10-raspbian-buster-full.zip

이 파일의 압축을 풀면 그림 2.4와 같이 2019-07-10-raspbian-buster-full.img라는 라즈비안 이미지 파일을 확인할 수 있습니다.

 
그림 압축을 해제한 라즈비안 이미지

라즈비안 이미지를 굽는 방법

라즈베리 파이에서는 부팅 디바이스로 마이크로 SD 카드를 사용합니다. 따라서 마이크로 SD 카드에 라즈비안 이미지를 설치해야 합니다. 이 과정을 가리켜 이미지를 마이크로 SD 카드에 굽는다고 표현합니다.

마이크로SD 카드에 라즈비안 이미지를 설치하는 과정은 다음 단계로 나눌 수 있습니다.

1. 마이크로 SD 카드 포맷(SDFormatter 사용)
2. 마이크로 SD 카드에 라즈비안 이미지 쓰기(Win32 Disk Imager 사용)

각 단계를 자세히 알아보겠습니다.

1. 마이크로 SD 카드 포맷

마이크로SD 카드 리더에 마이크로 SD 카드를 삽입한 후 컴퓨터의 USB 단자에 연결합니다. 그러면 다음 화면과 같이 하드디스크 드라이브에 SD 카드 드라이브가 표시됩니다.
 
그림 2.5 BOOT SD 카드 드라이브 확인

SD 카드를 포맷하는 데 사용할 SDFormatter를 내려받기 위해 다음 URL로 접속합니다.  
https://www.sdcard.org/downloads/ 

그럼 그림 2.6과 같은 화면이 나타나는데, 이 페이지의 왼쪽 메뉴에서 'SD Memory Formatter for Windows Download'로 표시된 부분을 클릭합니다.

  
그림 2.6 SD 카드 포매터 다운로드 페이지

그러면 그림 2.7과 같이 약관 페이지로 이동합니다.

  
그림 2.7 SD 카드 Formatter 프로그램 다운로드

이 페이지 하단으로 이동한 맨 오른쪽의 [Accept]를 선택하면 SD Formatter 4 프로그램(SDCardFormatterv5_WinEN.zip)을 내려받을 수 있습니다.


파일 다운로드가 끝나면 내려받은 SD Formatter 4 프로그램을 설치합니다. SD Formatter 4 프로그램을 설치한 후 실행하면 그림 2.9와 같은 화면을 볼 수 있습니다. 


그림 2.9 SD 카드 Formatter를 실행한 모습

이 화면에서 SD 카드를 포맷하기 위해 하단의 [Format] 버튼을 클릭합니다. 그럼 다음과 같은 경고 대화상자가 표시됩니다. [예]를 선택해 포맷을 진행합니다.

 
그림 2.10 포맷할 경우 모든 데이터가 지워진다는 내용의 경고 대화상자

포맷이 완료되면 다음과 같은 화면이 나타나고, [확인] 버튼을 클릭해 빠져나옵니다.

 
그림 2.11 SD 카드 포맷 완료

라즈베리 파이 설정 

라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)


[ARM] ARM 프로세서 학습 방법의 문제점 [Linux] ARM 프로세서 이야기

이제부터 ARM 프로세서에 대한 이야기를 조금 더 해보려고 합니다.
제 개발 인생을 돌이켜 보니 대부분의 시간을 사실 ARM 프로세서와 함께 보냈더군요. 
다음과 같은 조합으로 말이죠.

   * 'ARM + RTOS, ARM + 리눅스'

ARM 프로세서를 처음 접할 때의 느낌

그런데 제가 ARM 프로세서를 처음 접할 때의 느낌을 잊을 수가 없습니다. 그 느낌이 어땠나면;

   * 목욕탕에서 가장 뜨거운 온탕에 점프해 들어갔을 때

음, 잘 공감이 가시나요? 대중 목욕탕에 안 가신다고요? 그럼 다음의 예는 어떤가요?

   * 그럼 충치가 있어서 치과를 가서 진료실에서 기다릴 때

아직도 공감이 안된다고요? 난 한번도 치과에 가본 적이 없다고요? 그렇다면 어쩔 수 없죠. 더 이상의 예를 들기는 어렵겠군요.

ARM 프로세서를 배우기 꺼려하는 이유

이처럼 ARM 프로세서를 처음 접하는 분들은 정말 ARM 프로세서를 배우기 싫거든요. 물론 저도 그랬죠.
여러분, 치아에 이상이 있으면 바로 치과에 가시나요? 아니죠. 조금 아파도 버티다가 나중에 너무 아파서 버틸 수 없을 때 치과에 가죠.

   * ARM 프로세서도 마찬가지에요.

안 배우려고 하다가 어쩔 수 없이 배울 수 밖에 없는 게 ARM 프로세서인 것 같아요. 어쩔 수 밖에 배워야 한다는 사실은 임베디드로 밥을 먹고 살려면 ARM 프로세서는 반드시 잘 알아야 한다고 이야기할 수도 있겠네요. 

그런데 치아가 안 좋은 데 버티다가 치과에 가면 어떤 결과를 맞이하나요? 대부분 치료비가 더 들거나 치아 상태가 더 안 좋아지는 경우가 많죠.
ARM 프로세서도 마찬가지에요. ARM 프로세서를 배우기 싫어서 버티다가 나중에 ARM 프로세서를 어쩔 수 없이 배우면 더 고생하는 경우가 많아요.
그래서 ARM 프로세서를 미리 배워 놓는게 중요하죠.

그렇다면 ARM 프로세서를 배우기 어려운 이유가 무엇일까요?  제 생각에는 ARM 프로세서의 내용이 어렵다기 보단 ARM 프로세서를 공부하는 방법에 문제가 있는 경우가 많아요. 그래서 전 언제나 ARM 프로세서의 공부 방법에 대해 고민을 한답니다. 유튜브에 가면 유명한 스타 입시 강사분들이 있죠. 많은 분들이 공부하는 방법이 중요하다고 강조합니다. ARM 프로세서도 마찬가지인 것 같아요.

ARM 프로세서의 이론과 내용을 단순히 이해하고 암기하는 방식으로 공부하면 그것은 '망하는 지름길'이랍니다. 그럼 ARM 프로세서를 처음 접하면 만나는 걸림돌인 어셈블리 명령어에 대해 조금 더 설명을 드려 볼게요.

ARM 어셈블리 명령어를 배우기 어려운 이유

ARM 프로세서를 공부하면 가장 먼저 만나는 주인공은 어셈블리 명령어입니다. 대부분 ARM 어셈블리 명령어를 배우기 싫어합니다. 그 이유는;

   * ARM 어셈블리 명령어는 배우기 어렵다.

라고 생각하기 때문인데요. 그런데 사실은 ARM 어셈블리 명령어는 C 언어 문법보다 쉬워요. 이건 제가 장담합니다. 
여기서 한 가지 의문이 생깁니다.

   * 그렇다면 ARM 어셈블리 명령어가 왜 어렵다고 느꼈을까?

음, 사실 ARM 어셈블리 명령어가 어려운 게 아니고요. ARM 어셈블리를 공부하는 방법이 잘못 됐기 때문이에요.
여러분, 수학 공부를 할 때 공식을 맨날 외우고 잊어 먹고 외우고 잊어 먹고 하면 수학 공부가 재미있나요? 물론 아니겠죠. 그 원리를 제대로 이해한 다음에 자연히 공식을 체득하면 더 재미있게 수학을 공부할 수 있어요.

그런데 ARM 어셈블리 명령어를 공부하는 패턴을 보면 ARM 어셈블리 명령어를 외우는 방식을 고집하는 것 같아요. 물론 저도 그랬죠.

   * 그런데 ARM 어셈블리 명령어를 외우는 방식으로 공부하는 것은 망하는 지름길이에요.

많은 분들은 ARM 어셈블리 명령어를 50개 정도를 무리해서 외웁니다. 영어를 공부할 때 단어를 외우 듯이 말이죠.
여러분, 이 방법으로 절대 공부하지 말기 바래요. 아무리 잘 외웠다고 느껴도 그것은 스스로 착각일 뿐이죠.
그 이유는;

   * ARM 어셈블리 명령어를 외우면 반드시 잊어 먹어요.

어짜피 잊어 먹고 개발에 써 먹지도 못할 껀데 ARM 어셈블리 명령어를 왜 외우나요? 그 시간에 게임을 하거나 유뷰트를 보시길 바래요.

수년동안 ARM 프로세서와 함께 보낸 제가 뼈져리게 느낀 점은 다음과 같아요. 

   * ARM 프로세서의 내용보다 ARM 프로세서를 공부하는 방법이 훨씬 더 중요하다.
  
이를 조금 다르게 말씀드려 볼까요?

   * ARM 어셈블리 명령어의 내용보다 ARM 어셈블리 명령어를 배우는 방법이 훨씬 더 중요하다.

이제 ARM 어셈블리 명령어를 잘 배우는 방법을 소개하려고 합니다.

ARM 어셈블리 명령어를 배울 때 다음과 같은 원칙을 세울 필요가 있어요.

   * ARM 프로세서의 작동 원리와 함께 어셈블리 명령어를 익힌다.
   * ARM 어셈블리 명령어는 반드시 디버깅을 하면서 몸소 체험한다.
   * ARM 어셈블리 명령어는 C 언어와 함께 배운다.

이 원칙과 함께 ARM 어셈블리 명령어를 배우면 더 빨리 배울 수 있고 훨씬 더 오랫동안 기억할 수 있어요.
이제부터 세부 원칙을 소개하려고 해요.

[리눅스] 라즈베리 파이 실습을 위한 준비물 2. 라즈베리 파이 설정

라즈베리 파이를 설치하기에 앞서 다음과 같은 준비물이 필요합니다.
라즈베리 파이
USB 연결형 키보드
HDMI 케이블
충전기
마이크로 SD 카드 

제가 썼던 라즈베리 파이와 주변 장치는 그림 2.1과 같습니다.
 
 그림 라즈베리 파이와 주변 장치들

각 목록은 다음과 같습니다.
① 라즈베리 파이(이 책에서는 Raspberry Pi 3 Model B를 사용)
② USB 연결형 키보드 
③ 마우스 
④ 마이크로 SD 카드 리더
⑤ 마이크로 SD 카드
⑥ 라즈베리 파이 전원 케이블

라즈베리 파이와 전원 케이블 외에는 대부분 이미 가지고 있던 기기를 활용했습니다. 


라즈베리 파이 설정 

라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)


[리눅스] 라즈베리 파이(라즈베리파이)란? 2. 라즈베리 파이 설정

라즈베리 파이(Raspberry Pi)는 전 세계적으로 널리 쓰이는 리눅스 개발용 보드이자 소형 컴퓨터입니다. 설치 과정이 간단하고, 가격 대비 성능도 좋아 다양한 디바이스 드라이버를 구현하는 데 사용할 수 있습니다. 또한 교육용으로도 많이 쓰이며 실전 개발에서도 다양한 데모용 디바이스로 자주 활용되고 있습니다. 라즈베리 파이의 장점을 몇 가지 정리하면 다음과 같습니다.

막강한 커뮤니티

라즈베리 파이의 커뮤니티와 리소스는 막강합니다. 전 세계적으로 널리 쓰이므로 인터넷이나 유튜브에서 관련 자료를 쉽게 찾을 수 있습니다. 라즈베리 파이를 활용한 소스코드나 설정 방법도 인터넷에서 쉽게 찾아볼 수 있습니다.
 
저는 라즈베리 파이를 2018년 2월에 학교에 교수로 계신 선배를 통해 알게 됐습니다. 그분은 저에게 다음과 같이 조언을 해주셨습니다. “리눅스 교육용 보드는 라즈베리 파이가 대세다.” 

그래서 저는 바로 라즈베리 파이를 구입해 설치해보기로 마음먹었습니다. 그런데 예전에 며칠 동안 고생해서 리눅스를 설치했던 기억이 떠올랐습니다. 사실 임베디드 리눅스의 가장 큰 걸림돌은 설치 과정이라서 라즈베리 파이를 잘 설치할 수 있을까 걱정했습니다. 하지만 다른 누구의 도움도 받지 않고 인터넷 자료를 참고해 라즈베리 파이를 혼자 설치하고 실행할 수 있었습니다. 이는 라즈베리 파이 커뮤니티의 도움 덕분입니다.  

저렴한 가격

라즈베리 파이를 교육용 보드로 보는 분도 있습니다. 그런데 사실 라즈베리 파이는 소형 컴퓨터로 봐야 합니다. 본체가 신용카드 크기인 소형 컴퓨터가 이 가격이면 매우 저렴하다 볼 수 있습니다.

또한 라즈베리 파이를 구입하면 다른 기기가 별도로 필요하지 않습니다. 마이크로 SD 카드와 휴대폰 충전기와 비슷한 파워 케이블만 있으면 라즈베리 파이를 구동할 수 있습니다. 물론 HDMI 케이블과 HDMI 케이블을 연결해 쓸 수 있는 모니터가 있어야 합니다만 이는 가정이나 학교에서 어렵지 않게 구할 수 있습니다.

간단한 설치

라즈베리 파이 커뮤니티에서는 라즈베리 파이를 구동할 수 있는 다양한 이미지를 배포합니다. 이 중에서 '라즈비안'이라는 리눅스 이미지를 설치하기만 하면 리눅스 프로그래밍에 필요한 유틸리티 프로그램을 바로 쓸 수 있습니다.  

리눅스 배포판마다 다르긴 하지만 리눅스는 처음 설치하고 시스템을 설정하는 데 시간이 오래 걸립니다. 하지만 라즈베리 파이 커뮤니티에서 배포하는 라즈비안 이미지는 설치만 하면 이 과정을 모두 건너뛸 수 있습니다. 마이크로 SD카드를 포맷하고 라즈비안 이미지를 마이크로 SD 카드에 굽기만 하면 라즈비안을 라즈베리 파이에서 바로 실행할 수 있습니다.

최신 리눅스 커널을 지원

라즈비안은 최신 버전의 리눅스 커널을 지원합니다. 2018년에는 라즈비안 리눅스의 커널 버전이 4.14였는데 2019년에는 4.19로 업그레이드됐습니다. 2019년 상반기에 출시한 안드로이드 기반 기기는 대부분 4.14 버전에 머무르고 있습니다. 이처럼 라즈비안은 상용 리눅스 제품보다 더 업그레이드된 리눅스 커널에서 작동합니다.
  

 라즈베리 파이에서 구동할 수 있게 리눅스 프로그램과 유틸리티를 패키징한 것을 라즈비안이라고 부릅니다. 리눅스 서버에서 많이 쓰는 우분투와 비슷한 배포판입니다.


라즈베리 파이 설정 

라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)





[리눅스커널] GCC 지시어

[부록-A] GCC 지시어

리눅스 커널 코드를 읽다 보면 낯선 구문을 만날 가능성이 높습니다. 이 중 하나가 GCC 컴파일러 지시어입니다. 이번 시간에는 리눅스 커널에서 자주 쓰는 GCC 지시어를 소개합니다.

1. __init과 __section()

__init 키워드가 함수 선언부에 있으면 해당 함수는 init.text 섹션에 위치합니다. 이해를 돕기 위해 __init 키워드로 선언된 함수를 봅시다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/watchdog.c
01 void __init lockup_detector_init(void)
02 {
03 set_sample_period();
01번째 줄과 같이 lockup_detector_init() 함수 옆에 보이는 __init 구문입니다. 함수 선언부에 __init 키워드가 보이면 부팅 과정에서 1번 호출되는 함수라고 해석하면 됩니다.

__init 키워드로 선언된 함수는 언제 호출될까요? 다음 코드와 같이 do_one_initcall() 함수에서 부팅 과정에서 1번 호출됩니다.

https://elixir.bootlin.com/linux/v4.19.30/source/init/main.c
static void __init do_initcall_level(int level)
{
initcall_entry_t *fn;
...
trace_initcall_level(initcall_level_names[level]);
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(initcall_from_entry(fn));
}

init과 __section 매크로 코드 분석

이번에는 __init 키워드의 정체를 확인해보겠습니다. __init 키워드는 매크로 타입으로 다음과 같이 정의돼 있습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/init.h
#define __init          __section(.init.text) __cold notrace __latent_entropy

이어서 include/linux/compiler.h 파일을 열어보면 '__section(S)'는 '__attribute__ ((__section__(#S)))' 구문으로 치환됩니다.

# define __section(S) __attribute__ ((__section__(#S)))

여기서 __section(S) 매크로의 'S'를 유심히 볼 필요가 있습니다. 좀 복잡해 보입니다만 조금 풀어서 설명해 보겠습니다.

__init 키워드는 매크로 타입으로 __section(.init.text) 코드로 치환됩니다.

__section(.init.text)와 같이 입력이 .init.text이므로 #S 대신 .init.text로 치환됩니다. 이는 다음과 같이 표현할 수 있습니다. 

   * __attribute__ ((__section__(.init.text)))

여기서 __attribute__ 지시자는 컴파일러에게 함수 컴파일 속성을 지정하는 기능입니다. 그러면 __attribute__ ((__section__(.init.text)))는 어떤 의미일까요? 
   
이는 ‘.init.text’라는 섹션에 해당 함수의 코드를 위치시키라는 의미입니다. 여기서 섹션이란 비슷한 역할을 수행하는 코드 묶음을 의미합니다. 이 같은 방식으로 비슷한 속성의 코드나 변수들을 특정 섹션에 위치시키는 경우가 많습니다. 

.init.text 섹션 정보 확인

__init 키워드를 분석하다 보니 자연히 다음과 같은 의문이 생깁니다.

.init.text 섹션의 정체는 무엇일까?

리눅스에서 기본으로 제공하는 objdump 바이너리 유틸리티 프로그램을 다음 명령어로 실행하면 섹션 정보를 확인할 수 있습니다.

objdump -x vmlinux  | more

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .head.text    0000026c  80008000  80008000  00008000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .text         00607798  80100000  80100000  00010000  2**6
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .fixup        0000001c  80707798  80707798  00617798  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  3 .rodata       001c2c84  80800000  80800000  00618000  2**12
                  CONTENTS, ALLOC, LOAD, DATA
...
 17 .stubs        000002c0  ffff1000  80b00020  008a1000  2**5
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 18 .init.text    0004275c  80b002e0  80b002e0  008a82e0  2**5
                  CONTENTS, ALLOC, LOAD, READONLY, CODE

18번째 섹션인 .init.text는  0x80b002e0부터 위치해 있고 그 크기는 0x4275c라는 것을 알 수 있습니다. 당연히 .init.text 섹션은 0x80b002e0 ~ 0x80b42a3c 메모리 공간에 위치했다고 볼 수 있습니다.

2. inline

함수 선언부에 키워드로 inline을 지정하면 GCC 컴파일러는 함수 심벌을 만들지 않습니다. 이 같은 유형의 함수를 인라인 함수라고 부릅니다.

인라인 함수에 대해 알아보기에 앞서 인라인 함수를 지정하는 이유는 무엇인지 먼저 살펴봅시다. 커널 함수에서 어떤 함수를 호출하면 다음과 같은 동작을 수행합니다.

1. 스택 프레임의 매개변수를 메모리에 저장 
2. 함수 인자를 레지스터에 복사 
3. 실행 흐름 변경 

물론 이 동작은 어셈블리 코드로 확인할 수 있습니다. 그런데 만약 1초에 수십 번 이상 자주 호출되는 함수가 있다고 가정해보겠습니다. 함수에서 수행할 코드가 얼마 되지 않는데 위 동작을 반복하면 오버헤드라 볼 수 있습니다. 즉, 배보다 배꼽이 더 큰 상황입니다.

이해를 돕기 위해 예제 코드를 봅시다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/time/timer.c
static inline unsigned calc_index(unsigned expires, unsigned lvl)
{
expires = (expires + LVL_GRAN(lvl)) >> LVL_SHIFT(lvl);
return LVL_OFFS(lvl) + (expires & LVL_MASK);
}

calc_index() 함수의 구현부를 보니 '논리 연산을 수행'하는 2줄밖에 되지 않습니다. 그런데 calc_index() 함수는 커널 타이머를 처리할 때 매우 자주 호출됩니다. 인라인으로 선언하기에 좋은 함수입니다.
리눅스 커널 코드에서 inline 키워드로 선언된 함수를 보면 다음과 같이 해석하면 됩니다.

   * GCC는 이 함수에 대한 심벌을 생성하지 않는다.
   * 자주 호출될 가능성이 높다.

3. noinline

noinline 키워드로 함수를 선언하면 GCC 컴파일러는 이 함수를 인라인으로 처리하지 않습니다. 이해를 돕기 위해 예제 코드를 소개합니다.

https://elixir.bootlin.com/linux/v4.19.30/source/mm/slub.c
static noinline int alloc_debug_processing(struct kmem_cache *s,
struct page *page,
void *object, unsigned long addr)
{
if (s->flags & SLAB_CONSISTENCY_CHECKS) {
if (!alloc_consistency_checks(s, page, object, addr))
goto bad;
}

alloc_debug_processing() 함수에 noinline 키워드를 지정했습니다. 참고로 alloc_debug_processing() 함수는 슬럽 오브젝트를 할당할 때 오브젝트 오염을 점검하는 역할을 수행합니다.

그런데 GCC는 컴파일 과정에서 inline 키워드로 함수를 선언하지 않아도 '인라인으로 처리해도 적합한 함수'라고 판단하면 함수를 인라인 타입으로 컴파일합니다. 물론 GCC 컴파일러가 알아서 인라인으로 함수를 처리해주니 고맙다는 생각이 들 수 있습니다. 하지만 문제는 코드를 작성한 의도와 다르게 함수가 오동작할 수 있다는 점입니다.

한 가지 예를 들어 봅시다. 어떤 개발자가 __builtin_return_address() 매크로 함수를 써서 자신을 호출한 함수의 주소에 따라 다르게 처리하려고 합니다. 그런데 해당 함수가 인라인으로 처리되면 어떻게 될까요? 인라인으로 처리되니 함수의 주소나 심벌이 사라지게 됩니다. 따라서 __builtin_return_address() 매크로는 다른 결과를 반환해서 예상치 못한 동작을 하게 됩니다.


예전에 필자가 개발 도중 도저히 설명이 불가능한 문제를 만난 적이 있습니다. 수많은 시행착오 끝에 알아낸 근본 원인은 GCC가 자동으로 함수를 인라인으로 처리하는 것이었습니다.


 4. __noreturn

리눅스 커널에서는 자신을 호출한 함수로 되돌아가지 않는 함수가 있습니다. 이런 종류의 함수에 __noreturn 키워드를 붙이면 컴파일러가 최적화 작업을 추가로 수행합니다.

__noreturn 키워드로 선언한 예는 다음과 같습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/exit.c
void __noreturn do_exit(long code)
{
struct task_struct *tsk = current;
int group_dead;
...

do_exit() 함수는 프로세스를 종료하는 동작입니다. 당연한 이야기지만 함수를 실행하는 주인공인 프로세스가 소멸하니 이전 함수로 되돌아 갈 수 없습니다.
 
5. unused

GCC 컴파일러는 특정 함수를 호출하는 코드가 없을 때 함수를 호출한 적이 없다는 경고 에러 메시지를 출력합니다. 그래서 함수 선언부에 unused 키워드를 붙이면 GCC 컴파일러에게 함수가 호출되지 않는 듯해도 커널이 해당 함수를 사용한다고 알려줍니다. 

이해를 돕기 위해 관련 코드를 소개합니다.

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/mach-omap2/pm.c
int __maybe_unused omap_pm_nop_init(void)
{
return 0;
}

omap_pm_nop_init() 함수는 어느 코드에서도 호출하지 않지만 omap_pm_nop_init() 함수에 __maybe_unused 키워드를 붙이면 컴파일러는 경고 메시지를 출력하지 않습니다.

이 밖에도 함수 선언부에 unused 키워드를 지정하는 이유는 다음과 같습니다.

   * 어셈블리 코드에서 C 코드로 구현된 함수를 호출할 때
   * 함수에 전달된 인자를 받아서 해당 인자를 쓰지 않을 때

6. __builtin_return_address() 함수

__builtin_return_address 매크로를 사용하면 자신을 호출한 함수의 주소를 알 수 있습니다. 커널에서는 __builtin_return_address 매크로를 활용해 다양한 방식으로 디버깅 메시지를 출력합니다.

다음은 __builtin_return_address 매크로를 써서 디버깅 메시지를 출력하는 패치 코드입니다.

diff --git a/kernel/workqueue.c b/kernel/workqueue.c
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -2904,6 +2904,10 @@ bool __flush_work(struct work_struct *work)
01 {
02        struct wq_barrier barr;
03
04 +       long unsigned int caller_func_address = 0;
05 +       caller_func_address = (long unsigned int)__builtin_return_address(0);
06 +
07 +       trace_printk("caller: %pS [0x%08lx] \n", (void *)caller_func_address, (long unsigned int)caller_func_address);
08    if (WARN_ON(!wq_online))
09     return false;

이 패치 코드를 입력 방법을 소개하겠습니다. 다음 코드에서 “/* 패치 코드를 입력하세요 */” 부분에 04~07번째 줄의 패치 코드를 입력하면 됩니다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c
static bool __flush_work(struct work_struct *work, bool from_cancel)
{
struct wq_barrier barr;
/* 패치 코드를 입력하세요 */
if (WARN_ON(!wq_online))
return false;

위와 같은 패치 코드를 적용하면 어느 함수에서 __flush_work() 함수를 호출하는지 확인할 수 있습니다.

7. container_of

커널 코드에서 container_of는 많이 활용하는 매크로입니다. container_of() 매크로 함수는 구조체 필드의 주소로 구조체 시작 주소를 계산하는 기능을 제공합니다.

container_of란 매크로를 쉽게 표현하면 다음과 같습니다.

  * 구조체 시작 주소 = container_of(입력주소, 구조체, 해당 구조체 필드)

다음 예제 코드를 함께 보면서 container_of 매크로를 어떻게 활용하는지 살펴보겠습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c
01 static struct workqueue_struct *dev_to_wq(struct device *dev)
02 {
03 struct wq_device *wq_dev = container_of(dev, struct wq_device, dev);
04
05 return wq_dev->wq;
06 }

03번째 줄을 보면 container_of를 써서 struct wq_device *wq_dev 지역변수에 어떤 값을 대입합니다. 여기서 container_of(dev, struct wq_device, dev)는 코드는 다음과 같이 해석할 수 있습니다.

   * dev: 입력 주소
   * struct wq_device: 구조체
   * dev: wq_device 구조체에 위치한 필드

결과적으로 container_of 매크로를 쓰면 wq_device 구조체의 주소를 반환합니다.


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