2013년 8월 31일 토요일

[Linux Kernel] 19주차(2013.08.31) 후기

10차 ARM 리눅스 커널 스터디 19주차(2013.08.31) 후기입니다.

# 일시 : 2013.08.31 (19주차)
# 모임명 : NIPA지원_IAMROOT.ORG_10차ARM-C
# 장소 : 토즈 타워점
# 장소지원 : NIPA지원
# 참여인원 : 8인
# 스터디 진도 :
 - start_kernel()-> boot_cpu_init();
// 현재 cpu(core id)를 얻어서 cpu_XXX_bits[] 의 cpu를 셋한다.

# 코드 분석을 위한 팁

## vi에디터에서 #ifdef ~ #endif로 구분되어 있는 곳의 그룹 이동은
어떻게 할 수 있나요?

해당 부분에서 %를 누르시면 이동 가능합니다.

<code>
#ifdef iamroot
..
..
..
..
#endif <-여기서 %
</code>

## emacs에서 cscope 사용방법
emacs에서 cscope database를 생성하는 것이다.
1. emacs에서 생성하기
M-x cscope-index-file <RET> 를 입력하고, 원하는 디렉토리 (ex. /usr/src/sys) 를 입력한다.
이후 원하는 파일을 열고 cscope를 사용하면 된다.

2. sh에서 생성하기
$find . \( -name '*.c' -o -name '*.cc' -o -name '*.h' -o -name '*.s' \) -print

3. sh에서 생성하기 또 다른 방법
$find ./ -name *.[chS] -print > cscope.files

4. linux커널의 경우 Makefile 이용하기
$make ARCH=arm cscope

5. python 소스 분석을 할때 사용하는 방법
$find . \( -name '*.py' \) -print > cscope.files

## cscope 키 입력 설명
C-c s s Find symbol.
C-c s d Find global definition.
C-c s g Find global definition (alternate binding).
C-c s G Find global definition without prompting.
C-c s c Find functions calling a function.
C-c s C Find called functions (list functions called from a function).
C-c s # TODO:  Find text string.
C-c s e Find egrep pattern.
C-c s f Find a file.
C-c s i Find files #including a file.

# boot_cpu_init();
// 현재 cpu(core id)를 얻어서 cpu_XXX_bits[] 의 cpu를 셋한다.

<code> main.c
static void __init boot_cpu_init(void)                                                                  
{                                                                                                      
        int cpu = smp_processor_id();

        set_cpu_online(cpu, true);                                                                      
        set_cpu_active(cpu, true);                                                                      
        set_cpu_present(cpu, true);                                                                    
        set_cpu_possible(cpu, true);                                                                    
}
</code>

## int cpu = smp_processor_id();
// cpu : 0 이된다.

<code> smp.h
#ifdef CONFIG_DEBUG_PREEMPT // CONFIG_DEBUG_PREEMPT=y                                                    
  extern unsigned int debug_smp_processor_id(void);                                                      
# define smp_processor_id() debug_smp_processor_id()                                                    
#else                                                                                                    
# define smp_processor_id() raw_smp_processor_id()                                                      
#endif
</code>

## smp_processor_id()  debug_smp_processor_id()

<code>
notrace unsigned int debug_smp_processor_id(void)                                                        
{                                                                                                        
        unsigned long preempt_count = preempt_count();

        int this_cpu = raw_smp_processor_id();                                                            
                                                                                                         
        if (likely(preempt_count))  
                goto out;                                                                                
                                                                                                         
        if (irqs_disabled())                                                                              
                goto out;                                                                                

        if (cpumask_equal(tsk_cpus_allowed(current), cpumask_of(this_cpu)))                              
                goto out;                                                                                
                                                                                                         
        if (system_state != SYSTEM_RUNNING)                                                              
                goto out;                                                                                
                                                                                                         
        preempt_disable_notrace();                                                                        
                                                                                                         
        if (!printk_ratelimit())                                                                          
                goto out_enable;                                                                          
                                                                                                         
        printk(KERN_ERR "BUG: using smp_processor_id() in preemptible [%08x] "                            
                        "code: %s/%d\n",  
                        preempt_count() - 1, current->comm, current->pid);                                
        print_symbol("caller is %s\n", (long)__builtin_return_address(0));                                
        dump_stack();                                                                                    
                                                                                                         
out_enable:                                                                                              
        preempt_enable_no_resched_notrace();                                                              
out:                                                                                                      
        return this_cpu;                                                                                  
}
</code>  

## notrace가 의미하는 것이 뭘까요?
// 디버거등에 의한 프로 파일링을 막기 위해서 사용합니다.

<code>
notrace unsigned int debug_smp_processor_id(void)
</code>

gcc manual 에서 설명하는 notrace의 정의는 다음과 같습니다.

If -finstrument-functions is given,
profiling function calls will be generated at entry and exit of
most user-compiled functions.
Functions with this attribute will not be so instrumented.

#define notrace __attribute__((no_instrument_function)) 이라면,
이는 디버거와는 별 상관이 없는 것 같습니다.
물론 _cyg_profile_func_enter, _cyg_profile_func_exit 함수에서
디버거와 통신하는 뭔가가 있을 수도 있겠습니다만 저 같으면
보통 시간 재서 출력하는 루틴을 넣을 것 같네요.

예시>
<code>
#include <stdio.h>
#include <stdlib.h>


__attribute__ ((no_instrument_function))
void __cyg_profile_func_enter(void *this, void *callsite)
{
    printf ("__cyg_profile_func_enter\n");
}

__attribute__ ((no_instrument_function))
void __cyg_profile_func_exit(void *this, void *callsite)
{
    printf ("__cyg_profile_func_exit\n");
}

/* constructor, destuctor는 생략해도 됩니다. */
__attribute__ ((constructor))
void myConstructor( void )
{
    printf ("constructor\n");
}

__attribute__ ((destructor))
void myDestructor( void )
{
    printf ("destuctor!\n");
}


void func_a( void )
{
    printf ("func_a\n");
    return;
}

__attribute__ ((no_instrument_function))
void func_b( void )
{
    printf ("func_b\n");
    return;
}


int main()
{
    printf ("main\n");
    func_a();
    func_b();

    return 0;
}
</code>

결과는 다음과 같이 나옵니다.

<결과>
dqxt@dons-pleiades ~/study/kernel/instrument_fn $ gcc -o test -finstrument-functions test.c
dqxt@dons-pleiades ~/study/kernel/instrument_fn $ ./test
__cyg_profile_func_enter
constructor
__cyg_profile_func_exit
__cyg_profile_func_enter
main
__cyg_profile_func_enter
func_a
__cyg_profile_func_exit
func_b
__cyg_profile_func_exit
__cyg_profile_func_enter
destuctor!
__cyg_profile_func_exit
dqxt@dons-pleiades ~/study/kernel/instrument_fn $
</결과>

