Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

792223
1016
122608


[라즈베리파이] 라즈베리 파이 사용 시 주의사항 2. Raspberry pi configuration

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

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

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

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

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


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

다음과 같이 __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. Signal

-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. System Call

_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. Raspberry pi configuration

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

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 파일로 커널 어셈블리 코드를 분석할 수 있습니다. 참고로 이 책의 다음 장에서는 이번 장에서 소개한 방식으로 어셈블리 코드를 확인하고 다음과 같은 요소를 분석합니다. 

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

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

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

참고로 저도 처음으로 리눅스 커널 코드를 내려받고 디렉터리를 봤을 때 어떤 코드를 먼저 봐야 할지 감이 오지 않았습니다. 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. Raspberry pi configuration

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

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

라즈비안 메뉴를 실행해 언어(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 언어 설정을 적용한 후 한글 웹 페이지를 확인한 모습

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

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

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

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

다음 화면에서 [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 라즈베리 파이에서 터미널 실행

[라즈베리 파이] 라즈베리 파이를 부팅시키기 2. Raspberry pi configuration

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

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

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

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


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

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

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

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

[임베디드 에세이] '문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. Raspberry pi configuration

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 라즈비안 이미지 굽기 완료


.

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

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

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

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

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

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

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

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

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

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

라즈비안 이미지 다운로드

브라우저에서 다음 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 카드 포맷 완료

.


[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. Raspberry pi configuration

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

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

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

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

[리눅스] 라즈베리 파이(라즈베리파이)란? 2. Raspberry pi configuration

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

막강한 커뮤니티

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

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

저렴한 가격

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

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

간단한 설치

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

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

최신 리눅스 커널을 지원

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

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



[리눅스커널] 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 구조체의 주소를 반환합니다.


[임베디드] 뛰어난 임베디드 리눅스 프로그래머가 되기 위한 조건 임베디드 에세이

뛰어난 프로그래머가 되기 위한 가장 중요한 덕목은 뭘까? 개인적으로;

   * 바로 부지런히 새로운 것을 배우려는 자세라고 생각한다.

일단 난 게으른 편이고 새로운 지식이나 스킬을 배우는 속도도 정말 느리다. 그래서 이런 주제에 대해 이야기하는게 정말 부끄럽다. 그런데 한 가지 내가 잘한다고 느끼는 것을 말해보까? '바로 쪼잔하다는 거다.'

   * 쪼잔하다? 그게 뭔 소리니?

쪼잔하다는 게 뭔 소리인데 풀어서 설명을 해볼까? 코드를 한 줄 추가할 때나 분석할 때 스스로 나에게 다음과 같은 질문을 던진다.

   * 배열의 인덱스가 제대로 처리되고 있나?
   * 배열을 초기화하지 않고 사용하면 문제가 되지 않을까?
   * 이 코드 구간에서 레이스 컨디션이 발생하면 어떻게 될까?
   * 지금 보는 코드가 실행 중에 Preemption이 되거나 인터럽트가 발생하면 어떻게 될까?
   * 이 함수가 실행할 때 스택은 얼마나 쓸까?
   * 지역 변수를 추가하면 스택을 얼마나 증가할까?
   * 이 코드를 추가하면 GCC는 어떤 어셈블리 코드로 변환할까?
   * 함수로 전달되는 포인터가 NULL이면 어떻게 동작할까?
   * 전달되는 메모리 포인터는 슬럽일까? 슬럽이면 어떤 슬럽 오브젝트일까?
   * likely/unlikely 와 같은 GCC 빌트인 함수를 쓰면 GCC는 어떻게 코드를 재배치할까?
   * 이 함수가 혹시 인터럽트 컨텍스트에서 실행하면 어떻게 될까? 
   * 시그널을 보냈는데 시그널을 받을 프로세스가 Preemption이 되면 어쩌지?
   * 이 코드를 실행할 때 동적 메모리의 사용량이 부족하면 어떻게 될까?

정말 코드를 피곤하게 본다고 생각할 수 있다. 그런데 여기서 그치지 않는다. 내게 던진 질문에서 한 걸음만 더 나가볼까?  

   * 배열의 인덱스가 제대로 처리되고 있는지 어떻게 알 수 있을까?
   * 배열을 초기화하지 않고 사용하면 문제가 되지는 어떻게 검출할 수 있을까?
   * 이 코드 구간에서 레이스 컨디션이 발생해 문제가 발생할 것 같으면 어떻게 방지할까?
   * 지금 보는 코드가 실행 중에 Preemption이 되거나 인터럽트가 발생해 문제가 된다면 이를 어떻게 방지할까?
   * 이 함수가 실행할 때 스택을 많이 써서 스택 오버플로가 된다는 이를 어떻게 방지하고 검출할까?  
   * 지역 변수를 추가해 스택이 많이 증가한다면 이를 어떻게 검출할까?
   * 이 코드를 추가하면 GCC가 변환된 어셈블리 코드는 어떤 모습일까?
   * 함수로 전달되는 포인터가 NULL라 문제가 발생한다면 이를 어떻게 방지할까?
   * 전달되는 메모리 포인터는 슬럽이라면 남은 슬럽 오브젝트의 갯수는 몇 개일까?
   * likely/unlikely 와 같은 GCC 빌트인 함수를 써서 GCC가 재배치하는 코드는 어떤 모습일까?
   * 이 함수가 혹시 인터럽트 컨텍스트에서 실행해 문제가 생기면 이를 어떻게 방지해야 할까? 
   * 시그널을 보냈는데 시그널을 받을 프로세스가 Preemption이 된다면 이를 어떻게 방지해야 하나?
   * 이 코드를 실행할 때 동적 메모리의 사용량이 부족해 어떤 문제가 발생할까? 

여기서 두 걸음만 나가볼까? 이제는 적기도 귀찮다.

[임베디드] 6~12 개월만 학원에 다니면 리눅스 프로그래머가 될 수 있을까? 임베디드 에세이

예전에 다른 회사에서 일하는 친구가 이런 말을 했어요. 

   * 리눅스 프로그래밍(시스템 프로그래밍 + 디바이스 드라이버)은 뭐~ 6개월만 학원에서 공부하면 할 수 있어!

물론 전 그 자리에서 반박은 하지 않았어요. 그런데 이런 생각을 프로그래머 스스로 자신에 대해 이렇게 생각하는 것은 슬픈 것 같군요. 

  * 자신이 하고 있는 일이 6개월 동안 학원 강의만 들으면 되나?

예전부터 지금까지 청년 실업을 해소하려고 정부가 종종 내놓는 대책이 있는데요. 그게 뭘까요?

   * 바로 IT 인력 몇 만 명 양성 프로젝트였어요.

출처가 어디냐고요? 다음 신문 기사를 볼까요?

출처: https://www.hankyung.com/opinion/article/2019092796651
---
국내에서도 다양한 4차 산업혁명 교육 지원 프로그램이 쏟아져 나온다. 그러나 단순히 교육하는 데만 그쳐서는 의미를 갖기 어렵다. 실무 중심으로 교육을 강화하고, 취업 연계를 통해 교육받은 청년 인재들이 곧바로 기업 현장에 투입돼 4차 산업혁명 시대를 앞장서 이끌어 갈 수 있는 교육으로 확대돼야 한다.
---

이런 글도 있군요.

출처: https://www.ifs.or.kr/bbs/board.php?bo_table=News&wr_id=963
---
 우리도 좀 늦었지만 지금이라도 민·관이 긴밀히 협력하여 부지런히 IT서비스의 외연을 확장하는 등 4차 산업 혁명 분야에서 새로운 일자리를 만들어 내야한다. 
---

근데요, 이런 정책들이 뭐냐면요? 조금 더 구체적으로, 정규 학부 과정 대신 속성 학원이나 아카데미를 통해 6~12 개월 정도 기술 교육을 듣고 난 후 IT 회사에 취업을 시키겠다는 계획이에요.

음 그런데 이 제도가 성공적으로 정착될 수 있을까요? 만약 제대로 정착 시키지 못하면 어떤 결과를 초래할까요?

상식적으로 생각해봅시다. 실업자 해소를 위해 대안으로 제시 되는 직업이 많은 돈을 벌 수 있는 직업일까요? 이러한 정부 정책과 그로 인해 개발자 자신 조차 "프로그래머는 그냥 대충 코딩만 할 줄 알면 돼"라는 인식으로 스스로의 가치를 전락시킬 가능성이 높아 졌어요. 

시선을 돌려 의사에 대해서 조금 이야해볼께요.
 의/치/한의사들 (통칭해서 의사로 표현)은 일반인데 비해 돈을 많이 벌어요. 그 이유는 무엇일까요?

   * 의사가 되려면 매우 힘들기 때문이에요.

그런데 불쌍한 공대생들은 4학년 때 프로젝트 하느라 밤을 맨날 새도 졸업 후 무엇이 기다리고 있을까요?

   * 끊임없이 이어지는 야근과 저임금이에요. 

하지만 의대생들은 그 힘든 과정을 충분히 참을 수 있어요. (공대가 공부를 좀 많이 시키다고 해도 의대 본과 학생들 정도는 아니잖아요.) 의대생들은 적어도 졸업을 한 후에 개원을 하면 일정한 시간에 진료를 마칠 수 있는 분홍빛 미래가 있기 때문이죠. 이 말에 반박하는 의사들이 있을 텐데요. 음, 의사들이 아무리 힘들어도 여전히 돈을 많이 번다는 것은 '의대 졸업생들이 의사 외에 다른 직업을 절대 찾지 않는다'란 사실로 확인할 수 있어요. 물론 전 물론 의사들이 다른 직업보다 더 많은 돈을 받아야 하는 데 동의해요.

결론은 엄청나게 높은 진입 장벽이 의사들에게는 있는 것이죠. 하지만, 엔지니어라는 직업에게 이러한 수준의 장벽을 요구하는 것은 아니에요. 하지만 CPU 설계 기술자나 리눅스 엔지니어가 되려면 적어도 대학에서 해당 전공은 해야 한다고 생각해요. 학원에서 몇 달 배운다고 회로를 다루는 엔지니어가 되지는 않아요. 하지만 엔지니어 중에서도 가장 진입 장벽이 낮은 직업이 이제는 컴퓨터 프로그래머가 됐어요.

... To be Continued...

[ARM] ARM 프로세서를 배워야 하는 이유 [Linux] ARM 프로세서 이야기

이번 시간에는 ARM 프로세스를 왜 배워야하는지 이야기를 해보겠습니다.

우리가 어떤 것을 처음 배울 때는 그것을 왜 배워야 하는지 알면 좋습니다. 왜냐면, 무엇을 공부하기 전에 왜 배워야 하는지 알면 더 동기 부여가 되기 때문이죠. 다른 동료들이나 친구들이 ARM 프로세서는 잘 배워야 한다고 해서 등떠밀려 공부하면 잘 집중이 안되는 거 같습니다. 그렇지 않나요? 무엇보다 배운 내용이 어떻게 활용되는지 알면 공부할 때 더 힘이 날 겁니다.

소형 기기에서 ARM 프로세서를 많이 탑재

ARM 프로세스를 배우는 가장 큰 이유는 ARM 프로세서를 많이 쓰기 때문이에요. 대부분 휴대 기기에는 ARM 프로세서가 많이 탑재돼 있어요. 여러분들, 모두 휴대폰 1개는 다 들고 다니죠? 안드로이드 스마트폰에 탑재된 CPU가 무엇일까요? 그것은 ARM 프로세서에요.

휴대 전화 뿐만 아니라 사물 인터넷에도 ARM 프로세서를 많이 써요. 이렇게 소형 기기에서는 ARM 프로세서를 많이 사용하고 있어요. 여러분들은 ARM 프로세서가 저전력에 소모 전류가 적다는 사실을 알고 있죠? 그래서 소행 기기에 ARM을 많이 쓰는 거에요.

근데 ARM 프로세서를 소형 기기에 많이 쓴다는 사실은 조금 달리 생각해볼 수 있어요. 많은 소형 기기에서 ARM 프로세서를 사용하고 있다는 이야기는 많은 개발자들이 ARM 프로세서와 관련된 부분에 일을 하고 있따고 할 수 있죠. 많은 ARM 프로세서 개발자 풀이 있다고 볼 수 있는 거에요.

프로그램의 세부 동작 원리를 파악

이번에는 ARM 프로세서를 배워야 하는 다른 이유에 대해서 알아볼까요? ARM 프로세서의 기본 원리를 알면 프로그램을 CPU가 어떻게 돌리는지 정확히 알 수 있어요. 우리가 작성하는 코드는 C나 C++ 혹은 자바, 파이썬 언어인데요. 사실 이 코드들은 컴파일이 돼서 모두 ARM 어셈블리 코드로 돌게 돼 있어요. ARM 프로세서의 동작 원리를 알면 자연히 프로그램이 정말 어떻게 돌아가는지 알 수 있어요. 그래서 ARM 프로세서의 원리를 제대로 파악하는 개발자들은 조금 더 안정적인 코드를 작성할 수 있어요.

물론 ARM 프로세서의 세부 원리를 잘 몰라도 개발하는데 큰 지장은 없어요. 그런데 고급 개발자로 인정 받으려면 반드시 넘어야 하는 산이 ARM 프로세서랍니다. 혹시 '스택 오염, 스택 Overflow, 스택 Underflow' 이런 용어를 들어본 적이 있나요? 이런 문제는 ARM 프로세서의 세부 동작 원리를 모르면 문제를 분석하거나 해결할 수 없어요. 또한 Secure Monitor Call이란 소리를 들어본 적이 있나요? 'Secure Monitor Call'을 실행하고 나니 시스템이 무감이 됐다고 합니다. 음, 듣기만 해도 깝깝하군요. 모두 ARM 프로세서와 관련된 용어랍니다.

RTOS나 운영체제 커널의 세부 동작 원리를 파악

마지막으로 ARM 프로세서를 알아야 하는 이유는 RTOS나 운영체제의 세부 동작 원리를 배우고 싶을 때에요. 사실 ARM 프로세서는 RTOS을 돌리거나 리눅스와 같은 범용 운영체제와 함께 돌아가거든요. ARM 프로세서 혼자만 돌아가는 경우는 사실 없어요.

그런데 RTOS나 리눅스와 같은 운영체제의 핵심 원리를 알려면 ARM 프로세서를 알아야 돼요. 익셉션, 스케줄링과 같은 세부 동작은 대부분 ARM 어셈블리 코드로 돼 있기 때문이죠.

그리고 스타트업 코드라고 들어본 적이 있나요? 시스템이 처음 부팅할 때 실행하는 코드에요. RTOS나 리눅스 개발자들은 ARM의 스타트업 코드를 작성하기 마련인데, ARM 프로세서의 기본 원리를 알아야 관련 코드를 작성할 수 있죠.

사실, ARM 프로세서만을 잘하기 위해 ARM 프로세서를 배우는 사람은 많지는 않아요. 임베디드 분야의 개발자들이나 고급 시스템 개발자가 되고 싶은 분들이 ARM 프로세서를 배우려고 하죠. 

ARM 프로세서를 알고 모를 때 어떤 차이가 있을까?

ARM 프로세서를 잘 알고 모르고는 평소에 잘 들어나진 않는데요. 해결하기 어려운 문제를 만났을 때는 달라요.

'Secure Monitor Call'을 실행하고 나서 시스템이 무감됐다는 예시를 들었잖아요. 이 문제를 리포트 받고 ARM 프로세서를 잘 아는 분과 모르는 분이 어떻게 생각할까요? ARM 프로세서를 잘 모르는 분은 이게 뭔 소리인지 이해를 못하겠죠. 그래서 조용히 구글링을 하겠죠.

그런데 ARM 프로세서를 잘 아는 분은 이렇게 생각할 겁니다.

  * 'Secure Monitor Call'을 실행하면 트러스트 존으로 동작 모드를 변경할 텐데. 이 때 실행하는 어셈블리 코드의 아규먼트는 제대로 전달이 된 것일까?
 * Secure Mode와 Non-Secure 모드 별로 페이지 테이블의 시작주소인 TTBR 이 다른데. 혹시 이 설정이 잘못됐나?
 * 트러스트 존에 로그를 추가해서 어디까지 실행되는지 확인해볼까?
'Secure Monitor Call'을 여러 CPU가 실행하려고 시도하지는 않나? 해당 코드에 락을 좀 걸어볼까?

이렇게 전체 그림을 그리고 문제를 분석하는 개발자와 'Secure Monitor Call'이란 용어가 뭔 소리인지도 몰라 조용히 구글링을 하는 개발자 중 누가 먼저 빨리 문제를 해결할까요? 여러분이 관리자라면 둘 중 어느 개발자에게 문제를 맡길까요? 구글링을 하는 개발자는 아니겠죠?






[리눅스커널] 크래시 유틸리티: files -p '아이노드 주소' + files -d '덴트리 주소' 디버깅 [Debugging] Tips

크래시 유틸리티는 다양한 메모리 및 파일 시스템 디버깅 기능을 제공합니다.
그 중에 유용한 기능 중 하나를 소개합니다.

   * files -p '아이노드 주소'

먼저 다음 명령어를 입력해 오픈된 파일에 대한 파일 디스크립터, 아이노드 그리고 덴트리를 확인 합니다.

   * files <pid>

crash> files 1664
PID: 1664   TASK: dc270000  CPU: 1   COMMAND: "Chrome-proc.anim"
ROOT: /    CWD: /
 FD    FILE     DENTRY    INODE    TYPE  PATH
  0  de4d8200  e1a33ab0  e0ce8dd8  CHR   /dev/null
  1  de4d8200  e1a33ab0  e0ce8dd8  CHR   /dev/null
...
 32  dd0cb000  dd4c0000  e02a6af8  REG   /system/framework/framework-res.apk
 33  dd0cbf00  dd470558  e038daf8  REG   /data/media/0/Pictures/Screenshots/Screenshot_20191217-134455.png

이어서 아이노드 주소와 함께 아래 명령어를 입력합니다.

crash> files -p e038daf8
 INODE    NRPAGES
e038daf8      489

  PAGE    PHYSICAL   MAPPING    INDEX CNT FLAGS
e731eba0  7925d000  e038dbf4       269  1 40010228 uptodate,lru,arch_1,mappedtodisk
e7602300  90418000  e038dbf4       517  1 4001026c referenced,uptodate,lru,active,arch_1,mappedtodisk
e71e4980  6f54c000  e038dbf4       803  6 4021026c referenced,uptodate,lru,active,arch_1,mappedtodisk,r_readahead
e71ffdc0  702ee000  e038dbf4       804  4 4021022c referenced,uptodate,lru,arch_1,mappedtodisk,r_readahead
e7b6f900  bbac8000  e038dbf4       805  2 40210268 uptodate,lru,active,arch_1,mappedtodisk,r_readahead
e721e040  71202000  e038dbf4       806  2 40010268 uptodate,lru,active,arch_1,mappedtodisk
 

무려 489개의 페이지가 해당 Screenshot_20191217-134455.png 파일의 아이노드에 액세스하고 있습니다.

이번에는 다른 명령어를 입력해 볼까?

   * files -d <덴트리 주소>

crash> files -d dd470558
 DENTRY    INODE    SUPERBLK  TYPE  PATH
dd470558  e038daf8  e1cad800  REG  /data/media/0/Pictures/Screenshots/Screenshot_20191217-134455.png

덴트리에 대한 경로 파일을 확인할 수 있다.

해당 파일에 대한 슈퍼 블럭을 확인한 ex4 파일 시스템의 슈퍼 블럭임을 확인할 수 있다.

crash> struct super_block e1cad800
struct super_block {
  s_list = {
    next = 0xe1cae000,
    prev = 0xe4bc4000
  },
  s_dev = 265289728,
  s_blocksize_bits = 12 '\f',
  s_blocksize = 4096,
  s_maxbytes = 17592186040320,
  s_type = 0xc1733a2c <ext4_fs_type>,
  s_op = 0xc1017f00 <ext4_sops>,
  dq_op = 0xc1017f98 <ext4_quota_operations>,
  s_qcop = 0xc1017fc0 <ext4_qctl_operations>,
  s_export_op = 0xc1017f74 <ext4_export_ops>,

[임베디드] 꼰대 개발자가 되는 방법(2) 임베디드 에세이


여러분! 꼰대 개발자가 되고 싶나요? 꼰대로 이름을 날리고 싶다면 이 글을 꼼꼼히 끝까지 읽어 주세요.


스스로 많은 노력을 해야 한다.

꼰대 임베디드 개발자가 되기 위한 가장 중요한 요건을 말씀드릴 차례입니다. 이 요건을 제대로 충족해야 더 강력한 꼰대 개발자가 될 수 있습니다. 그 요건은 바로;

   * 오랫동안 컴퓨터에 앉아 있는 것입니다.

이전 포스팅에서 설명한 바와 같이, 꼰대 개발자는 자신이 성공했으며 다른 후배 개발자들이 자신을 선망하고 있다고 믿고 있습니다. 이런 믿음을 확신으로 바꾸려면 스스로 정말 자신에게 뿌듯할 정도로 최선의 노력을 다 해야 합니다 . 그런데 꼰대 개발자들의 최선의 노력은 대부분 '컴퓨터에 오래 앉아 있는 것입니다. 야근은 기본이고 주말에도 나와서 컴퓨터 앞에 앉아 있습니다.

가끔 이들은 주말에 노트북을 들고 스타벅스에 갑니다. 가장 많은 사람들이 다니는 곳에 앉아서 '빌드 셸 스크립트' 코드를 분석합니다. 그러는 자신의 모습이 너무 멋있다고 생각합니다.
  
   * 그래! 나 같이 뛰어난 개발자가 주말에도 스타벅스에서 코드를 분석하고 있어!
   * 난 역시 대단해! 

하지만 스타벅스에서 이상한 미소를 지으며 노트북을 바라보는 꼰대 개발자를 본 젊은이들은 이렇게 생각하고 바라 봅니다.

   * 검은 색 모자를 쓴 이상하게 눌러 쓴 40대 후반 아저씨가 뭘하고 있는 거지?
   * 머리도 1달 동안 안 감은 것아 노숙자 같기도 한데. 

꼰대 개발자도 이런 젊은이들의 시선을 느낍니다. 그런데 꼰대 개발자는 회사에 있는 후배 개발자 이외에도 일반 대중들도 자신을 선망의 눈빛으로 바라보고 있다고 확신하면서 즐겁게 빌드 스크립트 코드를 봅니다. 그러면서 다른 사람들에게 방해가 될 정도로 타자 소리를 내면서 노트북을 사용합니다.

자신의 개발 방식이 여전히 먹힐 것이라 확신한다.

'10년이면 강산이 변한다'는 말이 있습니다. 하지만 꼰대 개발자들은 개발은 다르다고 생각합니다. 내가 하는 말을 잘 듣고 그대로 따라하면 나 같은 레전드 개발자가 될 것이라 확신합니다. 나 같이 뛰어난 개발자가 모든 시간을 퍼부어 최선의 노력을 다하고 스타벅스에서도 열심히 코드를 분석하니까요!

하지만 후배 개발자들의 개발 방식을 보면 정말 마음에 들지 않습니다. 이상한 덤프를 분석하고 쓸때 없는 TRACE32와 같은 디버깅 장비를 사용합니다. 가끔 이상한 ftrace 메시지(리눅스)를 쳐다 봅니다. 

   * 난 저런거 안써도 문제를 잘 해결했는데!
   * 오실로스코프를 써서 파형을 보는 게 더 중요한데!

그래서 조용히 후배 개발자를 불러서 진심어린 조언을 해줍니다.
   
   * 너, TRACE32나 ftrace/perf 와 같은 이상한 디버깅 프로그램 쓰지마! 그런 거 잘 할 필요 없어!
     나 처럼 빌드 스크립트를 열심히 분석하고 회로도 공부해! 그래야 뛰어난 개발자가 될 수 있어!

어, 그런데 후배 개발자가 내 말을 듣지 않고 날 무시하는 듯한 표정과 함께 감히 대답도 하지 않습니다. 더 이상 조언을 하면 안될 것 같아 속으로만 이렇게 생각합니다.
   
   * 저 친구 저렇게 개발하면 실력이 늘지 않을 텐데!

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

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


[리눅스커널] 익명 페이지 메모리 누수(Memory Leak) 디버깅 - TRACE32 [Crash]Troubleshooting!!

많은 리눅스 시스템 개발자들은 자신이 개발하고 있는 리눅스 커널의 버전이 주류였으면 하는 바램이 있다. 하지만 현실은 다르다. 현재 리눅스 커널의 최신 버전인 4.19(LTS 기준)으로 개발하고 있어도 이전 리눅스 커널 버전에서 다뤘던 자료 구조에 대해 알고 있어야 한다.

이번에는 커널 3.10 버전에서 struct anon_vma 구조체와 struct address_space 구조체의 관계에 대해 알아보겠다.

먼저 다음 TRACE32 명령어를 입력해 3번째 프레임의 페이지 디스크립터를 확인하자.

$(TRACE32 명령어)  v.v %d %t %h %i (struct page[0x7FFF])*mem_map
  (static struct page [32767]) (struct page[0x7FFF])*mem_map = (
    [0] = ((long unsigned int) flags = 524392 = 0x00080068, (struct address_space *) mapping = 0xF70
    [1] = ((long unsigned int) flags = 2592 = 0x0A20, (struct address_space *) mapping = 0xF6E90564,
    [2] = ((long unsigned int) flags = 524392 = 0x00080068, (struct address_space *) mapping = 0xF70
    [3] = (
      (long unsigned int) flags = 524392 = 0x00080068,
      (struct address_space *) mapping = 0xF70A6861,
      (long unsigned int) index = 468946 = 0x000727D2,
      (void *) freelist = 0x000727D2,
      (bool) pfmemalloc = -46 = 0xD2,
      (unsigned int) counters = 0 = 0x0,
 
struct address_space 타입인 mapping 필드는 0xF70A6861 이란 주소를 저장하고 있다.

어, 뭔가 이상하다! 왜 주소 끝 부분이 0x1로 끝날까?

그 이유는 익명(ANON) 페이지의 mapping 필드는 다음과 같은 규칙으로 정해지기 때문이다.

   * page->mapping & 0xFFFFFFFE = struct anon_vma 구조체 주소

그렇다면 0xF70A6860 주소를 struct anon_vma 구조체로 캐스팅을 해서 확인해볼까?

$(TRACE32 명령어) v.v %d %t %h %i (struct anon_vma)0xF70A6860

  (static struct anon_vma) (struct anon_vma)0xF70A6860 = (
    (struct anon_vma *) root = 0xF70A6860,
    (struct rw_semaphore) rwsem = (
      (__s32) activity = 0 = 0x0,
      (raw_spinlock_t) wait_lock = (
        (arch_spinlock_t) raw_lock = (
          (u32) slock = 171772477 = 0x0A3D0A3D,
          (struct __raw_tickets) tickets = (
            (u16) owner = 2621 = 0x0A3D,
            (u16) next = 2621 = 0x0A3D))),
      (struct list_head) wait_list = (
        (struct list_head *) next = 0xF70A686C,
        (struct list_head *) prev = 0xF70A686C)),
    (atomic_t) refcount = ((int) counter = 1 = 0x1),
    (struct rb_root) rb_root = ((struct rb_node *) rb_node = 0xF733FCF0))

필드를 보니 제대로된 값을 저장하고 있는 듯 하다.
스핀락의 티켓과 오너의 값이 같으니 말이다.

이번에는 메모리 릭으로 의심이 되는 mm_struct 구조체를 확인해보자.

$(TRACE32 명령어) v.v %i %h %t (struct mm_struct)0xF7F41880 

  (static struct mm_struct) (struct mm_struct)0xF7F41880 = (
    (struct vm_area_struct *) mmap = 0xF706DE70 -> (
      (long unsigned int) vm_start = 0x48400000,
      (long unsigned int) vm_end = 0xB4C00000,
      (struct vm_area_struct *) vm_next = 0xF71F34D0,
      (struct vm_area_struct *) vm_prev = 0x0,
      (struct rb_node) vm_rb = ((long unsigned int) __rb_parent_color = 0xF71F34E1, (struct rb_node
      (long unsigned int) rb_subtree_gap = 0x48400000,
      (struct mm_struct *) vm_mm = 0xF7F41880,
      (pgprot_t) vm_page_prot = 0x079F,
      (long unsigned int) vm_flags = 0x00100073,
      (union) shared = ((struct) linear = ((struct rb_node) rb = ((long unsigned int) __rb_parent_co
      (struct list_head) anon_vma_chain = ((struct list_head *) next = 0xF733FCE8, (struct list_head
      (struct anon_vma *) anon_vma = 0xF70A6860,
      (struct vm_operations_struct *) vm_ops = 0x0,
      (long unsigned int) vm_pgoff = 0x00048400,
      (struct file *) vm_file = 0x0,
      (void *) vm_private_data = 0x0),
 
anon_vma의 구조체인 anon_vma 필드가 0xF70A6860 주소를 저장하고 있다.

그렇다면, 다음과 같은 규칙에 따라 익명 페이지의 페이지 디스크립터 mapping 필드는 
0xF70A6861 일 것이다.  

   * page->mapping & 0xFFFFFFFE = struct anon_vma 구조체 주소

크래시 유틸리티를 사용해 0xF70A6861 주소를 누가 얼만큼 저장하는지 알아볼까?
아래는 'search 0xF70A6861' 명령어를 입력한 출력 결과이다.

crash> search 0xF70A6861
ce600004: f70a6861
ce60004c: f70a6861
ce600070: f70a6861
ce600124: f70a6861
ce600148: f70a6861
ce60016c: f70a6861
ce600190: f70a6861
...

엄청난 결과가 출력이 된다.

전체 페이지 디스크립터 중 0xF70A6861을 얼마나 차지하는지 확인해볼까?
 
crash> search 0xF70A6861 | wc -l
443296

무려 443296 개나 된다.

페이지 한 개당 크기가 4096이니 다음과 같은 계산식으로 익명 페이지가 1,731MB 의 메모리 공간을 차지하고 있다.

443296 * 4096 = 0x6C3A_0000
0x1B0E80KB(0x6C3A_0000/1024) = 1,731MB(0x1B0E80/1024)


   * 결론은 유저 공간의 메모리 릭이다!

참고로, 익명 페이지는 거의 유저 공간에 어떤 파일을 읽거나 쓸 때 사용한다.

위에서 살펴본 덤프는 아래 콜스택과 같이 메모리가 부족해 OOM Killer이 발생해 추출됐다.

-000|out_of_memory()
-001|__alloc_pages_may_oom(inline)
-001|__alloc_pages_slowpath(inline)
-001|__alloc_pages_nodemask()
-002|alloc_pages_node(inline)
-002|__page_cache_alloc(inline)
-002|page_cache_alloc_cold(inline)
-002|page_cache_read(inline)
-002|filemap_fault()
-003|alloc_pages_node(inline)
-003|__do_fault()
-004|handle_pte_fault()
-005|pmd_page_vaddr(inline)
-005|__handle_mm_fault(inline)
-005|handle_mm_fault()
-006|__do_page_fault(inline)
-006|do_page_fault()
-007|do_PrefetchAbort()
-008|__pabt_usr(asm)
 -->|exception
 

[임베디드] 꼰대 개발자가 되는 방법(1) 임베디드 에세이

여러분! 꼰대 개발자가 되고 싶나요? 꼰대로 이름을 날리고 싶다면 이 글을 꼼꼼히 끝까지 읽어 주세요.

리눅스를 개발하면서 수 많은 꼰대들을 만났습니다. 그런데 연차가 늘어날 수록 꼰대를 만나는 확률이 점점 높아지고 있는 것 같은데요. 그 동안 제가 개발실에서 만나 뵈었던 꼰대님과 교류를 통해 얻은 교훈을 공유하고 싶어요.

꼰대가 되는 가장 빠른 방법: 스스로가 성공한 개발자라고 확신한다!
 
위대한 꼰대가 되기 위해서는 먼저 자신의 개발 능력이 상대방보다 100% 월등하다는 것을 확신해야 합니다. 개발 연차가 많던, 부장이나 개발 이사와 같이 직급이 높던, 성과급이나 월급을 많이 받던, 이 중 반드시 하나는 갖춰야 합니다. 그런데 여기서 가장 중요한 것은 다음과 같은 확신입니다.

   * 다른 개발자들도 내가 생각하는 성공의 정의에 따른다!

이런 확신을 지키기 위해서는 자신과 다른 분야에 몸담고 있는 개발자와 교류를 하지 않기 위해 엄청난 노력을 해야 합니다. 이 과정에서 자연스럽게 일상 생활에서 나와 다른 관점으로 개발하는 개발자들을 배척하게 됩니다. 여기까지 문장을 읽고 제가 무슨 소리를 하는지 잘 이해를 못하는 분들도 있을 것입니다. 꼰대 개발자들의 어록을 조금 말씀드려 볼까요? 물론 임베디드 리눅스 분야에 한정된 예시입니다.

   * 하드웨어 장비를 나처럼 못쓰는 인간들은 죽어야 해!
   * 빌드 스크립트와 소스 구조를 보는 게 제일 중요해! 이걸 못하는 놈들은 다들 허접들이야!

잘 이해가 가시나요? 자신이 뭔가 잘한다고 믿는 걸 다른 개발자가 못하면 허접 취급을 합니다. 그러면서 다음과 같은 주장을 펼칩니다.

   * 리눅스 커널 소스 코드는 절대로 볼 필요가 없어! 우리가 직접 커널의 스케줄러 소스 코드를 고치냐?
     리눅스 커널을 공부하는 인간은 참 쓸모 없는 짓을 하는 거야
   * 디버깅은 할 필요가 없어! 버그는 하루면 누구나 잡거든!

예시를 조금 들었으니 이제 조금 더 이해가 되시나요? 꼰대 임베디드 리눅스 개발자님은 이러지 않고서는 스스로 100% 확신하고 있는 믿음을 지키기가 어렵습니다.

한 회사에 오랫동안 붙어 있거나 오로지 한 분야에만 머물어 개발을 하는 것도 이런 확신을 지키는 것에 큰 도움이 됩니다. 보통 비슷한 개발을 하는 집단 내에 소속된 개발자 끼리는 비슷한 이야기를 하게 됩니다. 그렇게 몇 년간 비슷한 개발자들이 하는 이야기를  꾸준히 들으면 진짜 그렇게 느끼고 확신하게 되기 마련입니다. 

   * 하드웨어 장비를 나처럼 못쓰는 인간들은 죽어야 해!
   * 빌드 스크립트와 소스 구조를 보는 게 제일 중요해! 이걸 못하는 놈들은 다들 허접들이야!
   * 브링업을 못하는 개발자는 쓸모가 없어!
 
너무 넓은 선택지는 꼰대님들의 머리만 복잡하기 만들 뿐입니다.

꼰대 임베디드 개발자가 되기 위해 피해야 되는 것은 다른 나라로 여행을 하거나 해외 개발자와 협업하는 것입니다.  여러분이 꼰대 개발자가 되기 위해 피해야 할 가장 위험한 행동입니다. 물론 개발 분야를 바꾸는 것도 위험하지만 아예 해외 개발자와 함께 일하는 것도 주의하셔야 합니다. 해외 개발자들은 일단 수평적인 마인드로 소통하려고 하므로 이들과 일을 하려면 생각하는 방식을 많이 바꿔야 합니다.

   * 그래서 꼰대 개발자가 되려면 먼저 오픈 소스 활동을 해서는 안됩니다.

그래서 임베디드 꼰대 개발자님들이 그동안 기껏 쌓아 올린 지위나 명예가 크게 흔들릴 수도 있습니다. 최대한 해외 개발자와 함께 협업하는 것을 멀리 합시다. 

다른 개발자들도 나처럼 성공하고 싶어 한다고 확신한다!

자, 이제 그다음 해야 할 일은 다른 개발자들이 모두 다 나처럼 되고 싶어 한다고 스스로 확신하는 것입니다. 왜냐면 임베디드 리눅스 꼰대 개발자들은 다음과 같이 생각할 확률이 높거든요.

   * 나는 성공한 사람이다! 
   * 내가 속한 집단에서는 나처럼 되는 것 말고는 다른 선택지가 없다!
   * 다른 후배 개발자들은 당연히 나의 직급에 오르고 싶을 것이다.
   * 물론 당연히 나만큼 벌고 싶을 것이고, 내 명함을 받으면 고개를 조아리는 것이 당연한 일이다!  
   * 비록 회사 밖에서 사람들은 버릇없이 나를 40대 후반의 아저씨라고 부르지만, 나는 적어도 회사에서만큼은, 
      후배 개발자들 앞에서만큼은 절대적인 힘을 가진 무소불위의 존재다. 
   * 가끔, 술을 사달라고 연락을 하는 걸 보니 나를 존경하는 것이 틀림이 없다.
   * 술자리에서 내가 입만 열면 다들 조용해지면서 내 말에만 귀를 기울이니 내 말이 얼마나 값어치가 있는지는 도저히 반박할 구석이 없다.

그래서 다음과 같은 생각을 하게 됩니다.
 
   *  내가 이 자리까지 오르기 쉽지 않았지만, 특별히 기특한 너희들에게만 큰 은혜를 베풀겠다!

결국 꼰대 개발자들은 열심히 조언을 해줍니다. 

  *  너희들은 나처럼 개발을 해야 한다! 그래야 나와 같은 레전드 개발자가 될 수 있다!

이렇게 꼰대 개발자들이 나의 귀중한 시간을 할애해 자신의 소중한 노하우와 가르침을 전했는데도 그 말을 따라 하지 않는 후배 개발자들이 생깁니다. 심지어 아예 무시하는 개발자들도 생기죠. 이런 상황에서는 꼰대 개발자는 후배 개발자를 불러서 다그칩니다.

   * 너희 그러면 개발자로써 인정 못 받는다! 

하지만 후배 개발자들은 꼰대 개발자들의 조언을 들어 주는 척 하지만 시간이 지나면 행동은 예전과 그대로입니다.

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

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


[ARM프로세서] ARM 모드에 대한 소개 [Linux] ARM 프로세서 이야기

ARM을 공부하면 가장 먼저 만나는 용어가 있습니다. 바로 ARM 동작 모드입니다. ARM 모드는 ARM의 세부 동작을 배우려면 반드시 알아야 하므로 잘 익혀 둘 필요가 있습니다. 

특히 64비트 기반의 ARMv8 아키텍처는 기존 ARM의 모드에 대응되는 익셉션 레벨이란 개념을 도입했습니다. 익셉션 레벨이란 개념이 기존 ARMv7 아키텍처의 ARM 모드에서 발전됐으니 먼저 ARM의 동작 모드부터 살펴봅시다.

이 ARM 동작 모드를 잘 익혀야 시스템을 디자인하고 디버깅할 때 배운 내용을 유용하게 활용할 수 있습니다.  실행 모드를 잘 살펴 보면 유저(User) 모드는 표준 사용자 모드라고 부르는데 나머지 6가지 모드는 Privileged mode라고 분류됩니다. ARM User Manual을 열어 보면 각 모드에 대한 설명은 아래와 같이 되어 있습니다. 

#출처: DDI0406C_arm_architecture_reference_manual

User mode
An operating system to runs applications in User mode to restrict the use of system resources.
Application programs normally execute in User mode, and any program executed in User mode:
  • makes only unprivileged accesses to system resources, meaning it cannot access protected system resources
  • makes only unprivileged access to memory

System mode 
* Software executing in System mode executes at PL1. System mode has the same registers available
as User mode, and is not entered by any exception

Supervisor mode
* Supervisor mode is the default mode to which a Supervisor Call exception is taken.
* Executing a SVC (Supervisor Call) instruction generates an Supervisor Call exception, that is taken to Supervisor mode.
* A processor enters Supervisor mode on Reset.

Abort mode 
* Abort mode is the default mode to which a Data Abort exception or Prefetch Abort exception is taken.

Undefined mode
* Undefined mode is the default mode to which an instruction-related exception, including any
attempt to execute an UNDEFINED instruction, is taken.

FIQ mode 
* FIQ mode is the default mode to which an FIQ interrupt is taken.

IRQ mode 
* IRQ mode is the default mode to which an IRQ interrupt is taken.

Monitor mode
* Monitor mode is the mode to which a Secure Monitor Call exception is taken.

보시다시피 ARM 코어는 위에서 소개한 모드 중 하나를 사용합니다. 그런데 문제는 ARM 모드에 대한 내용으 읽어도 감이 잘 안온다는 점입니다. 

   * 무슨 모드가 이렇게 많아? 이걸 왜 쓰는 거지? 
   * 난 어떤 ARM 모드를 써야 할까?

의문은 끊이지 않습니다. 먼저 범용 리눅스를 기반으로 자주 사용하는 모드를 알아봅시다.
먼저 유저 모드(User mode) 입니다. 쉽게 설명하면 리눅스 애플리케이션은 유저 모드에서 동작합니다.

한 가지 예를 들겠습니다. 여러분이 라즈베리 파이나 우분투에서 다음과 같은 코드를 작성해 실행을 시켰습니다. 너무나 쉬운 예제 코드라 코드 내용과 컴파일을 하는 방법은 설명하지 않겠습니다.

#include <stdio.h>
int main() 
{
printf("Hello World! \n");
return 0;
}

위 코드는 라즈베리 파이나 우분투에서 어느 모드로 동작할 까요? ARM의 유저 모드입니다.
여기서 또 한 가지 의문이 생깁니다. 

   * 리눅스 애플리케이션이 ARM의 유저 모드에서 동작한다고 설명을 했다.
   * 당신은 이 사실을 어떻게 증명할 것인가?

좋은 질문입니다. 어떤 지식이든 그대로 답습을 하지 말고 배운 내용이 정말로 맞는지 증명해보는 습관을 갖는 것은 매우 중요합니다. '사실 - 증명'의 과정을 거치는 게 진정한 공부입니다.

현재 실행 중인 코드가 ARM의 어떤 모드에서 실행 중인지를 알려면 ARM에서 제공하는 CPSR(Current Program Status Register)를 읽어보면 됩니다. 보통 CPSR 레지스터의 [0:4] 비트가 0x10 이면 ARM의 유저 모드에서 동작한다고 볼 수 있습니다. 이 내용은 다음 포스팅에서 GDB 디버거로 실습을 통해 배워 보겠습니다.


[리눅스커널] 커널 동적 타이머를 사용해 실행 시각을 측정해보기 [Debugging] Tips

이번에는 조금 더 재미있는 패치 코드를 같이 볼까요? 다음은 패치 코드의 내용입니다.

diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index cd8b61b..128c998 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -49,7 +49,7 @@
 #include <linux/uaccess.h>
 #include <linux/sched/isolation.h>
 #include <linux/nmi.h>
-
+#include <linux/sched_clock.h>
 #include "workqueue_internal.h"

 enum {
@@ -2029,6 +2029,18 @@ static bool manage_workers(struct worker *worker)
        return true;
 }

+static int work_timer_debug = 1;
+static unsigned long long max_work_handler_time_stamp = 0;
+
+static void work_handler_elapsed_timer_func(struct timer_list *unused)
+{
+       unsigned long long curr_time = sched_clock();
+
+       pr_err("work_handler max elapsed time : %lld, cur time: %lld\n",
+                       max_work_handler_time_stamp, curr_time);
+}
+static DEFINE_TIMER(work_handler_elapsed_timer, work_handler_elapsed_timer_func);
+
 /**
  * process_one_work - process single work
  * @worker: self
@@ -2052,6 +2064,7 @@ __acquires(&pool->lock)
        bool cpu_intensive = pwq->wq->flags & WQ_CPU_INTENSIVE;
        int work_color;
        struct worker *collision;
+       u64 start_time, end_time, elapse_time;
 #ifdef CONFIG_LOCKDEP
        /*
         * It is permissible to free the struct work_struct from
@@ -2149,6 +2162,12 @@ __acquires(&pool->lock)
         * workqueues), so hiding them isn't a problem.
         */
        lockdep_invariant_state(true);
+
+       if (work_timer_debug) {
+               start_time = sched_clock();
+               mod_timer(&work_handler_elapsed_timer, msecs_to_jiffies(500) + jiffies);
+       }
+
        trace_workqueue_execute_start(work);
        worker->current_func(work);
        /*
@@ -2156,6 +2175,19 @@ __acquires(&pool->lock)
         * point will only record its address.
         */
        trace_workqueue_execute_end(work);
+
+       if (work_timer_debug) {
+               end_time = sched_clock();
+               del_timer_sync(&work_handler_elapsed_timer);
+               elapse_time = end_time - start_time;
+
+               if (elapse_time > max_work_handler_time_stamp) {
+                       max_work_handler_time_stamp = elapse_time;
+                       pr_err("work_handler elapsed time : %lld, handler: %pS\n",
+                                       max_work_handler_time_stamp, worker->current_func);
+               }
+       }
+
        lock_map_release(&lockdep_map);
        lock_map_release(&pwq->wq->lockdep_map);

@@ -2235,6 +2267,9 @@ static void set_pf_worker(bool val)
  *
  * Return: 0
  */


위 패치 코드는 2가지 기능을 지원하는데 세부 동작은 두 가지 시나리오에서 생각해볼 수 있습니다.

< 첫 번째 시나리오 >
1. 타이머 설정(500ms)
2.         worker->current_func(work); 함수 실행
3. 'worker->current_func(work)' 코드의 실행 시각이 500ms 이내이면 
   타이머 해제
4. 최대 실행 시각을 저장

< 두 번째 시나리오 >
1. 타이머 설정(500ms)
2.         worker->current_func(work); 함수 실행
3. 'worker->current_func(work)' 코드의 실행 시각이 500ms 이상이 걸리면  
   타이머 핸들러 함수가 호출됨
4. work_handler_elapsed_timer_func() 타이머 핸들러에서 에러 정보 출력
   : 관련 자료 구조
   : 프로세스의 콜스택

임베디드 리눅스 개발자분들은 이 포스팅에 올린 자료를 참고해서 더 빨리 퇴근하셨으면 좋겠습니다.





[리눅스커널] 인터럽트 핸들러 실행 시각 측정 디버깅 패치 [Debugging] Tips

가끔 인터럽트 핸들러의 처리 시간이 조금 더 정확히 보고 싶을 때가 있습니다.
이때, 다음과 같은 패치 코드를 적용하면 인터럽트 핸들러의 실행 시간을 측정할 수 있습니다.

패치 코드는 다음과 같습니다.

diff --git a/drivers/mailbox/bcm2835-mailbox.c b/drivers/mailbox/bcm2835-mailbox.c
index a03aeed..e353beb 100644
--- a/drivers/mailbox/bcm2835-mailbox.c
+++ b/drivers/mailbox/bcm2835-mailbox.c
@@ -72,17 +72,29 @@ static struct bcm2835_mbox *bcm2835_link_mbox(struct mbox_chan *link)
        return container_of(link->mbox, struct bcm2835_mbox, controller);
 }

+#define HANDLE_TIME 100
+
 static irqreturn_t bcm2835_mbox_irq(int irq, void *dev_id)
 {
+       u64 diff, start_time, curr_time;
        struct bcm2835_mbox *mbox = dev_id;
        struct device *dev = mbox->controller.dev;
        struct mbox_chan *link = &mbox->controller.chans[0];

+       start_time = sched_clock();
        while (!(readl(mbox->regs + MAIL0_STA) & ARM_MS_EMPTY)) {
                u32 msg = readl(mbox->regs + MAIL0_RD);
                dev_dbg(dev, "Reply 0x%08X\n", msg);
                mbox_chan_received_data(link, &msg);
        }
+
+       curr_time = sched_clock();
+       diff = curr_time - start_time;
+
+       if (diff > HANDLE_TIME)
+               trace_printk("interrupt: elapsed: %llu, start: %llu \n",
+                               diff, start_time);
+
        return IRQ_HANDLED;
 }

코드 내용은 너무 간단합니다. 나노초 단위의 시간 정보를 반환하는 sched_clock() 함수를 사용해서 
실행 시각을 계산하는 코드입니다. 인터럽트 함수의 실행 시간 정보를 저장하는 diff 지역 변수를 ftrace로출력합니다.

참고로 인터럽트 핸들러가 실행될 때는 인터럽트 컨텍스트입니다. 빨리 코드가 실행돼야 하니 printk() 함수를 써서 커널 로그를 출력하는 것은 자제해야 합니다.

Austin Kim, 궁금한 점이 있으면 댓글로 질문을 남겨주세요.






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