[커널 17차] 61주차

2021.10.31 00:51

ㅇㅇㅇ 조회 수:79

alternative()
- http://jake.dothome.co.kr/alternative/

 

- subsection 1의 의미?
- https://sourceware.org/binutils/docs/as/Sub_002dSections.html
- https://github.com/torvalds/linux/commit/f7b93d42945cc71e1346dd5ae07c59061d56745e
- veneer 때문에 replacement code에 relative branch 명령어가 있는 경우에는 문제가 발생하는 경우가 있다
- .altinstr_replacement 영역과 .text 영역이 너무 멀면 발생하는 문제인듯
- 그래서 subsection .altinstr_replacement에서 subsection 1로 변경하여 veneer를 제거한 것으로 보인다
- subsection 1로 하면 original code가 존재하는 text 영역의 함수의 끝 영역에 replacement code가 추가된다
- 즉 현재 함수의 RET 다음에 추가된다
- subsection은 object file 단위로 만들어지는 것으로 보이며 subsection 1을 사용하는 경우 현재 object file의 다음에 코드가 추가되는 것으로 보임

 

- .org : 현재 주소를 리셋하는 location counter 명령어
- 음수값을 가지면 뒤로 가야되는데 이 경우 에러가 발생한다
- alternative()에서는 orignal code와 replacement code의 길이가 같음을 체크하는 용도로 사용된다
- https://stackoverflow.com/questions/3407023/what-does-org-assembly-instruction-do
- https://developer.arm.com/documentation/100067/0611/armclang-Integrated-Assembler/Org-directive

 

- branch immediate alternative patching
- replacement 영역으로 점프하는 경우에는 offset 조정을 하면 text 영역이 아니라 replacement 영역으로 점프해버리므로 offset 조정을 하면 안된다

 

smp_prepare_boot_cpu()
- apply_boot_alternatives() 이후 부터
- 함수 system_uses_irq_prio_masking()으로 priority masking 기능이 있는지 판단하고 있으면 함수 init_gic_priority_masking()로 GIC priority mask register를 초기화한다

 

system_uses_irq_prio_masking()
- 커널 config CONFIG_ARM64_PSEUDO_NMI가 활성화 되어 있는 경우 함수 cpus_have_const_cap()으로 ARM64_HAS_IRQ_PRIO_MASKING capability가 있는지 체크해서 리턴한다

 

cpus_have_const_cap()
- 인자로 capability ID를 받아서 cpu에 해당 capability가 있는지 리턴한다
- 실제로는 비트맵 cpu_hwcaps에 해당 capability의 bit가 set 되어 있는지 확인하거나 static key array cpu_hwcap_keys[]의 값이 true인지 리턴하는 방식으로 구현되어 있다
- 함수 system_capabilities_finalized()로 시스템의 capability가 초기화 완료되었는지 확인하여 완료 여부에 따라 비트맵 cpu_hwcaps (완료 안됨) 또는 static key array cpu_hwcap_keys[] (완료됨) 를 사용한다
- 함수 system_capabilities_finalized()에서는 capability 완료 여부 확인을 위해 static key arm64_const_caps_ready를 사용한다

 

init_gic_priority_masking()
- 함수 gic_enable_sre()로 ICC_SRE_EL1 값을 확인하고 SRE (system register enable) 값을 확인한다. 0 (system register disable)이면 경고를 띄운다
- ICC_SRE_EL1 (interrupt controller system register enable)은 GIC에 접근하기 위해 system register가 사용 가능한지 (1) 아니면 memory mapped interface를 써야하는지 (0) 나타낸다
- https://developer.arm.com/documentation/ddi0595/2021-03/AArch64-Registers/ICC-SRE-EL1--Interrupt-Controller-System-Register-Enable-register--EL1-
- PSTATE로부터 daif mask 상태값을 읽어서 interrupt mask가 0이면 경고를 내보낸다
- 함수 gic_write_pmr()를 써서 GIC PMR (priority masking register)에 GIC_PRIO_IRQON | GIC_PRIO_PSR_I_SET 값을 쓴다. 이는 0xF0에 해당한다
- GIC_PRIO_PSR_I_SET 값은 4번째 비트인데 이 비트와 PSTATE의 I mask 값은 일치해야 한다

 

gic_write_pmr()
- ICC_PMR_EL1 (interrupt controller priority mask register)
- 하위 8비트로 interrupt priority를 설정, 해당 priority보다 우선 순위가 떨어지는 interrupt는 GIC 단에서 차단한다
- 설정값이 작을수록 우선 순위가 높다
- https://developer.arm.com/documentation/ddi0595/2020-12/AArch64-Registers/ICC-PMR-EL1--Interrupt-Controller-Interrupt-Priority-Mask-Register
- 인자값을 ICC_PMR_EL1에 쓰는 함수

 

