setup_processor()

  • 몇 개의 전역 변수에 CPU 아키텍처와 밀접한 구조체를 설정하고 재진입 익셉션 핸들러를 위한 스택 할당 등 CPU와 관련된 초기화를 수행한다.

 

setup_processor()

  • read_cpuid_id()
    • read_cpuid() 함수를 호출하는 단순 매크로
    • MIDR 레지스터를 통해 CPU_ID를 알아온다.
  • lookup_processor_type()
    • proc_info_list 구조체 포인터를 알아온다.
    • rpi2: __v7_ca7mp_proc_info: 위치를 가리킨다.
  • __get_cpu_architecture()
    • MMFR0 레지스터를 통해 CPU 아키텍처를 알아온다.
      • rpi2: 9(CPU_ARCH_ARMv7으로 define 됨)
  • 전역변수에 문자열 및 특정 구조체 포인터 대입
    • cpu_name
      • 라즈베리파이2: 문자열 “ARMv7 Processor”를 가리키는 주소가 담김
    • __cpu_architecture
      • 정수 값 9(CPU_ARCH_ARMv7으로 define 됨)
    • processor
      • v7_processor_functions 객체를 가리키는 processor 구조체 포인터
    • cpu_tlb
      • v7wbi_tlb_fns  객체를 가리키는 cpu_tlb_fns 구조체 포인터
    • cpu_user
      • v6_user_fns 객체를 가리키는 cpu_user_fns 구조체와 포인터
    • cpu_cache
      • v7_cache_fns 객체를 가리키는 cpu_cache_fns 구조체 포인터
        • rpi2: multi 캐시를 사용하지 않으므로 이 전역 변수는 사용되지 않음
    • 그외 설정되는 전역 변수 들
      • elf_hwcap, cachepolicy, erratum_a15_798181_handler, cacheid
  • cpuid_init_hwcaps()
    • elf_hwcap 전역 변수에 CPU 아키텍처가 지원하는 하드웨어 캐파(capacity)들을 대입한다.
  •  init_default_cache_policy()
    • cachepolicy 전역 변수에 아키텍처가 지원하는 1차 페이지 테이블(ARM 32bit에서는 PMD)의 캐시 정책 정책 값을 대입한다.
    • 라즈베리파이2: 4 (CPOLICY_WRITEALLOC)
  •  erratum_a15_798181_init()
    • CONFIG_ARM_ERRATA_798181가 설정되어 있는 경우만 동작
    • erratum_a15_798181_handler 전역 변수에 핸들러 함수 등록
      •  erratum_a15_798181_broadcast 또는 erratum_a15_798181_partial
  •  elf_hwcap_fixup()
    • elf_hwcap 전역 변수에서 CPU 아키텍처에 따라 HWCAP_TLS 또는 HWCAP_SWP를 제거한다.
  •  cacheid_init()
    • cacheid 전역변수 값에 캐시 형태를 설정한다.
    • 라즈베리파이2: CACHEID_VIPT_NONALIASING | CACHEID_VIPT_I_ALIASING
  •  cpu_init()
    • per-CPU 스택 설정
    • CPU 아키텍처 전용 초기화 함수 호출
    • 재진입 exception 핸들러를 위한 스택 설정

arch/arm/kernel/setup.c

static void __init setup_processor(void)
{
        struct proc_info_list *list;

        /*
         * locate processor in the list of supported processor
         * types.  The linker builds this table for us from the
         * entries in arch/arm/mm/proc-*.S
         */
        list = lookup_processor_type(read_cpuid_id());
        if (!list) {
                pr_err("CPU configuration botched (ID %08x), unable to continue.\n",
                       read_cpuid_id());
                while (1);
        }

        cpu_name = list->cpu_name;
        __cpu_architecture = __get_cpu_architecture();

#ifdef MULTI_CPU
        processor = *list->proc;
#endif
#ifdef MULTI_TLB
        cpu_tlb = *list->tlb;
#endif
#ifdef MULTI_USER
        cpu_user = *list->user;
#endif
#ifdef MULTI_CACHE
        cpu_cache = *list->cache;
#endif

        pr_info("CPU: %s [%08x] revision %d (ARMv%s), cr=%08lx\n",
                cpu_name, read_cpuid_id(), read_cpuid_id() & 15,
                proc_arch[cpu_architecture()], get_cr());

        snprintf(init_utsname()->machine, __NEW_UTS_LEN + 1, "%s%c",
                 list->arch_name, ENDIANNESS);
        snprintf(elf_platform, ELF_PLATFORM_SIZE, "%s%c",
                 list->elf_name, ENDIANNESS);
        elf_hwcap = list->elf_hwcap;

        cpuid_init_hwcaps();

#ifndef CONFIG_ARM_THUMB
        elf_hwcap &= ~(HWCAP_THUMB | HWCAP_IDIVT);
#endif
#ifdef CONFIG_MMU
        init_default_cache_policy(list->__cpu_mm_mmu_flags);
#endif
        erratum_a15_798181_init();

        elf_hwcap_fixup();

        cacheid_init();
        cpu_init();
}

 

read_cpuid()

  • MIDR 레지스터 값을 리턴한다.

arch/arm/include/asm/cputype.h

#define read_cpuid(reg)                                                 \
        ({                                                              \
                unsigned int __val;                                     \
                asm("mrc        p15, 0, %0, c0, c0, " __stringify(reg)  \
                    : "=r" (__val)                                      \
                    :                                                   \
                    : "cc");                                            \
                __val;                                                  \
        })

 

__stringify() 매크로

  • 인수에 문자열을 그대로 사용할 수 있도록 한다.
  • 컴파일 시 -DFOO=bar 옵션을 사용한 경우 __stringify(FOO)를 사용하는 경우 이를 bar로 변경해준다.

include/linux/stringify.h

/* Indirect stringification.  Doing two levels allows the parameter to be a
 * macro itself.  For example, compile with -DFOO=bar, __stringify(FOO)
 * converts to "bar".
 */

#define __stringify_1(x...)     #x
#define __stringify(x...)       __stringify_1(x)

 

lookup_processor_type()

  • lookup_processor_type() 함수를 통해 proc_info_list 구조체 포인터를 알아온다.

arch/arm/kernel/head-common.S

/*
 * This provides a C-API version of __lookup_processor_type
 */
ENTRY(lookup_processor_type)
        stmfd   sp!, {r4 - r6, r9, lr}
        mov     r9, r0
        bl      __lookup_processor_type
        mov     r0, r5
        ldmfd   sp!, {r4 - r6, r9, pc}
ENDPROC(lookup_processor_type)

 

 