당시에는 이게 뭘까 의미를 고민했었는데, 코드와 결과를 보니 명확해 지네요^^;
결국 각 function의 entry와 exit에서 해당 동작을 수행하는 것이었군요.

해당 코드 이력을 따라가 보면

전 : unsigned int debug_smp_processor_id(void) 에서
후 : notrace unsigned int debug_smp_processor_id(void) 로

notrace가 추가되었고, 여기에 다음과 같이 설명이 추가 되어 있습니다.

Mark with “notrace” functions in core code that should not be
traced. The “notrace” attribute will prevent gcc from adding
a call to ftrace on the annotated funtions.

저는 여기서 말한 ftrace (function trace)를 디버깅 목적으로도
많이 사용 하기 때문에 디버거라고 적었습니다.
원문을 따라적는다면 ftrace가 맞습니다.

함수가 실행될때 마다, debug_smp_processor_id()가 실행되어
processor ID를 읽어 오는 동작을 합니다.
여기서 이 함수를 통해서 preemption이 safe한 것인지 체크를 할 수도
있습니다.

하지만 SMP이고 CONFIG_DEBUG_PREEMPT 가 선언되어 있으므로
 debug_smp_processor_id()를 실행하여
a.b. 조건을 만족하여 현재 실행하고 있는
thread 구조체에 CPU_ID를 설정합니다.
 a. preemption이 safe 하여야 함
 b. ftrace와 같은 디버거들에 영향 받지 않아야 함

## preempt_count가 의미하는 것이 무엇일까요?
선점 당한 횟수를 말합니다.

<code> smp_processor_id.c
unsigned long preempt_count = preempt_count();
</code>

<code> preempt.h
#define preempt_count() (current_thread_info()->preempt_count)
</code>

preempt_count = preempt_count() : 0x4000 0001 가 됩니다.
preempt_count 양수 값으로 큰 값을 가지게 되면 선점 당하지 않게 됩니다.
여기서는 초기화를 하고 있어서 독점적으로 자원을 사용하기 위함입니다.

## this_cpu = raw_smp_processor_id();
// this_cpu : 0

<code> smp.h
#define raw_smp_processor_id() (current_thread_info()->cpu)
</code>

## if (likely(preempt_count)) goto out;
// preempt_count : 0x4000 0001 이므로 goto out으로 간다.

### likely가 의미하는 것은 무엇일까요?
컴파일러한테 branch여부를 알려주는 것입니다.
if문등에서 조건에 따른 분기를 할때 한쪽에
가중치(likely나 dislikely)를 주어서 최적화하게 하라는 의미입니다.

## current_thread_info()->cpu

<code> thread_info.h
static inline struct thread_info *current_thread_info(void)                                              
{                                                                                                        

        register unsigned long sp asm ("sp");                                                            
                                                                                                         
        // THREAD_SIZE: 8192, ~(THREAD_SIZE - 1): 0xffffe000                                            
        return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));                                          
}
</code>

### register unsigned long sp asm ("sp");
// stack이 8K로 정렬되어 있기 때문에 13비트를 clear시키면,
 stack의 맨 앞을 가리키게 된다.        

<picture>
|---|          0x4000 0000
|     |
|     | sp1 : 0x4000 1FFD
|     |
|---|           0x4000 2000
|     |
|     | sp  :   0x4000 3ABC
|     |
|---|           0x4000 4000
</picture>

stack이 8K 단위로 정렬되어 있기때문에 위에서 보는 바와 같이
base는 0x40000000, 0x40002000 부터 8K씩 정렬되어 있습니다.

따라서 THREAD_SIZE: 8192, ~(THREAD_SIZE - 1) : 8K-1은 0x1FFF가 되어
 ~(0x1FFF) = 0xFFFFE000으로 13bit를 클리어 하는 mask가 됩니다.

만약 SP(0x40001FFD)를 13bit를 클리어 하면
0x40001FFD & 0xFFFFE000 = 0x40000000로
현재 STACK의 맨 끝 ( stack은 아래> 로 자라므로..) 이자
current thread 주소를 반환하게 됩니다.

마찬가지로 만약 SP가 SP1(0x40003ABC) 에 있다면
0x40003ABC & 0xFFFFE000 = 0x40002000으로
현재 STACK의 맨 끝을 가르키게 됩니다.

### union구문이 이해가 잘 안가네요. 저렇게 사용할 수 있나요?

 _init_task_data는 init_task.h에서 .data..init_task 섹션으로 사용하겠다고
정의했습니다.

<code> init_task.c
union thread_union init_thread_union __init_task_data =                                                  
        { INIT_THREAD_INFO(init_task) };
</code>

### union의 3번째의 _init_task_data는 뭔가요?
3번째 속성은 attribute라고 보시면 됩니다.
여기서는 init_thread_union를 초기화하고 이것의 속성을
.data..init_task 섹션해서 사용하겠다는 것입니다.


# 빠진 사람이 많아서 진도를 if(likely(preempt_count)) goto out 이 아닌
조건 문까지 다 분석하기로 함

# if (irqs_disabled()) goto out;


## irqs_disabled()

<code> irqflags.h
#define irqs_disabled()                                 \                                                
        ({                                              \                                                
                unsigned long _flags;                   \                                                
                raw_local_save_flags(_flags);           \                                                
                raw_irqs_disabled_flags(_flags);        \                                                
        })
</code>

### raw_local_save_flags(_flags);

<code> irqflags.h
#define raw_local_save_flags(flags)                     \                                                
        do {                                            \                                                
                typecheck(unsigned long, flags);        \                                                
                flags = arch_local_save_flags();        \                                                
        } while (0)
</code>

#### typecheck() 함수에서 typeof가 의미하는 것이 무엇일까요?
먼저 typeof에 대해 gcc manual을 살펴보면 다음과 같이 정의합니다.
참조 링크 : gcc manual
http://gcc.gnu.org/onlinedocs/gcc/Typeof.html

 Another way to refer to the type of an expression is with typeof.
The syntax of using of this keyword looks like sizeof,
but the construct acts semantically like a type name defined with typedef.

요약하면 해당변수의 type을 반환하는 역할을 하는 매크로 함수입니다.
간단한 test code를 보면 더욱 이해가 명확할 것입니다.

<code>
int main()
{
    char s1;
    typeof(s1) s2;

    unsigned char u1;
    typeof(u1) u2;

    s2 = 0xff;
    u2 = 0xff;

    printf("s2 = %d\n", s2);
    printf("u2 = %d\n", u2);
    return 0;
}
</code>

결과

s2 = -1
u2 = 255

#### (void)(&__dummy == &__dummy2); 가 의미하는 바가 무엇일까요?
typecheck의 type이 같지 않으면
컴파일러가 컴파일 시, warning을 발생합니다.

예제 코드로 살펴보면,

<code>
#include <stdio.h>

#define typecheck(type,x) \
({      type __dummy; \
        typeof(x) __dummy2; \
        (void)(&__dummy == &__dummy2); \
        1; \
})

