[커널 17차] 59주차

2021.10.17 20:00

ㅇㅇㅇ 조회 수:167

pcpu_setup_first_chunk()
- pcpu_alloc_first_chunk() 이후부터 계속
- 전역 변수 pcpu_first_chunk에 생성한 dynamic chunk를 붙여준다
- 전역 변수 pcpu_nr_empty_pop_pages에 pcpu_first_chunk->nr_empty_pop_pages를 할당
- 함수 pcpu_chunk_relocate()를 이용해서 방금 생성한 dynamic chunk를 올바른 chunk slot에 할당한다
- 전역 변수 pcpu_nr_populated에 static/reserved/dynamic size의 합을 page down해서 더해준다
- 함수 pcpu_stats_chunk_alloc()를 이용해서 pcpu_stats의 nr_chunk를 increament한다
- 함수 trace_percpu_create_chunk()는 ftrace와 관련있는 것으로 보이며 생략
- 전역 변수 pcpu_base_addr에 각 group의 base address 중 가장 낮은 base address를 기록하고 리턴


pcpu_chunk_relocate()
- 인자로 chunk와 old slot 번호를 받아서 free size에 맞는 새 slot으로 chunk를 옮겨준다

- 함수 pcpu_chunk_slot()으로 현재 chunk의 free size에 맞는 새 slot index를 계산한다
- 새 slot index가 기존 slot index와 다른 경우 함수 __cpu_chunk_move()로 chunk를 새 slot으로 옮겨준다


pcpu_chunk_slot()
- 인자로 chunk를 받아서 free bytes에 맞는 새 slot index를 계산, 리턴한다

- chunk의 free_bytes가 4 바이트보다 작거나 chunk의 contig_hint가 0이면 0번째 index를 반환한다
- 아니면 함수 pcpu_size_to_slot()으로 chunk의 contig_hint 사이즈에 맞는 slot index를 구해서 리턴한다
- 함수 pcpu_size_to_slot()의 인자로는 contig_hint의 총 바이트 사이즈를 넘겨준다


pcpu_size_to_slot()
- 인자로 size 바이트 값을 받아서 적절한 slot index를 리턴한다
- size가 pcpu_unit_size와 같으면 최상위 index를 리턴한다 (pcpu_nr_slots - 1 : 이 slot은 오로지 empty chunk에게만 할당된다)
- 아니면 함수 __pcpu_size_to_slot()으로 slot index를 계산해서 리턴한다
- size에 따른 slot index : http://jake.dothome.co.kr/setup_per_cpu_areas/


__pcpu_chunk_move()
- 인자로 chunk, slot 번호, move_front를 받아서 chunk를 새 slot으로 이동시킨다
- move_front는 chunk가 상위 슬롯으로 가는지 하위 슬롯으로 가는지를 나타내는 bool 변수이다

- chunk가 pcpu_reserved_chunk인 경우 reserved chunk는 chunk slot으로 관리하지 않으므로 아무것도 하지 않는다
- 함수 pcpu_chunk_type()와 pcpu_chunk_list()를 이용하여 chunk type이 ROOT인지 CGROUP TYPE인지 구별하여 적절한 pcpu_slot을 구한다
- move_front인 경우 list_move() 함수를 이용하여 기존 slot에서 chunk를 제거하고 새 slot의 처음에 chunk를 삽입힌다
- move_front가 아닌 경우 list_move_tail() 함수를 이용하여 기존 slot에서 chunk를 제거하고 새 slot의 마지막에 chunk를 삽입한다


setup_per_cpu_areas()
- pcpu_embed_first_chunk() 함수가 끝나면 에러가 발생한 경우 커널 패닉을 띄운다
- 전역 변수 pcpu_base_addr (각 group의 base addr 중에서 가장 낮은 base addr)와 커널 이미지의 __per_cpu_start의 차이를 계산해서 delta에 저장한다
- 모든 possible cpu에 대해서 전역 변수 __per_cpu_offset[cpu]에 delta + pcpu_unit_offsets[cpu]를 저장한다. 여기서 pcpu_unit_offsets[cpu]는 pcpu_base_addr와 각 cpu group의 base addr 차이에 각 cpu의 unit size offset을 더한 것이다. 그러므로 각 pcpu var의 주소값을 위해서는 var kernel address + pcpu_unit_offsets[cpu]를 더하면 된다.


