1. numa_next_scan2. numa_scan_period相关代码分析3. 线程迁移的周期1. numa_next_scan扫描是针对内存进行的为了保证每次只有一个线程对内存进行扫描使用mm_struct中的numa_next_scan作为控制变量。线程扫描周期的控制变量numa_scan_period要作用到numa_next_scan上来影响扫描的周期。这个过程可以参考函数task_numa_work在task_numa_work这个函数中会先比较当前时间now和numa_next_scan只有当前时间now晚于numa_next_scan才会执行后面的流程。在下一步更新numa_next_scan下一次的numa_next_scan定义为now numa_scan_period下一次的扫描时间由numa_scan_period决定存储在numa_next_scan中。| | | | --- | --- | | 1 | // cmpxchg的函数的作用它的入参是一个指向要操作的变量的指针、期望值和新值返回值是操作前的变量值。 | | 2 | // 在这里它比较numa_next_scan的当前值和migrate这个值如果相等则修改为next_scan, 否则直接return, 但是问题是numa_scan_period的取值为什么会小于1000 | | 3 | // 这里的操作可以保证对于一个mm结构体, 同一时间只有一个线程来修改 | | 4 | migrate mm-numa_next_scan; | | 5 | if (time_before(now, migrate)) | | 6 | return; | | 7 | next_scan now msecs_to_jiffies(p-numa_scan_period); | | 8 | if (cmpxchg(mm-numa_next_scan, migrate, next_scan) ! migrate) | | 9 | return; |影响task-numa_scan_period的有三个函数- task_tick_numacurr-numa_scan_period task_scan_start(curr) - update_scan_period: curr-numa_scan_period task_scan_start(curr) - update_task_scan_period: curr-numa_scan_period clamp(curr-numa_scan_period diff, task_scan_min(curr), task_scan_max(curr))task_tick_numa这个函数中的numa_scan_period只有在numa_balancing特性初次运行的时候才会生效 update_scan_period: 这个函数中的numa_scan_period要更新需要等到任务所在的node发生改变时才会产生也就是任务发生跨node的迁移时才会被调用 update_task_scan_period: 这个函数在task_numa_placement函数中被调用task_numa_placement可以认为在很频繁地被调用所以numa_scan_period很快就会增长2. numa_scan_period相关代码分析在使能numa balancing特性进行第一次扫描或者线程发生跨node迁移后会调用函数task_scan_start来设置numa_scan_period的值我们来看一下task_scan_start函数的调用流程| | | | --- | --- | | 1 | // 扫描时间由两个值中的最大值决定 max(min, period) | | 2 | // min rss / nr_scan_pages | | 3 | // period: 由min和private和share内存的比例决定 | | 4 | task_scan_start | | 5 | - task_scan_min | | 6 | // 扫描的最小时间为 rss / nr_scan_pages, 其中rss为进程总共使用的物理内存大小, nr_scan_pages为单次扫描的物理内存大小 | | 7 | - task_nr_scan_windows || | | | --- | --- | | 1 | static inline unsigned long group_faults_priv(struct numa_group *ng); | | 2 | static inline unsigned long group_faults_shared(struct numa_group *ng); | | 3 | | | 4 | // rss代表总共使用的物理内存总共使用的物理内存的大小决定了task_nr_scan_windows的大小使用的物理内存越多扫描的越快 | | 5 | static unsigned int task_nr_scan_windows(struct task_struct *p) | | 6 | { | | 7 | unsigned long rss 0; | | 8 | unsigned long nr_scan_pages; | | 9 | | | 10 | /* | | 11 | * Calculations based on RSS as non-present and empty pages are skipped | | 12 | * by the PTE scanner and NUMA hinting faults should be trapped based | | 13 | * on resident pages | | 14 | */ | | 15 | nr_scan_pages sysctl_numa_balancing_scan_size (20 - PAGE_SHIFT); | | 16 | rss get_mm_rss(p-mm); | | 17 | if (!rss) | | 18 | rss nr_scan_pages; | | 19 | | | 20 | rss round_up(rss, nr_scan_pages); | | 21 | return rss / nr_scan_pages; | | 22 | } | | 23 | | | 24 | // 由2560这个值可以推断出, 一次扫描的最短时间是100ms | | 25 | /* For sanitys sake, never scan more PTEs than MAX_SCAN_WINDOW MB/sec. */ | | 26 | #define MAX_SCAN_WINDOW 2560 | | 27 | | | 28 | static unsigned int task_scan_min(struct task_struct *p) | | 29 | { | | 30 | unsigned int scan_size READ_ONCE(sysctl_numa_balancing_scan_size); | | 31 | unsigned int scan, floor; | | 32 | unsigned int windows 1; | | 33 | | | 34 | if (scan_size MAX_SCAN_WINDOW) | | 35 | windows MAX_SCAN_WINDOW / scan_size; | | 36 | floor 1000 / windows; | | 37 | | | 38 | scan sysctl_numa_balancing_scan_period_min / task_nr_scan_windows(p); | | 39 | return max_t(unsigned int, floor, scan); | | 40 | } | | 41 | | | 42 | // 对于group shared 和private分别指的是什么 | | 43 | static unsigned int task_scan_start(struct task_struct *p) | | 44 | { | | 45 | unsigned long smin task_scan_min(p); | | 46 | unsigned long period smin; | | 47 | struct numa_group *ng; | | 48 | | | 49 | /* Scale the maximum scan period with the amount of shared memory. */ | | 50 | rcu_read_lock(); | | 51 | ng rcu_dereference(p-numa_group); | | 52 | if (ng) { | | 53 | unsigned long shared group_faults_shared(ng); | | 54 | unsigned long private group_faults_priv(ng); | | 55 | | | 56 | period * refcount_read(ng-refcount); | | 57 | period * shared 1; | | 58 | period / private shared 1; | | 59 | } | | 60 | rcu_read_unlock(); | | 61 | | | 62 | return max(smin, period); | | 63 | } | | 64 | | | 65 | static unsigned int task_scan_max(struct task_struct *p) | | 66 | { | | 67 | unsigned long smin task_scan_min(p); | | 68 | unsigned long smax; | | 69 | struct numa_group *ng; | | 70 | | | 71 | /* Watch for min being lower than max due to floor calculations */ | | 72 | smax sysctl_numa_balancing_scan_period_max / task_nr_scan_windows(p); | | 73 | | | 74 | /* Scale the maximum scan period with the amount of shared memory. */ | | 75 | ng deref_curr_numa_group(p); | | 76 | if (ng) { | | 77 | unsigned long shared group_faults_shared(ng); | | 78 | unsigned long private group_faults_priv(ng); | | 79 | unsigned long period smax; | | 80 | | | 81 | period * refcount_read(ng-refcount); | | 82 | period * shared 1; | | 83 | period / private shared 1; | | 84 | | | 85 | smax max(smax, period); | | 86 | } | | 87 | | | 88 | return max(smin, smax); | | 89 | } |numa_faults_locality[3]作为数组总共存储了3个元素第一个元素代表remote访问的pages数第二个元素代表local访问的pages数第三个元素代表迁移失败的pages数目。update_task_scan_period函数中对于numa_scan_period的更新有两种方式完全没有有效的fault访问此时任务完全处于idle状态或是所有的内存访问区域都属于不能进行numa balancing操作的的区域。或者是内存迁移失败此时要么是内存迁移太过频繁或是内存要迁去的node已经负载过重此时直接将numa_scan_period的时间设置为原来的两倍当被local/ (local remote) 70% 或是 private / (private shared) 70%此时按照比例将numa_scan_period调大最大调大的幅度为原来的30%。否则要按比例降低numa_scan_period加快扫描。| | | | --- | --- | | 1 | // 该函数在task_numa_placement函数中被调用用于更新numa_scan_period, 根据本地访存的比例, 调整扫描的周期 | | 2 | /* | | 3 | * Increase the scan period (slow down scanning) if the majority of | | 4 | * our memory is already on our local node, or if the majority of | | 5 | * the page accesses are shared with other processes. | | 6 | * Otherwise, decrease the scan period. | | 7 | */ | | 8 | static void update_task_scan_period(struct task_struct *p, | | 9 | unsigned long shared, unsigned long private) | | 10 | { | | 11 | unsigned int period_slot; | | 12 | int lr_ratio, ps_ratio; | | 13 | int diff; | | 14 | | | 15 | unsigned long remote p-numa_faults_locality[0]; | | 16 | unsigned long local p-numa_faults_locality[1]; | | 17 | | | 18 | /* | | 19 | * If there were no record hinting faults then either the task is | | 20 | * completely idle or all activity is areas that are not of interest | | 21 | * to automatic numa balancing. Related to that, if there were failed | | 22 | * migration then it implies we are migrating too quickly or the local | | 23 | * node is overloaded. In either case, scan slower | | 24 | */ | | 25 | if (local shared 0 || p-numa_faults_locality[2]) { | | 26 | p-numa_scan_period min(p-numa_scan_period_max, | | 27 | p-numa_scan_period 1); | | 28 | | | 29 | p-mm-numa_next_scan jiffies | | 30 | msecs_to_jiffies(p-numa_scan_period); | | 31 | | | 32 | return; | | 33 | } | | 34 | | | 35 | /* | | 36 | * Prepare to scale scan period relative to the current period. | | 37 | * NUMA_PERIOD_THRESHOLD scan period stays the same | | 38 | * NUMA_PERIOD_THRESHOLD scan period decreases (scan faster) | | 39 | * NUMA_PERIOD_THRESHOLD scan period increases (scan slower) | | 40 | */ | | 41 | period_slot DIV_ROUND_UP(p-numa_scan_period, NUMA_PERIOD_SLOTS); | | 42 | lr_ratio (local * NUMA_PERIOD_SLOTS) / (local remote); | | 43 | ps_ratio (private * NUMA_PERIOD_SLOTS) / (private shared); | | 44 | | | 45 | if (ps_ratio NUMA_PERIOD_THRESHOLD) { | | 46 | /* | | 47 | * Most memory accesses are local. There is no need to | | 48 | * do fast NUMA scanning, since memory is already local. | | 49 | */ | | 50 | int slot ps_ratio - NUMA_PERIOD_THRESHOLD; | | 51 | if (!slot) | | 52 | slot 1; | | 53 | diff slot * period_slot; | | 54 | } else if (lr_ratio NUMA_PERIOD_THRESHOLD) { | | 55 | /* | | 56 | * Most memory accesses are shared with other tasks. | | 57 | * There is no point in continuing fast NUMA scanning, | | 58 | * since other tasks may just move the memory elsewhere. | | 59 | */ | | 60 | int slot lr_ratio - NUMA_PERIOD_THRESHOLD; | | 61 | if (!slot) | | 62 | slot 1; | | 63 | diff slot * period_slot; | | 64 | } else { | | 65 | /* | | 66 | * Private memory faults exceed (SLOTS-THRESHOLD)/SLOTS, | | 67 | * yet they are not on the local NUMA node. Speed up | | 68 | * NUMA scanning to get the memory moved over. | | 69 | */ | | 70 | int ratio max(lr_ratio, ps_ratio); | | 71 | diff -(NUMA_PERIOD_THRESHOLD - ratio) * period_slot; | | 72 | } | | 73 | | | 74 | p-numa_scan_period clamp(p-numa_scan_period diff, | | 75 | task_scan_min(p), task_scan_max(p)); | | 76 | memset(p-numa_faults_locality, 0, sizeof(p-numa_faults_locality)); | | 77 | } |3. 线程迁移的周期线程迁移的最短时间由numa_migrate_retry决定在函数task_numa_fault函数中根据numa_migrate_retry的取值确定是否要调用task_numa_placement和函数numa_migrate_preferred来执行线程迁移。| | | | --- | --- | | 1 | /* | | 2 | * Retry to migrate task to preferred node periodically, in case it | | 3 | * previously failed, or the scheduler moved us. | | 4 | */ | | 5 | if (time_after(jiffies, p-numa_migrate_retry)) { | | 6 | task_numa_placement(p); | | 7 | numa_migrate_preferred(p); | | 8 | } |numa_migrate_retry的取值在函数中numa_migrate_preferred中被修改被定义为内存扫描周期的1/16| | | | --- | --- | | 1 | /* Periodically retry migrating the task to the preferred node */ | | 2 | interval min(interval, msecs_to_jiffies(p-numa_scan_period) / 16); | | 3 | p-numa_migrate_retry jiffies interval; |