2014년 3월 31일 월요일

LLVM을 통해서 살펴본 Apple A7 (ARM64)의 특징

LLVM의 Backend과정은 머신에 종속적인 코드를 생성해 준다.
따라서 Backend과정에 사용되는 코드를 보면  Backend에 사용될 하드웨어의 사양이 유추가 가능한데. Apple에서 사용하는 iPhone5S, iPad Air, IPad Mini retina에 사용되는 A7의 하드웨어 사양을 알아보자. 물론 Apple 의 A7은 자세한 하드웨어 사양은 공개되지 않았다.

# A7의 OoO

최근은 SoC는 OoO를 지원한다. (Out of Order) 또한 OoO를 사용하기 위한 OpBuffer의 깊이도 중요한다. LLVM의 코드에 보면 이것을 알수 있다.

<code>
 15def CycloneModel : SchedMachineModel {                                      
 16  let IssueWidth = 6; // 6 micro-ops are dispatched per cycle.              
 17  let MicroOpBufferSize = 192; // Based on the reorder buffer.              
 18  let LoadLatency = 4; // Optimistic load latency.                          
 19  let MispredictPenalty = 16; // 14-19 cycles are typical.                  
 20}
</code>

OoO 는 6개가 Decode가 가능하며, Op 버퍼는 192이다. 그리고 파이프 라인 깊이는 16단임을 알수 있다.

이것을 Intel의 Haswell과 비교해 보자

                                     |  A7   | Haswell |                                          
| OoO                         |       6   |         4     |                                 
| OpBuffer                  |  192   |    192     |                   
| LoadLatency           |      4    |        4     |              
| MispredictPenalty  |     16   |      16     |                    

ARMv8 64와 X86을 동등 비교할 수는 없고, 또한 X86의 8개의 uOP ports를 사용한다.

하지만 Apple의 최소 ARM64는 구조로만 좁혀서 비교해 보면
X86의 그것과 대등한 수준이라고 할 수 있다.
다시말하면 현재 Apple SoC A7으로도  Desktop이나 Server군으로도
충분한 활용이 가능고 볼수 있다.
물론 이것은 Tool Chain까지의 이야기며 Application Lv은 또 다른 이야기가 될것이다.  

# A7의 Core Resource

## 4개의 Interger Piles 와 48엔트리의 버퍼

## 2개의 Branch Unit과 24엔트리 버퍼                                                                                                        
## 1개의 간접 Branch Unit                                                                            

## 2개의 Shifter Pipe 와 24엔트리 버퍼

## 1개의 Mul Pipe와 32엔트리 버퍼

## 1개의 Div Pipe와 16엔트리 버퍼

## 1개의 정수 Div

## 2개의 load/store pipe 와 28엔트리 버퍼

## 3개의 fp/vector pipe 와 48엔트리 버퍼

## 2개의 fp/vector 산술,곱하기 pipe 와 32엔트리 버퍼

## 1개의 fp/vector div,sqrt pipe 와 16엔트리 버퍼

## 1개의 fp 비교기 pipe 와 16엔트리 버퍼

2014년 3월 25일 화요일

[Linux Kernel] 47주차(2014.03.22) 후기

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

# 일시 : 2014.03.22 (47주차)
# 모임명 : NAVER개발자커뮤니티지원_IAMROOT.ORG_10차ARM-C
# 장소 : 토즈 타워점
# 장소지원 : NAVER 개발자 커뮤니티 지원 프로그램
# 참여인원 : 5명
# 스터디 진도 : 
 - start_kernel()-> page_alloc_init();
 - start_kernel()-> parse_early_param();
 - start_kernel()-> jump_label_init();
 - start_kernel()-> setup_log_buf(0);
 - start_kernel()-> pidhash_init();
 - start_kernel()-> vfs_caches_init_early();

# 46주차 마지막 부분 논의 (컴파일러 문제점)
## if문의 해석으로 논의하다가 중단했던 부분을 계속 토론하였습니다. 