proc_info_list 구조체

  • head.S에서 CPU 정보를 읽었었다.
  • 참고: kernel/head.S – __lookup_processor_type: | 문c
  • .init.data 섹션에 저장됨
  • 항목 설명과 라즈베리파이2에서의 값:
    • cpu_val:
      • 0x410f_c070
    • cpu_mask:
      • 0xff0f_fff0
    • __cpu_mm_mmu_flags: 메모리 주소용 페이지 엔트리 속성
      • MD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | PMD_SECT_AF | PMD_FLAGS_SMP
    • __cpu_io_mmu_flags: 입출력장치용 페이지 엔트리 속성
      • PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | PMD_SECT_AF
    • __cpu_flush:
      • __v7_ca7mp_setup() 함수 포인터
        • 내부에 CPU 설정 및 캐시 flush, ttbr 설정 등 캐시와 관련된 설정도 하는 루틴이 담겨있다.
    • arch_name:
      • 문자열 “armv7″을 가리키는 주소가 담김 (.rodata 섹션)
    • elf_name:
      • 문자열 “v7″을 가리키는 주소가 담김 (.rodata 섹션)
    • elf_hwcap:
      • 프로세서 아키텍처의 하드웨어 캐파(h/w capability)
      • rpi2: 0x168096
        • HWCAP_LPAE(b20) | HWCAP_IDIVT(b18) | HWCAP_IDIVA(b17) |  HWCAP_TLS(b15) | HWCAP_EDSP(b7) | HWCAP_FAST_MULT(b4) | HWCAP_THUMB(b2) | HWCAP_HALF(b1)
    • cpu_name:
      • 문자열 “ARMv7 Processor”를 가리키는 주소가 담김
    • proc:
      • v7_processor_functions 객체를 가리키는 processor 구조체 포인터
    • tlb:
      • v7wbi_tlb_fns 객체를 가리키는 cpu_tlb_fns 구조체 포인터
    • user:
      • v6_user_fns 객체를 가리키는 cpu_user_fns 구조체 포인터
    • cache:
      • v7_cache_fns 객체를 가리키는 cpu_cache_fns 구조체 포인터

arch/arm/include/asm/procinfo.h

/*
 * Note!  struct processor is always defined if we're
 * using MULTI_CPU, otherwise this entry is unused,
 * but still exists.
 *
 * NOTE! The following structure is defined by assembly
 * language, NOT C code.  For more information, check:
 *  arch/arm/mm/proc-*.S and arch/arm/kernel/head.S
 */
struct proc_info_list {
        unsigned int            cpu_val;
        unsigned int            cpu_mask;
        unsigned long           __cpu_mm_mmu_flags;     /* used by head.S */
        unsigned long           __cpu_io_mmu_flags;     /* used by head.S */
        unsigned long           __cpu_flush;            /* used by head.S */
        const char              *arch_name;
        const char              *elf_name;
        unsigned int            elf_hwcap;
        const char              *cpu_name;
        struct processor        *proc;
        struct cpu_tlb_fns      *tlb;
        struct cpu_user_fns     *user;
        struct cpu_cache_fns    *cache;
};

 

__v7_ca7mp_proc_info

  • .proc.info.init 섹션에 저장된다.
  • proc_info_list 구조체와 동일
  • 라즈베리파이2: ARM Cortex A7

arch/arm/mm/proc-v7.S

        /*
         * ARM Ltd. Cortex A7 processor.
         */
        .type   __v7_ca7mp_proc_info, #object
__v7_ca7mp_proc_info:
        .long   0x410fc070
        .long   0xff0ffff0
        __v7_proc __v7_ca7mp_setup
        .size   __v7_ca7mp_proc_info, . - __v7_ca7mp_proc_info

 

__v7_proc 매크로

arch/arm/mm/proc-v7.S

  • 라즈베리파이2:
    • cpu_arch_name: 문자열 “armv7″을 가리키는 주소가 담김 (.rodata 섹션)
    • cpu_elf_name: 문자열 “v7″을 가리키는 주소가 담김 (.rodata 섹션)
.macro __v7_proc initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0, proc_fns = v7_processor_functions
        ALT_SMP(.long   PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
                        PMD_SECT_AF | PMD_FLAGS_SMP | \mm_mmuflags)
        ALT_UP(.long    PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
                        PMD_SECT_AF | PMD_FLAGS_UP | \mm_mmuflags)
        .long   PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \
                PMD_SECT_AP_READ | PMD_SECT_AF | \io_mmuflags
        W(b)    \initfunc
        .long   cpu_arch_name
        .long   cpu_elf_name
        .long   HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \
                HWCAP_EDSP | HWCAP_TLS | \hwcaps
        .long   cpu_v7_name
        .long   \proc_fns
        .long   v7wbi_tlb_fns
        .long   v6_user_fns
        .long   v7_cache_fns
.endm

 

define_processor_functions 매크로

  • <name>_processor_functions라는 오브젝트를 만들어낸다.
    • 라즈베리파이2: v7_processor_functions
  • processor 구조체와 동일
  • .init.data 섹션에 저장됨

arch/arm/mm/proc-v7.S

        @ define struct processor (see <asm/proc-fns.h> and proc-macros.S)
        define_processor_functions v7, dabort=v7_early_abort, pabort=v7_pabort, suspend=1

arch/arm/mm/proc-macros.S

.macro define_processor_functions name:req, dabort:req, pabort:req, nommu=0, suspend=0
        .type   \name\()_processor_functions, #object
        .align 2
ENTRY(\name\()_processor_functions)
        .word   \dabort
        .word   \pabort
        .word   cpu_\name\()_proc_init
        .word   cpu_\name\()_proc_fin
        .word   cpu_\name\()_reset
        .word   cpu_\name\()_do_idle
        .word   cpu_\name\()_dcache_clean_area
        .word   cpu_\name\()_switch_mm

        .if \nommu
        .word   0
        .else
        .word   cpu_\name\()_set_pte_ext
        .endif

        .if \suspend
        .word   cpu_\name\()_suspend_size
#ifdef CONFIG_ARM_CPU_SUSPEND
        .word   cpu_\name\()_do_suspend
        .word   cpu_\name\()_do_resume
#else
        .word   0
        .word   0
#endif
        .else
        .word   0
        .word   0
        .word   0
        .endif

        .size   \name\()_processor_functions, . - \name\()_processor_functions
.endm

 

processor 구조체

arch/arm/include/asm/proc-fns.h

/*
 * Don't change this structure - ASM code relies on it.
 */
extern struct processor {
        /* MISC
         * get data abort address/flags
         */
        void (*_data_abort)(unsigned long pc);
        /*
         * Retrieve prefetch fault address
         */
        unsigned long (*_prefetch_abort)(unsigned long lr);
        /*
         * Set up any processor specifics
         */
        void (*_proc_init)(void);
        /*
         * Disable any processor specifics
         */
        void (*_proc_fin)(void);
        /*
         * Special stuff for a reset
         */
        void (*reset)(unsigned long addr) __attribute__((noreturn));
        /*
         * Idle the processor
         */
        int (*_do_idle)(void);
        /*
         * Processor architecture specific
         */
        /*
         * clean a virtual address range from the
         * D-cache without flushing the cache.
         */
        void (*dcache_clean_area)(void *addr, int size);

        /*
         * Set the page table
         */
        void (*switch_mm)(phys_addr_t pgd_phys, struct mm_struct *mm);
        /*
         * Set a possibly extended PTE.  Non-extended PTEs should
         * ignore 'ext'.
         */
#ifdef CONFIG_ARM_LPAE
        void (*set_pte_ext)(pte_t *ptep, pte_t pte);
#else
        void (*set_pte_ext)(pte_t *ptep, pte_t pte, unsigned int ext);
#endif

        /* Suspend/resume */
        unsigned int suspend_size;
        void (*do_suspend)(void *);
        void (*do_resume)(void *);
} processor;

 

cpu_user_fns 구조체

arch/arm/mm/copypage-v6.c

