[커널 17차] 62주차

2021.11.07 00:21

ㅇㅇㅇ 조회 수:123

리눅스 메모리 관련 자료
- https://hzliu123.github.io/linux-kernel/Physical%20Memory%20Management%20in%20Linux.pdf

 

build_all_zonelists_init()
- __build_all_zonelists(NULL) —> 이후 부터 진행
- 각 possible cpu에 대해 루프를 돌면서 per_cpu_pageset 변수 boot_pageset을 함수 setup_pageset()으로 초기화한다. 이 때 batch로 사용할 값은 0으로 입력한다

 

pcp cache
- percpu page frame cache
- 시스템에서 가장 많이 요청하는 페이지 할당 요청은 single page 요청이다
- 따라서 single page 할당 성능을 올리기 위해 single page allocation에서 사용되는 전용 cache 자료구조를 percpu 변수로 반들었는데 이게 pcpu pageset이다
- 각 zone별로 pcpu pageset이 존재한다
- precpu pageset은 용도별로 reclaimable, movable, unmovable로 나누어 관리된다
- 다음 변수들로 캐시된 페이지를 관리한다
- count : 캐시된 페이지의 수
- high : 캐시된 페이지가 high 값을 넘어가면 초과된 개수만큼 buddy system이 회수한다
- batch : 캐시된 페이지가 batch보다 작으면 부족한 개수만큼 buddy system에서 할당해온다
- 캐시된 페이지를 가리키는 list_head 포인터. 용도별로 reclaimable, movable, unmovable이 있다
- http://jake.dothome.co.kr/per-cpu-page-frame-cache/

 

per_cpu_pageset 구조체 변수 boot_pageset
- 부팅 과정에서 사용하는 pageset 변수인듯
- 일단 부팅 과정에서는 초기화를 위해 boot_pageset을 이용하여 각 zone의 pageset을 초기화한다

 

setup_pageset()
- 인자로 per_cpu_pageset과 batch 값을 받아서 pageset을 업데이트한다
- 함수 pageset_init()으로 per_cpu_pageset을 초기화한다
- 함수 pageset_set_batch()로 per_cpu_pageset을 요청에 맞게 업데이트한다

 

pageset_init()
- 인자로 per_cpu_pageset을 받아서 초기화한다
- per_cpu_pageset의 값을 모두 0으로 memset하여 초기화 (count/high/batch를 모두 0으로)
- 각 migrate type에 대해서 per_cpu_pageset의 page list_head들을 모두 초기화한다

 

pageset_set_batch()
- 인자로 per_cpu_pageset과 batch 값을 받아서 per_cpu_pageset를 업데이트한다
- 함수 pageset_update()로 high/batch를 업데이트한다. high는 batch의 6배로, batch는 최소 1 이상의 값으로 업데이트한다

 

pageset_update()
- 인자로 per_cpu_page, high, batch값을 받아 per_cpu_page의 high, batch를 업데이트한다
- 안전한 업데이트를 위해 memory barrier를 사용하여 batch를 1로 업데이트 —> high 업데이트 —> batch 업데이트 순서대로 처리한다
- https://github.com/torvalds/linux/commit/8d7a8fa97abeb4fd6b3975d32c9f859875157770

 

mminit_verify_zonelist()
- 각 노드에 대해서 fallback list와 non fallback list를 커널 로그로 출력한다

 

cpuset_init_current_mems_allowed()
- 함수 nodes_setall()를 이용해서 현재 task (init_task)의 mems_allowed 비트맵을 모두 1로 세팅한다
- 비트맵 mems_allowed은 노드 개수만큼의 비트맵으로 task 구조체에 속해있다


build_all_zonelists()
- build_all_zonelists_init() 이후 부터 진행
- 함수 nr_free_zone_pages()으로 모든 노드의 모든 존의 free page 수를 합하여 로컬 변수 vm_total_pages에 기록한다
- 시스템의 free pages 수가 pageblock_nr_pages * MIGRATE_TYPES 보다 작으면 (수십 MB 이하) 전역 변수 page_group_by_mobility_disabled를 1로 세팅하고 아니면 0으로 세팅한다
- pageblock_nr_pages은 현재 512
- MIGRATE_TYPES은 최대 7
- 커널 로그에 zonelist의 개수, page_group_by_mobility_disabled 여부, vm total pages 및 policy_zone을 출력한다
- policy_zone은 현재 normal일 것으로 생각된다

 

