2013년 9월 7일 토요일

[Linux Kernel] 20주차(2013.09.07) 후기

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

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

# 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

## printk_ratelimit() -> __printk_ratelimit(__func__) -> __ratelimit(&printk_ratelimit_state, func);


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

        // 5s 가 지났는지 검사 수행
        if (!rs->interval)
                return 1;
        if (!raw_spin_trylock_irqsave(&rs->lock, flags))
                return 0;

        if (!rs->begin)
                rs->begin = jiffies;

        // ARM10C 20130907 rs->interval = 5 * HZ
        // 시간 만료가 되었나?
        if (time_is_before_jiffies(rs->begin + rs->interval)) {
                if (rs->missed)
                        printk(KERN_WARNING "%s: %d callbacks suppressed\n",
                                func, rs->missed);
                rs->begin   = 0;
                rs->printed = 0;
                rs->missed  = 0;
        }
        // ARM10C 20130907 rs->burst = 10
        if (rs->burst && rs->burst > rs->printed) {
                rs->printed++;
                ret = 1;
        } else {
                rs->missed++;
                ret = 0;
        }
        raw_spin_unlock_irqrestore(&rs->lock, flags);

        return ret;
}
</code>

### raw_spin_trylock_irqsave(&rs->lock, flags)

<code>
#define raw_spin_trylock_irqsave(lock, flags) \
({ \
        local_irq_save(flags); \
        raw_spin_trylock(lock) ? \
        1 : ({ local_irq_restore(flags); 0; }); \
})
</code>

#### local_irq_save(flags);

<code> ::irqflags.h
#define local_irq_save(flags)                           \
        do {                                            \
                raw_local_irq_save(flags);              \
                trace_hardirqs_off();                   \
        } while (0)
</code>

##### raw_local_irq_save(flags)
// 현재 CPSR을 flag로 가져옴

<code>
#define raw_local_irq_save(flags)                       \
        do {                                            \
                typecheck(unsigned long, flags);        \
                flags = arch_local_irq_save();          \
                /*ARM10C 현재 cpsr을 flag로가져옴*/     \
        } while (0)
</code>

##### trace_hardirqs_off();

<code> ::irqflags.h
# define trace_hardirqs_off()           do { } while (0)    // ARM10C this
</code>

Q: 왜 함수의 네이밍을 hardirq라고 했을까요? hard의 의미가 있을까요?
A: hard의 의미는 processor상의 irq를 on/off라고 지칭하는 것입니다.
   soft는 변수를 가지고 제어하는 것입니다.

#### raw_spin_trylock(lock) ? 1 : ({ local_irq_restore(flags); 0; });

<code> :: spinlock.h
#define raw_spin_trylock(lock) __cond_lock(lock, _raw_spin_trylock(lock))
</code>

##### _raw_spin_trylock(lock)

<code> :: spinlock.h
int _lockfunc _raw_spin_trylock(raw_spinlock_t *lock)
{
        return __raw_spin_trylock(lock);
}
</code>

###### Q: _lockfunc은 무엇일까요?
attribute이고, 열어보시면 다음과 같이 정의되어 있습니다. spinlock section 이라고 보시면 됩니다.

#define __lockfunc __attribute__((section(".spinlock.text")))

하지만 18주차때 elf보시면 그런 section은 없습니다.

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

이 현상은 vmlinux.lds파일을 확인해 보시면, .text section에 spinlock.text가 포함된 것을 볼 수 있습니다.