struct cpu_user_fns v6_user_fns __initdata = {
        .cpu_clear_user_highpage = v6_clear_user_highpage_nonaliasing,
        .cpu_copy_user_highpage = v6_copy_user_highpage_nonaliasing,
};

 

define_tlb_functions 매크로

  • cpu_tlb_fns 구조체와 동일
  • v7wbi_tlb_fns 오브젝트가 만들어진다.
  • .init.data 섹션에 저장됨

arch/arm/mm/tlb-v7.S

        /* define struct cpu_tlb_fns (see <asm/tlbflush.h> and proc-macros.S) */
        define_tlb_functions v7wbi, v7wbi_tlb_flags_up, flags_smp=v7wbi_tlb_flags_smp

arch/arm/mm/proc-macros.S

.macro define_tlb_functions name:req, flags_up:req, flags_smp
        .type   \name\()_tlb_fns, #object
ENTRY(\name\()_tlb_fns)
        .long   \name\()_flush_user_tlb_range
        .long   \name\()_flush_kern_tlb_range
        .ifnb \flags_smp
                ALT_SMP(.long   \flags_smp )
                ALT_UP(.long    \flags_up )
        .else
                .long   \flags_up
        .endif
        .size   \name\()_tlb_fns, . - \name\()_tlb_fns
.endm

 

cpu_tlb_fns 구조체

arch/arm/include/asm/tlbflush.h

struct cpu_tlb_fns {
        void (*flush_user_range)(unsigned long, unsigned long, struct vm_area_struct *);
        void (*flush_kern_range)(unsigned long, unsigned long);
        unsigned long tlb_flags;
};

 

define_cache_functions 매크로

  • cpu_cache_fns 구조체와 동일
  • 라즈베리파이2: v7_cache_fns 오브젝트가 만들어진다.
  • .init.data 섹션에 저장됨

arch/arm/mm/cache-v7.S

        @ define struct cpu_cache_fns (see <asm/cacheflush.h> and proc-macros.S)
        define_cache_functions v7

arch/arm/mm/proc-macros.S

.macro define_cache_functions name:req
        .align 2
        .type   \name\()_cache_fns, #object
ENTRY(\name\()_cache_fns)
        .long   \name\()_flush_icache_all
        .long   \name\()_flush_kern_cache_all
        .long   \name\()_flush_kern_cache_louis
        .long   \name\()_flush_user_cache_all
        .long   \name\()_flush_user_cache_range
        .long   \name\()_coherent_kern_range
        .long   \name\()_coherent_user_range
        .long   \name\()_flush_kern_dcache_area
        .long   \name\()_dma_map_area
        .long   \name\()_dma_unmap_area
        .long   \name\()_dma_flush_range
        .size   \name\()_cache_fns, . - \name\()_cache_fns
.endm

 

cpu_cache_fns 구조체

arch/arm/include/asm/cacheflush.h

struct cpu_cache_fns {
        void (*flush_icache_all)(void);
        void (*flush_kern_all)(void);
        void (*flush_kern_louis)(void);
        void (*flush_user_all)(void);
        void (*flush_user_range)(unsigned long, unsigned long, unsigned int);

        void (*coherent_kern_range)(unsigned long, unsigned long);
        int  (*coherent_user_range)(unsigned long, unsigned long);
        void (*flush_kern_dcache_area)(void *, size_t);

        void (*dma_map_area)(const void *, size_t, int);
        void (*dma_unmap_area)(const void *, size_t, int);

        void (*dma_flush_range)(const void *, const void *);
};

 

cpuid_init_hwcaps()

  • ARMv7 이상의 CPU 아키텍처에서만 동작한다.
  • read_cpuid_ext()를 사용하여 레지스터 ISAR0.Devide_instrs를 읽어와서 아래 기능을 지원하면 elf_hwcap에 추가
    • HWCAP_IDIVA
      • 이 기능이 선택되면 HWCPA_IDIVT도 자동 선택된다.
    • HWCPA_IDIVT
  • read_cpuid_ext()를 사용하여 레지스터 MMFR0.vmsa를 읽어와서 값이 5(Long-descriptor translation table 지원)이상인 경우 hwcap에 추가
    • HWCAP_LPAE
      • 라즈베리파이2(bcm2709)의 경우 LPAE가 support된다.
  • 라즈베리파이2: 0x168097
    • HWCAP_LPAE(b20) | HWCAP_IDIVT(b18) | HWCAP_IDIVA(b17) |  HWCAP_TLS(b15) | HWCAP_EDSP(b7) | HWCAP_FAST_MULT(b4) | HWCAP_THUMB(b2) | HWCAP_HALF(b1) | HWCAP_SWP(b0)

arch/arm/kernel/setup.c

static void __init cpuid_init_hwcaps(void)
{
        unsigned int divide_instrs, vmsa;

        if (cpu_architecture() < CPU_ARCH_ARMv7)
                return;

        divide_instrs = (read_cpuid_ext(CPUID_EXT_ISAR0) & 0x0f000000) >> 24;

        switch (divide_instrs) {
        case 2:
                elf_hwcap |= HWCAP_IDIVA;
        case 1:
                elf_hwcap |= HWCAP_IDIVT;
        }

        /* LPAE implies atomic ldrd/strd instructions */
        vmsa = (read_cpuid_ext(CPUID_EXT_MMFR0) & 0xf) >> 0;
        if (vmsa >= 5)
                elf_hwcap |= HWCAP_LPAE;
}

 

elf_hwcap_fixup()

  • read_cpu_id()를 사용하여 레지스터 MIDR을 읽어 cpu id값을 읽어온다.
  • read_cpuid_part()를 사용하여 레지스터 MIDR의 CPU 파트를 비교하여 ARM1136이고 리비전코드가 r1p0 미만인 경우 elf_hwcap 에 HWCAP_TLS를 제거하고 루틴을 빠져나간다. 루틴을 빠져나가는 이유는 해당 조건의 CPU는 exclusive loads and store(ldrex/strex)를 지원하는 명령들이 없기 때문에 다음으로 이어지는 루틴을 실행할 필요가 없다.
    • rpi2: TLS 레지스터를 지원한다. (CP15의 TPIDRURO 레지스터)
  • read_cpuid_ext()를 사용하여 레지스터 ISAR3.SynchPrim_instrs 값 + ISAR4.SynchPrim_instrs를 읽어와서 0x13보다 크면 LDREX/STREX 명령이 지원되는 것으로 판단하여 elf_hwcap 에 HWCAP_SWP를 제거한다.
  • 라즈베리파이2: 0x168096
    • HWCAP_LPAE(b20) | HWCAP_IDIVT(b18) | HWCAP_IDIVA(b17) |  HWCAP_TLS(b15) | HWCAP_EDSP(b7) | HWCAP_FAST_MULT(b4) | HWCAP_THUMB(b2) | HWCAP_HALF(b1) | HWCAP_SWP(b0)

arch/arm/kernel/setup.c