먼저 함수 선언을 통해서 사용된 매개변수의 형식을 알아보았습니다. 
__mutex_lock_common 함수 선언은 다음과 같습니다. 
<code> 
static __always_inline int __sched                                                                                                       
__mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,                                                               
                    struct lockdep_map *nest_lock, unsigned long ip,                                                                     
                    struct ww_acquire_ctx *ww_ctx) 
</code>

이 함수에서 if문의 코드의 결과에 따라서 어떻게 되는지 논의한 부분은 이곳입니다. 
<code>
if(!__builtin_constant_p(ww_ctx == NULL)) {
</code>
여기서 *ww_ctx 는 함수를 호출할때 NULL로 매개변수를 전달했고, 중간에 변경된
부분이 없기 때문에 *ww_ctx : NULL 입니다. 
따라서 ww_ctx (NULL) == NULL이 상수 값이 되므로,
!__build_constant_p(상수)로 이 if문은 부정될 것입니다. 
이 가정이 맞는지 검증해 보았습니다. 

## J님의 테스트 과정
내부에 *ww_ctx를 NULL로 선언한 후 컴파일 옵션을 -O0, -O1, -O2, -O3로 변화해
가면서 결과가 어떻게 되는지 검증해 주셨습니다. 

<code>
int main(){
        const int *ww_ctx = NULL;
        if(!__builtin_constant_p(ww_ctx == NULL))
        {
                printf("in \n");
        }else
                printf("out\n");
}
</code>

root@ubuntu:/work/study/module/builtin# gcc builtin_1.c -O0
root@ubuntu:/work/study/module/builtin# ./a.out
in

root@ubuntu:/work/study/module/builtin# gcc builtin_1.c -O1
root@ubuntu:/work/study/module/builtin# ./a.out
out

root@ubuntu:/work/study/module/builtin# gcc builtin_1.c -O2
root@ubuntu:/work/study/module/builtin# ./a.out
out

root@ubuntu:/work/study/module/builtin# gcc builtin_1.c -O3
root@ubuntu:/work/study/module/builtin# ./a.out
out

J님의 검증 결론 컴파일 옵션에 따라 실행 결과는 다르다는 결론을 알 수 있었습니다.  

우리가 사용하는 커널의 Makefile을 보니 HOSTCXXFLAGS = -O2로 최적화합니다.
따라서 if문의 부정된다. 

## 리눅스 토발즈의 이 코드에 대한 회신

질문자의 메일을 보면 좀더 가독성이 좋은 코드가 좋지 않느냐는 의견입니다. 
__buildtin_> __builtin_constant_p(ww_ctx == NULL) is equal 
to __builtin_constant_p(ww_ctx != NULL), 
but the former is more readable, 
since it shows we expect ww_ctx to be null.

토발즈의 회신은 강경(???)합니다. 
Stop the f*cking around already! The  whole "we expect ww_ctx to be
null" thing shows that YOU DO NOT SEEM TO UNDERSTAND WHAT THE TEST
ACTUALLY IS!

The expression

  __builtin_constant_p(ww_ctx == NULL)

has ABSOLUTELY NOTHING to do with whether ww_ctx is NULL or not!
Christ, can you really not understand that?

그리고 계속되는 내용을 보면 컴파일러에 따라 다를 수 있다고 이야기
합니다.  

## 토론의 결론 
 우리의 논의를 거슬러 올라가 보면, 컴파일러가 최적화에 
따라서 이 코드는 실행이 달라 질 가능성이 있기 때문에 컴파일러를 
잘 선택해야 하고 가독성이나 이러한 오해를 줄이기 위해서 코드가 
고쳐지는 것이 맞다는 의견이었습니다.  

## 3.14에서 커널 코드를 알아보았습니다. 
우선 함수의 선언도 바뀌었네요. const bool use_ww_ctx 가 추가되었습니다. 
<code>
__mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,   
                    struct lockdep_map *nest_lock, unsigned long ip,         
                    struct ww_acquire_ctx *ww_ctx, const bool use_ww_ctx)    
