티스토리 뷰
7 smp 와 cpu topology
SMP
- 각각의 코어가 동일한 메모리 인터페이스를 공유하며, 동등한 액세스 권한을 얻는다.
- 태스크들은 모든 코어에서 동시에 실행될 수 있다.
고려사항
- cache coherency
- task migration
- explicit parallelism: 유저가 병렬처리를 실제로 제어
- 자원 동기화
7.1 SMP 를 위한 커널 지원
여러 cpu 를 상용하며 소비전력이 증가, 전력 관리의 필요성이 높아짐
- cpu 토폴로지
- IPC 인터페이스
- cpu hotplug
- cpufre/cpuidle : cpu 부하에 따라 성능과 전력 균형을 유지
7.2 cpu 토폴로지
토폴로지 : 물리적인 배치의 형태로 이루어진 구조를 설명하는 것.
멀티코어 cpu 를 추상화하기 위해 구성한다.
Hardware 구성
- 클러스터
- 코어
- 쓰레드
리눅스 커널에서의 cpu 토폴로지 계층 구조
하드웨어적 구조 정보를 토폴로지 정보로 기록 (추상화)
- 유저에게 cpu 구성 정보를 제공
- OS 가 각각의 cpu 에게 합리적인 작업을 예약할 수 있도록 스케줄러에게 코어에 대한 정보를 제공
일반적인 cpu 토폴로지
function | desc |
---|---|
topology_physical_package_id | 물리 소켓 번호(아키텍처마다 상이) |
topology_core_id | cpu 코어 ID(아키텍처마다 상이) |
topology_sibling_cpumask | 같은 코어에 속하는 cpu |
topology_core_cpu_mask | 동일한 클러스터 |
arch 의존정인 cpu 토폴로지
arch/arm64/include/asm/topology.h
struct cpu_topology {
int thread_id;
int core_id;
int cluster_id;
cpumask_t thread_sibling;
cpumask_t core_sibling;
};
extern struct cpu_topology cpu_topology[NR_CPUS];
#define topology_physical_package_id(cpu) (cpu_topology[cpu].cluster_id)
#define topology_core_id(cpu) (cpu_topology[cpu].core_id)
#define topology_core_cpumask(cpu) (&cpu_topology[cpu].core_sibling)
#define topology_sibling_cpumask(cpu) (&cpu_topology[cpu].thread_sibling)
arch/arm64/kernel/topolgy.c init_cpu_topology, reset_cpu_topology
위에 정의한 것들 초기화
static inline bool of_have_populated_dt(void)
{
return of_root != NULL;
}
static int __init parse_dt_topology(void)
{
struct device_node *cn, *map;
int ret = 0;
int cpu;
cn = of_find_node_by_path("/cpus");
if (!cn) {
pr_err("No CPU information found in DT\n");
return 0;
}
/*
* When topology is provided cpu-map is essentially a root
* cluster with restricted subnodes.
*/
map = of_get_child_by_name(cn, "cpu-map");
if (!map)
goto out;
...
}
void __init init_cpu_topology(void)
{
reset_cpu_topology();
/*
* Discard anything that was parsed if we hit an error so we
* don't use partial information.
*/
if (of_have_populated_dt() && parse_dt_topology()) // 문제되는 경우 다시 시도. root 가 없거나 dtb or fdt (device tree blob) 에 cpu map 이 없는 경우
reset_cpu_topology();
}
static void __init reset_cpu_topology(void)
{
unsigned int cpu;
for_each_possible_cpu(cpu) {
struct cpu_topology *cpu_topo = &cpu_topology[cpu];
cpu_topo->thread_id = -1;
cpu_topo->core_id = 0;
cpu_topo->cluster_id = -1;
cpumask_clear(&cpu_topo->core_sibling);
cpumask_set_cpu(cpu, &cpu_topo->core_sibling);
cpumask_clear(&cpu_topo->thread_sibling);
cpumask_set_cpu(cpu, &cpu_topo->thread_sibling);
}
}
7.2.1 MPIDR 해시 테이블
Multiprocessor Affinity Register
고성능 코어, 저성능 코어를 코어군으로 배치하여 코어를 전환 하는 등 cpu affinity 에 따른 명확한 구분을 위해 만들어짐
ARM 에서는 각 코어를 구분할 수 있도록 Affinity address 를 4단계로 사용. cluster & core 정도로 나뉨.
- 어느 클러스터(cluster)에 속한 어느 코어(core)인지를 식별하는 역할
그러면 이 레지스터를 가지고, 자원을 조회할때 빠르게 룩업 해야함
(이 physcical cpu ID/MPDIR 에 의존하는 동작은 대부분 mmu 가 꺼지고 인스트럭션 캐시가 꺼져있음, 주로 cpu 의 비활성화나 컨텍스트 저장/복원 시점)
그래서 이걸 해시 태이블로 만들어서 물리 ID 를 logical ID 로 찾아내야 함.
7-2 arch/arm64/kernel/setup.c smp_build_mpidr_hash
#define MPIDR_LEVEL_BITS 8
#define MPIDR_LEVEL_MASK ((1 << MPIDR_LEVEL_BITS) - 1)
#define MPIDR_AFFINITY_LEVEL(mpidr, level) \
((mpidr >> (MPIDR_LEVEL_BITS * level)) & MPIDR_LEVEL_MASK)
#define MPIDR_LEVEL_BITS_SHIFT 3
#define MPIDR_LEVEL_SHIFT(level) \
(((1 << level) >> 1) << MPIDR_LEVEL_BITS_SHIFT)
cpu_logical_map(cpu) {return cpu;}
Note fls(0) = 0, fls(1) = 1, fls(0x80000000) = 32.
static inline u32 mpidr_hash_size(void)
{
return 1 << mpidr_hash.bits;
}
static void __init smp_build_mpidr_hash(void)
{
u32 i, affinity, fs[4], bits[4], ls;
u64 mask = 0;
for_each_possible_cpu(i)
mask |= (cpu_logical_map(i) ^ cpu_logical_map(0)); // cpu index xor
for (i = 0; i < 4; i++) {
affinity = MPIDR_AFFINITY_LEVEL(mask, i); // 8비트씩 짜름
/*
* Find the MSB bit and LSB bits position
* to determine how many bits are required
* to express the affinity level.
*/
ls = fls(affinity); // msb 1~4
fs[i] = affinity ? ffs(affinity) - 1 : 0; // lsb
bits[i] = ls - fs[i]; // 사용 비트 수
}
// affinity 레벨별로 시프트 해야하는 비트수를 계산
// 레벨 비트 + 내 마지막 비트 - 이전 사용 비트
mpidr_hash.shift_aff[0] = MPIDR_LEVEL_SHIFT(0) + fs[0];
mpidr_hash.shift_aff[1] = MPIDR_LEVEL_SHIFT(1) + fs[1] - bits[0];
mpidr_hash.shift_aff[2] = MPIDR_LEVEL_SHIFT(2) + fs[2] -
(bits[1] + bits[0]);
mpidr_hash.shift_aff[3] = MPIDR_LEVEL_SHIFT(3) +
fs[3] - (bits[2] + bits[1] + bits[0]);
mpidr_hash.mask = mask; // 내 affinity 가 사용하는 모든 비트
mpidr_hash.bits = bits[3] + bits[2] + bits[1] + bits[0]; // 총 비트수
pr_debug("MPIDR hash: aff0[%u] aff1[%u] aff2[%u] aff3[%u] mask[%#llx] bits[%u]\n",
mpidr_hash.shift_aff[0],
mpidr_hash.shift_aff[1],
mpidr_hash.shift_aff[2],
mpidr_hash.shift_aff[3],
mpidr_hash.mask,
mpidr_hash.bits);
/*
* 4x is an arbitrary value used to warn on a hash table much bigger
* than expected on most systems.
*/
if (mpidr_hash_size() > 4 * num_possible_cpus()) // 너무 큰 경우 캐시를 날림
pr_warn("Large number of MPIDR hash buckets detected\n");
__flush_dcache_area(&mpidr_hash, sizeof(struct mpidr_hash));
}
7-3 arch/arm64/kernel/setup.c smp_setup_processor_id
physical cpu id 를 읽어 logical map 에 저장. 추후 인터럽트 컨트롤러 등에서 매핑을 위한 기초 자료로 활용
u64 __cacheline_aligned boot_args[4];
static inline u64 __attribute_const__ read_cpuid_mpidr(void)
{
return read_cpuid(MPIDR_EL1);
}
#define MPIDR_HWID_BITMASK 0xff00ffffff (31-24 를 초기화)
/*
* Logical CPU mapping.
*/
extern u64 __cpu_logical_map[NR_CPUS];
#define cpu_logical_map(cpu) __cpu_logical_map[cpu]
void __init smp_setup_processor_id(void)
{
u64 mpidr = read_cpuid_mpidr() & MPIDR_HWID_BITMASK; // CPU의 물리적 ID만 남기고, 어피니티 정보(다른 CPU의 정보)는 제거
cpu_logical_map(0) = mpidr; // 0번을 나로 설정. 부팅 하고있는 cpu 만 먼저 등록된다
/*
* clear __my_cpu_offset on boot CPU to avoid hang caused by
* using percpu variable early, for example, lockdep will
* access percpu variable inside lock_release
*/
set_my_cpu_offset(0);
pr_info("Booting Linux on physical CPU 0x%lx\n", (unsigned long)mpidr); // 그래서 여기서 부팅 cpu 의 mpidr 확인 가능
}
static inline void set_my_cpu_offset(unsigned long off)
{
asm volatile("msr tpidr_el1, %0" :: "r" (off) : "memory"); // off 가 입력값, memory 는 메모리 종속성 알려주기
}
7-4 include/linux/smp.h smp_processor_id
#define raw_smp_processor_id() (current_thread_info()->cpu)
current_thread_info()->cpu = boot_cpuid; // prepare 에서 이렇게 설정됨
7.2.2 cpumask map
7-5 kernel/cpu.c boot_cpu_init
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); // cpu 가 사용중인 상태고 스케줄러가 사용
set_cpu_active(cpu, true); // 온라인 상태이고 마이그레이션이 가능한 cpu
set_cpu_present(cpu, true); // 시스템 내 장착된 cpu 가 존재함
set_cpu_possible(cpu, true); // 동작하거나 핫플러그 가능 상태로 부팅시 고정
}
// cpu 순회돌때 플래그를 사용해서 돌기도 함
7-5 kernel/smp.c setup_nr_cpu_ids
// cpumask.h
#define cpumask_bits(maskp) ((maskp)->bits)
/* An arch may set nr_cpu_ids earlier if needed, so this would be redundant */
void __init setup_nr_cpu_ids(void)
{
nr_cpu_ids = find_last_bit(cpumask_bits(cpu_possible_mask),NR_CPUS) + 1; // mask 가 cpu 별로 하나씩이라 NR_CPUS 와 비슷한 역할
}
'개발 > 코드로 알아보는 ARM 리눅스 커널 TIL' 카테고리의 다른 글
250329 인터럽트 개념과 핸들러 등록 (8.1~8.3) (0) | 2025.03.29 |
---|---|
250322 Secondary Booting (7.3) (0) | 2025.03.22 |
250308 스케줄링 엔티티의 실행시간 관리하기 & 스케줄러 초기화 (6.3~6.4) (2) | 2025.03.08 |
250301 CFS 태스크 선택, 제거, 삽입 (6.3.1~6.3.4) (0) | 2025.03.01 |
250222 6.2.6 태스크 깨우기 (ttwu) (0) | 2025.02.22 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- SuffixArray
- 에러 위치 찾기
- Reciprocal n-body Collision Avoidance
- C++
- Quest2
- ad skip
- chrome-extension
- vr핏
- print shared_ptr class member variable
- RVO
- 봄날에 스케치
- boost
- 코어 남기기
- vrpit
- 잘못된 빨간줄
- hole-punching
- 면접
- red underline
- Visual Studio
- 클래스 맴버 변수 출력하기
- 영상 픽셀화 하기
- it's called a vrpit
- set value
- 카카오
- 우리는 vr핏이라고 부릅니다
- shared_from_this
- Obstacle Avoidance
- mysql
- cockroach db
- Golang
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
글 보관함