smp_prepare_boot_cpu()
- 함수 set_my_cpu_offset(), per_cpu_offset(), smp_processor_id() 함수를 이용해서 이전에 구한 pcpu_unit_offsets[cpu = 0] 값을 현재 0번 cpu의 TPIDR 레지스터에 저장한다
- 함수 cpuinfo_store_boot_cpu() —> 진행 중


smp_processor_id()
- percpu 변수 cpu_number값을 읽어오는 함수
- TPIDR_EL1 + &cpu_number로 cpu_number 변수의 주소값을 계산하고 referencing하여 값을 읽어서 리턴한다
- 처음에는 TPIDR_EL1 = 0이므로 kernel image의 변수값을 그대로 읽어온다
- 함수 __my_cpu_offset()을 이용해서 TPIDR_EL1 값을 읽어온다


__my_cpu_offset()
- inline assembly로 TPIDR_EL1 값을 읽어 온다
- asm volatile 대신 asm을 사용하여 TPIDR_EL1 값을 caching할 수 있도록 허용한다
- 또한 barrier() 이후 다시 호출시 caching되어 있는 값을 버리고 다시 TPIDR_EL1 값을 읽어오도록 하기 위해 input 값에 memory clobber “Q”를 current_stack_pointer = sp에 대해서 적용하고 있다. Input 값에 memory clobber Q를 사용하면 barrier() 이후엔 TPIDR_EL1 값을 다시 읽어오도록 강제된다. 다만 input 값에 현재 living register값을 사용하면 쓸데없는 load/store 명령어가 발생하지 않고, 특히 sp를 사용하는 것이 무의미한 명령어가 추가되지 않아 sp를 사용한다.

 

- 관련 패치
: https://linux-arm-kernel.infradead.narkive.com/JPUc9yvz/patch-arm-pcpu-ensure-my-cpu-offset-cannot-be-re-ordered-across-barrier
: https://github.com/torvalds/linux/commit/7158627686f02319c50c8d9d78f75d4c8d126ff2
: https://lists.infradead.org/pipermail/linux-arm-kernel/2013-December/219929.html
: http://www.iamroot.org/xe/index.php?document_srl=208290&mid=Programming
: https://stackoverflow.com/questions/14449141/the-difference-between-asm-asm-volatile-and-clobbering-memory

 

per_cpu_offset()
- __per_cpu_offset[] 값을 리턴한다

 

set_my_cpu_offset()
- inline assembly로 입력 인자값을 TPIDR_EL1에 저장한다


cpuinfo_store_boot_cpu()
- 매크로 per_cpu()로 0번 cpu의 per_cpu 데이터 cpu_data를 로컬 변수 포인터 info가 가리키도록 한다
- cpu_data는 구조체 cpuinfo_arm64 타입의 percpu 변수이다
- 구조체 cpuinfo_arm64는 cpu 구조체, kobject 및 cpu register 값들로 구성된다
- cpu 구조체는 node id, hot pluggable 변수와 device 구조체로 구성됨
- 함수 __cpuinfo_store_cpu()로 현재 cpu의 레지스터 값들을 읽어서 pcpu cpu_data에 업데이트시킨다
- 전역변수 boot_cpu_data가 현재 cpu의 cpu_data pcpu 변수를 나타내게 한다
- 함수 init_cpu_features()로 리눅스의 cpu feature 자료구조에 있는 feature들만 골라서 업데이트한다 <— 진행 중

 

per_cpu(var, cpu) 매크로
- per_cpu_offset() 함수를 이용해서 cpu의 percpu 변수 var의 값을 읽어온다

 

cpu capability 관련
- http://jake.dothome.co.kr/cpucaps64/

 

__cpuinfo_store_cpu()
- 기본적으로 함수 read_cpuid()를 이용해서 cpu register 값을 읽어서 info 구조체의 멤버변수에 저장한다


- 몇몇 register들은 특수 함수를 이용해서 읽어온다

 

- arch_timer_get_cntfrq() : CNTFRQ_EL0 레지스터를 읽어온다. system counter clock frequency를 읽게 된다.
: https://developer.arm.com/documentation/ddi0601/2020-12/AArch64-Registers/CNTFRQ-EL0--Counter-timer-Frequency-register?lang=en

 