.text : { /* Real text segment         */
 _stext = .; /* Text and read-only data        */
   __exception_text_start = .;
   *(.exception.text)
   __exception_text_end = .;
 
   . = ALIGN(8); *(.text.hot) *(.text) *(.ref.text) *(.devinit.text) *(.devexit.text) *(.cpuinit.text) *(.cpuexit.text) *(.text.unlikely)
   . = ALIGN(8); __sched_text_start = .; *(.sched.text) __sched_text_end = .;
   . = ALIGN(8); __lock_text_start = .; *(.spinlock.text) __lock_text_end = .;
   . = ALIGN(8); __kprobes_text_start = .; *(.kprobes.text) __kprobes_text_end = .;
   . = ALIGN(8); __idmap_text_start = .; *(.idmap.text) __idmap_text_end = .; . = ALIGN(32); __hyp_idmap_text_start = .; *(.hyp.idmap.text) __hyp_idmap_text_end = .;

###### __raw_spin_trylock(lock)

<code> ::spinklock_api_smp.h
static inline int __raw_spin_trylock(raw_spinlock_t *lock)
{
preempt_disable();
if (do_raw_spin_trylock(lock)) {
spin_acquire(&lock->dep_map, 0, 1, _RET_IP_);
return 1;
}
preempt_enable();
return 0;
}
</code>

####### preempt_disable();

<code> :: preempt_disable()
#define preempt_disable()/*ARM10C this*/ \
do { \
inc_preempt_count(); \
barrier(); \
} while (0)
</code>

######## inc_preempt_count();

<code> ::preempt.h
#define inc_preempt_count() add_preempt_count(1)
</code>

######### add_preempt_count(1)

<code> ::core.c
void __kprobes add_preempt_count(int val)
{
#ifdef CONFIG_DEBUG_PREEMPT // ARM10C Y 
        /*
         * Underflow?
         */
        // ARM10C 20130907 preempt_count()가  0 보다 작은 경우는 bug이다.
        if (DEBUG_LOCKS_WARN_ON((preempt_count() < 0)))
                return;
#endif
        preempt_count() += val;
        // ARM10C 20130907 0x40000001 + 1(val)
#ifdef CONFIG_DEBUG_PREEMPT
        /*
         * Spinlock count overflowing soon?
         */
        DEBUG_LOCKS_WARN_ON((preempt_count() & PREEMPT_MASK) >=
                                PREEMPT_MASK - 10);             
#endif                    
        // ARM10C 20130907 preempt_count = 0x40000002
       if (preempt_count() == val)     
                trace_preempt_off(CALLER_ADDR0, get_parent_ip(CALLER_ADDR1));
}
</code>

Q: _kprobes가 나오는데 무엇일까요?
A: 커널 코드에 원하는 작업을 동적으로 추가할 수 있는 강력한 디버깅 기법이다.
Ref. Kprobes 동작 방식 #1
http://www.chunghwan.com/ko/systems/how-kprobes-works-1/

Q: 계속 preempt count를 통하여 preemption을 막는다고 하는데 어떻게 할까요?
A: interrupt Handler에서 count를 이용하여 0이상이면
   preempt가 발생하지 못하게 할것이다.

Q: 그럼 숫자를 조금만 추가하면 될것인데, 왜 0x4000_0001가 들어갔을까요?
A: 지금은 충분한 숫자를 미리 넣어두었을 뿐이다.
   향후에 start_kernel()→sched_init()→init_idle() 함수에
   의해 다시 재조정 될 것이다.

Q: preempt_count()+=val이 가능한 문법인가? 함수에 어떻게 값을 더할 수 있나요?
A: 따라가보면 preempt_count()는 함수가 아니라 구조체 값입니다.
   그래서 가능합니다.

Q: 그래도 혼란스럽다. 지금은 scheduler가 off상태인데,
   preempt 자체가 필요가 있을까?
A: 말씀하신것처럼, 현재는 의미가 없을수도 있습니다.
   하지만 이 함수는 나중에 계속 사용하는 함수입니다.

####### do_raw_spin_trylock(raw_spinlock_t *lock)

<code>
static inline int do_raw_spin_trylock(raw_spinlock_t *lock)
{
return arch_spin_trylock(&(lock)->raw_lock);
}
</code>

######## arch_spin_lock()
원래는 arch_spin_trylock 코드를 봐야 하지만,
이해를 돕기 위해 arch_spin_lock 코드를 봤습니다.

<code>
static inline void arch_spin_lock(arch_spinlock_t *lock)
{
        unsigned long tmp;
        u32 newval;//다음 next 값
        arch_spinlock_t lockval;//현재 next 값
//ARM10C 20130907 
//"1:   ldrex   lockval, &lock->slock\n"
//현재 next(lockval)는 받아 놓고,
//"     add     newval, lockval, (1<<TICKET_SHIFT)\n" tickets.next += 1
//다음 next(newval) 는 += 1하고 저장 한다.
//"     strex   tmp, newval, &lock->slock\n"
//"     teq     tmp, #0\n"
//"     bne     1b"
        // lock->slock에서 실제 데이터를 쓸때(next+=1) 까지 루프
        // next+=1 의 의미는 표를 받기위해 번호표발행
        __asm__ __volatile__(
"1:     ldrex   %0, [%3]\n"
"       add     %1, %0, %4\n"
"       strex   %2, %1, [%3]\n"
"       teq     %2, #0\n"
"       bne     1b"
        : "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
        : "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
        : "cc");
 
        // 실재 lock을 걸기 위해 busylock 한다.
        // 받은 번호표의 순을 기다린다.(unlock에서 owner을 증가 시켜서)
        while (lockval.tickets.next != lockval.tickets.owner) {
                wfe();  // ARM10C 이벤트대기(irq,frq,부정확한 중단 또는 디버그 시작 요청 대기. 구현되지 않은 경우 NOP
               // arch_spin_unlock()의 dsb_sev();가 호출될때 깨어남
                lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);
                // ARM10C local owner값 업데이트
        }   
 
        smp_mb();
}
</code>