static void __init elf_hwcap_fixup(void)
{
        unsigned id = read_cpuid_id();
        unsigned sync_prim;

        /*
         * HWCAP_TLS is available only on 1136 r1p0 and later,
         * see also kuser_get_tls_init.
         */
        if (read_cpuid_part() == ARM_CPU_PART_ARM1136 &&
            ((id >> 20) & 3) == 0) {
                elf_hwcap &= ~HWCAP_TLS;
                return;
        }

        /* Verify if CPUID scheme is implemented */
        if ((id & 0x000f0000) != 0x000f0000)
                return;

        /*
         * If the CPU supports LDREX/STREX and LDREXB/STREXB,
         * avoid advertising SWP; it may not be atomic with
         * multiprocessing cores.
         */
        sync_prim = ((read_cpuid_ext(CPUID_EXT_ISAR3) >> 8) & 0xf0) |
                    ((read_cpuid_ext(CPUID_EXT_ISAR4) >> 20) & 0x0f);
        if (sync_prim >= 0x13)
                elf_hwcap &= ~HWCAP_SWP;
}

 

init_default_cache_policy()

  • 전역 변수 unsigned int 선언된 cachepolicy 값 결정
    •  메모리 속성(pmd 엔트리 속성)을 알아내고 미리 정의되어 있는 cache_policies[].pmd와 일치하는 배열 인덱스를 찾아낸다.
  • 라즈베리파이2: cachepolicy <- 구조체 배열의 인덱스인 4 (CPOLICY_WRITEALLOC 캐시 정책)
    • .policy = “writealloc”
    • .cr_mask = 0
    • .pmd = PMD_SECT_WBWA
    • .pte = L_PTE_MT_WRITEALLOC
    • .pte_s2 = s2_policy(L_PTE_S2_MT_WRITEBACK)

arch/arm/mm/mmu.c

static unsigned long initial_pmd_value __initdata = 0;

/*
 * Initialise the cache_policy variable with the initial state specified
 * via the "pmd" value.  This is used to ensure that on ARMv6 and later,
 * the C code sets the page tables up with the same policy as the head
 * assembly code, which avoids an illegal state where the TLBs can get
 * confused.  See comments in early_cachepolicy() for more information.
 */
void __init init_default_cache_policy(unsigned long pmd)
{
        int i;

        initial_pmd_value = pmd;

        pmd &= PMD_SECT_TEX(1) | PMD_SECT_BUFFERABLE | PMD_SECT_CACHEABLE;

        for (i = 0; i < ARRAY_SIZE(cache_policies); i++)
                if (cache_policies[i].pmd == pmd) {
                        cachepolicy = i;
                        break;
                }

        if (i == ARRAY_SIZE(cache_policies))
                pr_err("ERROR: could not find cache policy\n");
}

 

cacheid_init()

  • read_cpuid_cachetype()를 통해 캐시 타입을 읽어와서 cacheid에 data 캐시와 instruction 캐시 형태를 설정한다.
  • cacheid 전역변수 값에 캐시 형태를 설정한다.
    • bit0~2: d-cache 플래그
    • bit3~5: i-cache 플래그
    • 라즈베리파이2:
      • L1 d-cache: CACHEID_VIPT_NONALIASING
      • L1 i-cache: CACHEID_VIPT_I_ALIASING
        • L1 명령 캐시가 캐시 aliasing이 필요한 캐시이다.
          • cache aliasing 용어 대신 page coloring 또는 cache coloring이라는 용어를 사용하기도 한다.
  • cacheid 관련 define
    • CACHEID_VIVT
    • CACHEID_VIPT_NONALIASING
    • CACHEID_VIPT_ALIASING
    • CACHEID_VIPT
    • CACHEID_ASID_TAGGED
    • CACHEID_VIPT_I_ALIASING
    • CACHEID_PIPT

arch/arm/kernel/setup.c

static void __init cacheid_init(void)
{
        unsigned int arch = cpu_architecture();

        if (arch == CPU_ARCH_ARMv7M) {
                cacheid = 0;
        } else if (arch >= CPU_ARCH_ARMv6) {
                unsigned int cachetype = read_cpuid_cachetype();
                if ((cachetype & (7 << 29)) == 4 << 29) {
                        /* ARMv7 register format */
                        arch = CPU_ARCH_ARMv7;
                        cacheid = CACHEID_VIPT_NONALIASING;
                        switch (cachetype & (3 << 14)) {
                        case (1 << 14):
                                cacheid |= CACHEID_ASID_TAGGED;
                                break;
                        case (3 << 14):
                                cacheid |= CACHEID_PIPT;
                                break;
                        }
                } else {
                        arch = CPU_ARCH_ARMv6;
                        if (cachetype & (1 << 23))
                                cacheid = CACHEID_VIPT_ALIASING;
                        else
                                cacheid = CACHEID_VIPT_NONALIASING;
                }
                if (cpu_has_aliasing_icache(arch))
                        cacheid |= CACHEID_VIPT_I_ALIASING;
        } else {
                cacheid = CACHEID_VIVT;
        }

        pr_info("CPU: %s data cache, %s instruction cache\n",
                cache_is_vivt() ? "VIVT" :
                cache_is_vipt_aliasing() ? "VIPT aliasing" :
                cache_is_vipt_nonaliasing() ? "PIPT / VIPT nonaliasing" : "unknown",
                cache_is_vivt() ? "VIVT" :
                icache_is_vivt_asid_tagged() ? "VIVT ASID tagged" :
                icache_is_vipt_aliasing() ? "VIPT aliasing" :
                icache_is_pipt() ? "PIPT" :
                cache_is_vipt_nonaliasing() ? "VIPT nonaliasing" : "unknown");
}

 

 cpu_init()

  • 하나의 CPU 설정을 초기화한다.
  • set_my_cpu_offset()
    • TPIDRPRW(Thread ID  레지스터)를 사용하여 per-CPU 스택을 설정한다.
  • cpu_proc_init()
    • 아키텍처에 따른 프로세서 초기화 루틴 수행
      • 아키텍처가 커널 빌드 시 고정된 경우
        • #define cpu_proc_init                   __glue(CPU_NAME,_proc_init)
          • #define __glue(name,fn)         ____glue(name,fn)
            • #define ____glue(name,fn)       name##fn
      • 아키텍처가 커널 빌드 시 고정되지 않고 MULTI_CPU를 사용한 경우 (Device Tree를 사용하는 경우 MULTI_CPU를 사용한다)
        • #define cpu_proc_init                processor._proc_init
          • cpu_v7_proc_init()
            • 함수 내부에서는 아무것도 하지 않고 그냥 함수를 리턴한다.
      • 라즈베리파이2:
        • CONFIG_CPU_V7를 사용하면 MULTI_CPU가 define 되고 따라서 processor._proc_init 구조체를 통하여 cpu_v7_proc_init() 호출
  • 아래 모드들 전환한 후 재진입 exception 핸들러들을 위해 스택을 설정하고 마지막으로 다시 SVC_MODE로 돌아온다.
    • IRQ_MODE
    • ABT_MODE
    • UND_MODE
    • FIQ_MODE

arch/arm/kernel/setup.c

/*
 * cpu_init - initialise one CPU.
 *
 * cpu_init sets up the per-CPU stacks.
 */