</code>

그리고 우리가 논의했던 __builtin_constant_p(ww_ctx == NULL) 가 바뀌었습니다.
<code>
if (use_ww_ctx) { 
</code>

우리가 토론했던 것이 반영되었네요. 스터디 시간 중 많은 시간을 할애하여 토론한 
것과 커널의 방향이 같이 간다는 것을 입증한 것이기도 합니다. 
 이것을 보면 오픈소스가 어떻게 발전할 수 있는지 좋은 사례라고 생각합니다.
서로 이상하다고 생각되는 부분이 있으면 의문점을 제시하고 토론해서 
좋은 결론을 이끌어 냅는 것입니다.  

# 스터디 주요 함수의 처리한 내용 요약

- page_alloc_init();
 // cpu_chain에 page_alloc_cpu_notify를 연결함 (mutex lock/unlock 사용) 

- pr_notice("Kernel command line: %s\n", boot_command_line); 
 // "Kernel command line: console=ttySAC2,115200 init=/linuxrc"

- parse_early_param();
 // command arg에서 각 요소들을 파싱하여 early init section으로 
 // 설정된 디바이스 초기화.
 // 우리는 serial device가 검색이 되지만 config설정은 없어서 
 // 아무것도 안함. 

- parse_args("Booting kernel", static_command_line, __start___param,                              
                   __stop___param - __start___param,                                                    
                   -1, -1, &unknown_bootoption);                                                        
  // DTB에서 넘어온 bootargs를 파싱하여 param, val을 뽑아내고 그에 대응되는
  // kernel_param 구조체에 값을 등록함. 

- jump_label_init();
  // HAVE_JUMP_LABEL 이 undefined 이므로 NULL 함수

- setup_log_buf(0); 
  // defalut log_buf의 크기는 __LOG_BUF_LEN: 0x20000 (128KB) 임
  // early_param 에서 호출했던 log_buf_len 값이 있다면 
  // log_buf의 크기를 넘어온 크기로 만듬 

- pidhash_init();
  // pidhash의 크기를 16kB만큼 할당 받고 4096개의 hash list를 만듬 

- vfs_caches_init_early(); 
  // Dentry cache, Inode-cache용 hash를 위한 메모리 공간을 
  // 각각 512kB, 256kB만큼 할당 받고, 
  // 131072, 65536개 만큼 hash table을 각각 만듬

# 48주차는 sort_main_extable(); 부터 시작합니다. 
mm_init(); 을 들어가서 리눅스 커널의 메모리 관리자인 버디, 슬랩에 
대한 공부를 시작할 것으로 예상합니다. 

2014년 3월 19일 수요일

[Linux Kernel] 46주차(2014.03.15) 후기

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

# 일시 : 2014.03.15 (46주차)
# 모임명 : NAVER개발자커뮤니티지원_IAMROOT.ORG_10차ARM-C
# 장소 : 토즈 타워점
# 장소지원 : NAVER 개발자 커뮤니티 지원 프로그램
# 참여인원 : 5명
# 스터디 진도 : start_kernel()-> page_alloc_init();

# 45주차에서 GFP보충설명
memory관련 부분에서 계속 분석할 내용이라서 간단히 복습함

1. Zones 
 커널의 페이지테이블은 물리메모리의 PAGE_OFFSET부터 매핑해서 사용합니다.
물리 메모리를 zone 할당자가 관리합니다. 커널이 사용할 용도에 따라서 물리 페이지를 
zone으로 나누어 사용할 메모리 공간을 예약해 놓습니다. 
 ARM에서 zone 할당자는 3가지를 사용합니다. ZONE_NORMAL, ZONE_HIGHMEM, ZONE_MOVABLE 입니다.
GFP는 Get Free Page의 약어이며, gfp_mask를 설정하는 플래그를 사용해서 memory의 할당과 속성을 설정합니다. 
메모리를 관리하는 버디 시스템를 분석 할 때 자세하게 공부하기로 하였습니다. 
  
# 스터디 내용 : page_alloc_init(); 분석진행 중
2. hotcpu_notifier(page_alloc_cpu_notify,0);
컴파일러 옵션인 CONFIG_HOTPLUG_CPU (CPU 핫 스왑)이 활성화되어 있어, cpu_notifier(fn, pri) 매크로를 실행합니다. 
- cpu_notifier(fn.pri)에서 fn: page_alloc_cpu_notify_nb, pri : 0가 되어 page_alloc_cpu_notify_nb를 호출합니다.      
<code>
 #define cpu_notifier(fn, pri) {                                 \            
      static struct notifier_block fn##_nb =                  \                                                                    
                { .notifier_call = fn, .priority = pri };       \            
        register_cpu_notifier(&fn##_nb);               
}                                                          
</code>
2.1 register_cpu_notifier(struct notifier_block *nb)

2.1.1 cpu_maps_update_begin();
- mutex_lock(&cpu_add_remove_lock); 
  - lock-count를 매개변수로 해서 atomic_xchg()실행한 결과로 fastpath 인지 아닌지 체크한다. 
  - fastpath가 아니면 __mutex_lock_slowpath를 진행한다. 
  - lock-ower : current로 설정
  - __mutex_lock_slowpath 은 __mutex_lock_common을 호출

2.1.2 ret = raw_notifier_chain_register(&cpu_chain, nb);

2.1.3 cpu_maps_update_done();

# __mutex_lock_common 
 - preempt_disable();
   - 현재 task의 preempt count값을 증가시킨다.
 - mutex_acquire_nest(&lock->dep_map, subclass, 0, nest_lock, ip);
   - mutex_acquire_nest : NULL 함수로 컴파일 do{} while 0로 의미가 사라지게 되어 lock->dep_map의 맴버가 선언되지 않아도 안됨. 
 - spin_lock_mutex(&lock->wait_lock, flags);
   - mutex->wait_lock.rlock.raw_lock 를 스핀락했고 CPSR을 flag에 저장
 - debug_mutex_lock_common(lock, &waiter);
   - waiter를 magic으로 설정하고, INIT_LIST로 자료구조를 만듬
 - debug_mutex_add_waiter(lock, &waiter, task_thread_info(task));
   - 현재 thread가 spin lock이 걸렸다는 것을 blocked_on으로 표현함
   - blocked_on은 dead_lock을 detection할때 사용한다. 
 - list_add_tail(&waiter.list, &lock->wait_list); 
   - __list_add(&waiter.list, &lock->wait_list->prev, &lock->wait_list->head)
 - if (MUTEX_SHOW_NO_WAITER(lock) && (atomic_xchg(&lock->count, -1)== 1))
   - 조건문에 따라서 done을 진행한다. 
 - done이 아닌 부분은 다음에 mutex를 호출했을때 추가 분석

- lock_acquired(&lock->dep_map, ip);
  - // NULL function
- mutex_remove_waiter(lock, &waiter, current_thread_info()); 
  - // &waiter->list를 초기화함
- mutex_set_owner(lock);   
  - // lock->owner : init_task 가 됨


# mutex 주의점
자료위치 Documents/mutex-design.txt

- only one task can hold the mutex at a time
- only the owner can unlock the mutex
- multiple unlocks are not permitted
- recursive locking is not permitted
- a mutex object must be initialized via the API
- a mutex object must not be initialized via memset or copying
- task may not exit with mutex held
- memory areas where held locks reside must not be freed
- held mutexes must not be reinitialized
- mutexes may not be used in hardware or software interrupt 
- contexts such as tasklets and timers


# 토론 내용
 -  task, thread 에 대하여.
 - mutex와 semaphore
 - barrier, 
 - 컴파일러 (LLVM)와 최적화

# git log
modified:   arch/arm/include/asm/atomic.h
modified:   arch/arm/include/asm/cmpxchg.h
modified:   arch/arm/include/asm/spinlock.h
modified:   arch/arm/include/asm/spinlock_types.h
modified:   arch/arm/include/asm/thread_info.h
modified:   arch/arm/kernel/head-common.S
modified:   include/linux/compiler-gcc.h
modified:   include/linux/compiler.h
modified:   include/linux/cpu.h
modified:   include/linux/debug_locks.h
modified:   include/linux/hardirq.h
modified:   include/linux/init_task.h
modified:   include/linux/irqflags.h
modified:   include/linux/kernel.h
modified:   include/linux/list.h
modified:   include/linux/lockdep.h
modified:   include/linux/mutex.h
modified:   include/linux/preempt.h
modified:   include/linux/sched.h
modified:   include/linux/spinlock.h
modified:   init/init_task.c
modified:   init/main.c
modified:   kernel/cpu.c
modified:   kernel/mutex-debug.c
modified:   kernel/mutex-debug.h
modified:   kernel/mutex.c
modified:   mm/page_alloc.c

2014년 3월 12일 수요일

[Linux Kernel] 45주차(2014.03.08) 후기

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

# 일시 : 2014.03.08 (45주차)
# 모임명 : NAVER개발자커뮤니티지원_IAMROOT.ORG_10차ARM-C
# 장소 : 토즈 타워점
# 장소지원 : NAVER 개발자 커뮤니티 지원 프로그램
# 참여인원 : 5명
# 스터디 진도 : start_kernel()-> smp_prepare_boot_cpu(), build_all_zonelists(NULL,NULL);

# 스터디 내용 : smp_prepare_boot_cpu()
1. start_kernel()-> smp_prepare_boot_cpu()-> set_my_cpu_offset(per_cpu_offset(smp_processor_id()));
 - smp_processor_id() : 0
 - per_cpu_offset(0) : __per_cpu_offset[0]
 - set_my_cpu_offset : pcpu_unit_offsets[0] + __per_cpu_offset 에서의 pcpu_base addr의 옵셋
 - asm volatile("mcr p15, 0, %0, c13, c0, 4" : : "r" (off) : "memory");
 - A.R.M: B4.1.150 참조, TPIFRPRW를 설정함

# 스터디 내용 : build_all_zonelists(NULL,NULL);
2. start_kernel()->build_all_zonelists(NULL,NULL);
 - 함수의 인자 (pg_data_t *pgdat : NULL, struct zone *zone : NULL)

2.1 set_zonelist_order()
 - current_zonelist_order : 2로 설정 (ZONELIST_ORDER_ZONE의 값)

2.2 system_state == SYSTEM_BOOTING 이므로 if문을 실행
 - __build_all_zonelists(NULL); 
  - contig_page_data의 node_zonelist마다 값 설정
  - 각 cpu core마다 boot_pageset의 pcp (per_cpu_pages) 맴버를 설정 
  - cpu: 0, per_cpu(boot_pageset, 0): *(&boot_pageset + __per_cpu_offset[0])
  - cpu: 1, per_cpu(boot_pageset, 1): *(&boot_pageset + __per_cpu_offset[1])
  - cpu: 2, per_cpu(boot_pageset, 2): *(&boot_pageset + __per_cpu_offset[2])
  - cpu: 3, per_cpu(boot_pageset, 3): *(&boot_pageset + __per_cpu_offset[3])
 - mminit_verify_zonelist();
  - zonelists를 출력해 주는 함수지만, 처음 if문의 mminit_loglevel : 0 < MMINIT_VERIFY :1로 리턴됨
  - set_mminit_log_level(char *str)로 실행할 mminit log level을 설정해 줄 수 있다. 
  - 실행시 출력 결과 : "mminit::zonelist thisnode nid값:zone->name"
   
- cpuset_init_current_mems_allowed(); 
  - NULL 함수

2.3 vm_total_pages = nr_free_pagecache_pages();
 - vm_total_pages : 0x7f7d6

2.4 if (vm_total_pages < (pageblock_nr_pages * MIGRATE_TYPES))
 - vm_total_pages: 0x7f7d6, pageblock_nr_pages : 0x400, MIGRATE_TYPES: 4
 - page_group_by_mobility_disabled: 0

2.5 zonelist결과 출력
 - "Built 1 zonelists in Zone order, mobility grouping off."
 - "Total pages: 0x7f7d6"

# git log
   e768777..00c4bc9  master     -> origin/master
Updating e768777..00c4bc9
Fast-forward
 arch/arm/include/asm/barrier.h     |   1 +
 arch/arm/include/asm/percpu.h      |   3 +
 arch/arm/include/asm/thread_info.h |   1 +
 arch/arm/kernel/smp.c              |   2 +
 arch/arm/kernel/vmlinux.lds.S      |   5 +-
 include/asm-generic/current.h      |   3 +
 include/asm-generic/percpu.h       |  14 ++++
 include/asm-generic/sections.h     |   1 +
 include/asm-generic/topology.h     |   4 +-
 include/asm-generic/vmlinux.lds.h  |   6 ++
 include/linux/compiler-gcc.h       |   8 ++
 include/linux/compiler.h           |   1 +
 include/linux/cpuset.h             |   3 +-
 include/linux/gfp.h                |  96 +++++++++++++++++++++-
 include/linux/kernel.h             |   1 +
 include/linux/mempolicy.h          |   1 +
 include/linux/mmzone.h             |  62 +++++++++++++-
 include/linux/nodemask.h           |   9 +++
 include/linux/pageblock-flags.h    |   1 +
 include/linux/percpu-defs.h        |  13 ++-
 include/linux/sched.h              |   2 +-
 include/linux/smp.h                |   3 +-
 include/linux/topology.h           |   6 +-
 init/main.c                        |   7 ++
 kernel/bounds.c                    |   1 +
 lib/smp_processor_id.c             |   1 +
 mm/bootmem.c                       |   2 +
 mm/internal.h                      |   2 +
 mm/mm_init.c                       |   7 +-
 mm/mmzone.c                        |  14 ++++
 mm/page_alloc.c                    | 184 ++++++++++++++++++++++++++++++++++++++++--
 mm/percpu.c                        |  30 +++++++
 mm/vmscan.c                        |   1 +
 33 files changed, 470 insertions(+), 25 deletions(-)

# 46주차 진도는 page_alloc_init(); 부터 시작합니다. 

2014년 3월 5일 수요일

[Linux Kernel] 44주차(2014.03.01) 후기

* 44주차
10차 ARM 리눅스 커널 스터디 44주차(2014.03.01) 후기입니다. 

# 일시 : 2014.03.01 (44주차)
# 장소 : 토즈 타워점
# 참여인원 : 4명
# 스터디 진도 : start_kernel()-> setup_per_cpu_areas() -> pcpu_embed_first_chunk();

# 스터디 주요 논의 내용

# if(max_distance > (VMALLOC_END - VMALLOC_START) * ¾) {
}