######## LDREX, STREX에 대해서 다시 정리하겠습니다.

STREX는 swap결과에 따라 성공하면 tmp에 0을넣고, 실패하면 1을 넣는 구문입니다.

이 명령어들 자체가 예전 아키텍처에서 swp명령이 cost가(속도가 느림) 많이
발생하기 때문에, 나오게된 명령들입니다.해당 명령어가 추가되었다는 의미는,
AMBA 버스에 원래 기존 load signal이 존재하였는데, 속도문제로 transaction이
따로 추가되어 이 명령어들은 독립적으로 signal이 나옵니다.

그리고 혼란스러워 하시는것 같은데, spinlock은 atomic lock을 하기위한 수단일
뿐입니다.

상위 관점에서 STREX와 LDREX를 설명하면 아래와 같다.
일단 비교대상인 SWP는 당연히 Rn 주소에서 값을 읽어오고(Rt) 전달된 값(Rt2)을
write합니다.

LDREX는 항상 동작하며 Rn주소에서 읽은 값을 Rt에 저장함과 동시에
메모리 상태를 exclusive access상태로 변경

STREX는 메모리 상태에 따라 STR 실행 여부를 결정.
(실행 여부는 Rd(위의 tmp변수)값으로 확인)

※ STREX 실행 여부를 결정하는 메모리 상태는 CLREX 실행 여부,
다른 agent에 의한 STREX/STR 실행 여부 등에 의해 변경.


- 주의 -
LDREX/STREX는 HW 제약으로 정확하게 동일 어드레스가 아닌
비슷한 range에 들어도 동작하므로 SW작성에 주의가 필요.
또한 1코어내의 서로 다른 thread에서 수행하는 LDREX, STREX를 구분할수
없으므로 (정확하게는IMPLEMENTATION DEFINED)
※ 8차 스터디 글에서 나타낸 바와 같이 context switching시에 dummy STREX
 또는 CLREX의 수행이 필요.


HW적인 관점에서 STREX와 LDREX를 설명하면 아래와 같다. (SWP vs. STREX/LDREX)
1) 먼저 SWP은 bus가 lock된 (bus arbiter가 다른 master들이 해당 slave로
접근하지 못하도록 제어) 상태에서 ldr&str가 순차적으로 수행되는 단순한 구조



