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()

// 다음 스터디에 계속

댓글 없음:

댓글 쓰기