75%보다(크기를 계산해 보면 180MB) max_distance가 크면 안된다.

# pcpu_embed_first_chunk () -> pcpu_setup_first_chunk(ai, base)


dyn_size : 0x3000 (12kB)
size_sum : ai->static_size + ai->reserved_size + dyn_size
  // ai->static_size + 0x5000 (20kB : 8kB + 12kB)


# PRINTF 에서 *는 무엇을 의미할까?
참조 링크 : http://www.soen.kr/


pintf에서 *는 폭지정을 인수로부터 전달받는다.
보통은 %5이런식으로 %다음에 숫자가 폭을 지정하나,
가변적으로 폭을 지정하고 싶을때는 인자를 2개 사용해서  *와 출력을 사용한다.


## 예시 1: 폭지정의 예이다.  *으로 하면 폭이 3이되고 출력이 5가 출력된다.
printf(“wwww%*dwww”,3,5);
→ www__5www


## 예시 2 (길님):
35         fprintf( stderr, "%s%0*lx\n", "", 1, 0xf );
36         fprintf( stderr, "%s%0*lx\n", ",", 2, 0xf );
37         fprintf( stderr, "%s%0*lx\n", ",", 3, 0xf );
38         fprintf( stderr, "%s%0*lx\n", ",", 4, 0xf );