int main(void)
{
    int x;
    if (typecheck(int, x))
        printf("int x OK\n");
    if (typecheck(double, x))
        printf("double x OK\n");
    return(0);
}
</code>

결과

jin@jin-desktop:~/dev/test$ gcc -Wall -Wextra -o typecheck typecheck.c
typecheck.c: In function ‘main’:
typecheck.c:15: warning: comparison of distinct pointer types lacks a cast

결과(-Werror옵션 추가)

jin@jin-desktop:~/dev/test$ gcc -Wall -Wextra -Werror -o typecheck typecheck.c
cc1: warnings being treated as errors
typecheck.c: In function ‘main’:
typecheck.c:15: error: comparison of distinct pointer types lacks a cast

주소값을 비교한다고 착각할 수 있는데, 이 구문은 type을 기반으로 비교를
하기 때문에 주소값은 의미가 없습니다.
참조 링크 : about typecheck in linux kernel
http://stackoverflow.com/questions/10393844/about-typecheck-in-linux-kernel

#### 1; \ 가 의미하는 것은 무엇일까요?
1이 의미하는 것은 단순히 항상 1을 리턴한다는 의미입니다.
이 부분을 이용하여 바로 위 코드의 결과처럼 구문 check가 가능합니다.

결과
jin@jin-desktop:~/dev/test$ ./typecheck
int x OK
double x OK

참조 링크 : typecheck code
http://comments.gmane.org/gmane.linux.kernel/1094113

### flags = arch_local_save_flags();        \

<code>
static inline unsigned long arch_local_save_flags(void)                                                  
{                                                                                                        
        unsigned long flags;                                                                            
        asm volatile(                                                                                    
                "       mrs     %0, " IRQMASK_REG_NAME_R "      @ local_save_flags"                      
                : "=r" (flags) : : "memory", "cc");                                                      
        return flags;                                                                                    
}
</code>

## raw_irqs_disabled_flags(_flags);
arch_irq_disabled_flags()의 경우 함수가 2개 나옵니다. 어떤점이 틀릴까요?

<code> ::arch/arm/include/asm/irqflags.h

static inline int arch_irqs_disabled_flags(unsigned long flags)
{
        return flags & PSR_I_BIT;
}
</code>

<code> ::include/asm-generic/irqflags.h

#ifndef arch_irqs_disabled_flags
static inline int arch_irqs_disabled_flags(unsigned long flags)
{
        return flags == ARCH_IRQ_DISABLED;
}
#endif
</code>

위의 code는 CPSR이 존재하는 경우 분기하는 함수이며,
아래 code는 CPSR이 없을 경우 처리하는 함수입니다.

# if (cpumask_equal(tsk_cpus_allowed(current), cpumask_of(this_cpu))) goto out;

## cpumask_of(this_cpu)



<code> :: cpumask.h
#define cpumask_of(cpu) (get_cpu_mask(cpu))
</code>

### get_cpu_mask()

<code> :: cpumask.h
static inline const struct cpumask *get_cpu_mask(unsigned int cpu)                                      
{                                                                                                      
        const unsigned long *p = cpu_bit_bitmap[1 + cpu % BITS_PER_LONG];                              
        p -= cpu / BITS_PER_LONG;                                                                      
        return to_cpumask(p);                                                                          
}
</code>

#### 1) local에서 const를 선언하면 stack을 사용하는 것인가요?
상수로 보기 때문에 stack을 사용합니다.

#### 2) 위 code에서 %연산자는 왜 할까요?

CPU의 개수만큼 지원하기 위함입니다.
추가로, 리눅스는 최대 32개만큼 CPU를 지원한다고 보면 됩니다.

#### 3) 계속 용어가 혼란 스럽네요.
위 code에서 BIT_PER_LONG과 BITS_TO_LONGS의 차이가 무엇일까요?

BIT_PER_LONG은 우리 타겟 기준 32로 설정되어 있습니다.
BITS_TO_LONGS는 ROUND_UP이라고 보시면 됩니다.

BITS에 해당하는 값이 LONG으로 몆개가 되는 지 계산하는 매크로 입니다.
쿼드 코어면 값이 CPU가 4개 이므로 long크기의 비트 (32) 보다 작으므로
1개의 long이면 된다는 것입니다.
만약 32개 이상이라면 2개의 long이 필요하겠지요.
즉, 32비트면 CPU가 최대 32개 까지 되니까 long 1개로 표현 가능하지만,
64비트면 8바이트로 되니까, long이 2개 있어야 하는 것입니다.

#### 4) 위 code에서 2차원 배열로 선언을 해놓고, 포인터는 1차원 배열이네요.
 어떻게 해석 해야 합니까?

포인터가 배열의 앞대가리(표현이 참 재밌네요…)를 보고 있는 것입니다.

#### 5) cpu_bit_bitmap[] 배열을 이렇게 선언한다니 신기하네요.
주석처럼 나온 결과가 궁금합니다.

#define MASK_DECLARE_1(x)       [x+1][0] = (1UL << (x))
#define MASK_DECLARE_2(x)       MASK_DECLARE_1(x), MASK_DECLARE_1(x+1)
#define MASK_DECLARE_4(x)       MASK_DECLARE_2(x), MASK_DECLARE_2(x+2)
#define MASK_DECLARE_8(x)       MASK_DECLARE_4(x), MASK_DECLARE_4(x+4)