2) LDREX와 STREX의 경우 최근에 access한 agent, 값 등에 대한 정보를
local monitor와 global monitor에 저장하고, STREX의 수행여부를 결정



따라서 LDREX, STREX의 경우 Core 또는 memory IF에 monitor를 구현하기 위한
tag memory와 FSM이 필요하다.
monitor관련 자세한 내용은 AARM v7의 A3.4절 참조, Figure A3-3,4에
local, global monitor의 state machine이 나타나 있다.

SWP를 Exclusive access(LDREX, STREX)로 대체한 이유에 대한 ARM사의 설명입니다.
Ref.: ARM Synchronization Primitives, A1.2 Limitations of SWP and SWPB

요약하면,
1. SWP의 (LOAD+STORE)를 LDREX, STREX로 분리하여 Pipeline stall 및
interrupt latency 향상

2. Multicore등의 환경에서 memory lock으로 인한 메모리 성능 저하 방지
(bus상에서 하나의 master가 lock하고 있는 slave에 다른 master는 lock 해제시까지
 대기해야함)

3. SWP은 LOCK만 구현가능하지만(swap만으로는 increment/decrement가 불가),
LDREX,STREX는 세마포어 구현이 가능하다.(물론 lock이 구현된다면 lock으로
semaphore구현은 가능, 다만 여기서는 직접 구현 가능 여부를 말함)

※ SWP은 AXI4(ACE) 이후부터 지원되지 않음(관련 시그널이 삭제되었음)

관련한 학술적 내용은 Computer Architecture 교재나 강의 자료의 synchronization을
읽어보시는 것도 좋습니다.
fetch and add/compare and swap(CAS)/load linked & store conditional(LLSC)로
분류하여 설명한 자료들이 있네요.

######## SPINLOCK에 대해서 다시한번 짚고 넘어가야 할 것 같습니다.

spin lock은 mutiprocessor system에서 여러 processor가
동시에 critical section에 진입하지 못하도록 하는 synchronization 기법이다.
한 processor가 lock을 가지고 있으면 다른 processor들은 unlock될 때까지
busy-wait하다가 lock을 차지하기 위해 동시에 lock 변수에 접근(write)한다.

기존의 spin lock은 2가지 문제점이 존재하였습니다.
1) 각 processor간에 lock을 획득하는 순서 보장 못함
2) 한 processor가 lock 변수에 write를 하면 다른 processor의 cache line이
invalidate되어 성능이 악화

이를 해결하기 위해서 2.6.25버전부터 ticker spin lock이 도입되어,
각 processor들은 자신만의 ticket을 부여받고
자기 차례가 돌아오는 경우에만 write를 시도하여,
순서대로 lock을 얻고 전체적으로 cache miss 횟수를 줄입니다.
(Ref. [Linux] ticket spin lock)

라고는 하지만, 역시 이해하기에는 그림이 짱짱이죠?
ticket spin lock은 은행 or 영화관의 티켓 발권기로 보시면 됩니다.


위와같이 티켓을 뽑았습니다.
번호는 79번이네요. 앞에 8명이나 있으니 계속 대기해야 합니다.



당연히 창구를 이용하고 있다면,
다른 사람은 대기 해야 겠죠?
그러다가 창구번호(tickets.owner)가 자신의 번호표(tickets.next)와 같아지면,
그때 이용 가능합니다.

Q: 그런데 이런경우 spin lock의 단점이 보입니다.
   자료형이 U16으로 선언되어 있는데,
   이 숫자를 넘어가면 overflow가 일어날 수 있겠네요.
A: 프로세서의 개수라서 16비트면 2^16개 입니다.
   임베디드에서 그럴 확률은 아직 머나먼 미래의 얘기일 것 같습니다.

Q: wfe()는 무슨 기능을 하나요?
A: event가(irq, fiq등) 들어오기 전까지 sleep이라고 보시면 됩니다.
   보통은 interrupt가 들어와야 깨어 납니다.
   여기서는 다른 프로세서가 시그널을 보낼것 입니다.