출력결과
f
,0f
,00f
,000f


#  BUG_ON 에서 체크하는 것들
ai->nr_groups : 1
ai_static_size : 실제 할당된 size
~PAGE_MASK : 0x00000FFF
!base_addr :  128K만큼 물리주소 0x5FFFFFFF 근처에서 할당받는 주소
~PAGE_MASK : 0x00000FFF
ai->unit_size : 0x8000 (32kB로 가정)
ai->unit_size : 0x8000 & ~PAGE_MASK :
ai->unit_size < PCPU_MIN_UNIT_SIZE : 0x3000 (12kB)
ai->dyn_size < PERCPU_DYNAMIC_EARLY_SIZE
pcpu_verify_alloc_info(ai) <0;


# 4kB단위로 할당 받는 메모리들
group_offsets :
group_sizes:
unit_map :
unit_off :


# 각 cpu에 해당하는 unit_map[0~3] 와 할당 받은 unit_off를 설정하는 과정
1283        for (group = 0, unit = 0; group < ai->nr_groups; group++, unit += i) {           
1284                const struct pcpu_group_info *gi = &ai->groups[group];                   
1285                                                                                         
1286                group_offsets[group] = gi->base_offset;                                  
1287                group_sizes[group] = gi->nr_units * ai->unit_size;                       
1288                                                                                         
1289                for (i = 0; i < gi->nr_units; i++) {                                     
1290                        cpu = gi->cpu_map[i];                                            
1291                        if (cpu == NR_CPUS)                                              
1292                                continue;                                                
1293                                                                                         
1294                        PCPU_SETUP_BUG_ON(cpu > nr_cpu_ids);                             
1295                        PCPU_SETUP_BUG_ON(!cpu_possible(cpu));                           
1296                        PCPU_SETUP_BUG_ON(unit_map[cpu] != UINT_MAX);                    
1297                                                                                         
1298                        unit_map[cpu] = unit + i;                                        
1299                        unit_off[cpu] = gi->base_offset + i * ai->unit_size;             
1300                                                                                         
1301                        /* determine low/high unit_cpu */                                
1302                        if (pcpu_low_unit_cpu == NR_CPUS ||                              
1303                            unit_off[cpu] < unit_off[pcpu_low_unit_cpu])                 
1304                                pcpu_low_unit_cpu = cpu;                                 
1305                        if (pcpu_high_unit_cpu == NR_CPUS ||                             
1306                            unit_off[cpu] > unit_off[pcpu_high_unit_cpu])                
1307                                pcpu_high_unit_cpu = cpu;                                
1308                }                                                                        
1309        }


