안녕하세요 ^^
여러가지 과정을 거쳐 어느정도 프로세서 초기화에 대한 감을 잡았습니다.
요즘에는 오히려 윈도우보다 리눅스 소스를 보고 있는데 아직 찾지 못하겠는 부분이 있어 질문드리고자 합니다.
전 질답에서는 전원이 인가되고 부터 CPU 초기화가 이뤄지고 BSP가 부트코드를 실행한다는 것을 알게 되었고,
또한 프로세서 구조체를 세팅하고 윈도우 같은 경우는 FS레지스터에 이 값을 저장해서 스케쥴러와 같은 코드에서
이용한다는 것을 알게 되었는데, 아직 윈도우에서 FS레지스터를 세팅하는 부분을 못 찾은 것 처럼
리눅스에서도 각 프로세서 구조체를 세팅하는 것과 스케쥴링시에 각 프로세서의 Run Queue 를 얻어오는 부분은 알아냈습니다.
그런데 알지 못하겠는건, 각 프로세서에 어떻게 자신의 Unique 한 RunQueue 를 얻어내냐는 것입니다.
#define this_rq() (&__get_cpu_var(runqueues)) #define __get_cpu_var(var) (*SHIFT_PERCPU_PTR(&per_cpu_var(var), my_cpu_offset)) #define SHIFT_PERCPU_PTR(__p, __offset) RELOC_HIDE((__p), (__offset)) #define RELOC_HIDE(ptr, off) ( { unsigned long __ptr; __asm__ ("" : "=r"(__ptr) : ""(ptr)); (typeof(ptr)) (__ptr + (off)); }) #define per_cpu(var, cpu) (*SHIFT_PERCPU_PTR(&per_cpu_var(var), per_cpu_offset(cpu))) #define per_cpu_offset(x) (__per_cpu_offset[x]) |
위 매크로들은 sched_init() 에서 this_rq() 를 통해 current rq 를 얻어내는 부분이라고 생각하는 코드입니다.
런큐든 PCR 이든 참 특이하게 메모리를 chunk 로 얻어와 cpu 아키텍쳐에 맞는 offset으로 나누고,
memcpy 를 통해서 초기화 데이터를 복사하는 과정은 재밌있더군요.
어쨌든 결과적으로 얘기해서 각자 자신의 런큐를 얻거나, 혹은 수 많은 런큐 중에 프로세서에서 실행될 런큐를 선택하든지 제 생각에 리눅스 커널에서는 setup_per_cpu_areas() 를 통해서 만들어놓은 CPU 공간을 각 프로세서에 배정을 해줄거라고 생각했는데, 그런 로직은 보지 못했습니다.
343 for (i = 0; i < NR_CPUS; i++, ptr += size) {
344 __per_cpu_offset[i] = ptr - __per_cpu_start;
345 memcpy(ptr, __per_cpu_start, __per_cpu_end - __per_cpu_start);
346 }
어느 코드를 보던 -_- 제 눈에는 메모리에 값을 대입하는 걸로 밖에 안보이는데요.
리눅스에서는 프로세서 를 도대체 어떻게 관리하는건지 혼란스러워지네요 @_@;;
답변 부탁드립니다.
댓글 6
-
노서영
2010.11.30 13:11
-
노서영
2010.11.30 15:30
제가 조금 포인틀 잘못잡은 듯 하군요. 여러가지 말씀을 하셨는데, 죄송합니다만..다 이해는 못했습니다 ^^;
질문이 "각 프로세서에 자신의 rq 주소를 어떻게 세팅해줘서 사용되냐는 겁니다" 로 귀결되는 것 같네요? 맞나요?
kernel/sched.c에 보시면 다음과 같은 코드가 있습니다.
static DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);
짐작하시겠지만, struct rq type의구조체 변수를 정적으로 선언하고 있습니다. 이 때 cacheline align을 시킵니다 (사족: 이부분은 SMP에서 false sharing과 연관이 있습니다.) DEFINE_PER_CPU_SHARED_ALIGNED는 include/linux/percpu-defs.h 에 정의되어 있습니다.#define DEFINE_PER_CPU_SHARED_ALIGNED(type, name) DEFINE_PER_CPU_SECTION(type, name, PER_CPU_SHARED_ALIGNED_SECTION) ____cacheline_aligned_in_smp ...... #define DEFINE_PER_CPU_SECTION(type, name, section) __attribute__((__section__(PER_CPU_BASE_SECTION section))) PER_CPU_ATTRIBUTES PER_CPU_DEF_ATTRIBUTES __typeof__(type) per_cpu__##name
섹션 애트리뷰트를 이용해서 struct rt 타입의 구조체 per_cpu__runqueues가 ".data.percpu" section에 위치하도록 하고 있습니다.
이 섹션은 setup_per_cpu_areas() 함수내에서 각 프로세서 개수만큼 복사되고 프로세서별로 사용됩니다.
for_each_possible_cpu(i) { __per_cpu_offset[i] = ptr - __per_cpu_start; memcpy(ptr, __per_cpu_start, __per_cpu_end - __per_cpu_start); ptr += size; }
cpu_rq(cpu) -----> (&per_cpu(runqueues, (cpu))) 를 통해서 해당 cpu가 가지고 있는 .data.percup 섹션의 복사본에서 strut rt 구조체 변수인 per_cpu__runqueues의 주소를 참조하여 자신의 runqueue를 접근한다고 보면 되겠습니다.역시 오류가 있으시면 잡아주시고, 도움이 되었길 바랍니다.
-
박한범
2010.11.30 13:37
답변 감사드립니다.
/////////////////////////
2번째 질문은 너무 브로드하게 보이는데 제가 이해한 선에서 설명을 드리면, 각 프로세서는 큐들을 가지고 있습니다. 제가 큐들이라고 표현한 것은 per-CPU runqueue 데이터 구조체인 struct rq안에 다음 구조체가 있기 때문입니다.
//////////////////////////맞습니다. 조금 더 윗 부분을 설명해주시면 제가 원한 답변이 될 것 같아요.
rq 가 DLL이든 LL이든 연결되어 관리되고 있는 것은 맞는데, 각 프로세서에 자신의 rq 주소를 어떻게 세팅해줘서 사용되냐는 겁니다 ^^.
예를 들면, 윈도우를 보면 rq 와 비슷한 개념의 구조체를 세팅하고,
이 rq 리스트의 head를 PCR 구조체가 가지고 있습니다. 고로 프로세서가 만약 비선점이라면 이 rq들을 순차적으로 실행할 것입니다. 하지만 특정 경우에 분명히 인터럽트가 발생하며 선점이 되야됩니다. 이럴 경우 선점을 위한 코드가 각 프로세서마다 존재하냐? 그건 아니죠. 그렇기 때문에 인터럽트가 걸렸을 때
자신의 프로세서 관리 구조체의 주소를 불러와, current thread의 컨텍스트 스위칭이 이뤄져야 합니다.
^^ 이 부분을 어디서 찾을 수 있는지 설명해주셨으면 좋겠습니다.
윈도우의 흐름은 제 생각에 이렇습니다.
1. 동일한 프로세서 메모리 할당과 초기화
2. 각 CPU 의 FS 레지스트리 세팅
3. start_kernel() 의 윈도우 버전 함수가 종료되며 BKL 과 같은 것을 풀어
4. 각 프로세서가 idle 상태에 들어갑니다.
5. Windows에서는 이제 어느 프로세서가 인터럽트를 처리하든 신경쓰지 않고 쓰레드 디스패칭만 각 코어 구조체마다 잘 해주면 됩니다.
6. 인터럽트가 걸리면, 인터럽트를 처리하는 코드는 FS레지스터를 참조해 자신의 PCR 주소를 얻어 자신의 RQ에서 task 를 가져와 처리를 합니다.
- 끗 -
-
조태문
2010.11.30 23:51
박한범님! 연락주세요.
-
김남형
2010.12.01 01:02
어느 버전의 커널을 보셨는지 모르겠지만
최신 커널에서는 말씀하신 setup_per_cpu_areas() 내에서 setup_percpu_segment()를 호출하여
각 CPU 별 percpu 영역에 대한 segment descriptor를 GDT에 설정해 둔 뒤
AP가 부팅되어 cpu_init() 함수를 실행할 때 switch_to_new_gdt() -> load_percpu_segment()를 호출하여
FS에 해당 segment descriptor의 index를 저장하는 것을 찾을 수 있습니다.
-
박한범
2010.12.01 07:09
저는 웹에서 검색했는데, 아마 찾아본 웹 페이지나 소스를 웹에서 볼 수 있게 해주는 웹페이지가 구형 버전인가 봅니다.
하지만 구형 버전이라도 SMP를 지원하는 커널 버전이기 때문에 알려주신 함수들이 없이도 SMP를 구현할 수 있는 방법이 있다고 보입니다.
어쨌든 알려주셔서 정말 감사합니다.
기회가 된다면 이 부분의 차이점을 정리해서 올려보도록 하겠습니다. 행복한 하루 되세요~ :D
.
질문하신 내용에 답이 될지 모르겠지만, 참고하세요 ^^;
질문은 두가지로 보여집니다.
1. "각 프로세서에 어떻게 자신의 Unique 한 RunQueue 를 얻어내냐는 것입니다"
2. "리눅스에서는 프로세서 를 도대체 어떻게 관리하는건지 혼란스러워지네요"
1번의 경우는 sched_init() 함수의 코드를 보면 다음과 같은 코드가 있습니다.
프로세서 i의 런큐들은 cpu_rq(i)를 통해 얻어올 수 있습니다. cpu_rq()는 다음과 같이 정의되어 있습니다.
2번째 질문은 너무 브로드하게 보이는데 제가 이해한 선에서 설명을 드리면, 각 프로세서는 큐들을 가지고 있습니다. 제가 큐들이라고 표현한 것은 per-CPU runqueue 데이터 구조체인 struct rq안에 다음 구조체가 있기 때문입니다.
cfs는 Completely Fair Scheduler이고 rt는 Real Time을 의미합니다. CFS Run Queue의 경우는 Red Black Tree로 Task를 관리합니다. 따라서 가장 왼쪽에 있는 Task가 다음에 스케쥴링 대상이될 Task를 의미합니다. Linux는 이렇게 스케쥴링될 대상을 관리합니다.
제가 이해한 선에서 간략하게 답변을 했는데....오류가 있으면 잡아주시고 보충하실 내용이 있으시면 추가해주시면 감사하겠습니다.