local_daif_restore() 복습
- interrupt enable 요청이 들어오면 PMR에는 GIC_PRIO_IRQON를 쓰고 PSTATE I bit은 0으로 설정
- interrupt disable 요청이 abort disable과 함께 들어오면 PMR에는 GIC_PRIO_IRQON | GIC_PRIO_PSR_I_SET을 쓰고 PSTATE I bit은 1로 설정
- interrupt disable 요청이 abort enable과 함께 들어오면 PMR에는 GIC_PRIO_IRQOFF을 쓰고 PSTATE I bit은 0으로 설정한다

 

boot_cpu_hotplug_init()
- 함수 cpumask_set_cpu()로 전역변수 비트맵 cpus_booted_once_mask의 현재 cpu id bit를 set한다
- 함수 this_cpu_write()로 percpu cpuhp_cpu_state 구조체 변수 cpuhp_state의 state 멤버변수를 CPUHP_ONLINE으로 설정한다

 

cpuhp_cpu_state 구조체
- cpu hot plug 상태를 나타내는 구조체

 

cpuhp_cpu_state 구조체 전역 변수 cpuhp_state
- percpu로 정의되어 각 cpu의 hot plug 상태를 나타낸다

 

this_cpu_write(pcp, val)
- percpu 변수 pcp에 val를 쓰는 함수
- __pcpu_size_call() 매크로로 구현되어 pcp 변수 크기에 맞게 val를 write한다
- 현재 상황에서는 this_cpu_write_4() 매크로로 구현되며 이 매크로는 _pcp_protect() 매크로와 __percpu_write_32() 함수로 구현됨

 

_pcp_protect(op, pcp, …)
- 함수 preempt_disable_notrace()로 preempt disable을 수행한다
- 요청된 op()을 pcp percpu 변수에 대해 수행
- 함수 preempt_enable_notrace()로 preempt enable을 수행한다

 

preempt_disable_notrace()
- __preempt_count_inc() 수행으로 preempt count 증가
- barrier() 수행

 

preempt_enable_notrace()
- barrier() 수행
- __preempt_count_dec_and_test() 수행 후 함수 __preempt_schedule_notrace()로 스케줄링 수행

 

__percpu_write_32(ptr, val)
- __percpu_write_##sz() 함수 매크로로 구현
- ptr 주소에 val을 쓰는 함수


build_all_zonelists()
- 각 노드에서 메모리를 할당하다가 부족해지면 근처에 있는 다른 노드로 메모리 할당을 넘긴다. 이 때 각 노드별로 메모리 할당을 넘겨줄 노드 및 존을 우선순위대로 리스트를 만들어서 관리하는데 이를 fallback zonelist하고 하며 이 함수에서 각 노드 별 fallback zonelist를 생성한다
- http://jake.dothome.co.kr/build_all_zonelists/

- 전역 변수 system_state는 초기 값이 SYSTEM_BOOTING 이므로 함수 build_all_zonelists_init()을 수행한다

 

build_all_zonelists_init()
- 함수 __build_all_zonelists()를 수행하여 각 노드별 fallback zonelist를 생성한다 <— 여기까지 진행

 

__build_all_zonelists()
- 인자로 pg_data_t 포인터를 받아서 (node_data에 해당) 해당 노드의 fallback zonelist를 생성한다. 만약 인자가 NULL이면 모든 노드에 대해 각 노드별 fallback zonelist를 생성한다

- 로컬 스핀락을 잡는다
- 전역 변수 배열 node_load[]를 memset하여 0으로 초기화한다. node_load[]는 각 노드별 로드를 나타내는 것으로 각 노드 입장에서 fallback 순서를 잡을 때 사용한다
- 각 online node에 대해 루프를 돌면서 함수 build_zonelists()를 이용하여 해당 노드에 대한 fallback zonelist를 생성한다 <— 여기까지 진행

 

build_zonelists()
- 인자로 노드를 나타내는 pg_data_t 포인터를 받아 해당 노드에 대한 fallback zonelist를 생성한다
- 현재 처리되는 노드를 local node라고 한다