void notrace cpu_init(void)
{
#ifndef CONFIG_CPU_V7M
        unsigned int cpu = smp_processor_id();
        struct stack *stk = &stacks[cpu];

        if (cpu >= NR_CPUS) {
                pr_crit("CPU%u: bad primary CPU number\n", cpu);
                BUG();
        }

        /*
         * This only works on resume and secondary cores. For booting on the
         * boot cpu, smp_prepare_boot_cpu is called after percpu area setup.
         */
        set_my_cpu_offset(per_cpu_offset(cpu));

        cpu_proc_init();

        /*
         * Define the placement constraint for the inline asm directive below.
         * In Thumb-2, msr with an immediate value is not allowed.
         */
#ifdef CONFIG_THUMB2_KERNEL
#define PLC     "r"
#else
#define PLC     "I"
#endif

        /*
         * setup stacks for re-entrant exception handlers
         */
        __asm__ (
        "msr    cpsr_c, %1\n\t"
        "add    r14, %0, %2\n\t"
        "mov    sp, r14\n\t"
        "msr    cpsr_c, %3\n\t"
        "add    r14, %0, %4\n\t"
        "mov    sp, r14\n\t"
        "msr    cpsr_c, %5\n\t"
        "add    r14, %0, %6\n\t"
        "mov    sp, r14\n\t"
        "msr    cpsr_c, %7\n\t"
        "add    r14, %0, %8\n\t"
        "mov    sp, r14\n\t"
        "msr    cpsr_c, %9"
            :
            : "r" (stk),
              PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),
              "I" (offsetof(struct stack, irq[0])),
              PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),
              "I" (offsetof(struct stack, abt[0])),
              PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),
              "I" (offsetof(struct stack, und[0])),
              PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE),
              "I" (offsetof(struct stack, fiq[0])),
              PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE)
            : "r14");
#endif
}

 

관련 전역 변수

arch/arm/kernel/setup.c

unsigned int processor_id;
EXPORT_SYMBOL(processor_id);
unsigned int __machine_arch_type __read_mostly;
EXPORT_SYMBOL(__machine_arch_type);
unsigned int cacheid __read_mostly;
EXPORT_SYMBOL(cacheid);

unsigned int __atags_pointer __initdata;

unsigned int system_rev;
EXPORT_SYMBOL(system_rev);

unsigned int system_serial_low;
EXPORT_SYMBOL(system_serial_low);

unsigned int system_serial_high;
EXPORT_SYMBOL(system_serial_high);

unsigned int elf_hwcap __read_mostly;
EXPORT_SYMBOL(elf_hwcap);

unsigned int elf_hwcap2 __read_mostly;
EXPORT_SYMBOL(elf_hwcap2);


#ifdef MULTI_CPU
struct processor processor __read_mostly;
#endif
#ifdef MULTI_TLB
struct cpu_tlb_fns cpu_tlb __read_mostly;
#endif
#ifdef MULTI_USER
struct cpu_user_fns cpu_user __read_mostly;
#endif
#ifdef MULTI_CACHE
struct cpu_cache_fns cpu_cache __read_mostly;
#endif
#ifdef CONFIG_OUTER_CACHE
struct outer_cache_fns outer_cache __read_mostly;
EXPORT_SYMBOL(outer_cache);
#endif

 

__read_mostly

참고: 함수선언부 관련 매크로-1 | 문c

Bitmap Operations

<kernel v5.10>

Bitmap Operations

  • 비트맵은 하나 이상의 unsigned long 타입을 사용한다.
  • nbits는 항상 컴파일 시 상수로 평가되어야 한다. 그렇지 않고 변수로 평가되는 경우 많은 inline으로 인해 코드 크기가 매우 커진다.
  • nbits가 1개의 unsigned long 데이터 타입으로 취급할 수 있는 경우와 그렇지 않은 경우의 구현 루틴이 나뉘어 있다.
    • 1개의 unsigned long 데이터로 처리하는 경우 보통 한 번의 조작 루틴을 사용하여 빠른 처리가 가능하다.
    • 여러 개의 unsigned long 데이터로 처리하는 경우 다량의 데이터 처리를 하느라 느리다.

 

bitmap 아키텍처별 헤더 및 c 파일

비트맵을 다루는 api들은 bitmap operation과 bit operations 양쪽에 구현되어 있다.
bitmap operation은 다음과 같이 기본 구현 헤더와 generic 라이브러리 파일로 준비되어 있다.
  • 기본 구현 헤더
    • include/linux/bitmap.h
  • Generic 라이브러리
    • lib/bitmap.c

 

비트맵 선언

DECLARE_BITMAP()

include/linux/bitmap.h

#define DECLARE_BITMAP(name,bits) \
        unsigned long name[BITS_TO_LONGS(bits)]

정적 비트맵을 선언하는 매크로 함수이다.

  • 예) DECLARE_BITMAP(foo, 96);
    • unsigned long foo[2];     (64bits 기준)

 

비트맵 API들

include/linux/bitmap.h

/**
 * DOC: bitmap overview
 *
 * The available bitmap operations and their rough meaning in the
 * case that the bitmap is a single unsigned long are thus:
 *
 * The generated code is more efficient when nbits is known at
 * compile-time and at most BITS_PER_LONG.
 *
 * ::
 *
 *  bitmap_zero(dst, nbits)                     *dst = 0UL
 *  bitmap_fill(dst, nbits)                     *dst = ~0UL
 *  bitmap_copy(dst, src, nbits)                *dst = *src
 *  bitmap_and(dst, src1, src2, nbits)          *dst = *src1 & *src2
 *  bitmap_or(dst, src1, src2, nbits)           *dst = *src1 | *src2
 *  bitmap_xor(dst, src1, src2, nbits)          *dst = *src1 ^ *src2
 *  bitmap_andnot(dst, src1, src2, nbits)       *dst = *src1 & ~(*src2)
 *  bitmap_complement(dst, src, nbits)          *dst = ~(*src)
 *  bitmap_equal(src1, src2, nbits)             Are *src1 and *src2 equal?
 *  bitmap_intersects(src1, src2, nbits)        Do *src1 and *src2 overlap?
 *  bitmap_subset(src1, src2, nbits)            Is *src1 a subset of *src2?
 *  bitmap_empty(src, nbits)                    Are all bits zero in *src?
 *  bitmap_full(src, nbits)                     Are all bits set in *src?
 *  bitmap_weight(src, nbits)                   Hamming Weight: number set bits
 *  bitmap_set(dst, pos, nbits)                 Set specified bit area
 *  bitmap_clear(dst, pos, nbits)               Clear specified bit area
 *  bitmap_find_next_zero_area(buf, len, pos, n, mask)  Find bit free area
 *  bitmap_find_next_zero_area_off(buf, len, pos, n, mask, mask_off)  as above
 *  bitmap_next_clear_region(map, &start, &end, nbits)  Find next clear region
 *  bitmap_next_set_region(map, &start, &end, nbits)  Find next set region
 *  bitmap_for_each_clear_region(map, rs, re, start, end)
 *                                              Iterate over all clear regions
 *  bitmap_for_each_set_region(map, rs, re, start, end)
 *                                              Iterate over all set regions
 *  bitmap_shift_right(dst, src, n, nbits)      *dst = *src >> n
 *  bitmap_shift_left(dst, src, n, nbits)       *dst = *src << n
 *  bitmap_cut(dst, src, first, n, nbits)       Cut n bits from first, copy rest
 *  bitmap_replace(dst, old, new, mask, nbits)  *dst = (*old & ~(*mask)) | (*new & *mask)
 *  bitmap_remap(dst, src, old, new, nbits)     *dst = map(old, new)(src)
 *  bitmap_bitremap(oldbit, old, new, nbits)    newbit = map(old, new)(oldbit)
 *  bitmap_onto(dst, orig, relmap, nbits)       *dst = orig relative to relmap
 *  bitmap_fold(dst, orig, sz, nbits)           dst bits = orig bits mod sz
 *  bitmap_parse(buf, buflen, dst, nbits)       Parse bitmap dst from kernel buf
 *  bitmap_parse_user(ubuf, ulen, dst, nbits)   Parse bitmap dst from user buf
 *  bitmap_parselist(buf, dst, nbits)           Parse bitmap dst from kernel buf
 *  bitmap_parselist_user(buf, dst, nbits)      Parse bitmap dst from user buf
 *  bitmap_find_free_region(bitmap, bits, order)  Find and allocate bit region
 *  bitmap_release_region(bitmap, pos, order)   Free specified bit region
 *  bitmap_allocate_region(bitmap, pos, order)  Allocate specified bit region
 *  bitmap_from_arr32(dst, buf, nbits)          Copy nbits from u32[] buf to dst
 *  bitmap_to_arr32(buf, src, nbits)            Copy nbits from buf to u32[] dst
 *  bitmap_get_value8(map, start)               Get 8bit value from map at start
 *  bitmap_set_value8(map, value, start)        Set 8bit value to map at start
 *
 * Note, bitmap_zero() and bitmap_fill() operate over the region of
 * unsigned longs, that is, bits behind bitmap till the unsigned long
 * boundary will be zeroed or filled as well. Consider to use
 * bitmap_clear() or bitmap_set() to make explicit zeroing or filling
 * respectively.
 */

 