// cpu_bit_bitmap[33][1]
const unsigned long cpu_bit_bitmap[BITS_PER_LONG+1][BITS_TO_LONGS(NR_CPUS)] = {
//      MASK_DECLARE_8(0) 이 아래와 같이 확장됨
//      [1][0] = (1UL << 0),
//      [2][0] = (1UL << 1),
//      [3][0] = (1UL << 2),
//      [4][0] = (1UL << 3),
//      [5][0] = (1UL << 4),
//      [6][0] = (1UL << 5),
//      [7][0] = (1UL << 6),
//      [8][0] = (1UL << 7),

MASK_DECLARE_8(0),      MASK_DECLARE_8(8),
MASK_DECLARE_8(16),     MASK_DECLARE_8(24),
MASK_DECLARE_8(0) =⇒ MASK_DECLARE_4(x), MASK_DECLARE_4(x+4)가 되고
MASK_DECLARE_4(0) =⇒ MASK_DECLARE_2(x), MASK_DECLARE_2(x+2)가 되며,
MASK_DECLARE_2(0) =⇒ MASK_DECLARE_1(x), MASK_DECLARE_1(x+1)가 되어,
MASK_DECLARE_1(x) =⇒ [x+1][0] = (1UL « (x)) 로 계산되게 됩니다.



#### 6) return 하는 to_cpumask()함수에서 대체 1이 왜 쓰였을까요?
이렇게 하면 항상 true인데 code가 이해 안되네요.

<code>
#define to_cpumask(bitmap)                                              \
        ((struct cpumask *)(1 ? (bitmap)                                \
                            : (void *)sizeof(__check_is_bitmap(bitmap))))
</code>

C++에서 구조체가 있긴 하지만, 어떤 내용인지는 모를경우 이런식으로 쓰는
경우를 보긴 했습니다.

9차 선배들이 토의한 다음과 같은 내용이 있었습니다.
삼항 연산자가 필요한 이유 : syntax 검사용인듯 합니다.
아래 참고 링크를 보면 bitmap 의 type을 체크하여 compile warnning을
발생시키는 용도라고 합니다.

 Actually, the call is not used for runtime, but to check at compile time
that the macro parameter bitmap is actually of type unsigned long *
(hence the name of the function which does only that). If bitmap is
of the wrong type, an warning will be issued, and a compilation warning
in the kernel build is always a serious matter.

참조 링크 :
http://stackoverflow.com/questions/15636214/how-is-for-each-possible-cpu-expanded-in-cpufreq-c-file/15640553#15640553

## tsk_cpus_allowed(current)

<code> :: sched.h
#define tsk_cpus_allowed(tsk) (&(tsk)->cpus_allowed)
</code>

<code>
#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))

### 1) long과 같이 sizeof로 확인하는 값은 누가 결정하는 것인가요?
아키텍처로 알 수 있나요?

컴파일러가 결정해 줍니다.

### 2) 그래서 결국 .cpus_allowed 값이 왜 3이라는 것인가?
값의 계산과정을 알고싶습니다.

<code>
#define BITMAP_LAST_WORD_MASK(nbits)                                    \
(                                                                       \
        ((nbits) % BITS_PER_LONG) ?                                     \
                (1UL<<((nbits) % BITS_PER_LONG))-1 : ~0UL               \
)
</code>

NR_CPUS에 따라 다르며 NR_CPUS=2 인 경우 3입니다.
cpus_allowed는 cpu 수 만큼의 bit를 갖는 bitmap을 만듭니다.

예1: NR_CPUS = 4
> LAST_MASK: (1<<(4%32))-1 =  0b10000 - 1 = 0xf
> unsigned long cpu_bits[1] = {0x0000000ful};

예2: NR_CPUS = 40 = 32+8
> LAST_MASK: (1<<(8%32))-1 = (1<<8)-1  = 0b100000000 - 1 = 0xff
> unsigned long cpu_bits[2] = {
>  [0 ... 0] = 0xfffffffful,
>  [1]       = 0x000000fful};

### 3) 변수 this_cpu값을 몰라서 계산이 혼란스럽다.
이 값은 0인가? 0이라면 그 이유는?

<code>
notrace unsigned int debug_smp_processor_id(void)
{
unsigned long preempt_count = preempt_count();//0x4000_0001

int this_cpu = raw_smp_processor_id();
</code>

<code>
#define raw_smp_processor_id() (current_thread_info()->cpu)
</code>

#### thread_info 구조체

<code>
struct thread_info {
unsigned longflags; /* low level flags */
intpreempt_count;   /* 0 => preemptable, <0 => bug */
mm_segment_# TODO: addr_limit; /* address limit */
struct task_struct*task; /* main task structure */
struct exec_domain*exec_domain;/* execution domain */
__u32cpu; /* cpu */
__u32cpu_domain; /* cpu domain */
struct cpu_context_savecpu_context;/* cpu context */
...

#### init_thread_union 선언 및 초기화

/*
 * Initial thread structure. Alignment of this is handled by a special
 * linker map entry.
 */
// ARM10C 20130824
union thread_union init_thread_union __init_task_data =
{ INIT_THREAD_INFO(init_task) };

#### init_thread_union 초기화 매크로

#define INIT_THREAD_INFO(tsk)\
{\
.task= &tsk,\
.exec_domain= &default_exec_domain,\
.flags= 0,\
.preempt_count= INIT_PREEMPT_COUNT,\
.addr_limit= KERNEL_DS,\
.cpu_domain= domain_val(DOMAIN_USER, DOMAIN_MANAGER) |\
domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) |\
domain_val(DOMAIN_IO, DOMAIN_CLIENT),\
.restart_block= {\
.fn= do_no_restart_syscall,\
},\
}

init_thread_union 으로 선언시 초기화 매크로인 INIT_THREAD_INFO 에서
cpu 값을 초기화 하지 않는다.
init_thread_union의 멤버 cpu도 초기화 값을 따로 넣지 않으므로 0 일 것이다.

우리 소스에서는 어떤 값이 들어와도 그걸 0으로 하는것이 맞다고 본다.
하지만, task struct와 실제 돌고 있는 cpu가 같을까?
그것이 의심 스러운 것이다.

C99 standard에서 부분만 초기화된 경우 나머지는 0으로 초기화한다고
정의하므로 .cpu와 같이 초기화하지 않으면 0이다.

C99 Standard 6.7.8.21
  If there are fewer initializers in a brace-enclosed list than
there are elements or members of an aggregate, or fewer characters
in a string literal used to initialize an array of known size than
there are elements in the array, the remainder of the aggregate
shall be initialized implicitly the same as objects that have
static storage duration.

ref:http://stackoverflow.com/questions/10828294/c-and-c-partial-initialization-of-automatic-structure

커널초기화과정에서 현재 수행중인 cpu를 logical id 0으로 매핑하므로
init_thread_union의 cpu : 0이 맞다.

### init_task에 들어가능 상세 정보
<code> init_task.c
28// __init_task_data: __attribute__((__section__(".data..init_task")))                                    
29// INIT_THREAD_INFO(init_task):                                                                          
30// {                                                                                                      
31//      .task           = &init_task,                                                                    
32//      .exec_domain    = &default_exec_domain,                                                          
33//      .flags          = 0,                                                                              
34//      .preempt_count  = 0x40000001,                                                                    
35//      .addr_limit     = KERNEL_DS,                                                                      
36//      .cpu_domain     = domain_val(DOMAIN_USER, DOMAIN_MANAGER) |                                      
37//                        domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) |                                    
38//                        domain_val(DOMAIN_IO, DOMAIN_CLIENT),                                          
39//      .restart_block  = {                                                                              
40//              .fn     = do_no_restart_syscall,                                                          
41//      },                                                                                                
42// }                                                                                                      
43//                                                                                                        
44// union thread_union init_thread_union __attribute__((__section__(".data..init_task"))) =                
45// {                                                                                                      
46//      .task           = &init_task,
47//      .exec_domain    = &default_exec_domain,                                                          
48//      .flags          = 0,                                                                              
49//      .preempt_count  = 0x40000001,                                                                    
50//      .addr_limit     = KERNEL_DS,                                                                      
51//      .cpu_domain     = domain_val(DOMAIN_USER, DOMAIN_MANAGER) |                                      
52//                        domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) |                                    
53//                        domain_val(DOMAIN_IO, DOMAIN_CLIENT),                                          
54//      .restart_block  = {                                                                              
55//              .fn     = do_no_restart_syscall,                                                          
56//      },                                                                                                
57// }
</code>

## cpumask_equal()


<code>
#define small_const_nbits(nbits) \
        (_builtin_constant_p(nbits) && (nbits) <= BITS_PER_LONG)
</code>

### 1) _builtin_constant_p의 의미는 무엇인가요?

인수가 컴파일 시 상수로 알려져 있으면 정수 1을 반환,
그렇지 않으면 0을 반환합니다.

모드
GNU 모드에서만 지원됩니다.

구문
int _builtin_constant_p(…)

인수 설명:
해당 값이 테스트될 인수입니다.

제한
반환 값이 0이라고 해도 값이 상수가 아님을 의미하지는 않지만
컴파일러는 그 값이 -O 옵션의 지정된 값을 가진 상수라는 것을
나타내지 못합니다.

### 2) 위 code에서 &&와 ⇐이 있는데, 무엇이 먼저 실행 되는 것인가요?
 당연히 앞에 있는 &&이 실행되어야 하는 것 아닌가요?

결론만 얘기하면, ⇐가 &&보다 먼저 실행됩니다.

C언어 우선순위를 짚고 넘어가야 할 것 같습니다.


# preempt_disable_notrace();


<code>
#define preempt_disable_notrace() \                                                                      
do { \                                                                                                  
        inc_preempt_count_notrace(); \                                                                  
        barrier(); \                                                                                    
} while (0)
</code>

## 1) barrier()함수가 너무 많은데 우리는 무엇을 불러오나요?
그리고 다시한번 의미를 짚고 넘어갑시다.

우리는 다음과 같은 명령어를 실행합니다.

<code> :: include/linux/compiler-gcc.h

#define barrier() _asm_ _volatile_("": : :"memory")

</code>

include/linux/compiler.h에 정의된 barrier는 #ifndef barrier 구문으로
실행이 안됨.
(#define으로 정의한 barrier()도 check)

<code>
#ifndef barrier
# define barrier() _memory_barrier()
#endif
</code>

이 barrier는 기존에 우리가 스터디 했던 hardware적인 명령어가 아니라,
compiler에서 막아주는 barrier 명령이다.
꼭 ARM이 아니어도, x86이나 다른 CPU로 구동될 수 있다.

barrier는 메모리를 순차적으로 접근하도록 보장하기 위한 것이다.
즉, CPU가 반드시 소스 코드 상에 나온 순서대로 instruction을 실행하도록
지시한다. (중략)

추가 자세한 내용은 Reference 참조:
[Linux] 최적화 장벽 :
http://studyfoss.egloos.com/5128961

## 2) 조금 이해가 안간다. compiler에서 barrier를 이용하여 막는다고 해도,
멀티코어 환경에서는 불가능 할 것 같다.

다음 사이트에 관련 내용이 있으니, 조금 더 이해하고 토론하도록 하자.

Working of _asm_ _volatile_ (“” : : : “memory”)
Memory Barrier라는 것이 Memory의 Protectioin을 거는 것입니다.
일단 두 개의 프로세서가 하나의 메모리 영역에 접근한다고 했을 때,
접근시에 동기화가 필요합니다.
프로세서 A에서 값을 기록했다가 프로세서 B에서 값을 읽거나 할 경우에
프로텍션을 걸어줌으로써, 해당되는 프로세서가 값을 읽지 못하도록
만드는 것입니다.

## 3) inline 어셈에서 clobber에 해당하는 memory가 나왔습니다.
무슨 의미일까요?

clobber 필드에 “memory”라고 표기 하는것은 모든 메모리 타입 저장 장치
(모든 레지스터, 모든 프래그, 모든 메모리 등)의 값이 변경됨을 의미합니다.
gcc는 이럴경우 asm volatile(””: : :“memory”) 경계를 넘어가는 최적화 또는
instruction scheduling을 수행하지 않습니다.
때문에 asm volatile(””: : :“memory”)를 사용하면 이전 코드의 수행 완료를
보장할 수 있고 이후 코드가 asm volatile(””: : :“memory”) 이전에 수행하는
것을 방지 할 수 있습니다.

## 4) 주석에 있는 avoid recursion의 의미는 무엇일까요?

barrier를 이용하여 재귀적인 호출을 피합니다.

# if (!printk_ratelimit()) goto out_enable;

<code> :: printk.h
#define printk_ratelimit() __printk_ratelimit(__func__)
</code>

## 1) 아래 code에서 _func_이 의미하는 것이 무엇인가요?

현재 호출한 함수를 의미합니다. 여기서는 debug_smp_processor_id() 입니다.

## 2) SPINLOCK을 우리가 공부했었던가요? 개념의 이해가 필요합니다.

간단하게 언급하면, 멀티코어가 되면서 나온것이 spinlock입니다.
기존의 mutex가 시간이 오래 걸리기 때문에, 나오게 되었습니다.

spinlock에 대해 각자 공부하고 토론하면 좋을 것 같습니다.

spinlock이 single core에서 의미없는 이유만 기재하겠습니다.
1. Lock을 얻을 수 없다면, 계속해서 Lock을 확인하며 얻을 때까지 기다린다.
이른바 바쁘게 기다리는 busy waiting이다.
2. 바쁘게 기다린다는 것은 루프를 돌면서 최대한 다른 스레드에게 CPU를
양보하지 않는 것이다.
3. Lock이 곧 사용가능해질 경우 컨텍스트 스위치를 줄여 CPU의 부담을
덜어준다. 하지만, 만약 어떤 스레드가 Lock을 오랫동안 유지한다면
오히려 CPU 시간을 많이 소모할 가능성이 있다.
4. 하나의 CPU나 하나의 코어만 있는 경우에는 유용하지 않다.
그 이유는 만약 다른 스레드가 Lock을 가지고 있고
그 스레드가 Lock을 풀어주려면 싱글 CPU 시스템에서는 어차피
컨텍스트 스위치가 일어나야 하기 때문이다.

## 3) ratelimit함수를 보면 interval과 burst로 제한을 걸고 있다.
왜 그런걸까요?

실제로 사운드카드 같은 것은 printk를 이용해서 디버깅을 하다보면,
많이 끊기는 현상이 발생합니다.
따라서 제한을 둔것이 아닌가 싶습니다.

## 4) rs→interval이 timer를 통해서 값이 변하는 것같다.
하지만 어디서 값이 변경되는지 확인이 필요하다.

<code>
int __ratelimit(struct ratelimit_state *rs, const char *func)
{
        unsigned long flags;
        int ret;

        // 5s 가 지났는지 검사 수행
        if (!rs->interval)
                return 1;
...
}
</code>

## 5) 아래 strexeq 구문이 이해가 안간다.

<code>
static inline int arch_spin_trylock(arch_spinlock_t *lock)
{
        unsigned long tmp;
        u32 slock;

        // lock->slock이0 이면 unlocked
        // lock->slock이0x10000 이면 locked

//"     ldrex   slock, lock->slock\n"
//"     subs    tmp,   slock, slock, ror #16\n"         : tmp = slock - (slock >> 16)
//"     addeq   slock, slock, (1 << TICKET_SHIFT)\n"    : slock = 0x10000
//"     strexeq tmp,   slock, lock->slock"              : tmp는 strexeq의 수행 결과 값
        _asm_ _volatile_(
"       ldrex   %0, [%2]\n"
"       subs    %1, %0, %0, ror #16\n"
"       addeq   %0, %0, %3\n"
"       strexeq %1, %0, [%2]"
        : "=&r" (slock), "=&r" (tmp)
        : "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
        : "cc");

        if (tmp == 0) {
                smp_mb();
                return 1;
        } else {
                return 0;
        }
}
</code>

- LDREX (배타적인 로드)는 메모리로 부터 데이터를 읽고,
동시에 메모리 주소에 태그합니다.
- STREX (배타적인 스토어는 데이터를 메모리에 저장하는데,
태그가 여전히 유효한 경우만 저장합니다.
- 그외의 경우에는 메모리는 수정되지 않습니다.

참조 링크 : [Ref.] http://www.iamroot.org/xe/Kernel_8_ARM/65971

## 6) 위 code에서 그러면 LDREX와 STREX는 쌍(pair)으로 구동이 되는 건가요?

 멀티 프로세서가 도입되면서 SWP 명령이 성능 이슈를 회피하기 위해,
ARM v6 이후의 아키텍쳐에서 SWP는 폐기되었습니다.
멀티 프로세서 환경에서 프로세서들 사이에 동일한 공유 메모리 접근을
동기화 하기 위해서는 global exclusive monitor를 사용합니다.
global exclusive monitor는 각 프로세서가 베타적으로 접근하는 주소를
태그해 놓습니다.
LDREX를 통해 베타적 상태를 태그해 둔 메모리에 다른 프로세서가
단순 str 연산이나 strex를 수행하면,global exclusive monitor는
해당 태그 상태를 open으로 변경하게되어 베타적 접근이 실패된 것을
알려 주게 됩니다.

참조링크 : [Ref.] http://www.iamroot.org/xe/Kernel_8_ARM/66152

2013년 8월 28일 수요일

[Linux Kernel]] 18주차(2013.08.24) 후기

10차 ARM 리눅스 커널 스터디 18주차(2013.08.24) 후기입니다.

일시 : 2013.08.24 (18주차)

모임명 : NIPA지원_IAMROOT.ORG_10차ARM-C

장소 : 토즈 타워점

장소지원 : NIPA지원

참여인원 : 13인

스터디 진도 :

  • start_kernel()-> lockdep_init();
    // chasshash_table을 CLASSHAS_SIZE (2048개)만큼 리스트 자료구조를
    // 만듭니다.
  • start_kernel()-> smp_setup_processor_id();
    // 현재 실행되는 cpu(여기서는 부팅중인 cpu)의 logical map을 설정합니다.
  • start_kernel()-> debug_object_early_init();
    // NULL 함수, object를 트래킹하기 위한 구조체 초기화
  • start_kernel()-> boot_init_stack_early();
    // NULL 함수, stack의 오염여부를 확인하는 함수
    • start_kernel()-> cgroup_init_early();
      // NULL 함수, cgroup 초기화
    • start_kernel()-> local_irq_disable();
      // irq를 disable한다.

start_kernel()로 진입

KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)

  • KERNEL_RAM_VADDR : 0xC000 8000
    • PAGE_OFFSET: 0xC0000000
    • TEXT_OFFSET: 0x8000
    • KERNEL_RAM_VADDR 아래 16kB에 page table(PGD)가 위치한다.
    • swapper_pg_dir : page table
  • PMD : Page Middle Directory
    • PG_DIR_SIZE : 0x4000
    • PMD_ORDER 2
  • swapper_pg_dir : KERNEL_RAM_VADDR - PG_DIR_SIZE
    • KERNEL_RAM_VADDR : 0xC000 8000
    • PG_DIR_SIZE : 0x4000
    • swapper_pg_dir : 0xC000 4000

Kernel startup entry point

커널 c코드(start_kernel)로 진입할 때 다음의 설정을 한 후 진입한다.
  • MMU = off
  • D-cache = off
  • I-cache = dont care
  • r0 = 0,
  • r1 = machine nr
  • r2 = atags 또는 dtb pointer

arch/arm/kernel/head-common.S

133        b       start_kernel

start_kernel()

어셈블 코드에서 C코드인 start_kernel()을 실행했다.
asmlinkage void __init start_kernel(void)                                                              
{                                                                                                      
        char * command_line;                                                                            
        extern const struct kernel_param __start___param[], __stop___param[];

asmlinkage가 무엇인가요?

smlinkage는 어셈블리 코드에서 함수를 호출하도록 컴파일 해달라는 의미입니다.
C언어에서 어셈블리 코드를 호출할 때 함수의 인자를 넘기거나 리턴 값을 받을
때 호출 하는 규약이 필요합니다.
ARM은 인자를 줄 때 특정 레지스터를 사용하도록 정해저 있지만, x86의 경우는
그렇지 못하기 때문에 컴파일시 일반적인 최적화에 의해서 레지스터를 통해
인자를 전달하게 만들어 지면, 어셈블리에서 호출할 때 레지스터가 겹친다는지
하는 문제가 발생하게 됩니다.
이를 피하기 위하여 스택을 이용해서 인자를 주고 받도록 컴파일러에게 알려
주는 것입니다.

command_line 은 어떤 값이 들어가게 될까요?

  • ATAGS를 사용할 경우
    • 부트로더에서 콘솔 설정등을 한 값을 ATAGS로 전달합니다.
  • DTB 사용시
    • DTB에서 콘솔 설정등을 한 값을 DTB에 저장해 놓고 이값을 읽어 와서
      command_line을 설정합니다.
  • CMDLINE의 예시)
    • “console=ttySAC2,115200n8 vmalloc=512M androidboot.console=ttySAC2”

init start_kernel(void) 에서 init는 무엇을 의미하나요?

  • init 섹션으로 정의한 것입니다. init 섹션은 초기화가 끝나면 메모리
    활용을 위하여 제거합니다.
  • 정의
    #define __init          __section(.init.text) __cold notrace
    

__cold 의 의미

gcc manual에 보면 cold는 다음과 같이 정의합니다.
The cold attribute on functions is used to inform the compiler that
the function is unlikely to be executed.The function is optimized
for size rather than speed and on many targets it is placed
into special subsection of the text section so all cold functions appears
close together improving code locality of non-cold parts of program.
여기서 The function is optimized for size rather than speed 라고 하여
컴파일러에게 size 를 최적화하게 하는 옵션입니다.

전역변수로 선언한 data는 어디로 들어갈까요?

start_kernel로 들어오기 전에 mmap_switched_data에서 .data와 그 크기, .bss와 그 크기를 설정하고 초기화를 했었습니다.
컴파일된 커널을 readelf로 읽어 보면 아래와 같습니다.
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .head.text        PROGBITS        c0008000 008000 0001c0 00  AX  0   0  4
  [ 2] .text             PROGBITS        c00081c0 0081c0 35c32c 00  AX  0   0 64
  [ 3] .rodata           PROGBITS        c0365000 365000 0e2ef0 00   A  0   0 64
  [ 4] __bug_table       PROGBITS        c0447ef0 447ef0 005b20 00   A  0   0  1
  [ 5] __ksymtab         PROGBITS        c044da10 44da10 0060b0 00   A  0   0  4
  [ 6] __ksymtab_gpl     PROGBITS        c0453ac0 453ac0 003640 00   A  0   0  4
  [ 7] __ksymtab_strings PROGBITS        c0457100 457100 01522c 00   A  0   0  1
  [ 8] __param           PROGBITS        c046c32c 46c32c 000750 00   A  0   0  4
  [ 9] __modver          PROGBITS        c046ca7c 46ca7c 000584 00   A  0   0  4
  [10] __ex_table        PROGBITS        c046d000 46d000 000fc8 00   A  0   0  8
  [11] .ARM.unwind_idx   ARM_EXIDX       c046dfc8 46dfc8 019e20 00  AL 16   0  4
  [12] .ARM.unwind_tab   PROGBITS        c0487de8 487de8 0024a8 00   A  0   0  4
  [13] .notes            NOTE            c048a290 48a290 000024 00  AX  0   0  4
  [14] .vectors          PROGBITS        00000000 490000 000020 00  AX  0   0  4
  [15] .stubs            PROGBITS        00001000 491000 000240 00  AX  0   0 32
  [16] .init.text        PROGBITS        c048b260 493260 01d4fc 00  AX  0   0 32
  [17] .exit.text        PROGBITS        c04a875c 4b075c 000c30 00  AX  0   0  4
  [18] .init.arch.info   PROGBITS        c04a938c 4b138c 000054 00   A  0   0  4
  [19] .init.tagtable    PROGBITS        c04a93e0 4b13e0 000048 00   A  0   0  4
  [20] .init.smpalt      PROGBITS        c04a9428 4b1428 0001f0 00   A  0   0  4
  [21] .init.pv_table    PROGBITS        c04a9618 4b1618 0002f4 00   A  0   0  1
  [22] .init.data        PROGBITS        c04a9910 4b1910 00ba4c 00  WA  0   0  8
  [23] .data..percpu     PROGBITS        c04b6000 4be000 001d00 00  WA  0   0 64
  [24] .data             PROGBITS        c04b8000 4c0000 035100 00  WA  0   0 64
  [25] .bss              NOBITS          c04ed100 4f5100 041ae0 00  WA  0   0 64

kernel_param;

struct kernel_param {
        const char *name;
        const struct kernel_param_ops *ops;
        u16 perm;
        s16 level;
        union {
                void *arg;
                const struct kparam_string *str;
                const struct kparam_array *arr;
        };
};

lockdep_init();

  • chasshash_table을 CLASSHAS_SIZE (2048개)만큼 리스트 자료구조를 만듭니다.
void lockdep_init(void)                                                                                
{                                                                                                      
        int i;                                                                                          

        if (lockdep_initialized)                                                                        
                return;                                                                                

        for (i = 0; i < CLASSHASH_SIZE; i++)                                                            
                INIT_LIST_HEAD(classhash_table + i);                                                    

        for (i = 0; i < CHAINHASH_SIZE; i++)
                INIT_LIST_HEAD(chainhash_table + i);                                                    

        lockdep_initialized = 1;                                                                        
}

lockdep_initialized

  • 정의
    static int lockdep_initialized;
    

CLASSHASH_SIZE: 2048

INIT_LIST_HEAD

  • list형 자료구조를 만드는 함수입니다.
  • lockdep_init()에서는 chainhash_table를 연결 자료 구조를 만듭니다.
::lockdep.c
static struct list_head classhash_table[CLASSHASH_SIZE];
list.h
static inline void INIT_LIST_HEAD(struct list_head *list)                                                
{                                                                                                        
        list->next = list;                                                                              
        list->prev = list;                                                                              
}

smp_setup_processor_id()

  • 코드가 두가지 검색되네요.
::main.c
void __init __weak smp_setup_processor_id(void)                                                        
{                                                                                                      
}
setup.c
void __init smp_setup_processor_id(void)                                                                
{                                                                                                      
        int i;                                                                                          
        u32 mpidr = is_smp() ? read_cpuid_mpidr() & MPIDR_HWID_BITMASK : 0;                            
        u32 cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);                                                      

        cpu_logical_map(0) = cpu;                                                                      
        for (i = 1; i < nr_cpu_ids; ++i)                                                                
                cpu_logical_map(i) = i == cpu ? 0 : i;                                                  

        set_my_cpu_offset(0);                                                                          

        printk(KERN_INFO "Booting Linux on physical CPU 0x%x\n", mpidr);                                
}

main.c에서 정의한 함수에서 weak는 무엇을 의미할까요?

gcc manual에 _weak는 다음과 같이 정의합니다.
The weak attribute causes the declaration to be emitted as a weak symbol
rather than a global. This is primarily useful in defining library
functions which can be overridden in user code,
though it can also be used with non-function declarations.
Weak symbols are supported for ELF targets,
and also for a.out targets when using the GNU assembler and linker.
결국 같은 이름이 정의된 함수가 있으면, 그 함수를 실행하고,
없을 경우에 weak가 정의된 함수를 실행합니다.
여기서는 smp_setup_processor_id()함수를 찾으면,
arch/arm/kernel/setup.c 에 함수가 있으므로 이 함수를 실행합니다.

그렇다면 왜 weak를 사용할까요?

가정을 해보겠습니다.
SMP 설정을 안하는 single core CPU가 있을 수 있습니다.
이런 경우, local에서는 아무 동작을 안하는 것으로 합니다.
SMP를 사용하는 CPU의 경우, overwrite해서 사용합니다.
결국 범용성을 위한 코드라고 할 수 있겠네요.

mpidr

  • is_smp() : 1 이므로
  • mpidr : read_cpuid_mpidr() & MPIDR_HWID_BITMASK 가 됩니다.
    • read_cpuid_mpidr()
    • MPIDR_HWID_BITMASK : 0xFFFFFF
u32 mpidr = is_smp() ? read_cpuid_mpidr() & MPIDR_HWID_BITMASK : 0;

is_smp()

  • Kernel config을 확인해서 SMP이면 1을 아니면 0을 리턴합니다.
static inline bool is_smp(void)                                                                          
{                                                                                                        
#ifndef CONFIG_SMP // CONFIG_SMP=y                                                                      
        return false;                                                                                    
#elif defined(CONFIG_SMP_ON_UP) // CONFIG_SMP_ON_UP=y                                                     
        extern unsigned int smp_on_up;                                                                  
        // !! 를 사용하는 이유? return 값을 0/1로 만들기 위해 사용                                      
        return !!smp_on_up;                                                                              
#else                                                                                                    
        // ARM10C 20131026                                                                              
        return true;                                                                                    
#endif                                                                                                  
}

!!smp_on_up 을 사용하는 이유는 무엇일까요?

  • return 값을 0이나 1로 만들기 위해서 입니다.
  • 이렇게 하는 이유는 !는 논리연산이므로 결과값이 0이나 1이 되며
  • !!를 사용하면 2중 부정된 참(1)이나 거짓(0)로 결과값이 나옵니다.

read_cpuid_mpidr()

  • SMP에서 CPU ID를 말합니다.
  • A.R.M: B4.1.106 MPIDR 참조
    • Multiprocessor Affinity Register, VMSA
static inline unsigned int __attribute_const__ read_cpuid_mpidr(void)                                    
{                                                                                                        
        return read_cpuid(CPUID_MPIDR);                                                                  
}

attribute_const 는 무엇을 의미 할까요?

static inline unsigned int attribute_const read_cpuid_mpidr(void)
Compilation Tools 참조 설명서를 보면, 다음과 같이 정의되어 있습니다.
많은 함수는 전달되는 인수만 검사하므로 리턴 값을 제외하고는 영향을 주지
않습니다. 이것은 함수가 전역 메모리를 읽는 것이 허용되지 않기 때문에
_attribute_(pure)보다 훨씬 더 엄격한 클래스입니다.
함수가 인수에만 작동하는 것으로 알려져 있다면, 공통 하위식 제거와 루프
최적화가 필요할 수 있습니다.
즉, 함수를 실행 결과 값을 상수처럼 사용하겠다는 것입니다.

read_cpuid(CPUID_MPIDR);

  • reg : CPUID_MPIDR
#define read_cpuid(reg)                                                 \                                
        ({                                                              \                                
                unsigned int __val;                                     \                                
                asm("mrc        p15, 0, %0, c0, c0, " __stringify(reg)  \                                
                    : "=r" (__val)                                      \                                
                    :                                                   \                                
                    : "cc");                                            \                                
                __val;                                                  \                                
        })
인라인 어셈블리의 구조가 이해가 잘 안됩니다. 그리고 cc는 무엇인가요?
GCC inline assembly guide를 보면, 다음과 같이 정의되어 있습니다.
inline assembly에서 정해주어야 하는 것들은 다음과 같습니다.
1) assembly 코드
2) output 변수들
3) input 변수들
4) output외에 값이 바뀌는 레지스터들
이제 각 부분에 대해 살펴봅시다.
asm volatile (asms : output : input : clobber);
asms
쌍따옴표로 둘러싸인 assembly 문자열.
문자열안에서 %n 형태로 input, output 인자들을 사용할 수 있으며
매개별수들이 치환된면 그대로 컴파일 된 assembly에 나타납니다.
output
쉼표로 구분된 “constraint” (variable)들의 리스트이며
각각이 이 inline assembly에서 쓰이는 output 인자를 나타냅니다.
input
output과 같은 형태이며 input 인자들을 나타냅니다.
clobber
쉼표로 구분되는 쌍따옴표로 둘러싸인 레지스터 이름들의 리스트이며
input, output에 나오진 않았지만
해당 assembly를 수행한 결과로 값이 바뀌는 레지스터들을 나타냅니다.
CC는 cpu status flags 의 수정이 발생되는 명령어(ex. adds, strex)가
있을 경우 clobbered list 에 “cc” 를 명시하며,
memory 의 내용을 수정하는 명령이 있을경우
(ex. str) clobbered list 에 “memory”를 명시합니다.