Q: subs %1, %0, %0, ror #16\n”에서 ror연산의 의미가 무엇일까요?
<code>
static inline int arch_spin_trylock(arch_spinlock_t *lock)
{
        unsigned long tmp;
        u32 slock;
 
        __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");
...
</code>

A: ror연산은 shift연산과 비슷하지만, 오른쪽으로 회전하라는 의미입니다.

   따라서, 위 구문은 next와 owner의 값이 같은지 체크하는 구문입니다.

Q: 우리는 lock과 try-lock 함수를 보았는데, 무엇이 틀린가요?
A: lock을 획득할 때까지 기다리는 것입니다. (block)
   try-lock은 시도하다가 안되면 그냥 넘어가는 lock입니다. (non-block)

Q: lock 전후로 dmb, dsb명령을 수행합니다. 왜 이렇게 사용할까요?
<code>
static inline void arch_spin_unlock(arch_spinlock_t *lock)
{ 
        smp_mb();   
        lock->tickets.owner++;
        dsb_sev(); // ARM10C 이벤트발생 
} 
</code>
A: 앞의 dmb 명령은 미리 정리를 해서 안섞이게 할려고 하는 것이다.
   중요한 키가 존재하고, 내가 작업을 하고 있는데
   owner를 미리 바꾸면 값이 이상해 진다.

   owner를 추가하고 dsb 명령을 수행하는 이유는 memory까지 확실히 전달되어야,
   다음에 다른 프로세서가 깨어나서 공유자원을 보더라도 확실할 것이다.
   결국 owner값을 보호하기 위해서 하는 것이다.

Q: _CHECKER_가 의미하는 바가 무엇일까요?
<code>
#ifdef __CHECKER__
  # define __user         __attribute__((noderef, address_space(1)))
  # define __kernel       __attribute__((address_space(0)))
  # define __safe         __attribute__((safe))
         ...
  # define __cond_lock(x,c)       ((c) ? ({ __acquire(x); 1; }) : 0)
 
#else
  # define __user
  # define __kernel          
  # define __safe            
         ...
  # define __cond_lock(x,c) (c) //ARM10C this
#endif
</code>
A: Sparse에 관한 설정으로 리눅스용 코드 정적 분석 툴입니다.
    Sparse is a computer software tool designed to find possible coding faults
   in the Linux kernel
   상세 정보는 /documentation/sparse.txt에 정의되어 있습니다.
 
   __CHECKER__는 compiler.h에 있습니다. raw_spin_trylock -> __cond_lock 매크로를
   호출할 때 __CHECKER__에 따라서 __cond_lock이 선택됩니다.
   그림에서 노란색을 참조하세요.

compiler_h.png
## printf_ratelimit()-> time_is_before_jiffies()

<code> __ratelimit():: ratelimit.c
...
if (time_is_before_jiffies(rs->begin + rs->interval)) {
if (rs->missed)
printk(KERN_WARNING "%s: %d callbacks suppressed\n",
func, rs->missed);
rs->begin   = 0;
rs->printed = 0;
rs->missed  = 0;

}
...
</code>

Q1): jiffies가 무엇인가요?
A  : time tick이 발생할때 마다 추가되는 변수입니다. type은 long입니다.

Q2): 요즘 시스템은 time tick이 얼마나 되나요?
A  : 예전에는 100이었는데 요즘은 1000입니다. 즉, 1ms입니다.
     ms는 이걸로 측정하고, ns는 busy wait 해버립니다.
     실제로 개발하면 sleep을 많이 사용하는데, 시간이 정확하지는 않습니다.

     time tick과 jiffies은 timer isr에 의해 update된다.
     현재 IRQ가 disabled상태이므로 의미가 없다는 이야기.
     scheduler 얘기가 나온것은 보통 timer isr에 의해 수행되는 것이
     scheduler이기 때문에 나중에 scheduler가 enabled되는 시점에
     time tick 등이 update되어 해당 ratelimit함수가 유효해지기
     때문에 등장한 내용.

