# 일시 : 2013.08.31 (19주차)
# 모임명 : NIPA지원_IAMROOT.ORG_10차ARM-C
# 장소 : 토즈 타워점
# 장소지원 : NIPA지원
# 참여인원 : 8인
# 스터디 진도 :
- start_kernel()-> boot_cpu_init();
// 현재 cpu(core id)를 얻어서 cpu_XXX_bits[] 의 cpu를 셋한다.
# 코드 분석을 위한 팁
## vi에디터에서 #ifdef ~ #endif로 구분되어 있는 곳의 그룹 이동은
어떻게 할 수 있나요?
해당 부분에서 %를 누르시면 이동 가능합니다.
<code>
#ifdef iamroot
..
..
..
..
#endif <-여기서 %
</code>
## emacs에서 cscope 사용방법
emacs에서 cscope database를 생성하는 것이다.
1. emacs에서 생성하기
M-x cscope-index-file <RET> 를 입력하고, 원하는 디렉토리 (ex. /usr/src/sys) 를 입력한다.
이후 원하는 파일을 열고 cscope를 사용하면 된다.
2. sh에서 생성하기
$find . \( -name '*.c' -o -name '*.cc' -o -name '*.h' -o -name '*.s' \) -print
3. sh에서 생성하기 또 다른 방법
$find ./ -name *.[chS] -print > cscope.files
4. linux커널의 경우 Makefile 이용하기
$make ARCH=arm cscope
5. python 소스 분석을 할때 사용하는 방법
$find . \( -name '*.py' \) -print > cscope.files
## cscope 키 입력 설명
C-c s s Find symbol.
C-c s d Find global definition.
C-c s g Find global definition (alternate binding).
C-c s G Find global definition without prompting.
C-c s c Find functions calling a function.
C-c s C Find called functions (list functions called from a function).
C-c s # TODO: Find text string.
C-c s e Find egrep pattern.
C-c s f Find a file.
C-c s i Find files #including a file.
# boot_cpu_init();
// 현재 cpu(core id)를 얻어서 cpu_XXX_bits[] 의 cpu를 셋한다.
<code> main.c
static void __init boot_cpu_init(void)
{
int cpu = smp_processor_id();
set_cpu_online(cpu, true);
set_cpu_active(cpu, true);
set_cpu_present(cpu, true);
set_cpu_possible(cpu, true);
}
</code>
## int cpu = smp_processor_id();
// cpu : 0 이된다.
<code> smp.h
#ifdef CONFIG_DEBUG_PREEMPT // CONFIG_DEBUG_PREEMPT=y
extern unsigned int debug_smp_processor_id(void);
# define smp_processor_id() debug_smp_processor_id()
#else
# define smp_processor_id() raw_smp_processor_id()
#endif
</code>
## smp_processor_id() debug_smp_processor_id()
<code>
notrace unsigned int debug_smp_processor_id(void)
{
unsigned long preempt_count = preempt_count();
int this_cpu = raw_smp_processor_id();
if (likely(preempt_count))
goto out;
if (irqs_disabled())
goto out;
if (cpumask_equal(tsk_cpus_allowed(current), cpumask_of(this_cpu)))
goto out;
if (system_state != SYSTEM_RUNNING)
goto out;
preempt_disable_notrace();
if (!printk_ratelimit())
goto out_enable;
printk(KERN_ERR "BUG: using smp_processor_id() in preemptible [%08x] "
"code: %s/%d\n",
preempt_count() - 1, current->comm, current->pid);
print_symbol("caller is %s\n", (long)__builtin_return_address(0));
dump_stack();
out_enable:
preempt_enable_no_resched_notrace();
out:
return this_cpu;
}
</code>
## notrace가 의미하는 것이 뭘까요?
// 디버거등에 의한 프로 파일링을 막기 위해서 사용합니다.
<code>
notrace unsigned int debug_smp_processor_id(void)
</code>
gcc manual 에서 설명하는 notrace의 정의는 다음과 같습니다.
If -finstrument-functions is given,
profiling function calls will be generated at entry and exit of
most user-compiled functions.
Functions with this attribute will not be so instrumented.
#define notrace __attribute__((no_instrument_function)) 이라면,
이는 디버거와는 별 상관이 없는 것 같습니다.
물론 _cyg_profile_func_enter, _cyg_profile_func_exit 함수에서
디버거와 통신하는 뭔가가 있을 수도 있겠습니다만 저 같으면
보통 시간 재서 출력하는 루틴을 넣을 것 같네요.
예시>
<code>
#include <stdio.h>
#include <stdlib.h>
__attribute__ ((no_instrument_function))
void __cyg_profile_func_enter(void *this, void *callsite)
{
printf ("__cyg_profile_func_enter\n");
}
__attribute__ ((no_instrument_function))
void __cyg_profile_func_exit(void *this, void *callsite)
{
printf ("__cyg_profile_func_exit\n");
}
/* constructor, destuctor는 생략해도 됩니다. */
__attribute__ ((constructor))
void myConstructor( void )
{
printf ("constructor\n");
}
__attribute__ ((destructor))
void myDestructor( void )
{
printf ("destuctor!\n");
}
void func_a( void )
{
printf ("func_a\n");
return;
}
__attribute__ ((no_instrument_function))
void func_b( void )
{
printf ("func_b\n");
return;
}
int main()
{
printf ("main\n");
func_a();
func_b();
return 0;
}
</code>
결과는 다음과 같이 나옵니다.
<결과>
dqxt@dons-pleiades ~/study/kernel/instrument_fn $ gcc -o test -finstrument-functions test.c
dqxt@dons-pleiades ~/study/kernel/instrument_fn $ ./test
__cyg_profile_func_enter
constructor
__cyg_profile_func_exit
__cyg_profile_func_enter
main
__cyg_profile_func_enter
func_a
__cyg_profile_func_exit
func_b
__cyg_profile_func_exit
__cyg_profile_func_enter
destuctor!
__cyg_profile_func_exit
dqxt@dons-pleiades ~/study/kernel/instrument_fn $
</결과>
당시에는 이게 뭘까 의미를 고민했었는데, 코드와 결과를 보니 명확해 지네요^^;
결국 각 function의 entry와 exit에서 해당 동작을 수행하는 것이었군요.
해당 코드 이력을 따라가 보면
전 : unsigned int debug_smp_processor_id(void) 에서
후 : notrace unsigned int debug_smp_processor_id(void) 로
notrace가 추가되었고, 여기에 다음과 같이 설명이 추가 되어 있습니다.
Mark with “notrace” functions in core code that should not be
traced. The “notrace” attribute will prevent gcc from adding
a call to ftrace on the annotated funtions.
저는 여기서 말한 ftrace (function trace)를 디버깅 목적으로도
많이 사용 하기 때문에 디버거라고 적었습니다.
원문을 따라적는다면 ftrace가 맞습니다.
함수가 실행될때 마다, debug_smp_processor_id()가 실행되어
processor ID를 읽어 오는 동작을 합니다.
여기서 이 함수를 통해서 preemption이 safe한 것인지 체크를 할 수도
있습니다.
하지만 SMP이고 CONFIG_DEBUG_PREEMPT 가 선언되어 있으므로
debug_smp_processor_id()를 실행하여
a.b. 조건을 만족하여 현재 실행하고 있는
thread 구조체에 CPU_ID를 설정합니다.
a. preemption이 safe 하여야 함
b. ftrace와 같은 디버거들에 영향 받지 않아야 함
## preempt_count가 의미하는 것이 무엇일까요?
선점 당한 횟수를 말합니다.
<code> smp_processor_id.c
unsigned long preempt_count = preempt_count();
</code>
<code> preempt.h
#define preempt_count() (current_thread_info()->preempt_count)
</code>
preempt_count = preempt_count() : 0x4000 0001 가 됩니다.
preempt_count 양수 값으로 큰 값을 가지게 되면 선점 당하지 않게 됩니다.
여기서는 초기화를 하고 있어서 독점적으로 자원을 사용하기 위함입니다.
## this_cpu = raw_smp_processor_id();
// this_cpu : 0
<code> smp.h
#define raw_smp_processor_id() (current_thread_info()->cpu)
</code>
## if (likely(preempt_count)) goto out;
// preempt_count : 0x4000 0001 이므로 goto out으로 간다.
### likely가 의미하는 것은 무엇일까요?
컴파일러한테 branch여부를 알려주는 것입니다.
if문등에서 조건에 따른 분기를 할때 한쪽에
가중치(likely나 dislikely)를 주어서 최적화하게 하라는 의미입니다.
## current_thread_info()->cpu
<code> thread_info.h
static inline struct thread_info *current_thread_info(void)
{
register unsigned long sp asm ("sp");
// THREAD_SIZE: 8192, ~(THREAD_SIZE - 1): 0xffffe000
return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
}
</code>
### register unsigned long sp asm ("sp");
// stack이 8K로 정렬되어 있기 때문에 13비트를 clear시키면,
stack의 맨 앞을 가리키게 된다.
<picture>
|---| 0x4000 0000
| |
| | sp1 : 0x4000 1FFD
| |
|---| 0x4000 2000
| |
| | sp : 0x4000 3ABC
| |
|---| 0x4000 4000
</picture>
stack이 8K 단위로 정렬되어 있기때문에 위에서 보는 바와 같이
base는 0x40000000, 0x40002000 부터 8K씩 정렬되어 있습니다.
따라서 THREAD_SIZE: 8192, ~(THREAD_SIZE - 1) : 8K-1은 0x1FFF가 되어
~(0x1FFF) = 0xFFFFE000으로 13bit를 클리어 하는 mask가 됩니다.
만약 SP(0x40001FFD)를 13bit를 클리어 하면
0x40001FFD & 0xFFFFE000 = 0x40000000로
현재 STACK의 맨 끝 ( stack은 아래> 로 자라므로..) 이자
current thread 주소를 반환하게 됩니다.
마찬가지로 만약 SP가 SP1(0x40003ABC) 에 있다면
0x40003ABC & 0xFFFFE000 = 0x40002000으로
현재 STACK의 맨 끝을 가르키게 됩니다.
### union구문이 이해가 잘 안가네요. 저렇게 사용할 수 있나요?
_init_task_data는 init_task.h에서 .data..init_task 섹션으로 사용하겠다고
정의했습니다.
<code> init_task.c
union thread_union init_thread_union __init_task_data =
{ INIT_THREAD_INFO(init_task) };
</code>
### union의 3번째의 _init_task_data는 뭔가요?
3번째 속성은 attribute라고 보시면 됩니다.
여기서는 init_thread_union를 초기화하고 이것의 속성을
.data..init_task 섹션해서 사용하겠다는 것입니다.
# 빠진 사람이 많아서 진도를 if(likely(preempt_count)) goto out 이 아닌
조건 문까지 다 분석하기로 함
# if (irqs_disabled()) goto out;
<code> irqflags.h
#define irqs_disabled() \
({ \
unsigned long _flags; \
raw_local_save_flags(_flags); \
raw_irqs_disabled_flags(_flags); \
})
</code>
### raw_local_save_flags(_flags);
<code> irqflags.h
#define raw_local_save_flags(flags) \
do { \
typecheck(unsigned long, flags); \
flags = arch_local_save_flags(); \
} while (0)
</code>
#### typecheck() 함수에서 typeof가 의미하는 것이 무엇일까요?
먼저 typeof에 대해 gcc manual을 살펴보면 다음과 같이 정의합니다.
참조 링크 : gcc manual
http://gcc.gnu.org/onlinedocs/gcc/Typeof.html
Another way to refer to the type of an expression is with typeof.
The syntax of using of this keyword looks like sizeof,
but the construct acts semantically like a type name defined with typedef.
요약하면 해당변수의 type을 반환하는 역할을 하는 매크로 함수입니다.
간단한 test code를 보면 더욱 이해가 명확할 것입니다.
<code>
int main()
{
char s1;
typeof(s1) s2;
unsigned char u1;
typeof(u1) u2;
s2 = 0xff;
u2 = 0xff;
printf("s2 = %d\n", s2);
printf("u2 = %d\n", u2);
return 0;
}
</code>
결과
s2 = -1
u2 = 255
#### (void)(&__dummy == &__dummy2); 가 의미하는 바가 무엇일까요?
typecheck의 type이 같지 않으면
컴파일러가 컴파일 시, warning을 발생합니다.
예제 코드로 살펴보면,
<code>
#include <stdio.h>
#define typecheck(type,x) \
({ type __dummy; \
typeof(x) __dummy2; \
(void)(&__dummy == &__dummy2); \
1; \
})
int main(void)
{
int x;
if (typecheck(int, x))
printf("int x OK\n");
if (typecheck(double, x))
printf("double x OK\n");
return(0);
}
</code>
결과
jin@jin-desktop:~/dev/test$ gcc -Wall -Wextra -o typecheck typecheck.c
typecheck.c: In function ‘main’:
typecheck.c:15: warning: comparison of distinct pointer types lacks a cast
결과(-Werror옵션 추가)
jin@jin-desktop:~/dev/test$ gcc -Wall -Wextra -Werror -o typecheck typecheck.c
cc1: warnings being treated as errors
typecheck.c: In function ‘main’:
typecheck.c:15: error: comparison of distinct pointer types lacks a cast
주소값을 비교한다고 착각할 수 있는데, 이 구문은 type을 기반으로 비교를
하기 때문에 주소값은 의미가 없습니다.
참조 링크 : about typecheck in linux kernel
http://stackoverflow.com/questions/10393844/about-typecheck-in-linux-kernel
#### 1; \ 가 의미하는 것은 무엇일까요?
1이 의미하는 것은 단순히 항상 1을 리턴한다는 의미입니다.
이 부분을 이용하여 바로 위 코드의 결과처럼 구문 check가 가능합니다.
결과
jin@jin-desktop:~/dev/test$ ./typecheck
int x OK
double x OK
참조 링크 : typecheck code
http://comments.gmane.org/gmane.linux.kernel/1094113
### flags = arch_local_save_flags(); \
<code>
static inline unsigned long arch_local_save_flags(void)
{
unsigned long flags;
asm volatile(
" mrs %0, " IRQMASK_REG_NAME_R " @ local_save_flags"
: "=r" (flags) : : "memory", "cc");
return flags;
}
</code>
## raw_irqs_disabled_flags(_flags);
arch_irq_disabled_flags()의 경우 함수가 2개 나옵니다. 어떤점이 틀릴까요?
<code> ::arch/arm/include/asm/irqflags.h
static inline int arch_irqs_disabled_flags(unsigned long flags)
{
return flags & PSR_I_BIT;
}
</code>
<code> ::include/asm-generic/irqflags.h
#ifndef arch_irqs_disabled_flags
static inline int arch_irqs_disabled_flags(unsigned long flags)
{
return flags == ARCH_IRQ_DISABLED;
}
#endif
</code>
위의 code는 CPSR이 존재하는 경우 분기하는 함수이며,
아래 code는 CPSR이 없을 경우 처리하는 함수입니다.
# if (cpumask_equal(tsk_cpus_allowed(current), cpumask_of(this_cpu))) goto out;
## cpumask_of(this_cpu)
#define cpumask_of(cpu) (get_cpu_mask(cpu))
</code>
### get_cpu_mask()
<code> :: cpumask.h
static inline const struct cpumask *get_cpu_mask(unsigned int cpu)
{
const unsigned long *p = cpu_bit_bitmap[1 + cpu % BITS_PER_LONG];
p -= cpu / BITS_PER_LONG;
return to_cpumask(p);
}
</code>
#### 1) local에서 const를 선언하면 stack을 사용하는 것인가요?
상수로 보기 때문에 stack을 사용합니다.
#### 2) 위 code에서 %연산자는 왜 할까요?
CPU의 개수만큼 지원하기 위함입니다.
추가로, 리눅스는 최대 32개만큼 CPU를 지원한다고 보면 됩니다.
#### 3) 계속 용어가 혼란 스럽네요.
위 code에서 BIT_PER_LONG과 BITS_TO_LONGS의 차이가 무엇일까요?
BIT_PER_LONG은 우리 타겟 기준 32로 설정되어 있습니다.
BITS_TO_LONGS는 ROUND_UP이라고 보시면 됩니다.
BITS에 해당하는 값이 LONG으로 몆개가 되는 지 계산하는 매크로 입니다.
쿼드 코어면 값이 CPU가 4개 이므로 long크기의 비트 (32) 보다 작으므로
1개의 long이면 된다는 것입니다.
만약 32개 이상이라면 2개의 long이 필요하겠지요.
즉, 32비트면 CPU가 최대 32개 까지 되니까 long 1개로 표현 가능하지만,
64비트면 8바이트로 되니까, long이 2개 있어야 하는 것입니다.
#### 4) 위 code에서 2차원 배열로 선언을 해놓고, 포인터는 1차원 배열이네요.
어떻게 해석 해야 합니까?
포인터가 배열의 앞대가리(표현이 참 재밌네요…)를 보고 있는 것입니다.
#### 5) cpu_bit_bitmap[] 배열을 이렇게 선언한다니 신기하네요.
주석처럼 나온 결과가 궁금합니다.
#define MASK_DECLARE_1(x) [x+1][0] = (1UL << (x))
#define MASK_DECLARE_2(x) MASK_DECLARE_1(x), MASK_DECLARE_1(x+1)
#define MASK_DECLARE_4(x) MASK_DECLARE_2(x), MASK_DECLARE_2(x+2)
#define MASK_DECLARE_8(x) MASK_DECLARE_4(x), MASK_DECLARE_4(x+4)
// cpu_bit_bitmap[33][1]
const unsigned long cpu_bit_bitmap[BITS_PER_LONG+1][BITS_TO_LONGS(NR_CPUS)] = {
// MASK_DECLARE_8(0) 이 아래와 같이 확장됨
// [1][0] = (1UL << 0),
// [2][0] = (1UL << 1),
// [3][0] = (1UL << 2),
// [4][0] = (1UL << 3),
// [5][0] = (1UL << 4),
// [6][0] = (1UL << 5),
// [7][0] = (1UL << 6),
// [8][0] = (1UL << 7),
MASK_DECLARE_8(0), MASK_DECLARE_8(8),
MASK_DECLARE_8(16), MASK_DECLARE_8(24),
MASK_DECLARE_8(0) =⇒ MASK_DECLARE_4(x), MASK_DECLARE_4(x+4)가 되고
MASK_DECLARE_4(0) =⇒ MASK_DECLARE_2(x), MASK_DECLARE_2(x+2)가 되며,
MASK_DECLARE_2(0) =⇒ MASK_DECLARE_1(x), MASK_DECLARE_1(x+1)가 되어,
MASK_DECLARE_1(x) =⇒ [x+1][0] = (1UL « (x)) 로 계산되게 됩니다.
#### 6) return 하는 to_cpumask()함수에서 대체 1이 왜 쓰였을까요?
이렇게 하면 항상 true인데 code가 이해 안되네요.
<code>
#define to_cpumask(bitmap) \
((struct cpumask *)(1 ? (bitmap) \
: (void *)sizeof(__check_is_bitmap(bitmap))))
</code>
C++에서 구조체가 있긴 하지만, 어떤 내용인지는 모를경우 이런식으로 쓰는
경우를 보긴 했습니다.
9차 선배들이 토의한 다음과 같은 내용이 있었습니다.
삼항 연산자가 필요한 이유 : syntax 검사용인듯 합니다.
아래 참고 링크를 보면 bitmap 의 type을 체크하여 compile warnning을
발생시키는 용도라고 합니다.
Actually, the call is not used for runtime, but to check at compile time
that the macro parameter bitmap is actually of type unsigned long *
(hence the name of the function which does only that). If bitmap is
of the wrong type, an warning will be issued, and a compilation warning
in the kernel build is always a serious matter.
참조 링크 :
http://stackoverflow.com/questions/15636214/how-is-for-each-possible-cpu-expanded-in-cpufreq-c-file/15640553#15640553
## tsk_cpus_allowed(current)
<code> :: sched.h
#define tsk_cpus_allowed(tsk) (&(tsk)->cpus_allowed)
</code>
<code>
#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))
### 1) long과 같이 sizeof로 확인하는 값은 누가 결정하는 것인가요?
아키텍처로 알 수 있나요?
컴파일러가 결정해 줍니다.
### 2) 그래서 결국 .cpus_allowed 값이 왜 3이라는 것인가?
값의 계산과정을 알고싶습니다.
<code>
#define BITMAP_LAST_WORD_MASK(nbits) \
( \
((nbits) % BITS_PER_LONG) ? \
(1UL<<((nbits) % BITS_PER_LONG))-1 : ~0UL \
)
</code>
NR_CPUS에 따라 다르며 NR_CPUS=2 인 경우 3입니다.
cpus_allowed는 cpu 수 만큼의 bit를 갖는 bitmap을 만듭니다.
예1: NR_CPUS = 4
> LAST_MASK: (1<<(4%32))-1 = 0b10000 - 1 = 0xf
> unsigned long cpu_bits[1] = {0x0000000ful};
예2: NR_CPUS = 40 = 32+8
> LAST_MASK: (1<<(8%32))-1 = (1<<8)-1 = 0b100000000 - 1 = 0xff
> unsigned long cpu_bits[2] = {
> [0 ... 0] = 0xfffffffful,
> [1] = 0x000000fful};
### 3) 변수 this_cpu값을 몰라서 계산이 혼란스럽다.
이 값은 0인가? 0이라면 그 이유는?
<code>
notrace unsigned int debug_smp_processor_id(void)
{
unsigned long preempt_count = preempt_count();//0x4000_0001
int this_cpu = raw_smp_processor_id();
</code>
<code>
#define raw_smp_processor_id() (current_thread_info()->cpu)
</code>
#### thread_info 구조체
<code>
struct thread_info {
unsigned longflags; /* low level flags */
intpreempt_count; /* 0 => preemptable, <0 => bug */
mm_segment_# TODO: addr_limit; /* address limit */
struct task_struct*task; /* main task structure */
struct exec_domain*exec_domain;/* execution domain */
__u32cpu; /* cpu */
__u32cpu_domain; /* cpu domain */
struct cpu_context_savecpu_context;/* cpu context */
...
#### init_thread_union 선언 및 초기화
/*
* Initial thread structure. Alignment of this is handled by a special
* linker map entry.
*/
// ARM10C 20130824
union thread_union init_thread_union __init_task_data =
{ INIT_THREAD_INFO(init_task) };
#### init_thread_union 초기화 매크로
#define INIT_THREAD_INFO(tsk)\
{\
.task= &tsk,\
.exec_domain= &default_exec_domain,\
.flags= 0,\
.preempt_count= INIT_PREEMPT_COUNT,\
.addr_limit= KERNEL_DS,\
.cpu_domain= domain_val(DOMAIN_USER, DOMAIN_MANAGER) |\
domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) |\
domain_val(DOMAIN_IO, DOMAIN_CLIENT),\
.restart_block= {\
.fn= do_no_restart_syscall,\
},\
}
init_thread_union 으로 선언시 초기화 매크로인 INIT_THREAD_INFO 에서
cpu 값을 초기화 하지 않는다.
init_thread_union의 멤버 cpu도 초기화 값을 따로 넣지 않으므로 0 일 것이다.
우리 소스에서는 어떤 값이 들어와도 그걸 0으로 하는것이 맞다고 본다.
하지만, task struct와 실제 돌고 있는 cpu가 같을까?
그것이 의심 스러운 것이다.
C99 standard에서 부분만 초기화된 경우 나머지는 0으로 초기화한다고
정의하므로 .cpu와 같이 초기화하지 않으면 0이다.
C99 Standard 6.7.8.21
If there are fewer initializers in a brace-enclosed list than
there are elements or members of an aggregate, or fewer characters
in a string literal used to initialize an array of known size than
there are elements in the array, the remainder of the aggregate
shall be initialized implicitly the same as objects that have
static storage duration.
ref:http://stackoverflow.com/questions/10828294/c-and-c-partial-initialization-of-automatic-structure
커널초기화과정에서 현재 수행중인 cpu를 logical id 0으로 매핑하므로
init_thread_union의 cpu : 0이 맞다.
### init_task에 들어가능 상세 정보
<code> init_task.c
28// __init_task_data: __attribute__((__section__(".data..init_task")))
29// INIT_THREAD_INFO(init_task):
30// {
31// .task = &init_task,
32// .exec_domain = &default_exec_domain,
33// .flags = 0,
34// .preempt_count = 0x40000001,
35// .addr_limit = KERNEL_DS,
36// .cpu_domain = domain_val(DOMAIN_USER, DOMAIN_MANAGER) |
37// domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) |
38// domain_val(DOMAIN_IO, DOMAIN_CLIENT),
39// .restart_block = {
40// .fn = do_no_restart_syscall,
41// },
42// }
43//
44// union thread_union init_thread_union __attribute__((__section__(".data..init_task"))) =
45// {
46// .task = &init_task,
47// .exec_domain = &default_exec_domain,
48// .flags = 0,
49// .preempt_count = 0x40000001,
50// .addr_limit = KERNEL_DS,
51// .cpu_domain = domain_val(DOMAIN_USER, DOMAIN_MANAGER) |
52// domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) |
53// domain_val(DOMAIN_IO, DOMAIN_CLIENT),
54// .restart_block = {
55// .fn = do_no_restart_syscall,
56// },
57// }
</code>
## cpumask_equal()
#define small_const_nbits(nbits) \
(_builtin_constant_p(nbits) && (nbits) <= BITS_PER_LONG)
</code>
### 1) _builtin_constant_p의 의미는 무엇인가요?
인수가 컴파일 시 상수로 알려져 있으면 정수 1을 반환,
그렇지 않으면 0을 반환합니다.
모드
GNU 모드에서만 지원됩니다.
구문
int _builtin_constant_p(…)
인수 설명:
해당 값이 테스트될 인수입니다.
제한
반환 값이 0이라고 해도 값이 상수가 아님을 의미하지는 않지만
컴파일러는 그 값이 -O 옵션의 지정된 값을 가진 상수라는 것을
나타내지 못합니다.
### 2) 위 code에서 &&와 ⇐이 있는데, 무엇이 먼저 실행 되는 것인가요?
당연히 앞에 있는 &&이 실행되어야 하는 것 아닌가요?
결론만 얘기하면, ⇐가 &&보다 먼저 실행됩니다.
C언어 우선순위를 짚고 넘어가야 할 것 같습니다.
# preempt_disable_notrace();
#define preempt_disable_notrace() \
do { \
inc_preempt_count_notrace(); \
barrier(); \
} while (0)
</code>
## 1) barrier()함수가 너무 많은데 우리는 무엇을 불러오나요?
그리고 다시한번 의미를 짚고 넘어갑시다.
우리는 다음과 같은 명령어를 실행합니다.
<code> :: include/linux/compiler-gcc.h
#define barrier() _asm_ _volatile_("": : :"memory")
</code>
include/linux/compiler.h에 정의된 barrier는 #ifndef barrier 구문으로
실행이 안됨.
(#define으로 정의한 barrier()도 check)
<code>
#ifndef barrier
# define barrier() _memory_barrier()
#endif
</code>
이 barrier는 기존에 우리가 스터디 했던 hardware적인 명령어가 아니라,
compiler에서 막아주는 barrier 명령이다.
꼭 ARM이 아니어도, x86이나 다른 CPU로 구동될 수 있다.
barrier는 메모리를 순차적으로 접근하도록 보장하기 위한 것이다.
즉, CPU가 반드시 소스 코드 상에 나온 순서대로 instruction을 실행하도록
지시한다. (중략)
추가 자세한 내용은 Reference 참조:
[Linux] 최적화 장벽 :
http://studyfoss.egloos.com/5128961
## 2) 조금 이해가 안간다. compiler에서 barrier를 이용하여 막는다고 해도,
멀티코어 환경에서는 불가능 할 것 같다.
다음 사이트에 관련 내용이 있으니, 조금 더 이해하고 토론하도록 하자.
Working of _asm_ _volatile_ (“” : : : “memory”)
Memory Barrier라는 것이 Memory의 Protectioin을 거는 것입니다.
일단 두 개의 프로세서가 하나의 메모리 영역에 접근한다고 했을 때,
접근시에 동기화가 필요합니다.
프로세서 A에서 값을 기록했다가 프로세서 B에서 값을 읽거나 할 경우에
프로텍션을 걸어줌으로써, 해당되는 프로세서가 값을 읽지 못하도록
만드는 것입니다.
## 3) inline 어셈에서 clobber에 해당하는 memory가 나왔습니다.
무슨 의미일까요?
clobber 필드에 “memory”라고 표기 하는것은 모든 메모리 타입 저장 장치
(모든 레지스터, 모든 프래그, 모든 메모리 등)의 값이 변경됨을 의미합니다.
gcc는 이럴경우 asm volatile(””: : :“memory”) 경계를 넘어가는 최적화 또는
instruction scheduling을 수행하지 않습니다.
때문에 asm volatile(””: : :“memory”)를 사용하면 이전 코드의 수행 완료를
보장할 수 있고 이후 코드가 asm volatile(””: : :“memory”) 이전에 수행하는
것을 방지 할 수 있습니다.
## 4) 주석에 있는 avoid recursion의 의미는 무엇일까요?
barrier를 이용하여 재귀적인 호출을 피합니다.
# if (!printk_ratelimit()) goto out_enable;
<code> :: printk.h
#define printk_ratelimit() __printk_ratelimit(__func__)
</code>
## 1) 아래 code에서 _func_이 의미하는 것이 무엇인가요?
현재 호출한 함수를 의미합니다. 여기서는 debug_smp_processor_id() 입니다.
## 2) SPINLOCK을 우리가 공부했었던가요? 개념의 이해가 필요합니다.
간단하게 언급하면, 멀티코어가 되면서 나온것이 spinlock입니다.
기존의 mutex가 시간이 오래 걸리기 때문에, 나오게 되었습니다.
spinlock에 대해 각자 공부하고 토론하면 좋을 것 같습니다.
spinlock이 single core에서 의미없는 이유만 기재하겠습니다.
1. Lock을 얻을 수 없다면, 계속해서 Lock을 확인하며 얻을 때까지 기다린다.
이른바 바쁘게 기다리는 busy waiting이다.
2. 바쁘게 기다린다는 것은 루프를 돌면서 최대한 다른 스레드에게 CPU를
양보하지 않는 것이다.
3. Lock이 곧 사용가능해질 경우 컨텍스트 스위치를 줄여 CPU의 부담을
덜어준다. 하지만, 만약 어떤 스레드가 Lock을 오랫동안 유지한다면
오히려 CPU 시간을 많이 소모할 가능성이 있다.
4. 하나의 CPU나 하나의 코어만 있는 경우에는 유용하지 않다.
그 이유는 만약 다른 스레드가 Lock을 가지고 있고
그 스레드가 Lock을 풀어주려면 싱글 CPU 시스템에서는 어차피
컨텍스트 스위치가 일어나야 하기 때문이다.
## 3) ratelimit함수를 보면 interval과 burst로 제한을 걸고 있다.
왜 그런걸까요?
실제로 사운드카드 같은 것은 printk를 이용해서 디버깅을 하다보면,
많이 끊기는 현상이 발생합니다.
따라서 제한을 둔것이 아닌가 싶습니다.
## 4) rs→interval이 timer를 통해서 값이 변하는 것같다.
하지만 어디서 값이 변경되는지 확인이 필요하다.
<code>
int __ratelimit(struct ratelimit_state *rs, const char *func)
{
unsigned long flags;
int ret;
// 5s 가 지났는지 검사 수행
if (!rs->interval)
return 1;
...
}
</code>
## 5) 아래 strexeq 구문이 이해가 안간다.
<code>
static inline int arch_spin_trylock(arch_spinlock_t *lock)
{
unsigned long tmp;
u32 slock;
// lock->slock이0 이면 unlocked
// lock->slock이0x10000 이면 locked
//" ldrex slock, lock->slock\n"
//" subs tmp, slock, slock, ror #16\n" : tmp = slock - (slock >> 16)
//" addeq slock, slock, (1 << TICKET_SHIFT)\n" : slock = 0x10000
//" strexeq tmp, slock, lock->slock" : tmp는 strexeq의 수행 결과 값
_asm_ _volatile_(
" ldrex %0, [%2]\n"
" subs %1, %0, %0, ror #16\n"
" addeq %0, %0, %3\n"
" strexeq %1, %0, [%2]"
: "=&r" (slock), "=&r" (tmp)
: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
: "cc");
if (tmp == 0) {
smp_mb();
return 1;
} else {
return 0;
}
}
</code>
- LDREX (배타적인 로드)는 메모리로 부터 데이터를 읽고,
동시에 메모리 주소에 태그합니다.
- STREX (배타적인 스토어는 데이터를 메모리에 저장하는데,
태그가 여전히 유효한 경우만 저장합니다.
- 그외의 경우에는 메모리는 수정되지 않습니다.
참조 링크 : [Ref.] http://www.iamroot.org/xe/Kernel_8_ARM/65971
## 6) 위 code에서 그러면 LDREX와 STREX는 쌍(pair)으로 구동이 되는 건가요?
멀티 프로세서가 도입되면서 SWP 명령이 성능 이슈를 회피하기 위해,
ARM v6 이후의 아키텍쳐에서 SWP는 폐기되었습니다.
멀티 프로세서 환경에서 프로세서들 사이에 동일한 공유 메모리 접근을
동기화 하기 위해서는 global exclusive monitor를 사용합니다.
global exclusive monitor는 각 프로세서가 베타적으로 접근하는 주소를
태그해 놓습니다.
LDREX를 통해 베타적 상태를 태그해 둔 메모리에 다른 프로세서가
단순 str 연산이나 strex를 수행하면,global exclusive monitor는
해당 태그 상태를 open으로 변경하게되어 베타적 접근이 실패된 것을
알려 주게 됩니다.
참조링크 : [Ref.] http://www.iamroot.org/xe/Kernel_8_ARM/66152