실행 후 :
// unit : 4
unit_off[0] 의 offset  0x0
unit_off[1] 의 offset  0x8000 * 1
unit_off[2] 의 offset  0x8000 * 2

unit_off[3] 의 offset  0x8000 * 3

# 부팅 중 percpu관련 메시지 예시 ( x86, 우분투 12.04)
[    0.000000] setup_percpu: NR_CPUS:256 nr_cpumask_bits:256 nr_cpu_ids:1 nr_nod
e_ids:1
[    0.000000] PERCPU: Embedded 29 pages/cpu @ffff88003fc00000 s86720 r8192 d238
72 u2097152
[    0.000000] pcpu-alloc: s86720 r8192 d23872 u2097152 alloc=1*2097152
[    0.000000] pcpu-alloc: [0] 0
[    0.000000] Built 1 zonelists in Node order, mobility grouping on.  Total pages: 257913


# pcpu_first_chunk 는 어떤 값이 될까?
1527        pcpu_first_chunk = dchunk ?: schunk;
컴파일로 실험해 보니
dchunk가 NULL이 아니라면 pcup_first_chunk = dchunk가 된다. (우리는 이것)
dchunk가 NULL이라면 pcpu_first_chunk = schunk가 된다. 

#         pcpu_chunk_relocate(pcpu_first_chunk, -1);
&pcpu_slot[11] (list)에 pcpu_first_chunk(&dchunk)가 할당됨