MPIDR_HWID_BITMASK : 0xFFFFFF

#define MPIDR_HWID_BITMASK 0xFFFFFF

결국 smp_setup_processor_id()함수의 동작은

현재 실행되는 cpu(여기서는 부팅중인 cpu)의 logical map을 설정합니다.

하지만 MPIDR의 ID가 혼란스럽다.

예를들어 quad 코어인 경우, TRM에 따르면 0x03으로 설정되지 않을까?
그렇지 않다.
전체 core 개수에 따라 결정하는 것이 아니라,
0번 CPU의 경우 0을 읽고,
1번 CPU의 경우 1을 읽고 이런식으로 번호를 갖습니다.
다음의 예시를 참조하세요.
// if cpu=0
// cpu_logical_map[0] = 0 // current
// cpu_logical_map1 = 1 // others
// cpu_logical_map2 = 2 // others
// cpu_logical_map3 = 3 // others
// if cpu=1
// cpu_logical_map[0] = 1 // current
// cpu_logical_map1 = 0 // others
// cpu_logical_map2 = 2 // others
// cpu_logical_map3 = 3 // others

그렇다면 CP15는 각 core마다 1개씩 있다는 얘기입니까?

네 각 core당 1개씩 있습니다.
TRM 2.1을 확인해 보면 다음 그림과 같이 각 core마다 CP15를 포함하여 register set을 가지고 있습니다.