GFP
- get free page의 약자로 메모리 요청시 free page를 받아올 때 사용하는 용어

 

GFP 플래그
- GFP_DMA / GFP_HIGHMEM, GFP_DMA32, GFP_MOVABLE : zone을 지칭하는 플래그들
- GFP_RECLAIMABLE : reclaim 가능한 영역 표시
- GFP_IO, GFP_FS, GFP_DIRECT_RECLAIM, GFP_KSWAPD_RECLAIM : 메모리 할당 요청시 IO 사용 가능, File System 사용 가능, reclaim 가능, swapping 사용 가능
- GFP_HARDWALL : cpuset 내의 로컬 메모리 풀에서만 할당하라는 요청
- GFP_ATOMIC : interrupt context과 같이 특수한 상황에서 메모리 할당 요청을 할 때 사용. reclaim 불가능
- https://stackoverflow.com/questions/19566186/may-i-know-in-linux-kernel-what-is-the-purpose-of-gfp-hardwall-flag?rq=1

- GFP_KERNEL : GFP_RECLAIM | GFP_IO | GFP_FS
- GFP_USER : GFP_RECLAIM | GFP_IO | GFP_FS | GFP_HARDWALL
- GFP_HIGHUSER : GFP_USER | GFP_HIGHMEM
- GFP_HIGHUSER_MOVABLE : GFP_HIGHUSER | GFP_MOVABLE

 

메모리 사용 관련 자료
- https://www.kernel.org/doc/gorman/html/understand/understand005.html

 

GFP_ZONE_TABLE
- 요청 플래그에 따라서 메모리를 할당할 zone type을 리턴해주는 테이블

 

gfp_zone()
- 인자로 gfp 플래그를 받아서 그에 맞는 zone type을 리턴한다
- 플래그를 GFP_ZONEMASK로 마스킹해서 bit를 구한다
- bit에 맞게 GFP_ZONE_TABLE을 shift 해서 zone type을 구하고 리턴한다

 

nr_free_zone_pages()
- 인자로 highest zone type을 받아 시스템 각 노드에서 highest zone type보다 같거나 낮은 zone들의 high watermark를 초과하는 free page 수를 합해서 리턴한다

- 함수 node_zonelist()로 현재 노드를 기준으로 fallback zonelist를 가져온다
- 매크로 for_each_zone_zonelist()를 이용해서 인자로 받은 offset보다 같거나 낮은 zone index에 대해 루프를 돌면서 각 zone에 대해 free page 개수, high watermark page 수를 구한다
- 각 zone별로 free page 수에서 high watermark page 수를 뺀 값을 누적하여 리턴한다

 

node_zonelist()
- 인자로 node id와 GFP flag를 받아서 flag에 따라 fallback 또는 non fallback zonelist를 리턴한다
- flag에 GFP_THISNODE가 없으면 non fallback, 있으면 non fallback zonelist를 리턴
- 현재는 GFP_KERNEL을 요청받았으므로 non fallback zonelist가 리턴된다

 

매크로 for_each_zone_zonelist()
- 매크로 for_each_zone_zonelist_nodemask()를 이용하여 high index보다 같거나 낮은 zone들, 그리고 node mask에 set되어 있는 node id에 해당하는 zone들을 iteration해준다
- 매크로 for_each_zone_zonelist_nodemask()는 first_zones_zonelist(), next_zones_zonelist()를 이용한다
- 함수 first_zones_zonelist()은 next_zones_zonelist()를 이용한다

 

next_zones_zonelist()
- 인자로 받은 노드 마스크가 NULL이고 highest zoneidx보다 같거나 낮으면 현재 zoneref를 리턴한다
- 아니면 함수 __next_zones_zonelist()로 다음 zoneref를 찾아 리턴한다

 