# 왜 11 slot에 할당될까?
dchunk->free_size 가 0x3000 (12kB)이기 때문이다. 
정확히는 소스코드에서 계산되고
return max(highbit - PCPU_SLOT_BASE_SHIFT + 2, 1);
우리는 highbit가 14이기 때문에 return max(11,1 ) = 11 이된다.

slot number와 slot size를 정리하면 다음과 같다.

| slot number | slot size |
|-----------+-------------|
| 13 | 32kB | 
| 12 | 16kB |
| 11 | 8kB | 
| 10 | 4kB |
| 09 | 2kB |
| 08 | 1kB |
| 07 | 512B | 
| 06 | 256B |
| 05 | 128B |
| 04 | 64B |
| 03 | 32B |
| 02 | 16B |
| 01 | 8B |
| 0 | 4B |
|-----------+-------------|


# chunk란 ?

청크는 사전적 의미는 덩어리라는 뜻이다. 정보 덩어리를 나누고 나누어진 덩어리에 대한 정보를 찾기 위한 색인을 정의하여 편리하게 찾기 위한 방법이다.

# 왜 perCPU메모리를 할당해서 사용할까?
1. 각 CPU가 사용하는 메모리 자원을 locking을 위해서 이다.
2. 캐쉬 invalid를 향상 시킨다.