bitmap_zero()

include/linux/bitmap.h

static inline void bitmap_zero(unsigned long *dst, unsigned int nbits)
{
        unsigned int len = BITS_TO_LONGS(nbits) * sizeof(unsigned long);
        memset(dst, 0, len);
}

비트맵 @dst의 @nbits 만큼의 비트를 0으로 클리어한다.

 

small_const_nbits()

include/linux/bitmap.h

#define small_const_nbits(nbits) \
        (__builtin_constant_p(nbits) && (nbits) <= BITS_PER_LONG && (nbits) > 0)

CPU 아키텍처가 한 번의 operation으로 비트맵을 처리할 수 있는지 여부를 판단한다.

  • 먼저 __builtin_constant_p()를 사용하여 인수가 상수(변수가 아닌)인지를 알아낸다.
    • 인수가 상수인 경우 true를 리턴하고,
    • 변수인 경우는 무조건 false를 리턴한다.
      • BITS_PER_LONG(32 or 64) 값 보다 작거나 같은 경우 true

 

비트맵 bitops API들

아래는 bitmap에서 사용할 수 있는 bitops 함수들이다.

  • 상위 7개의 비트맵 조작 함수는 atomic 하게 처리하도록 구현되어 있다.
  • 하위 5개의 비트맵 검색용 함수이다.
  • 참고: Bit Operations | 문c

 

include/linux/bitmap.h

/**
 * DOC: bitmap bitops
 *
 * Also the following operations in asm/bitops.h apply to bitmaps.::
 *
 *  set_bit(bit, addr)                  *addr |= bit
 *  clear_bit(bit, addr)                *addr &= ~bit
 *  change_bit(bit, addr)               *addr ^= bit
 *  test_bit(bit, addr)                 Is bit set in *addr?
 *  test_and_set_bit(bit, addr)         Set bit and return old value
 *  test_and_clear_bit(bit, addr)       Clear bit and return old value
 *  test_and_change_bit(bit, addr)      Change bit and return old value
 *  find_first_zero_bit(addr, nbits)    Position first zero bit in *addr
 *  find_first_bit(addr, nbits)         Position first set bit in *addr
 *  find_next_zero_bit(addr, nbits, bit)
 *                                      Position next zero bit in *addr >= bit
 *  find_next_bit(addr, nbits, bit)     Position next set bit in *addr >= bit
 *  find_next_and_bit(addr1, addr2, nbits, bit)
 *                                      Same as find_next_bit, but in
 *                                      (*addr1 & *addr2)
 */

 


비트맵 카운트

bitmap_weight()

include/linux/bitmap.h

static inline int bitmap_weight(const unsigned long *src, unsigned int nbits)
{
        if (small_const_nbits(nbits))
                return hweight_long(*src & BITMAP_LAST_WORD_MASK(nbits));
        return __bitmap_weight(src, nbits);
}

src 비트맵의 nbits 이내에서 1로 설정되어 있는 bit 수를 리턴한다.

 

__bitmap_weight()

lib/bitmap.c

int __bitmap_weight(const unsigned long *bitmap, unsigned int bits)
{
        unsigned int k, lim = bits/BITS_PER_LONG;
        int w = 0;

        for (k = 0; k < lim; k++)
                w += hweight_long(bitmap[k]);

        if (bits % BITS_PER_LONG)
                w += hweight_long(bitmap[k] & BITMAP_LAST_WORD_MASK(bits));

        return w;
}
EXPORT_SYMBOL(__bitmap_weight);

bitmap의 nbits 이내에서 1로 설정되어 있는 bit 수를 리턴한다.

 


할당할 연속된 공간 찾기

bitmap_find_next_zero_area()

include/linux/bitmap.h

/**                     
 * bitmap_find_next_zero_area - find a contiguous aligned zero area
 * @map: The address to base the search on
 * @size: The bitmap size in bits
 * @start: The bitnumber to start searching at
 * @nr: The number of zeroed bits we're looking for
 * @align_mask: Alignment mask for zero area
 *
 * The @align_mask should be one less than a power of 2; the effect is that
 * the bit offset of all zero areas this function finds is multiples of that
 * power of 2. A @align_mask of 0 means no alignment is required.
 */
static inline unsigned long
bitmap_find_next_zero_area(unsigned long *map,
                           unsigned long size,
                           unsigned long start,
                           unsigned int nr,  
                           unsigned long align_mask)
{
        return bitmap_find_next_zero_area_off(map, size, start, nr,
                                              align_mask, 0);
}

bitmap에서 제한된 size 범위내에서 start 위치부터 align_mask 된 연속된 nr 갯수의 0 비트를 찾아 그 비트 위치를 반환한다. (based 0)

 

bitmap_find_next_zero_area_off()

lib/bitmap.c

/**                     
 * bitmap_find_next_zero_area_off - find a contiguous aligned zero area
 * @map: The address to base the search on
 * @size: The bitmap size in bits
 * @start: The bitnumber to start searching at
 * @nr: The number of zeroed bits we're looking for
 * @align_mask: Alignment mask for zero area
 * @align_offset: Alignment offset for zero area.
 *
 * The @align_mask should be one less than a power of 2; the effect is that
 * the bit offset of all zero areas this function finds plus @align_offset
 * is multiple of that power of 2.
 */
unsigned long bitmap_find_next_zero_area_off(unsigned long *map,
                                             unsigned long size,
                                             unsigned long start,
                                             unsigned int nr,
                                             unsigned long align_mask,
                                             unsigned long align_offset)
{       
        unsigned long index, end, i;          
again:          
        index = find_next_zero_bit(map, size, start);

        /* Align allocation */
        index = __ALIGN_MASK(index + align_offset, align_mask) - align_offset;
                        
        end = index + nr;
        if (end > size)
                return end;
        i = find_next_bit(map, end, index); 
        if (i < end) {
                start = i + 1; 
                goto again;
        }
        return index;
}  
EXPORT_SYMBOL(bitmap_find_next_zero_area_off);