__next_zones_zonelist()
- 노드 마스크가 NULL이면 현재 zone이 highest_zoneidx보다 같거나 낮아질 때까지 다음 zone으로 패스한다
- 노드 마스크가 NULL이 아니면 현재 zone이 highest_zoneidx보다 같거나 낮고 zone이 유효한 node에 존재할 때까지 다음 zone으로 패스한다
- 구한 zoneref를 리턴한다

 

CPU Hot Plug 과정

CPU-up : (BP) OFFLINE —> BRINGUP_CPU —> (AP) AP_OFFLINE —> (IRQ_OFF) AP_ONLINE —> AP_ACTIVE
CPU-down : (AP) AP_ACTIVE —> AP_ONLINE_IDLE —> TEARDOWN_CPU —> (stop_machine) AP_OFFLINE / AP_IDLE_DEAD —> (BP) BRINGUP_CPU —> OFFLINE

 

CPU hot plug state는 enum cpuhp_state 로 정의되어 있다

 

enum cpuhp_state
- include/linux/cpuhotplug.h
- CPUHP_INVALID = -1, CPUHP_OFFLINE = 0, …, CPUHP_ONLINE 까지 정의되어 있음
- 중간에 architecture/SoC specific한 state들도 많이 들어있다
- 또한 CPUHP_BP_PREPARE_DYN ~ CPUHP_BP_PREPARE_DYN_END (20개), CPUHP_AP_ONLINE_DYN ~ CPUHP_AP_ONLINE_DYN_END (30개) 와 같이 dynamic하게 state를 정의하여 사용할 수 있는 영역들도 존재한다. 이 영역들은 device driver 등에서 cpuhp state가 필요할 때 사용한다고 함

 

CPU HP state가 변동하게 되면 bringup이냐 teardown이냐에 따라 서로 다른 callback 함수가 호출된다
- Bringup 시 CPU HP state는 증가하며 이 때 각 진입하는 state 별로 startup callback 함수가 호출되어 실행된다
- 마찬가지로 teardown시 CPU HP state는 감소하며 이 때 각 진입하는 state별로 teardown callback 함수가 호출되어 실행된다
- 함수 cpuhp_up_callbacks()과 함수 cpuhp_down_callbacks()에서 해당 과정을 보여준다
- 위 함수들은 각각 _cpu_up(), _cpu_down()에서 사용된다

 

CPU HP state의 startup/teardown callback 함수들은 cpuhp_setup_state_nocalls() 등과 같은 함수에 의해서 등록된다

 

cpuhp_step 구조체
- CPU hotplug 상태를 관리하기 위한 state machine step을 나타내는 구조체
- 이름, state의 startup callback, teardown callback, state에 해당하는 CPU들을 가리키는 list,  cant_stop 변수 및 multi_instance 변수를 가지고 있다

 

cpuhp_step 구조체 전역 변수 배열 cpuhp_hp_states[]
- 각 cpuhp_state 상태 별로 cpuhp_step 구조체 entry를 가지고 있는 배열
- 중요한 몇 가지 state에 대해서만 초기화가 되어 있고 나머지는 함수 cpuhp_setup_state_nocalls() 등을 통해 name, callback 함수 등을 등록하게 되어 있다

 

cpuhp_cpu_state 구조체
- 현재 cpu의 hotplug 상태를 나타내는 구조체
- 현재 cpu의 state, target, fail 등 정보를 가지고 있음

 

percpu cpuhp_cpu_state 구조체 변수 cpuhp_state
- percpu 변수로 cpuhp_cpu_state 구조체이며 fail 변수가 CPUHP_INVALID로 초기화 되어 있다

 

page_alloc_init()
- 노드 중 메모리가 존재하는 노드가 1개 뿐이면 전역변수 hashdist를 0으로 한다. 1개보다 큰 경우엔 초기값인 1 그대로 둔다
- 함수 cpuhp_setup_state_nocalls()를 이용해서 cpuhp state CPUHP_PAGE_ALLOC_DEAD에 대해 teardown callback 함수 page_alloc_cpu_dead()를 등록한다. 이 때 등록시 callback은 수행하지 않으며 startup callback 등록도 하지 않는다

 

cpuhp_setup_state_nocalls()
- __cpuhp_setup_state() 함수를 이용해서 CPUHP callback 함수를 등록한다

 