# 스터디 log 1차 
  38f9803..e31c467  master     -> origin/master
Updating 38f9803..e31c467
Fast-forward
arch/arm/include/asm/pgtable.h |   4 +-
include/asm-generic/sections.h |   1 +
include/linux/bitops.h         |   7 +++
include/linux/cpumask.h        |   4 ++
include/linux/kern_levels.h    |   4 ++
include/linux/kernel.h         |   3 +
include/linux/log2.h           |  13 ++++-
include/linux/percpu.h         |   1 +
include/uapi/linux/kernel.h    |   2 +
kernel/cpu.c                   |   1 +
lib/bitmap.c                   |  29 ++++++++++
lib/vsprintf.c                 |   2 +
mm/percpu-km.c                 |   6 ++
mm/percpu.c                    | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
14 files changed, 235 insertions(+), 6 deletions(-)

# 스터디 log 2차
   e31c467..e768777  master     -> origin/master
Updating e31c467..e768777
Fast-forward
 drivers/gpu/drm/vmwgfx/svga3d_surfacedefs.h |   1 +
 include/asm-generic/bitops/non-atomic.h     |   4 +
 include/linux/bitmap.h                      |  15 ++
 include/linux/bitops.h                      |   1 +
 include/linux/cpumask.h                     |  13 +-
 include/linux/kernel.h                      |   1 +
 include/linux/list.h                        |  17 ++
 include/linux/poison.h                      |   1 +
 include/linux/types.h                       |   1 +
 mm/percpu.c                                 | 258 ++++++++++++++++++++++++++-----
 10 files changed, 273 insertions(+), 39 deletions(-)

# 44주차 스터디를 정리해서 그림을 그리면 다음이 된다. 
20140301_study_percpu_map.JPG