- read_cpuid_effective_cachetype() : CTR_EL0를 읽어온다. 여기서 IDC = 0이면 CLIDR_EL1 레지스터를 추가로 읽어서 LoC = 0이거나 LoUIS = 0 & LoUU = 0이면 IDC 필드를 1로 바꿔서 읽는다
: https://developer.arm.com/documentation/ddi0601/2020-12/AArch64-Registers/CTR-EL0--Cache-Type-Register?lang=en#fieldset_0-15_14
: https://developer.arm.com/documentation/ddi0601/2020-12/AArch64-Registers/CLIDR-EL1--Cache-Level-ID-Register?lang=en#fieldset_0-26_24
: https://developer.arm.com/documentation/101433/0102/Register-descriptions/AArch64-system-registers/CLIDR-EL1--Cache-Level-ID-Register--EL1?lang=en
: https://developer.arm.com/architectures/cpu-architecture/a-profile/exploration-tools/feature-names-for-a-profile

 

Stage 2 forced write-back 관련 내용
- https://patchwork.kernel.org/project/kvm/patch/20180702150250.16550-2-marc.zyngier@arm.com/

 

- AArch32가 존재할 경우 32bit 관련 register들도 읽어온다
- ID_AA64PFR0_EL1 레지스터에 각 EL3/2/1/0에 AArch32 구현 여부가 적혀있다
- https://stackoverflow.com/questions/64949775/how-to-find-out-if-an-armv8-processor-supports-the-aarch32-execution-state

 

- CPU가 SVE를 지원하고, 커널도 SVE에 CONFIG를 켜고 빌드된 경우 함수 read_zcr_features()을 이용하여 zcr 레지스터 값을 info에 저장한다

 

- SVE 관련 레지스터
- https://developer.arm.com/documentation/ddi0601/2020-12/AArch64-Registers/ZCR-EL1--SVE-Control-Register--EL1-
- https://developer.arm.com/documentation/ddi0595/2020-12/AArch64-Registers/CPACR-EL1--Architectural-Feature-Access-Control-Register?lang=en#fieldset_0-17_16-1

 

- SVE 관련 명령어
- https://developer.arm.com/documentation/ddi0602/2021-09/SVE-Instructions/RDVL--Read-multiple-of-vector-register-size-to-scalar-register-

 

- SVE 관련 내용
- https://developer.arm.com/documentation/102476/0001/SVE-architecture-fundamentals
- https://www.kernel.org/doc/Documentation/arm64/sve.txt
- https://developer.arm.com/documentation/102131/latest/
- https://developer.arm.com/documentation/101726/0400/Coding-for-Scalable-Vector-Extension--SVE-/SVE-Vector-Length-Agnostic--VLA--programming

 

SVE 관련 패치
- https://lists.cs.columbia.edu/pipermail/kvmarm/2017-August/026706.html
- https://libc-alpha.sourceware.narkive.com/6LV28LYF/patch-v2-00-28-arm-scalable-vector-extension-sve

 

- 함수 cpuinfo_detect_icache_policy()로 i-cache policy를 읽어서 전역 변수 __icache_flags에 업데이트하고 현재 i-cache policy를 커널 로그에 출력한다


read_zcr_features()
- 현 CPU가 지원하는 최대 SVE vector length를 구해서 ZCR 레지스터값으로 만들어서 리턴한다

- 함수 sve_kernel_enable()로 EL1에서 SVE 명령 및 ZCR 접근 명령이 trap에 걸리지 않도록 한다
- ZCR_EL1 레지스터에 1FF 값을 write하여 최대 vector length가 되도록 한다
- ZCR_EL1 값을 읽어서 zcr 로컬 변수에 쓴다
- zcr 변수의 vector length bit field를 clear한다
- 함수 sve_get_vl()로 현재 설정된 vector length를 byte 값 단위로 읽는다
- 함수 sve_vq_from_vl()로 byte 단위의 vector length를 quadword (=16 byte) 단위로 나누어 quadword 개수로 변환한다
- 구한 quadword 개수에 1을 뺀 값을 zcr 변수에 OR 한다
- zcr을 리턴한다

 