Q3): typecheck()를 실제로도 사용하나요?
<code>
#define time_after(a,b)         \
        (typecheck(unsigned long, a) && \
         typecheck(unsigned long, b) && \
         ((long)(b) - (long)(a) < 0))
</code>
A   : 실제로 본적은 없습니다.
      다만 개발할때 보통 지역변수로 flag를 기입하니까
      그 error를 체크하지 않을까 합니다.

      오늘 Computer Architecture 강의를 들으러 가서 이것저것 여쭤봤는데,
      교수님께서 이런 얘기를 해주시네요.
      그냥 참고하시기 바랍니다.

      Hardware적으로 type을 체크할 수는 없다.
      (Commercial 기준, research용 hardware는 tag를 따로 기입한다고 합니다.)
      따라서, 현재로서는 software로 type을 체크할 수 밖에 없다.

# boot_cpu_init()
-> set_cpu_online(),
-> set_cpu_active(),
-> set_cpu_present(),
-> set_cpu_possible()


<code>
static void __init boot_cpu_init(void)
{
int cpu = smp_processor_id();
/* Mark the boot cpu "present", "online" etc for SMP and UP case */
set_cpu_online(cpu, true);
set_cpu_active(cpu, true);
set_cpu_present(cpu, true);
set_cpu_possible(cpu, true);
}
</code>

 시스템 상에 존재하는 CPU의 상태 정보는 다음과 같은 4개의 비트맵
 (cpumask_t)으로 관리한다.

 cpu_possible_mask - 해당 비트에 대한 CPU가 존재할 수 있다.
 cpu_present_mask - 해당 비트에 대한 CPU가 존재한다.
 cpu_online_mask - 해당 비트에 대한 CPU가 존재하며 스케줄러가 이를 관리한다.
 cpu_active_mask - 해당 비트에 대한 CPU가 존재하며 task migration 시 이를
                   이용할 수 있다.

## this_cpu 값
 this_cpu는 threadinfo에 저장된 cpuid로 logical id로 physical core id는 0이
아닐 수 있다. boot rom 등에서 MPCore중 1개의 core만 enable하고 수행하는데
이때의 core id는 선택가능하다.(physical id는 any)

 linux kernel은 초기화를 수행한 boot core를 logical ID 0로 정한다.
smp_setup_processor_id()에서 현재 수행하는 core id를 검사(MPIDR)하여
logical id 0에 mapping 한다.

 현재 threadinfo에 저장된 값은 logical ID로 0이다.
physical id가 필요한 경우 다음과 같은 코드가 사용된다.

  cpu_logical_map(smp_processor_id())

 추가적으로 확인이 필요한 부분은 일부만 초기화하는 global variable의 data를
compiler가 항상 나머지는 0으로 초기화하는 가이다.
일단 gcc에서는 일부만 초기화하는 전역 변수라도 elf상에는
전체 size가 .data 섹션에 만들어지며 초기화된 값 이외에도 0으로 저장되고 있다.

## 어셈블리로 set_bit를 하는 이유가 무엇일까요?
<code>
  .macro bitop, name, instr      
ENTRY(  \name           )
UNWIND( .fnstart        )
        ands    ip, r1, #3     
        strneb  r1, [ip]                @ assert word-aligned           
        mov     r2, #1
        and     r3, r0, #31             @ Get bit offset
        mov     r0, r0, lsr #5 
        add     r1, r1, r0, lsl #2      @ Get word offset
        mov     r3, r2, lsl r3 
1:      ldrex   r2, [r1]       
        \instr  r2, r2, r3     
        strex   r0, r2, [r1]   
        cmp     r0, #0         
        bne     1b
        bx      lr
UNWIND( .fnend          )
ENDPROC(\name           )
        .endm
</code>

함수명 그대로 atomic하게 실행하려고 하려는 것입니다.