bitmap에서 제한된 size 범위내에서 start 위치부터 align_mask 된 연속된 nr 갯수의 0 비트를 찾아 그 비트 위치를 반환한다. (based 0)

  • bitmap_find_next_zero_area() 함수와 다르게 align_offset을 추가하여 align_mask로 정렬 시 align_offset을 더해 정렬한 후 다시 뺀다.
    • cma_alloc() 함수에서 사용된다.

 

BITMAP_FIRST_WORD_MASK()

include/linux/bitmap.h

#define BITMAP_FIRST_WORD_MASK(start) (~0UL << ((start) & (BITS_PER_LONG - 1)))

@start 이상의 비트들이 모두 1로 설정된 값에서, @start가 위치한 처음 워드(unsigned long)만을 반환한다.

 

예) 32비트 시스템에서 인수에 따른 결과는

  • start = 0: 0xffff_ffff (0이 아님에 주의)
  • start = 1: 0xffff_fffe
  • start = 16: 0xffff_0000
  • start = 32: 0xffff_ffff
  • start = 40: 0xffff_ff00

 

예) 64비트 시스템에서 인수에 따른 결과는

  • start = 0: 0xffff_ffff_ffff_ffff (0이 아님에 주의)
  • start = 1: 0xffff_ffff_ffff_fffe
  • start = 16: 0xffff_ffff_ffff_0000
  • start = 32: 0xffff_ffff_0000_0000
  • start = 40: 0xffff_ff00_0000_0000
  • start = 66: 0xffff_ffff_ffff_fffc

 

BITMAP_LAST_WORD_MASK()

include/linux/bitmap.h

#define BITMAP_LAST_WORD_MASK(nbits) (~0UL >> (-(nbits) & (BITS_PER_LONG - 1)))

@nbits 이하의 비트들이 모두 1로 설정된 값에서, @nbits가 위치한 마지막 워드(unsigned long)만을 반환한다.

 

예) 32비트 시스템에서 인수에 따른 결과는

  • nbits = 0: 0xffff_ffff (0이 아님에 주의)
  • nbits = 1: 0x0000_0001
  • nbits = 16: 0x0000_ffff
  • nbits = 32: 0xffff_ffff
  • nbits = 40: 0x0000_000f

 

예) 64비트 시스템에서 인수에 따른 결과는

  • nbits = 0: 0xffff_ffff_ffff_ffff (0이 아님에 주의)
  • nbits = 1: 0x0000_0000_0000_0001
  • nbits = 16: 0x0000_0000_0000_ffff
  • nbits = 32: 0x0000_0000_ffff_ffff
  • nbits = 40: 0x0000_00ff_ffff_ffff
  • nbits = 66: 0x0000_0000_0000_0003

 

참고

 

Sparse

특징

  • Sparse는 MIT가 라이센스를 가지고 있는 코드 정적 분석 도구이다
    • 2003년 토발즈가 작성하기 시작
    • 2006년 부터 메인테이너로 Triplett가 관리
    • 2009년부터 Christopher Li가 관리
  • 설치 시 gcc 확장 형태로 내장 속성이 추가된다.
    • address_space: 섞여서 사용되는 포인터의 주소 공간 체크
    • noderef: dereferencing 금지 (*x로 포인터 전달 금지, &x는 가능)
    • context: 동기화(lock) 함수에서 사용
    • bitwise: integer 타입을 제한하여 타입 미스매치를 체크
    • 그 외 safe, force, nocast, 등
  • 다른 C 코드 정적 분석 도구
    • Smatch, Coccinelle, Splint, Uno, BLAST

설치

자동 설치

$ sudo apt-get install sparse

수동 설치

$ git clone git://git.kernel.org/pub/scm/devel/sparse/sparse.git
$ cd sparse
$ make
$ make install

~/.bashrc에 다음 내용을 추가한다.

export PATH=$PATH:$HOME/sparse/bin

 

사용법

  • C 옵션
    • make C=1
    • C=1 옵션은 모든 C 파일, C=2 옵션은 수정된 파일만 컴파일
  • 엔디안 체크 옵션
    • make C=2 CF=”-D__CHECK_ENDIAN__”

 


Sparse 체킹 기능들

타입 체킹

enum 대신 사용할 수 있다.

        typedef int __bitwise pm_request_t;

        enum pm_request {
                PM_SUSPEND = (__force pm_request_t) 1,
                PM_RESUME = (__force pm_request_t) 2
        };

 

        typedef int __bitwise pm_request_t;

        #define PM_SUSPEND ((__force pm_request_t) 1)
        #define PM_RESUME ((__force pm_request_t) 2)

 

타입 선언이 서로 다른 것들을 찾아 경고한다.

void foo(__u64 hostbyte_value)
{
    /* __be64 bigendian_value = cpu_to_be64(hostbyte_value); */
    __be64 bigendian_value = hostbyte_value; /* INCORRECT */
    ...
}

 

타입 선언과 관련한 경고 메시지들

  • warning: restricted degrades to integer
  • warning: restricted degrades to integer
  • error: incompatible types for operation (-)
  • argument has type restricted unsigned short [usertype] a

 

락 체킹

  • __must_hold
    • 함수의 진출입 시 lock이 걸려있어야 한다. (진출입 시 체크)
  • __acquires
    • 함수를 빠져나갈 때에는 lock이 걸려있어야 한다. (진출 시 체크)
  • __releases
    • 함수에 진입하였을 때에는 lock이 걸려있어야 한다. (진입 시 체크)

 

 

주소 공간 체킹

address_space()

다음 항목들은 사용자가 지정한 주소에 대해 gcc 컴파일러로 하여금 사용자의 코드가 적절한 주소 공간을 사용하는 변수를 사용했는지 체크하도록 한다.

  • __user
    • 유저 주소
  • __kernel
    • 커널 주소
  • __iomem
    • io 주소
  • __percpu
    • percpu 주소
  • __rcu
    • rcu 주소

 

다음과 같이 __percpu를 사용한 예를 보여준다.

int *p;
DEFINE_PER_CPU(int , a);
p = this_cpu_ptr(&a);               <--- (O)
p = &a;                             <--- (X) Sparce 정적 툴을 사용하여 컴파일 시 에러 출력

 

주의: int 타입의 per-cpu 변수 a에 대한 실제 주소는 &a – <cpu별  offset>이 사용되는 주소이므로 일반 적인 포인터 변수와 같이 사용될 수 없다.

 


__CHECKER__

sparse 정적 툴 사용 시 __CHECKER__가 define되어 동작한다. 다음 코드는 리눅스에서 Sparse 기능을 사용할 때 사용하는 속성들을 매크로로 정의하였다.

include/linux/compiler_types.h