__cpuhp_setup_state()
- cpus_read_lock()으로 CPU up/down lock을 잡는다
- 함수 __cpuhp_setup_state_cpuslocked()로 CPUHP callback 함수를 등록한다
- cpus_read_unlock()으로 CPU up/down lock을 푼다

 

__cpuhp_setup_state_cpuslocked()
- 인자로 cpuhp_state, state name, callback invoke 여부, startup/teardown callback 함수, multi_instance 여부를 받아 전역 변수 배열 cpuhp_hp_states[]에 등록하고, 필요시 startup callback 함수를 실행한다

- 인자로 들어온 cpuhp_state가 OFFLINE ~ ONLINE 범위를 벗어나거나 state name이 NULL이면 에러 처리한다
- 전역 변수 배열 cpuhp_hp_states[]에 state name이 등록되지 않으면 미등록으로 처리되므로 반드시 state name이 유효해야 한다
- cpuhp_state_mutex lock을 잡는다
- 함수 cpuhp_store_callbacks()로 callback 함수와 이름 및 부가 정보를 전역 변수 배열 cpuhp_hp_states[]에 등록한다
- 등록 요청한 state가 CPUHP_AP_ONLINE_DYN이고 리턴값이 양수인 경우 state 번호를 리턴값으로 하고 리턴값은 0으로 만든다
- 만약 리턴값이 0이 아니거나, callback invoke를 하지 않도록 요청하였거나, startup callback이 NULL인 경우엔 cpuhp_state_mutex lock을 풀고 리턴한다. 이 때 등록 요청한 state가 CPUHP_AP_ONLINE_DYN인 경우엔 state 번호를 리턴한다. 그 외엔 본래 ret 값을 리턴한다
- 각 present CPU에 대해서 다음을 수행한다
1) percpu 변수 cpuhp_state를 가져와 현재 cpu의 cpuhp state를 구한다
2) 현재 cpu state가 요청 state보다 ID 값이 작으면 다음 CPU로 continue
3) 아니면 함수 cpuhp_issue_call()로 startup callback 함수를 수행한다 —> 여기부터 진행 예정

 

cpuhp_store_callbacks()
- 인자로 요청 state와 이름, callback 함수, multi instance 정보를 받아 전역 변수 cpuhp_hp_states[]에 등록한다

- 만약 요청 state가 CPUHP_AP_ONLINE_DYN이거나 CPUHP_BP_PREPARE_DYN이면 비어있는 state를 골라서 dynamic state를 만들고 그 state값을 요청 state로 돌려준다. 이 때 함수 cpuhp_reserve_state()가 가용한 state를 구하는데 사용된다
- 함수 cpuhp_get_step()으로 전역 변수 cpuhp_hp_states[]로부터 state에 해당하는 cpuhp_step 구조체 엔트리를 가져온다
- 해당 step entry가 이미 이름이 있으면 에러 리턴
- 아니면 해당 step entry에 다음 항목을 등록한다
- startup callback, teardown callback, state 이름, multi instance
- 그리고 list head 변수 list도 초기화하고 리턴한다

 

cpuhp_reserve_state()
- 요청된 state가 CPUHP_AP_ONLINE_DYN이거나 CPUHP_BP_PREPARE_DYN가 아니면 에러 리턴한다
- 요청된 state가 CPUHP_AP_ONLINE_DYN이면 전역 변수 배열 cpuhp_hp_states[]에서 CPUHP_AP_ONLINE_DYN부터 CPUHP_AP_ONLINE_DYN_END까지 루프를 돌면서 이름이 NULL인 entry를 찾아서 그 state index를 리턴한다
- 요청된 state가 CPUHP_BP_PREPARE_DYN이면 전역 변수 배열 cpuhp_hp_states[]에서 CPUHP_BP_PREPARE_DYN부터 CPUHP_BP_PREPARE_DYN_END까지 루프를 돌면서 이름이 NULL인 entry를 찾아서 그 state index를 리턴한다
- 비어 있는 entry가 없으면 에러 리턴한다

 

cpuhp_get_step()
- 인자로 state index를 받아서 전역 변수 배열 cpuhp_hp_states[]에서 index에 해당하는 엔트리를 리턴한다

XE Login