## mov를 사용할때 상수값은 8비트만 사용하고, 로테이션 하고 그랬는데,
   실제 필드에서도 그렇게 사용하나요?

대부분의 경우 pseudo instruction을 사용한다.(예: LDR rn, =0xXXXXXXXX)
컴파일러가 알아서 MOV로 풀지 ldr로 가져올지 결정한다.
대부분의 상황에서 cache가 hit되므로 LDR등이 1cycle로 수행가능하다.
cache disabled이고 loop안에서 반복 사용되고 bottleneck이라 성능 최적화가
필요한 경우에나 고민하여 작성한다.

## 나머지 set_cpu_active(), set_cpu_present(), set_cpu_possible()는
   전부 동작이 같습니다.

시스템 상에 존재하는 CPU의 상태 정보는 다음과 같은 4개의 비트맵 (cpumask_t)으로 관리한다.

cpu_possible_mask - 해당 비트에 대한 CPU가 존재할 수 있다.
: 시스템에서 현재 핫플러그(hot plug)가 가능한 CPU들에 대한 비트맵이다.
해당 맵은 부팅시 시스템이 지원할 CPU의 수에 대한 것으로 비트가 설정되면
추가되거나 제거될 수 없다.

cpu_present_mask - 해당 비트에 대한 CPU가 존재한다.
: 시스템에 존재하는 CPU를 나타내는 비트맵이지만, CPU들이 모두 온라인상태는
 아니다. 예를들어, ACPI와 같은 서브시스템에 의해 물리적으로 핫플러그가
 처리될 때 해당 비트가 설정된다.

cpu_online_mask - 해당 비트에 대한 CPU가 존재하며 스케줄러가 이를 관리한다.
: 온라인(사용중인) 되어있는 모든 CPU에 대한 비트들을 1로 설정한 비트맵이다.
 커널 스케쥴링이나 디바이스로부터 인터럽트를 받을 준비가 되어있다면
 해당 비트가 설정된다. 인터럽트를 포함한 모든 OS 서비스가 다른 CPU로
 이동되면 해당 비트는 지워진다.

cpu_active_mask - 해당 비트에 대한 CPU가 존재하며 task migration 시
이를 이용할 수 있다.

리눅스 콘솔에서 cpu에 대한 possible, online, present의 정보는
/sys/devices/system/cpu 정보에 파일을 참조하여 알 수 있다.

drwxr-xr-x  9 root root    0 2013-09-12 15:39 ./
drwxr-xr-x 12 root root    0 2013-09-12 15:39 ../
drwxr-xr-x  7 root root    0 2013-09-12 15:53 cpu0/
drwxr-xr-x  7 root root    0 2013-09-12 15:53 cpu1/
drwxr-xr-x  7 root root    0 2013-09-12 15:53 cpu2/
drwxr-xr-x  7 root root    0 2013-09-12 15:53 cpu3/
drwxr-xr-x  3 root root    0 2013-09-12 15:53 cpufreq/
drwxr-xr-x  2 root root    0 2013-09-12 15:53 cpuidle/
-r--r--r--  1 root root 4096 2013-09-12 15:53 kernel_max
-r--r--r--  1 root root 4096 2013-09-12 15:53 offline
-r--r--r--  1 root root 4096 2013-09-12 15:53 online
drwxr-xr-x  2 root root    0 2013-09-12 15:53 perf_events/
-r--r--r--  1 root root 4096 2013-09-12 15:53 possible
-r--r--r--  1 root root 4096 2013-09-12 15:53 present
-rw-r--r--  1 root root 4096 2013-08-19 11:37 sched_mc_power_savings
 
jin@jin-desktop:/sys/devices/system/cpu$ cat possible 
0-7
jin@jin-desktop:/sys/devices/system/cpu$ cat present 
0-3
jin@jin-desktop:/sys/devices/system/cpu$ cat online
0-3
jin@jin-desktop:/sys/devices/system/cpu$ cat offline 
4-7,8-15

댓글 없음:

댓글 쓰기