sve_kernel_enable()
- CPACR_EL1 레지스터를 읽고 16번째 bit를 set하여 업데이트한다
- 이후 isb() 배리어를 수행한다

 

sve_get_vl()
- ZCR_EL1에 설정된 LEN 값으로부터 현재 설정된 effective vector length를 byte 단위로 읽어서 리턴한다
- vector length는 (LEN + 1) x 128 bit = (LEN + 1) x 16 byte 이다 (LEN은 0 ~ 15 까지)

 

sve_vq_from_vl()
- vector length를 byte 단위로 받아서 quadword 개수로 변환해서 리턴한다
- quadword 길이가 128 bit = 16 byte이므로 vector length byte를 16으로 나누어 리턴한다


cpuinfo_detect_icache_policy()
- smp_processor_id() 함수로부터 cpu 번호를 읽는다
- info->reg_ctr로부터 L1IP bit field를 구한다
- L1IP bit field의 encoding에 따라 다음을 수행한다
1. VPIPT (VM-aware PIPT) 인 경우 __icache_flags의 1번 bit를 set한다 (vpipt bit)
2. VIPT 인 경우 __icache_flags의 0번 bit를 set한다 (aliasing bit)
3. 나머지는 bit set을 하지 않는다
- L1IP 값에 따라 현재 i-cache policy를 커널 로그에 출력한다


arm64_ftr_bits 구조체
- arm64 cpu의 register의 각 feature bit field를 기술하기 위한 구조체
- 각 bits 값들이 signed 인지, user visible 인지, strict sanity check가 필요한지에 대한 정보가 있음 (boolean value)
- ftr_type이라는 enumerator가 있어서 값이 정확하지 않아도 safe한지에 대한 정보를 제공한다
- exact (정확한 값을 써야함, linux는 predefined safe value 사용), lower safe (낮은 값이 안전), higher safe (높은 값이 안전), higher or zero safe (높은 값이 안전하고 0이 가장 높은 값)으로 ftr_type이 나누어진다
- shift, width로 feature의 위치와 너비를 나타낸다 (u8)
- safe_val로 안전한 설정값을 나타낸다 (u8)

 

arm64_ftr_reg 구조체
- arm64 cpu의 register를 기술하는 구조체
- name : register name
- strict_mask : 이 register의 feature 중에 strict sanity check가 필요한 feature들만을 발라내기 위한 mask
- user_mask :
- sys_val :
- user_val :
- ftr_bits : 이 register의 feature들을 나타내는 arm64_ftr_bits 구조체 포인터. 한 레지스터에 여러 feature bits들이 있으므로 배열이 들어간다.

 

__ftr_reg_entry 구조체
- 전역 변수 배열 arm64_ftr_regs[]의 type으로 오직 이 변수만을 위한 구조체
- sys_id : 레지스터 ID, opcode에서 사용하는 binary code이다
- reg : 레지스터를 나타내는 arm64_ftr_reg 구조체

 

전역 변수 arm64_ftr_regs[] 배열
- __ftr_reg_entry 구조체 배열로 CPU register 정보를 나타낸다
- 각 엔트리에 ARM64_FTR_REG() 매크로를 이용해서 arm64 cpu의 각 feature register를 정의함

 

ARM64_FTR_REG(id, table) 매크로
- id는 regsiter를 나타내는 opcode macro로 sys_id에 저장
- 또한 매크로 이름을 string으로 reg의 name에 저장한다
- table 변수의 시작 주소를 reg의 ftr_bits에 저장한다
- 각 table들은 전역 변수 arm64_ftr_bits 배열로 저장되어 있다 (한 레지스터에 여러 개 feature가 존재하므로)
- ARM64_FTR_BITS() 매크로로 각 arm64_ftr_bits 구조체가 정의된다

 

ARM64_FTR_BITS() 매크로
- arm64_ftr_bits 구조체에 sign, visible, …, safe_val 등의 값들을 채워서 초기화하고 리턴하는 매크로