- fallback zonelist 생성을 위해 fallback 우선 순위를 나타내는 node_order[] 배열을 생성한다
- 우선 node_order[] 배열을 0으로 초기화한다
- 함수 find_next_best_node()로 루프를 돌면서 다음 fallback 순위 노드를 구한다. 처음엔 자기 자신 노드가 시작 노드가 된다
- 지금 fallback 노드와 local node와의 거리가 이전 fallback 노드와 local node와 거리가 다르면 현재 fallback 노드의 node_load[]에 load를 쓴다. load는 nr_online_nodes로 초기화되어 있고 루프를 돌 때마다 1씩 감소한다
- node_order[] 배열에 루프 순서대로 fallback 노드를 넣는다
- 이전 fallback 노드는 prev_node에 넣고 load는 1씩 감소시킨다
- 루프가 끝나 node_order[] 배열이 완성되면 함수 build_zonelists_in_node_order()로 local 노드에 대한 fallback zonelist를 생성한다
- 이후 함수 build_thisnode_zonelists()로 local 노드에 대한 non-fallback zonelist도 생성한다

 

find_next_best_node()
- 인자로 기준이 되는 local node와 node bitmap을 받아서 다음 fallback 후보가 될 노드를 리턴한다. node bitmap엔 아직까지 선택되지 않은 노드는 0, 이미 선택된 노드는 1로 set되어 있고 후보 노드가 더 이상 없으면 -1을 리턴한다

 

- local node가 아직 선택되지 않았으면 local node를 bitmap에 set하고 local node를 리턴한다


- 메모리가 존재하는 노드에 대해서 루프를 돌면서 아래를 수행한다
1) 현재 노드가 이미 선택되었으면 continue
2) 아니면 local node와 거리를 계산하여 val에 저장
3) 현재 노드가 local node보다 작은 ID를 가지면 val에 +1하여 페널티 부여
4) 현재 노드의 cpumask를 보고 cpu가 비어 있지 않으면 +1 페널티 부여
5) val에 MAX_NODE_LOAD*MAX_NUMNODES 값을 곱한다. MAX_NUMNODES은 더 이상 필요없으나 (node_load[]가 더 이상 덧셈으로 업데이트되지 않으므로) 아직 남겨져 있는 것으로 보임
6) 현재 노드의 node_load[]를 val에 더한다. 즉 로드가 적은 노드를 fallback으로 선호한다
7) min val 및 min val을 가지는 노드를 갱신한다

 

- 루프가 종료되고 best node가 -1이 아니면 (즉 후보가 하나라도 있으면) 발견된 노드를 bitmap에 set하고 해당 노드를 리턴한다

 

build_zonelists_in_node_order()
- 인자로 현재 노드 정보를 가지는 pg_data_t 포인터, node_order[] 배열, online node의 수를 받는다
- 받은 node_order[] 배열 순서대로 현재 노드의 fallback zonelist에 노드들을 순서대로 삽입한다

- 현재 노드의 zonerefs = fallback zonelist는 pgdat->node_zonelists[ZONELIST_FALLBACK]._zonerefs 이다
- 루프를 돌면서 node_order[]의 순서대로 fallback 노드를 가져온다
- 함수 build_zonerefs_node()를 이용해서 zonerefs에 현재 fallback 노드를 추가한다
- 모든 fallback 노드가 추가되었으면 zonerefs에 NULL 노드를 추가하고 리턴한다

 

build_zonerefs_node()
- 인자로 fallback 노드 정보 pg_data_t 포인터와 fallback zonelist를 받아서 fallback 노드를 fallback zonelist에 추가한다

- fallback 노드의 zone을 역순으로 하여 (ZONE_DEVICE —> ZONE_MOVABLE —> ZONE_NORMAL —> ZONE_DMA32 —> ZONE_DMA) 함수 zoneref_set_zone()를 이용하여 zonelist에 zone을 추가한다
- 이 때 함수 check_highest_zone()를 이용해서 movable이 아닌 zone 중 가장 높은 zone을 전역 변수 policy_zone으로 설정한다
- 추가가 모두 끝났으면 추가한 zone 개수를 리턴한다

 

zoneref_set_zone()
- 인자로 zone과 zonelist를 받아서 zonelist에 zone을 추가하고 zone index도 추가한다
- zone index는 정순이다 (0 : ZONE_DMA, 1: ZONE_NORMAL, …)

 

build_thisnode_zonelists()
- 인자로 local node를 받아서 non fallback zonelist를 만든다
- 함수 build_zonerefs_node()를 이용해서 non fallback zonelist에 zone들을 추가하고 마지막에 null zone을 추가한다
- non fallback zonelist는 pgdat->node_zonelists[ZONELIST_NOFALLBACK]._zonerefs 이다

XE Login