티스토리 뷰

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 와 비슷한 역할
}
댓글