#ifdef __CHECKER__
# define __user         __attribute__((noderef, address_space(1)))
# define __kernel       __attribute__((address_space(0)))
# define __safe         __attribute__((safe))
# define __force        __attribute__((force))
# define __nocast       __attribute__((nocast))
# define __iomem        __attribute__((noderef, address_space(2)))
# define __must_hold(x) __attribute__((context(x,1,1)))
# define __acquires(x)  __attribute__((context(x,0,1)))
# define __releases(x)  __attribute__((context(x,1,0)))
# define __acquire(x)   __context__(x,1)
# define __release(x)   __context__(x,-1)
# define __cond_lock(x,c)       ((c) ? ({ __acquire(x); 1; }) : 0)
# define __percpu       __attribute__((noderef, address_space(3)))
# define __rcu          __attribute__((noderef, address_space(4)))
# define __private      __attribute__((noderef))
extern void __chk_user_ptr(const volatile void __user *);
extern void __chk_io_ptr(const volatile void __iomem *);
# define ACCESS_PRIVATE(p, member) (*((typeof((p)->member) __force *) &(p)->member))
#else /* __CHECKER__ */
# ifdef STRUCTLEAK_PLUGIN
#  define __user __attribute__((user))
# else
#  define __user
# endif
# define __kernel
# define __safe
# define __force
# define __nocast
# define __iomem
# define __chk_user_ptr(x) (void)0
# define __chk_io_ptr(x) (void)0
# define __builtin_warning(x, y...) (1)
# define __must_hold(x)
# define __acquires(x)
# define __releases(x)
# define __acquire(x) (void)0
# define __release(x) (void)0
# define __cond_lock(x,c) (c)
# define __percpu
# define __rcu
# define __private
# define ACCESS_PRIVATE(p, member) ((p)->member)
#endif /* __CHECKER__ */

 

__bitwise__

include/uapi/linux/types.h

/*
 * Below are truly Linux-specific types that should never collide with
 * any application/library that wants linux/types.h.
 */
#ifdef __CHECKER__
#define __bitwise__ __attribute__((bitwise))
#else
#define __bitwise__
#endif
#define __bitwise __bitwise__

 

참고

 

Exclusive loads and store

Exclusive loads and store (ldrex/strex)

  • ldrex/strex가 쌍이되어 특정 메모리 값을 읽고 저장하기 전에 값이 다른 요인(preemption, 다른 CPU, cache coherent port를 가진 장치 등)에 의해 바뀌었는지 알아낼 수 있다.
  • preemption되는 경우 실패하게 하는 테크닉은 s/w 루틴으로 해결
    • preemption되는 경우를 대비하여 context switch에서 무조건 clrex를 수행하여 강제로 실패를 만든다. 그렇게 하여 원래 수행하던 태스크로 다시 돌아왔을 때 strex가 항상 실패를 하게 할 수 있다.
  • local monitor
    • 각 CPU(core)에 연결되도록 설계되었다.
    • non-shared 메모리에서 exclusive 명령을 사용 할 때에는 local monitor를 사용하여 exclusive/open access 상태를 확인할 수 있다.
  • global monitor
    • cache coherent 기능이 있는 AXI 버스와 메모리 인터페이스 중간에 연결되도록 설계되었다.
      • CPU(core) 뿐만 아니라 cache coherent port가 부착된 device 장치들도 AXI 버스를 통해 메모리와 연결되어 이의 중간에 연결된 global monitor를 사용할 수 있다.
    • shareable 메모리에서 exclusive 명령을 사용할 때에는 global monitor를 사용하여 exclusive/open access 상태를 확인할 수 있다.
  • local monitor와 global monitor는 단순한 state machine으로 구현되었고 ldrex/strex 사용 시 최근에 access한 CPU(core), tag 주소 및 상태(state) 들이 저장되고 이 tag 주소와 상태를 사용하여 strex의 수행 여부를 결정한다.
    참고: Cache Coherent Protocol | 문c

global_monitor1

 

Exclusive monitor State machine

  • 메모리가 ldr/str 등으로 사용될 때에는 open access 상태에 있다.
  • 메모리가 ldrex를 사용할 때에는 exclusive 상태로 마크된다.
  • ldrex를 수행한 메모리 주소(캐시 라인에 포함된 주소)의 데이터가 변화되면 open access 상태로 바뀐다.
  • 또 한 번 다른 CPU에 의해 strex를 수행하려고 하는 경우 exclusive 상태가 아니므로 실패한다.
  • strex를 하는 경우 항상 cache line은 clean & invalidate 된다.
  • 주소 지정이 항상 cache line에 관련되어 있기 때문에 원하지 않게 실패가 될 수도 있음을 고려해야 한다.

global_monitor2

 

Cache bouncing (Cache line contention)

  • spin lock에서 CPU가 lock을 얻기 위해 쉬지 않고 서로 ldrex/strex를 반복적으로 호출(spin)하는 경우 해당 cache line 역시 반복적으로 clean & invalidate 후 해당 CPU에서 load 되면서 성능이 급격히 떨어지게 된다. 이러한 문제를 해결하기 위해 항상 ldrex/strex의 명령을 사용하지 않고 먼저 읽어 확인 한 후 정상일 때 기록을 시도하는 방법으로 수정하여 락 메커니즘에서 cache bouncing 문제를 해결하였다.
  • old ARM 아키텍처에서는 ldrex/strex 대신 swp 명령 하나로 동작하여 cache bouncing에 대해 대처하기 어려웠다.

cache_bouncing

 

swp(deprecated) → ldrex/strex

  • swp 명령을 사용하게 되면 메모리 장치의 값을 바꾸기 위해 fetch interface 장치와 store interface 장치를 순서대로 사용하는데 이의 수행은 CPU cycle이 많이 소요된다.
  • 소요되는 CPU cycle만큼 인터럽트가 pending이 되는데 이로 인해 인터럽트 latency의 증가 이슈 발생하였다.
  • 이를 해결하기 위해 swp를 한 쌍의 ldrex와 strex를 추가하여 swp 명령을 대치하였다.
  • swp 명령은 ARMv6 이후 부터 deprecated.
  • 참고: ARM Synchronization Primitives | ARM infocenter – 다운로드 pdf

 

 

 

Profiling

gcc 컴파일 시 아래 옵션을 사용하면 profiling 함수들을 호출할 수 있다.

  • -finstrument-functions

 

사용자(user-compiled) 함수로 들어갔다 나올때마다 다음 함수가 호출된다.

  • __cyg_profile_func_enter()
  • __cyg_profile_func_exit()
__attribute__ ((no_instrument_function)) 
	void __cyg_profile_func_enter(void *this, void *call_site)
{
    (...사용자 코드...)
}

__attribute__ ((no_instrument_function)) 
	void __cyg_profile_func_exit(void *this, void *call_site)
{
    (...사용자 코드...)
}

 

트레이스를 위하여 처음 profiling 함수를 들어가기 전과 마지막 profiling 함수를 사용 후에 각각 한 번씩만 다음 속성으로 선언된 함수가 호출된다.

  • __attribute__ ((constructor))
  • __attribute__ ((destructor))
void __attribute__ ((constructor)) trace_begin (void)
{
        (...사용자 코드...)
}

void __attribute__ ((destructor)) trace_end (void)
{
       (...사용자 코드...)
}

 

profiling 함수 호출 순서

예를 들어 main() 함수에서 foo() 함수를 호출하는 경우의 함수 흐름

profiling

 

notrace

디버거등에 의한 프로 파일링을 막기 위해 사용되는 매크로

#define notrace __attribute__((no_instrument_function))

 

참고