init_cpu_features()
- 인자로 cpuinfo_arm64 구조체 info 포인터를 받는다
- 함수 sort_ftr_regs()로 미리 정의되어 있는 전역 변수 구조체 arm64_ftr_regs의 sanity를 체크한다
- 함수 init_cpu_ftr_reg()를 이용하여 각 레지스터에 대해 info의 register 자료구조를 초기화한다
- AArch32 bit가 지원되는 경우 32bit feature register들에 대해서도 자료구조를 업데이트한다
- SVE가 지원되는 경우 함수 sve_init_vq_map()를 이용하여 지원되는 SVE vector length를 표시하는 비트맵 sve_vq_map, sve_vq_partial_map을 업데이트한다 <— 여기까지 진행

 

sort_ftr_regs()
- 배열 arm64_ftr_regs[]의 각 엔트리에 대해서 돌면서 sanity를 체크한다
- 각 feature의 순서가 descending order가 맞는지, bit 값들의 overlap이 없는지를 체크한다
- 또한 각 regsiter의 sys_id 값이 ascending order가 맞는지도 체크한다


init_cpu_ftr_reg(sys_reg, new)
- 인자로 register opcode sys_reg와 실제 cpu hardware에서 읽어온 sys_reg에 해당하는 레지스터 값 new를 받는다
- get_arm64_ftr_reg() 함수로 sys_reg에 해당하는 arm64_ftr_regs[] 배열의 arm64_ftr_reg 구조체 배열을 가져온다
- 루프를 돌면서 각 arm64_ftr_reg 배열의 각 entry에 대해서 다음을 수행한다
1) 함수 arm64_ftr_mask()로 ftr_mask를 구한다
2) 함수 arm64_ftr_value()로 new로부터 현재 feature에 해당하는 ftr_new를 구한다
3) 함수 arm64_ftr_set_value()로 ftr_new 값을 register 포맷으로 encoding한다
4) valid_mask에 feature mask를 OR하여 누적시킨다
5) feature가 strict가 아니면 strict_mask (처음에 all F로 초기화되어 있음) 에서 feature mask를 clear한다
6) feature가 visible이면 user_mask에 feature mask를 OR하여 누적시킨다
7) feature가 visible이 아니면 user_value에 현재 feature의 safe_val을 추가한다
- arm64_ftr_reg 구조체에 위에서 구한 register value, mask들을 업데이트하고 리턴한다


get_arm64_ftr_reg(sys_id)
- binary search로 arm64_ftr_regs의 entry 중 sys_id가 일치하는 것을 찾아서 그 reg 멤버 변수를 리턴한다

 

arm64_ftr_mask()
- arm64_ftr_bits 구조체를 인자로 받아서 shift/width를 이용해서 해당 feature의 bit field에 해당하는 mask를 만들어 리턴한다

 

arm64_ftr_value()
- register value값 new와 arm64_ftr_bits 구조체를 인자로 받아서 shift/width/sign을 이용해서 feature에 해당하는 bit field만 골라서 리턴한다

 

arm64_ftr_set_value()
- arm64_ftr_bits 구조체, reg, feature value를 인자로 받는다
- arm64_ftr_bits 구조체로부터 feature mask를 만들고 이를 이용하여 feature value를 reg의 bit field에 삽입한뒤 리턴한다

 

전역 변수 sve_vq_map 비트맵
- 512 bit 비트맵으로 각 비트가 지원되는 SVE vector length를 나타낸다
- SVE vector 길이가 최대 2048 bit이나 이를 quadword 길이인 16 byte 단위로 bitmap 변환하여 표시한다
- 따라서 1 ~ 16까지의 index를 가지나 bit map 표기시 512에서 빼서 index를 설정한다
- 따라서 511 (지원되는 vector length 1 quadword) ~ 496 (지원되는 vector length 16 quadword) index에 bitmap이 표기된다
- 같은 기능의 sve_vq_partial_map 비트맵이 있으며 이는 최소 1개의 cpu에 적용되는 지원 vector length를 표시한다

 

sve_init_vq_map()
- 함수 sve_probe_vqs()로 지원되는 SVE vector length를 비트맵 sve_vq_map에 표시한다
- 그리고 sve_vq_partial_map 비트맵에 sve_vq_map 비트맵의 내용을 카피한다

 

sve_probe_vqs()
- 512부터 1까지 루프를 돌면서 ZCR_EL1의 LEN 필드에 값을 쓰고 실제 하드웨어적으로 적용되는 vector length를 추출하여 비트맵 sve_vq_map에 표시한다

XE Login