debug_objects_early_init();

// NULL 함수
// object를 트래킹하기 위한 구조체 초기화

boot_init_stack_canary();

// NULL 함수
// stack의 오염여부를 확인하는 함수

cgroup_init_early();

// NULL 함수
// cgroup 초기화

local_irq_disable();

// IRQ를 disable한다.
#define local_irq_disable() \                                                                            
        do { raw_local_irq_disable(); trace_hardirqs_off(); } while (0)

macro 지정을 do-while문으로 하는 이유는 뭘까요?

매크로시 여러 line으로 되어있으면,
컴파일러에 따라서 문제가 있을 수 있습니다.
이런 문제를 예방하기 위해서 do-while문으로 묶어서 처리합니다.
이렇게 사용하면 좋은 점은 다음과 같습니다.
  • 컴파일러 경고 없이 빈 구문 입력
  • 지역 변수 선언 가능한 블럭 제공
  • 조건문 등에도 문제 없는 복잡한 매크로 사용
  • if() {} else에 쓰였을 때 {};(세미콜론)에 의한 문제에도 안전
  • GNU C extension으로 ({ }) 역시 동일한 효과를 낼 수 있음
참조 링크 : MACRO: do {…} while(0), ({})
http://www.iamroot.org/xe/Kernel_9_ARM/82494

raw_local_irq_disable();

#define raw_local_irq_disable()         arch_local_irq_disable()

arch_local_irq_disable();

// 인터럽트를 disable 한다.
static inline void arch_local_irq_disable(void)                                                          
{                                                                                                        
        asm volatile(                                                                                    
                "       cpsid i                 @ arch_local_irq_disable"                                
                :                                                                                        
                :                                                                                        
                : "memory", "cc");                                                                        
}

boot_cpu_init()

// 다음 스터디에 계속