ZONE 비트맵 (API)

 

for_each_zone_zonelist()

include/linux/mmzone.h

/**             
 * for_each_zone_zonelist - helper macro to iterate over valid zones in a zonelist at or below a given zone index
 * @zone - The current zone in the iterator
 * @z - The current pointer within zonelist->zones being iterated
 * @zlist - The zonelist being iterated
 * @highidx - The zone index of the highest zone to return
 *
 * This iterator iterates though all zones at or below a given zone index.
 */
#define for_each_zone_zonelist(zone, z, zlist, highidx) \
        for_each_zone_zonelist_nodemask(zone, z, zlist, highidx, NULL)

zonelist에서 highest_zoneidx 이하의 zone 엔트리들에 대해 루프를 돌며 zone과 z(zoneref)를 반환한다.

 

for_each_zone_zonelist_nodemask()

include/linux/mmzone.h

/**
 * for_each_zone_zonelist_nodemask - helper macro to iterate over valid zones in a zonelist at or below a given zone index and within a nodemask
 * @zone - The current zone in the iterator
 * @z - The current pointer within zonelist->zones being iterated
 * @zlist - The zonelist being iterated
 * @highidx - The zone index of the highest zone to return
 * @nodemask - Nodemask allowed by the allocator
 *
 * This iterator iterates though all zones at or below a given zone index and
 * within a given nodemask
 */
#define for_each_zone_zonelist_nodemask(zone, z, zlist, highidx, nodemask) \
        for (z = first_zones_zonelist(zlist, highidx, nodemask, &zone); \
                zone;                                                   \
                z = next_zones_zonelist(++z, highidx, nodemask),        \
                        zone = zonelist_zone(z))                        \

zonelist에서 highest_zoneidx 이하의 zone이면서 nodes 비트맵에 설정된 노드들인 zone 엔트리들에 대해 루프를 돌며 zone과 z(zoneref)를 반환한다.

 

/**
 * first_zones_zonelist - Returns the first zone at or below highest_zoneidx within the allowed nodemask in a zonelist
 * @zonelist - The zonelist to search for a suitable zone
 * @highest_zoneidx - The zone index of the highest zone to return
 * @nodes - An optional nodemask to filter the zonelist with
 * @zone - The first suitable zone found is returned via this parameter
 *
 * This function returns the first zone at or below a given zone index that is
 * within the allowed nodemask. The zoneref returned is a cursor that can be
 * used to iterate the zonelist with next_zones_zonelist by advancing it by
 * one before calling.
 */
static inline struct zoneref *first_zones_zonelist(struct zonelist *zonelist,
                                        enum zone_type highest_zoneidx,
                                        nodemask_t *nodes,
                                        struct zone **zone)
{
        struct zoneref *z = next_zones_zonelist(zonelist->_zonerefs,
                                                        highest_zoneidx, nodes);
        *zone = zonelist_zone(z);
        return z;
}

zonelist에서 highest_zoneidx 이하의 zone이면서 nodes 비트맵에 설정된 노드들에서 가장 처음 발견된 zone을 반환한다.

 

아래 그림은 zonelist의 처음 부터 검색을 하여 ZONE_NORMAL 타입을 초과하지 않는 zone에 대해 적합한 zone을 알아온다.

first_zones_zonelist-1b

 

next_zones_zonelist()

mm/mmzone.c

/**
 * next_zones_zonelist - Returns the next zone at or below highest_zoneidx within the allowed nodemask using a cursor within a zonelist as a starting point
 * @z - The cursor used as a starting point for the search
 * @highest_zoneidx - The zone index of the highest zone to return
 * @nodes - An optional nodemask to filter the zonelist with
 *
 * This function returns the next zone at or below a given zone index that is
 * within the allowed nodemask using a cursor as the starting point for the
 * search. The zoneref returned is a cursor that represents the current zone
 * being examined. It should be advanced by one before calling
 * next_zones_zonelist again.
 */
/* Returns the next zone at or below highest_zoneidx in a zonelist */
struct zoneref *next_zones_zonelist(struct zoneref *z,
                                        enum zone_type highest_zoneidx,
                                        nodemask_t *nodes)
{
        /*
         * Find the next suitable zone to use for the allocation.
         * Only filter based on nodemask if it's set
         */
        if (likely(nodes == NULL))
                while (zonelist_zone_idx(z) > highest_zoneidx)
                        z++;
        else
                while (zonelist_zone_idx(z) > highest_zoneidx ||
                                (z->zone && !zref_in_nodemask(z, nodes)))
                        z++;

        return z;
}

zonelists에서 처음 부터 검색하여 zone이 highest_zoneidx 이하의 zone인 경우 그  zone을 리턴하되 인수 nodes에 대해 아래와 같이 처리한다.

  • 인수 nodes가 지정된 경우 zonelists의 각 zone은 nodes에 포함된 zone으로 한정한다.
  • 인수 nodes가 지정되지 않은 경우 node에 대해 제한 없다.

 

  • if (likely(nodes == NULL))
    • 높은 확률로 nodes가 null인 경우
  • while (zonelist_zone_idx(z) > highest_zoneidx) z++;
    • zonelist에서 zone 타입이 highest_zoneidx를 초과하는 경우 다음 존을 계속 진행한다.
  • while (zonelist_zone_idx(z) > highest_zoneidx || (z->zone && !zref_in_nodemask(z, nodes))) z++;
    • zonelist에 zone 타입이 highest_zoneidx를 초과하거나 인수 nodes에 포함되어 있지 않은 경우 다음 존을 계속 진행한다.

아래 그림은 z 부터 검색을 하여 ZONE_NORMAL 타입을 초과하지 않는 zone에 대해 적합한 zone을 알아온다.

next_zones_zonelist-1b

 

zonelist_zone()

include/linux/mmzone.h

static inline struct zone *zonelist_zone(struct zoneref *zoneref)
{
        return zoneref->zone;
}

zonelist에서 zoneref의 zone 을 리턴한다.

 

zonelist_zone()

include/linux/mmzone.h

static inline int zonelist_zone_idx(struct zoneref *zoneref)
{                                       
        return zoneref->zone_idx;
}

zonelist에서 zoneref의 zone 타입을 리턴한다. (based 0)

 

zref_in_nodemask()

mm/mmzone.c

static inline int zref_in_nodemask(struct zoneref *zref, nodemask_t *nodes)
{
#ifdef CONFIG_NUMA
        return node_isset(zonelist_node_idx(zref), *nodes);
#else
        return 1;
#endif /* CONFIG_NUMA */
}

nodes 노드 비트맵에 zref 의 노드가 포함 여부를 리턴한다. NUMA 시스템이 아닌 경우는 항상 1을 리턴한다.

 

ZONE과 관련한 GFP

gfp_zone()

include/linux/gfp.h

static inline enum zone_type gfp_zone(gfp_t flags)
{               
        enum zone_type z;
        int bit = (__force int) (flags & GFP_ZONEMASK);
                        
        z = (GFP_ZONE_TABLE >> (bit * ZONES_SHIFT)) &
                                         ((1 << ZONES_SHIFT) - 1);
        VM_BUG_ON((GFP_ZONE_BAD >> bit) & 1);
        return z;
}

ZONE 비트 정보가 포함된 flags를 사용하여 zone_type (based 0)을 리턴한다.

  •  int bit = (__force int) (flags & GFP_ZONEMASK);
    • ZONE에 대한 __GFP_**** 플래그를 추출한다.
  • z = (GFP_ZONE_TABLE >> (bit * ZONES_SHIFT)) & ((1 << ZONES_SHIFT) – 1);
    • zone type(based 0)을 알아와서 리턴한다.

 

 

/*
 * GFP_ZONE_TABLE is a word size bitstring that is used for looking up the
 * zone to use given the lowest 4 bits of gfp_t. Entries are ZONE_SHIFT long
 * and there are 16 of them to cover all possible combinations of
 * __GFP_DMA, __GFP_DMA32, __GFP_MOVABLE and __GFP_HIGHMEM.
 *
 * The zone fallback order is MOVABLE=>HIGHMEM=>NORMAL=>DMA32=>DMA.
 * But GFP_MOVABLE is not only a zone specifier but also an allocation
 * policy. Therefore __GFP_MOVABLE plus another zone selector is valid.
 * Only 1 bit of the lowest 3 bits (DMA,DMA32,HIGHMEM) can be set to "1".
 *
 *       bit       result
 *       =================
 *       0x0    => NORMAL
 *       0x1    => DMA or NORMAL
 *       0x2    => HIGHMEM or NORMAL
 *       0x3    => BAD (DMA+HIGHMEM)
 *       0x4    => DMA32 or DMA or NORMAL
 *       0x5    => BAD (DMA+DMA32)
 *       0x6    => BAD (HIGHMEM+DMA32)
 *       0x7    => BAD (HIGHMEM+DMA32+DMA)
 *       0x8    => NORMAL (MOVABLE+0)
 *       0x9    => DMA or NORMAL (MOVABLE+DMA)
 *       0xa    => MOVABLE (Movable is valid only if HIGHMEM is set too)
 *       0xb    => BAD (MOVABLE+HIGHMEM+DMA)
 *       0xc    => DMA32 (MOVABLE+DMA32)
 *       0xd    => BAD (MOVABLE+DMA32+DMA)
 *       0xe    => BAD (MOVABLE+DMA32+HIGHMEM)
 *       0xf    => BAD (MOVABLE+DMA32+HIGHMEM+DMA)
 *
 * ZONES_SHIFT must be <= 2 on 32 bit platforms.
 */

#if 16 * ZONES_SHIFT > BITS_PER_LONG
#error ZONES_SHIFT too large to create GFP_ZONE_TABLE integer
#endif

#define GFP_ZONE_TABLE ( \
        (ZONE_NORMAL << 0 * ZONES_SHIFT)                                      \
        | (OPT_ZONE_DMA << ___GFP_DMA * ZONES_SHIFT)                          \
        | (OPT_ZONE_HIGHMEM << ___GFP_HIGHMEM * ZONES_SHIFT)                  \
        | (OPT_ZONE_DMA32 << ___GFP_DMA32 * ZONES_SHIFT)                      \
        | (ZONE_NORMAL << ___GFP_MOVABLE * ZONES_SHIFT)                       \
        | (OPT_ZONE_DMA << (___GFP_MOVABLE | ___GFP_DMA) * ZONES_SHIFT)       \
        | (ZONE_MOVABLE << (___GFP_MOVABLE | ___GFP_HIGHMEM) * ZONES_SHIFT)   \
        | (OPT_ZONE_DMA32 << (___GFP_MOVABLE | ___GFP_DMA32) * ZONES_SHIFT)   \
)

 

/*
 * GFP_ZONE_BAD is a bitmap for all combinations of __GFP_DMA, __GFP_DMA32
 * __GFP_HIGHMEM and __GFP_MOVABLE that are not permitted. One flag per
 * entry starting with bit 0. Bit is set if the combination is not
 * allowed.
 */
#define GFP_ZONE_BAD ( \
        1 << (___GFP_DMA | ___GFP_HIGHMEM)                                    \
        | 1 << (___GFP_DMA | ___GFP_DMA32)                                    \
        | 1 << (___GFP_DMA32 | ___GFP_HIGHMEM)                                \
        | 1 << (___GFP_DMA | ___GFP_DMA32 | ___GFP_HIGHMEM)                   \
        | 1 << (___GFP_MOVABLE | ___GFP_HIGHMEM | ___GFP_DMA)                 \
        | 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_DMA)                   \
        | 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_HIGHMEM)               \
        | 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_DMA | ___GFP_HIGHMEM)  \
)

 

ZONES_SHIFT

include/linux/page-flags-layout.h

/*
 * When a memory allocation must conform to specific limitations (such
 * as being suitable for DMA) the caller will pass in hints to the
 * allocator in the gfp_mask, in the zone modifier bits.  These bits
 * are used to select a priority ordered list of memory zones which
 * match the requested limits. See gfp_zone() in include/linux/gfp.h
 */
#if MAX_NR_ZONES < 2
#define ZONES_SHIFT 0
#elif MAX_NR_ZONES <= 2
#define ZONES_SHIFT 1
#elif MAX_NR_ZONES <= 4
#define ZONES_SHIFT 2
#else
#error ZONES_SHIFT -- too many zones configured adjust calculation
#endif

zone 을 표현하기 위해 좌측 쉬프트를 해야 하는 비트 수

  • zone이 1개인 경우 0 비트 (zone 비트가 필요 없음)
  • zone이 2개인 경우 1 비트
  • zone이 3~4개인 경우 2비트
  • zone이 5개 이상인 경우 에러

 

gfpflags_to_migratetype()

include/linux/gfp.h

/* Convert GFP flags to their corresponding migrate type */
static inline int gfpflags_to_migratetype(const gfp_t gfp_flags)
{               
        WARN_ON((gfp_flags & GFP_MOVABLE_MASK) == GFP_MOVABLE_MASK);
        
        if (unlikely(page_group_by_mobility_disabled))
                return MIGRATE_UNMOVABLE;

        /* Group based on mobility */
        return (((gfp_flags & __GFP_MOVABLE) != 0) << 1) |
                ((gfp_flags & __GFP_RECLAIMABLE) != 0);
}

gfp_flags에서 migrate type을 알아내어 반환한다.

 

참고

 

CPU 비트맵 (API)

 

for_each_cpu()

include/linux/cpumask.h

/**
 * for_each_cpu - iterate over every cpu in a mask
 * @cpu: the (optionally unsigned) integer iterator
 * @mask: the cpumask pointer
 *
 * After the loop, cpu is >= nr_cpu_ids.
 */
#define for_each_cpu(cpu, mask)                         \
        for ((cpu) = -1;                                \
                (cpu) = cpumask_next((cpu), (mask)),    \
                (cpu) < nr_cpu_ids;)
  • mask가 가리키는 비트맵에서 각 비트가 1로 기록된 노드만큼 루프를 돈다.
    • nr_cpu_ids는 초기에 NR_CPUS 값이 설정되었다가 나중에 possible cpu id + 1 값으로 설정된다.

 

for_each_possible_cpu()

include/linux/cpumask.h

#define for_each_possible_cpu(cpu) for_each_cpu((cpu), cpu_possible_mask)
  • cpu_possible_mask가 가리키는 비트맵에서 각 비트가 1로 기록된 노드만큼 루프를 돈다.

 

for_each_online_cpu()

include/linux/cpumask.h

#define for_each_online_cpu(cpu)   for_each_cpu((cpu), cpu_online_mask)
  • cpu_online_mask가 가리키는 비트맵에서 각 비트가 1로 기록된 노드만큼 루프를 돈다.

 

for_each_present_cpu()

include/linux/cpumask.h

#define for_each_present_cpu(cpu)  for_each_cpu((cpu), cpu_present_mask)
  • cpu_present_mask가 가리키는 비트맵에서 각 비트가 1로 기록된 노드만큼 루프를 돈다.

 

on_each_cpu_mask()

/**
 * on_each_cpu_mask(): Run a function on processors specified by
 * cpumask, which may include the local processor.
 * @mask: The set of cpus to run on (only runs on online subset).
 * @func: The function to run. This must be fast and non-blocking.
 * @info: An arbitrary pointer to pass to the function.
 * @wait: If true, wait (atomically) until function has completed
 *        on other CPUs.
 *
 * If @wait is true, then returns once @func has returned.
 *
 * You must not call this function with disabled interrupts or from a
 * hardware interrupt handler or from a bottom half handler.  The
 * exception is that it may be used during early boot while
 * early_boot_irqs_disabled is set.
 */                     
void on_each_cpu_mask(const struct cpumask *mask, smp_call_func_t func,
                        void *info, bool wait)
{
        int cpu = get_cpu();    

        smp_call_function_many(mask, func, info, wait);
        if (cpumask_test_cpu(cpu, mask)) {
                unsigned long flags;
                local_irq_save(flags);
                func(info);
                local_irq_restore(flags);
        }               
        put_cpu();
}        
EXPORT_SYMBOL(on_each_cpu_mask);

func 함수를 다른 cpu에서 모두 수행시키고 현재 cpu에서도 수행시킨다. 인수 wait이 true인 경우 모든 cpu의 수행이 완료될 때까지 기다린다.

 

cpumask_first()

include/linux/cpumask.h

/**
 * cpumask_first - get the first cpu in a cpumask
 * @srcp: the cpumask pointer
 *
 * Returns >= nr_cpu_ids if no cpus set.
 */
static inline unsigned int cpumask_first(const struct cpumask *srcp)
{
        return find_first_bit(cpumask_bits(srcp), nr_cpumask_bits);
}

srcp가 가리키는 cpu 상태 관련 비트맵의 nr_cpumask_bits 까지에서 1로 설정된 첫 cpu id(based 0)를 알아온다.

 

cpumask_next()

include/linux/cpumask.h

/**
 * cpumask_next - get the next cpu in a cpumask
 * @n: the cpu prior to the place to search (ie. return will be > @n)
 * @srcp: the cpumask pointer
 *
 * Returns >= nr_cpu_ids if no further cpus set.
 */
static inline unsigned int cpumask_next(int n, const struct cpumask *srcp)
{
        /* -1 is a legal arg here. */
        if (n != -1)
                cpumask_check(n);
        return find_next_bit(cpumask_bits(srcp), nr_cpumask_bits, n+1);
}

srcp가 가리키는 cpu 상태 관련 비트맵의 nr_cpumask_bits 까지에서 n+1 번째 비트부터 1로 설정된 cpu id(based 0)를 찾아 알아온다.

 

cpumask_next_zero()

include/linux/cpumask.h

/**
 * cpumask_next_zero - get the next unset cpu in a cpumask
 * @n: the cpu prior to the place to search (ie. return will be > @n)
 * @srcp: the cpumask pointer
 *
 * Returns >= nr_cpu_ids if no further cpus unset.
 */
static inline unsigned int cpumask_next_zero(int n, const struct cpumask *srcp)
{
        /* -1 is a legal arg here. */
        if (n != -1)
                cpumask_check(n);
        return find_next_zero_bit(cpumask_bits(srcp), nr_cpumask_bits, n+1);
}

srcp가 가리키는 cpu 상태 관련 비트맵의 nr_cpumask_bits 까지에서 n+1 번째 비트부터 0으로 설정된 cpu id(based 0)를 찾아 알아온다.

 

cpumask_next_and()

lib/cpumask.c

/**
 * cpumask_next_and - get the next cpu in *src1p & *src2p
 * @n: the cpu prior to the place to search (ie. return will be > @n)
 * @src1p: the first cpumask pointer
 * @src2p: the second cpumask pointer
 *
 * Returns >= nr_cpu_ids if no further cpus set in both.
 */ 
int cpumask_next_and(int n, const struct cpumask *src1p,
                     const struct cpumask *src2p)
{
        while ((n = cpumask_next(n, src1p)) < nr_cpu_ids)
                if (cpumask_test_cpu(n, src2p))
                        break;
        return n;
}
EXPORT_SYMBOL(cpumask_next_and);

src1p와 src2p가 가리키는 두 개의 cpu 상태 관련 비트맵의 n+1 번째 비트부터 두 비트맵이 동시에 1로 설정된 cpu id(based 0)를 찾아 알아온다.

 

cpumask_first_and()

include/linux/cpumask.h

/**
 * cpumask_first_and - return the first cpu from *srcp1 & *srcp2
 * @src1p: the first input
 * @src2p: the second input
 *       
 * Returns >= nr_cpu_ids if no cpus set in both.  See also cpumask_next_and().
 */
#define cpumask_first_and(src1p, src2p) cpumask_next_and(-1, (src1p), (src2p))

src1p와 src2p가 가리키는 두 개의 cpu 상태 관련 비트맵의 1 번째 비트부터 두 비트맵이 동시에 1로 설정된 cpu id(based 0)를 찾아 알아온다.

 

CPU 상태

cpumask_t 타입

include/linux/cpumask.h

typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;

 

cpu 비트맵 배열

다음은 unsigned long 배열 형태의 비트맵이다.

  • cpu_all_bits[]
  • cpu_possible_bits[]
  • cpu_online_bits[]
  • cpu_present_bits[]
  • cpu_active_bits[]

다음은 위 4개의 비트맵을 가리키는 const 포인터이다.

  • *cpu_possible_mask
  • *cpu_online_mask
  • *cpu_present_mask
  • *cpu_active_mask

kernel/cpu.c

const DECLARE_BITMAP(cpu_all_bits, NR_CPUS) = CPU_BITS_ALL;
EXPORT_SYMBOL(cpu_all_bits);

#ifdef CONFIG_INIT_ALL_POSSIBLE
static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly
        = CPU_BITS_ALL;
#else
static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly;
#endif
const struct cpumask *const cpu_possible_mask = to_cpumask(cpu_possible_bits);
EXPORT_SYMBOL(cpu_possible_mask);

static DECLARE_BITMAP(cpu_online_bits, CONFIG_NR_CPUS) __read_mostly;
const struct cpumask *const cpu_online_mask = to_cpumask(cpu_online_bits);
EXPORT_SYMBOL(cpu_online_mask);

static DECLARE_BITMAP(cpu_present_bits, CONFIG_NR_CPUS) __read_mostly;
const struct cpumask *const cpu_present_mask = to_cpumask(cpu_present_bits);
EXPORT_SYMBOL(cpu_present_mask);

static DECLARE_BITMAP(cpu_active_bits, CONFIG_NR_CPUS) __read_mostly;
const struct cpumask *const cpu_active_mask = to_cpumask(cpu_active_bits);
EXPORT_SYMBOL(cpu_active_mask);

 

전역 변수

nr_cpu_ids

kernel/smp.c

/* Setup number of possible processor ids */
int nr_cpu_ids __read_mostly = NR_CPUS;
EXPORT_SYMBOL(nr_cpu_ids);
  • 초기 값은 NR_CPUS로 설정됨

 

setup_max_cpus

kernel/smp.c

/* Setup configured maximum number of CPUs to activate */
unsigned int setup_max_cpus = NR_CPUS;
EXPORT_SYMBOL(setup_max_cpus);
  • 초기 값은 NR_CPUS로 설정됨

 

커널 파라메터

“nosmp”

kernel/smp.c

static int __init nosmp(char *str)
{
        setup_max_cpus = 0;
        arch_disable_smp_support();

        return 0;
}

early_param("nosmp", nosmp);

커널 파라메터 “nosmp”가 입력되면 setup_max_cpus에 0을 대입하고 arch_disable_smp_support() 함수를 통해 smp 기능이 동작하지 않도록 한다.

  • 현재 이 커널 파라메터는 x86 시스템에만 구현되어 있다.

 

arch_disable_smp_support()

kernel/smp.c

/*
 * Setup routine for controlling SMP activation
 *
 * Command-line option of "nosmp" or "maxcpus=0" will disable SMP
 * activation entirely (the MPS table probe still happens, though).
 *
 * Command-line option of "maxcpus=<NUM>", where <NUM> is an integer
 * greater than 0, limits the maximum number of CPUs activated in
 * SMP mode to <NUM>.
 */

void __weak arch_disable_smp_support(void) { }
  • 각 아키텍처에서 정의된 함수를 사용하며 없는 경우 weak 함수로 정의된 빈 함수가 동작한다.

 

“nr_cpus”

kernel/smp.c

/* this is hard limit */
static int __init nrcpus(char *str)
{
        int nr_cpus;

        get_option(&str, &nr_cpus);
        if (nr_cpus > 0 && nr_cpus < nr_cpu_ids)
                nr_cpu_ids = nr_cpus;

        return 0;
}

early_param("nr_cpus", nrcpus);

커널 파라메터 “nr_cpus”로 입력 받은 cpu 수를 nr_cpu_ids에 대입한다.  대입 가능 범위는 1~nr_cpus 만큼이다.

  • 예) “nr_cpus=2”

 

get_option()

lib/cmdline.c

/**
 *      get_option - Parse integer from an option string 
 *      @str: option string
 *      @pint: (output) integer value parsed from @str
 *
 *      Read an int from an option string; if available accept a subsequent
 *      comma as well.
 *
 *      Return values:
 *      0 - no int in string
 *      1 - int found, no subsequent comma
 *      2 - int found including a subsequent comma
 *      3 - hyphen found to denote a range
 */

int get_option(char **str, int *pint)
{
        char *cur = *str;

        if (!cur || !(*cur))
                return 0;
        *pint = simple_strtol(cur, str, 0);
        if (cur == *str)
                return 0;
        if (**str == ',') {
                (*str)++; 
                return 2;
        }
        if (**str == '-')
                return 3;
        
        return 1;
}
EXPORT_SYMBOL(get_option);

str 문자열을 정수로 변환하여 pint에 저장하고 리턴 되는 값은 다음과 같다.

  • 정수 변환이 되지 않는 경우 0
  • 정수 변환 후, 옵션 문자열이 없는 경우 1
  • 정수 변환 후, 옵션 문자열이 ‘,’인 경우 2
  • 정수 변환 후, 옵션 문자열이 ‘-‘인 경우 3

 

  • if (!cur || !(*cur)) return 0;
    • cur가 null이거나 cur 문자열이 “”인 경우 0을 리턴한다.
  • *pint = simple_strtol(cur, str, 0);
    • cur를 파싱하고 숫자로 변환하여 pint에 저장한다. 변환된 문자열의 다음 문자를 가리키는 주소를 출력 인수 str에 저장한다.
  • if (cur == *str) return 0;
    • 문자열을 숫자로 변환되지 않는 경우 0을 리턴한다.
  • if (**str == ‘,’) { (*str)++; return 2; }
    • 변환 후 다음 문자가 ‘,’인 경우 str을 증가시키고 2를 리턴한다.
  • if (**str == ‘-‘) return 3;
    • 변환 후 다음 문자가 ‘-‘인 경우 str을 증가시키고 3을 리턴한다.
  • 변환 후 문자가 ‘,’ 또는 ‘-‘가 아닌 경우 1을 리턴한다.

 

“maxcpus”

kernel/smp.c

static int __init maxcpus(char *str)
{
        get_option(&str, &setup_max_cpus);
        if (setup_max_cpus == 0)
                arch_disable_smp_support();

        return 0;
}

early_param("maxcpus", maxcpus);

커널 파라메터 “maxcpus”로 입력 받은 값을 setup_max_cpus에 대입한다.

  • 만일 0이 입력되면 smp 기능이 동작하지 않게 한다.
    • 현재 0 값의 허용은 x86 시스템에만 구현되어 있다.
  • 예) “maxcpus=2”

 


싱글 비트 설정된 cpumask

get_cpu_mask()

include/linux/cpumask.h

static inline const struct cpumask *get_cpu_mask(unsigned int cpu)
{
        const unsigned long *p = cpu_bit_bitmap[1 + cpu % BITS_PER_LONG];
        p -= cpu / BITS_PER_LONG;
        return to_cpumask(p);
}

@cpu에 해당하는 싱글비트 설정된 cpumask 비트맵을 반환한다.

  • NR_CPUS가 어떤 값이든 요청 cpu에 대해 싱글 비트가 설정된 cpumask를 만들어내기위해 컴파일 타임에 특수한 형태의 static 배열 cpu_bit_bimap[][]을 만든다.
  • 예) NR_CPUS=192일 때,각 cpu에 대해 반환되는 cpumask는
    • get_cpu_mask(0) = 0x0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000
    • get_cpu_mask(1) = 0x0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0001
    • get_cpu_mask(4) = 0x0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0008
    • get_cpu_mask(65) = 0x0000_0000_0000_0000_0000_0000_0000_0001_0000_0000_0000_0000
    • get_cpu_mask(99) = 0x0000_0000_0000_0000_0000_0001_0000_0000_0000_0000_0000_0000
    • get_cpu_mask(129) = 0x0000_0000_0000_0001_0000_0000_0000_0000_0000_0000_0000_0000
    • get_cpu_mask(192) = 0x8000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000

 

cpu_bit_bitmap 배열

kernel/cpu.c

/*
 * Special-case data structure for "single bit set only" constant CPU masks.
 *
 * We pre-generate all the 64 (or 32) possible bit positions, with enough
 * padding to the left and the right, and return the constant pointer
 * appropriately offset.
 */
const unsigned long cpu_bit_bitmap[BITS_PER_LONG+1][BITS_TO_LONGS(NR_CPUS)] = {

        MASK_DECLARE_8(0),      MASK_DECLARE_8(8),
        MASK_DECLARE_8(16),     MASK_DECLARE_8(24),
#if BITS_PER_LONG > 32
        MASK_DECLARE_8(32),     MASK_DECLARE_8(40),
        MASK_DECLARE_8(48),     MASK_DECLARE_8(56),
#endif
};
EXPORT_SYMBOL_GPL(cpu_bit_bitmap);

get_cpu_mask() 함수에서 사용하기 위한 특수한 형태의 싱글 비트 설정된 cpumask를 컴파일 타임에 준비한다.

예) NR_CPUS=192인 경우 long 값이 64비트를 처리할 수 있으므로, 192개의 cpu를 처리하려면 3개의 배열을 사용해야 한다.

  • cpu_bit_bitmap[64+1][3] ={
    • {0x0, 0x0, 0x0},
    • {0x1, 0x0, 0x0},
    • {0x4, 0x0, 0x0},
    • {0x8, 0x0, 0x0},
    • {0x10, 0x0, 0x0},
    • {0x20, 0x0, 0x0},
    • {0x40, 0x0, 0x0},
    • {0x80, 0x0, 0x0},
    • {0x100, 0x0, 0x0},
    • {0x200, 0x0, 0x0},
    • {0x400, 0x0, 0x0},
    • {0x800, 0x0, 0x0},
    • {0x1000, 0x0, 0x0},
    • {0x2000, 0x0, 0x0},
    • {0x4000, 0x0, 0x0},
    • {0x8000, 0x0, 0x0},
    • {0x8000_0000_0000_0000, 0x0, 0x0}

 

unsigned logn cpu_bit_bitmap[NR_CPUS+1];과 같은 형태로 선언하고 배열내의 각 cpumask 값을 런타임에 준비할 수도 있지만, NR_CPUS 값이 클 때 메모리가 많이 낭비되며, 런타임에 별도의 초기화를 수행해야 하는 단점이 있다. 따라서 이 루틴을 설계한 개발자는 특수한 형태의 배열을 컴파일 타임에 준비해두고 이를 교묘하게 활용하여 get_cpu_mask() 함수를 통해 요청한 cpu에 대한 싱글 비트가 설정된 값을 담은 cpumask를 반환할 수 있게 설계하였다.

 

NR_CPUS=192일 때 little 엔디안 cpu을 사용하는 시스템에서 cpu_bit_bitmap을 덤프하면 다음과 같다.

  • cpu=5일 때 오렌지색에 해당하는 cpumask를 반환한다.
    • =0x10
  • cpu=73일 때 파란색에 해당하는 cpumask를 반환한다.(unsigned long 단위를 cpu/64만큼 뒤로 이동한 포인터를 반환한다.)
    • =0x1_00_0000_0000_0000_0000
  • cpu=192일 때 초록색에 해당하는 cpumask를 반환한다.
    • =0x80_0000_0000_0000_0000_0000_0000_0000_0000
0x00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[0]
0x01 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[1]
0x02 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[2]
0x04 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[3]
0x08 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[4]
0x10 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[5]
0x20 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[6]
0x40 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[7]  
0x80 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[8]  
0x00 01 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[9]  
0x00 02 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[10]  
0x00 04 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[11]  
0x00 08 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[12]  
0x00 10 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[13]  
0x00 20 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[14]  
0x00 40 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[15]  
0x00 80 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[16]
(...생략...)
0x00 00 00 00 00 00 00 40 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[63]
0x00 00 00 00 00 00 00 80 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[64]

 

NR_CPUS=192일 때 big 엔디안 cpu를 사용하는 시스템에서 cpu_bit_bitmap을 덤프하면 다음과 같다.

  • cpu=5일 때 오렌지색에 해당하는 cpumask를 반환한다.
    • =0x10
  • cpu=73일 때 파란색에 해당하는 cpumask를 반환한다.
    • =0x1_00_0000_0000_0000_0000
  • cpu=192일 때 초록색에 해당하는 cpumask를 반환한다.
    • =0x80_0000_0000_0000_0000_0000_0000_0000_0000
0x00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[0]
0x00 00 00 00 00 00 00 01 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[1] 
0x00 00 00 00 00 00 00 02 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[2] 
0x00 00 00 00 00 00 00 04 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[3] 
0x00 00 00 00 00 00 00 08 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[4] 
0x00 00 00 00 00 00 00 10 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[5] 
0x00 00 00 00 00 00 00 20 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[6] 
0x00 00 00 00 00 00 00 40 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[7] 
0x00 00 00 00 00 00 00 80 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[8] 
0x00 00 00 00 00 00 01 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[9] 
0x00 00 00 00 00 00 02 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[10] 
0x00 00 00 00 00 00 04 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[11] 
0x00 00 00 00 00 00 08 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[12] 
0x00 00 00 00 00 00 10 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[13] 
0x00 00 00 00 00 00 20 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[14] 
0x00 00 00 00 00 00 40 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[15] 
0x00 00 00 00 00 00 80 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[16] 
(...생략...)
0x40 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[63]
0x80 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 : cpu_bit_bitmap[64]

 

MASK_DECLARE_x 매크로

kernel/cpu.c

/*
 * cpu_bit_bitmap[] is a special, "compressed" data structure that
 * represents all NR_CPUS bits binary values of 1<<nr.
 *
 * It is used by cpumask_of() to get a constant address to a CPU
 * mask value that has a single bit set only.
 */

/* cpu_bit_bitmap[0] is empty - so we can back into it */
#define MASK_DECLARE_1(x)       [x+1][0] = (1UL << (x))
#define MASK_DECLARE_2(x)       MASK_DECLARE_1(x), MASK_DECLARE_1(x+1)
#define MASK_DECLARE_4(x)       MASK_DECLARE_2(x), MASK_DECLARE_2(x+2)
#define MASK_DECLARE_8(x)       MASK_DECLARE_4(x), MASK_DECLARE_4(x+4)

MASK_DECLARE_8(x)은 다음과 같이 변화한다.

  • [x+1][0] = 1UL << x,
  • [x+1+1][0] = 1UL << x+1,
  • [x+2+1][0] = 1UL << x+2,
  • [x+3+1][0] = 1UL << x+3,
  • [x+4+1][0] = 1UL << x+4,
  • [x+5+1][0] = 1UL << x+5,
  • [x+6+1][0] = 1UL << x+6,
  • [x+7+1][0] = 1UL << x+7,

 

참고

NODE 비트맵 (API)

<kernel v5.0>

NODE 비트맵 (API)

노드 관리용 자료 구조

node_data

pglist_data 구조체를 통해 노드 관리를 수행한다. 디폴트로 4개의 멀티 노드를 사용한다.

arch/arm64/mm/numa.c

struct pglist_data *node_data[MAX_NUMNODES] __read_mostly;
EXPORT_SYMBOL(node_data);

 

노드 비트맵

arch/arm64/mm/numa.c

nodemask_t numa_nodes_parsed __initdata;

NUMA 시스템에서 사용할 노드 비트맵이다.

 

include/linux/nodemask.h

#define node_online_map         node_states[N_ONLINE]
#define node_possible_map       node_states[N_POSSIBLE]

온라인 노드 비트맵과 possible 노드 비트맵이다.

  • NUMA 노드가 발견되면 node_set_online(nid) 함수를 통해 node_states[N_ONLINE] 비트맵에 해당 노드를 설정한다.

 

노드 상태

enum node_states

include/linux/nodemask.h

/*
 * Bitmasks that are kept for all the nodes.
 */
enum node_states {
        N_POSSIBLE,             /* The node could become online at some point */
        N_ONLINE,               /* The node is online */
        N_NORMAL_MEMORY,        /* The node has regular memory */
#ifdef CONFIG_HIGHMEM
        N_HIGH_MEMORY,          /* The node has regular or high memory */
#else
        N_HIGH_MEMORY = N_NORMAL_MEMORY,
#endif
        N_MEMORY,               /* The node has memory(regular, high, movable) */
        N_CPU,          /* The node has one or more cpus */
        NR_NODE_STATES
};

노드의 상태 인덱스 값으로 사용한다.

 

nodemask_t 타입

include/linux/nodemask.h

typedef struct { DECLARE_BITMAP(bits, MAX_NUMNODES); } nodemask_t;

 

node_states[] 배열

mm/page_alloc.c

/*
 * Array of node states.
 */
nodemask_t node_states[NR_NODE_STATES] __read_mostly = {
        [N_POSSIBLE] = NODE_MASK_ALL,
        [N_ONLINE] = { { [0] = 1UL } },
#ifndef CONFIG_NUMA
        [N_NORMAL_MEMORY] = { { [0] = 1UL } },
#ifdef CONFIG_HIGHMEM
        [N_HIGH_MEMORY] = { { [0] = 1UL } },
#endif
#ifdef CONFIG_MOVABLE_NODE
        [N_MEMORY] = { { [0] = 1UL } },
#endif
        [N_CPU] = { { [0] = 1UL } },
#endif  /* NUMA */
};
EXPORT_SYMBOL(node_states);

 

cpu_to_node_map[]

다음은 cpu -> node 관계를 매핑하는 배열이다.

arch/arm64/mm/numa.c

static int cpu_to_node_map[NR_CPUS] = { [0 ... NR_CPUS-1] = NUMA_NO_NODE };

 

numa_distance[]

다음은 동적으로 노드 간 거리(distance)를 저장한 numa_distance[] 배열을 만들어 관리한다.

  • 배열의 크기는 numa_distance_cnt * numa_distance_cnt를 사용한다.

arch/arm64/mm/numa.c

static int numa_distance_cnt;
static u8 *numa_distance;

 


노드 관련 API들

노드 관련 순회(iterator)

for_each_node()

include/linux/nodemask.h

#define for_each_node(node)        for_each_node_state(node, N_POSSIBLE)
  • node_states[N_POSSIBLE] 노드 비트맵에서 각 비트가 1로 기록된 노드만큼 루프를 돈다.

 

for_each_online_node()

include/linux/nodemask.h

#define for_each_online_node(node) for_each_node_state(node, N_ONLINE)
  • node_states[N_ONLINE] 노드 비트맵에서 각 비트가 1로 기록된 노드만큼 루프를 돈다.

 

for_each_node_state()

include/linux/nodemask.h

#define for_each_node_state(__node, __state) \
        for_each_node_mask((__node), node_states[__state])
  • node_states[__state] 노드 비트맵에서 각 비트가 1로 기록된 노드만큼 루프를 돈다.

 

for_each_node_mask()
#if MAX_NUMNODES > 1
#define for_each_node_mask(node, mask)                  \
        for ((node) = first_node(mask);                 \
                (node) < MAX_NUMNODES;                  \
                (node) = next_node((node), (mask)))
#else /* MAX_NUMNODES == 1 */
#define for_each_node_mask(node, mask)                  \
        if (!nodes_empty(mask))                         \
                for ((node) = 0; (node) < 1; (node)++)
#endif /* MAX_NUMNODES */

 

  • NUMA 시스템에서는 전체 노드 중 mask 노드 비트맵에서 1로 기록된 노드만큼 루프를 돈다.
  • UMA 시스템에서는 node 번호 0에 대해 1번만 for 루프를 수행한다.

 

상태 관련 함수

  • static inline int node_state(int node, enum node_states state)
    • node_states[state] 비트맵에서 node 번째 비트 유무를 리턴한다.
  • static inline void node_set_state(int node, enum node_states state)
    • node_states[state] 비트맵에 node 번째 비트를 1로 설정한다.
  • static inline void node_clear_state(int node, enum node_states state)
    • node_states[state] 비트맵에 node 번째 비트를 0으로 클리어한다.
  • static inline int num_node_state(enum node_states state)
    • node_states[state] 비트맵에서 1로 설정된 비트의 수를 알아온다.

 

first_node()

include/linux/nodemask.h

#define first_node(src) __first_node(&(src))
static inline int __first_node(const nodemask_t *srcp)
{
        return min_t(int, MAX_NUMNODES, find_first_bit(srcp->bits, MAX_NUMNODES));
}

노드를 나타내는 src 비트맵에서 첫 번째 노드 번호(based 0)를 알아온다. 알아온 노드 번호는 MAX_NUMNODES를 초과하지 않도록 제한되다.

 

next_node()

include/linux/nodemask.h

#define next_node(n, src) __next_node((n), &(src))
static inline int __next_node(int n, const nodemask_t *srcp)
{
        return min_t(int,MAX_NUMNODES,find_next_bit(srcp->bits, MAX_NUMNODES, n+1));
}

노드를 나타내는 src 비트맵에서 n+1 번째 부터 비트를 검색하여 1이 발견되는 위치의 노드 번호를 알아온다. 알아온 노드 번호는 MAX_NUMNODES를 초과하지 않도록 제한된다.

 

nodes_empty()

include/linux/nodemask.h

#define nodes_empty(src) __nodes_empty(&(src), MAX_NUMNODES)
static inline int __nodes_empty(const nodemask_t *srcp, unsigned int nbits)
{
        return bitmap_empty(srcp->bits, nbits);
}
  • src 노드 비트맵이 비어 있는지 여부를 알아온다.

 

find_next_best_node()

mm/page_alloc.c

/**
 * find_next_best_node - find the next node that should appear in a given node's fallback list
 * @node: node whose fallback list we're appending
 * @used_node_mask: nodemask_t of already used nodes
 *
 * We use a number of factors to determine which is the next node that should
 * appear on a given node's fallback list.  The node should not have appeared
 * already in @node's fallback list, and it should be the next closest node
 * according to the distance array (which contains arbitrary distance values
 * from each node to each node in the system), and should also prefer nodes
 * with no CPUs, since presumably they'll have very little allocation pressure
 * on them otherwise.
 * It returns -1 if no node is found.
 */
static int find_next_best_node(int node, nodemask_t *used_node_mask)
{
        int n, val;
        int min_val = INT_MAX;
        int best_node = NUMA_NO_NODE;
        const struct cpumask *tmp = cpumask_of_node(0);

        /* Use the local node if we haven't already */
        if (!node_isset(node, *used_node_mask)) {
                node_set(node, *used_node_mask);
                return node;
        }

        for_each_node_state(n, N_MEMORY) {

                /* Don't want a node to appear more than once */
                if (node_isset(n, *used_node_mask))
                        continue;

                /* Use the distance array to find the distance */
                val = node_distance(node, n);

                /* Penalize nodes under us ("prefer the next node") */
                val += (n < node);

                /* Give preference to headless and unused nodes */
                tmp = cpumask_of_node(n);
                if (!cpumask_empty(tmp))
                        val += PENALTY_FOR_NODE_WITH_CPUS;

                /* Slight preference for less loaded node */
                val *= (MAX_NODE_LOAD*MAX_NUMNODES);
                val += node_load[n];

                if (val < min_val) {
                        min_val = val;
                        best_node = n;
                }
        }

        if (best_node >= 0)
                node_set(best_node, *used_node_mask);

        return best_node;
}

사용된 노드를 제외하고 현재 노드의 가장 인접한 다음 노드 id를 찾아 리턴한다.

  • const struct cpumask *tmp = cpumask_of_node(0);
    • 0번 노드에 대응하는 cpumask
  • if (!node_isset(node, *used_node_mask)) { node_set(node, *used_node_mask); return node; }
    • 요청 노드가 used_node_mask 비트맵에 포함되어 있지 않으면 used_node_mask 비트맵에 노드에 대응하는 비트를 설정하고 노드 id를 리턴한다.
  • for_each_node_state(n, N_MEMORY) {
    • N_MEMORY 노드 상태 비트맵에 대해 루프를 돈다.
  • if (node_isset(n, *used_node_mask)) continue;
    • 이미 사용 중으로 표기된 노드인 경우 다음 노드를 진행한다.
  • val = node_distance(node, n);
    • 노드간 거리값을 알아온다.
      • NUMA가 아닌 경우 같은 노드인 경우 10, 아닌 경우 20이다.
      • NUMA인 경우 노드 설계마다 다르며 노드 간에 속도가 빠를 수록 수치가 작다. (인접 노드)
  • val += (n < node);
    • node 번호보다 작은 노드에 대해 모두 거리를 합산한다.
  • tmp = cpumask_of_node(n);
    • n번 노드에 대한 cpu 비트맵 주소를 알아온다.
  • if (!cpumask_empty(tmp)) val += PENALTY_FOR_NODE_WITH_CPUS;
    • tmp가 가리키는 비트맵이 비어있는 경우 1을 더한다.
  • val *= (MAX_NODE_LOAD*MAX_NUMNODES);
    • MAX_NODE_LOAD
      • nr_online_nodes
  • val += node_load[n];
    • node_load[n]을 추가한다.
  • if (val < min_val) { min_val = val; best_node = n; }
    • 가장 작은 값인 경우 min_val과 best_node를 갱신한다.
  • if (best_node >= 0) node_set(best_node, *used_node_mask);
    • 노드가 선택되었으면 used_node_mask가 가리키는 비트맵에 해당 노드 비트를 설정한다.

 

cpumask_of_node()

arch/arm64/include/asm/numa.h

/* Returns a pointer to the cpumask of CPUs on Node 'node'. */
static inline const struct cpumask *cpumask_of_node(int node)
{
        return node_to_cpumask_map[node];
}

node_to_cpumask_map[] 배열에서 노드 id에 대응하는 cpu 비트맵을 가리키는 cpumask를 알아온다.

 

참고

smp_prepare_boot_cpu()

<kernel v5.0>

smp_prepare_boot_cpu() – ARM32

arch/arm/kernel/smp.c

/*
 * Mark the boot cpu "online" so that it can call console drivers in
 * printk() and can access its per-cpu storage.
 */
void __init smp_prepare_boot_cpu(void)
{
        set_my_cpu_offset(per_cpu_offset(smp_processor_id()));
}

smp 시스템에서 부트 cpu에 대한 사전 준비를 수행한다.

  • TPIDRPRW 레지스터를 사용하여 현재 cpu에 대한 per-cpu offset 값을 저장하여 per-cpu 변수에 대한 빠른 access를 가능하게 한다.

 

smp_prepare_boot_cpu() – ARM64

arch/arm64/kernel/smp.c

void __init smp_prepare_boot_cpu(void)
{
        set_my_cpu_offset(per_cpu_offset(smp_processor_id()));
        /*
         * Initialise the static keys early as they may be enabled by the
         * cpufeature code.
         */
        jump_label_init();
        cpuinfo_store_boot_cpu();
}

smp 시스템에서 부트 cpu에 대한 사전 준비를 수행한다.

  • 코드 라인 3에서 per-cpu를 사용하기 전에 부트 cpu에 대한 offset 값을 tpidr에 기록한다.
  • 코드 라인 8에서 static 키를 사용하기 위해 jump 라벨 엔트리들에 대한 초기화를 수행한다.
  • 코드 라인 9에서 부트 cpu에 대한 arm64 cpu 정보를 읽어온다.

 


ARM64 CPU 정보

cpuinfo_store_boot_cpu() – ARM64

arch/arm64/kernel/cpuinfo.c

void __init cpuinfo_store_boot_cpu(void)
{
        struct cpuinfo_arm64 *info = &per_cpu(cpu_data, 0);
        __cpuinfo_store_cpu(info);

        boot_cpu_data = *info;
        init_cpu_features(&boot_cpu_data);
}

부트업 cpu에 대해 각종 시스템 레지스터 값들을 읽어 cpuinfo_arm64에 저장하고, cpu features들을 초기화한다.

 

__cpuinfo_store_cpu()

arch/arm64/kernel/cpuinfo.c

static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info)
{
        info->reg_cntfrq = arch_timer_get_cntfrq();
        /*
         * Use the effective value of the CTR_EL0 than the raw value
         * exposed by the CPU. CTR_E0.IDC field value must be interpreted
         * with the CLIDR_EL1 fields to avoid triggering false warnings
         * when there is a mismatch across the CPUs. Keep track of the
         * effective value of the CTR_EL0 in our internal records for
         * acurate sanity check and feature enablement.
         */
        info->reg_ctr = read_cpuid_effective_cachetype();
        info->reg_dczid = read_cpuid(DCZID_EL0);
        info->reg_midr = read_cpuid_id();
        info->reg_revidr = read_cpuid(REVIDR_EL1);

        info->reg_id_aa64dfr0 = read_cpuid(ID_AA64DFR0_EL1);
        info->reg_id_aa64dfr1 = read_cpuid(ID_AA64DFR1_EL1);
        info->reg_id_aa64isar0 = read_cpuid(ID_AA64ISAR0_EL1);
        info->reg_id_aa64isar1 = read_cpuid(ID_AA64ISAR1_EL1);
        info->reg_id_aa64mmfr0 = read_cpuid(ID_AA64MMFR0_EL1);
        info->reg_id_aa64mmfr1 = read_cpuid(ID_AA64MMFR1_EL1);
        info->reg_id_aa64mmfr2 = read_cpuid(ID_AA64MMFR2_EL1);
        info->reg_id_aa64pfr0 = read_cpuid(ID_AA64PFR0_EL1);
        info->reg_id_aa64pfr1 = read_cpuid(ID_AA64PFR1_EL1);
        info->reg_id_aa64zfr0 = read_cpuid(ID_AA64ZFR0_EL1);

        /* Update the 32bit ID registers only if AArch32 is implemented */
        if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) {
                info->reg_id_dfr0 = read_cpuid(ID_DFR0_EL1);
                info->reg_id_isar0 = read_cpuid(ID_ISAR0_EL1);
                info->reg_id_isar1 = read_cpuid(ID_ISAR1_EL1);
                info->reg_id_isar2 = read_cpuid(ID_ISAR2_EL1);
                info->reg_id_isar3 = read_cpuid(ID_ISAR3_EL1);
                info->reg_id_isar4 = read_cpuid(ID_ISAR4_EL1);
                info->reg_id_isar5 = read_cpuid(ID_ISAR5_EL1);
                info->reg_id_mmfr0 = read_cpuid(ID_MMFR0_EL1);
                info->reg_id_mmfr1 = read_cpuid(ID_MMFR1_EL1);
                info->reg_id_mmfr2 = read_cpuid(ID_MMFR2_EL1);
                info->reg_id_mmfr3 = read_cpuid(ID_MMFR3_EL1);
                info->reg_id_pfr0 = read_cpuid(ID_PFR0_EL1);
                info->reg_id_pfr1 = read_cpuid(ID_PFR1_EL1);

                info->reg_mvfr0 = read_cpuid(MVFR0_EL1);
                info->reg_mvfr1 = read_cpuid(MVFR1_EL1);
                info->reg_mvfr2 = read_cpuid(MVFR2_EL1);
        }

        if (IS_ENABLED(CONFIG_ARM64_SVE) &&
            id_aa64pfr0_sve(info->reg_id_aa64pfr0))
                info->reg_zcr = read_zcr_features();

        cpuinfo_detect_icache_policy(info);
}

현재 cpu에 대한 각종 시스템 레지스터 값들을 읽어 cpuinfo_arm64에 저장한다.

 

read_cpuid_effective_cachetype()

arch/arm64/include/asm/cache.h

/*
 * Read the effective value of CTR_EL0.
 *
 * According to ARM ARM for ARMv8-A (ARM DDI 0487C.a),
 * section D10.2.33 "CTR_EL0, Cache Type Register" :
 *
 * CTR_EL0.IDC reports the data cache clean requirements for
 * instruction to data coherence.
 *
 *  0 - dcache clean to PoU is required unless :
 *     (CLIDR_EL1.LoC == 0) || (CLIDR_EL1.LoUIS == 0 && CLIDR_EL1.LoUU == 0)
 *  1 - dcache clean to PoU is not required for i-to-d coherence.
 *
 * This routine provides the CTR_EL0 with the IDC field updated to the
 * effective state.
 */
static inline u32 __attribute_const__ read_cpuid_effective_cachetype(void)
{
        u32 ctr = read_cpuid_cachetype();

        if (!(ctr & BIT(CTR_IDC_SHIFT))) {
                u64 clidr = read_sysreg(clidr_el1);

                if (CLIDR_LOC(clidr) == 0 ||
                    (CLIDR_LOUIS(clidr) == 0 && CLIDR_LOUU(clidr) == 0))
                        ctr |= BIT(CTR_IDC_SHIFT);
        }

        return ctr;
}

Cache 타입 레지스터 값을 읽어온다. (실제 PoU 동작이 필요 없는 경우 읽어온 idc 필드를 수정한다.)

  • CTR 레지스터의 IDC 필드가 0이면 PoU를 위해 데이터 캐시의 clean이 필요한 경우이다.  만일 CLIDR 레지스터를 읽어 LoC=0 또는 LoU=0이면 idc 비트를 반대로 1로 설정한다. 이렇게 하여 데이터 캐시의 clean이 필요 없다라고 설정한다.
  • 참고: ARM64 시스템 주요 레지스터 | 문c

 

read_cpuid_cachetype()

arch/arm64/include/asm/cputype.h

static inline u32 __attribute_const__ read_cpuid_cachetype(void)
{
        return read_cpuid(CTR_EL0);
}

Cache 타입 레지스터 값을 읽어온다.

 

cpuinfo_detect_icache_policy()

arch/arm64/kernel/cpuinfo.c

static void cpuinfo_detect_icache_policy(struct cpuinfo_arm64 *info)
{
        unsigned int cpu = smp_processor_id();
        u32 l1ip = CTR_L1IP(info->reg_ctr);

        switch (l1ip) {
        case ICACHE_POLICY_PIPT:
                break;
        case ICACHE_POLICY_VPIPT:
                set_bit(ICACHEF_VPIPT, &__icache_flags);
                break;
        default:
                /* Fallthrough */
        case ICACHE_POLICY_VIPT:
                /* Assume aliasing */
                set_bit(ICACHEF_ALIASING, &__icache_flags);
        }

        pr_info("Detected %s I-cache on CPU%d\n", icache_policy_str[l1ip], cpu);
}

인스트럭션 캐시 타입을 알아와서 반환한다. 캐시 타입은 PIPT, VPIPT 또는 VIPT 중 하나이다.

 

다음은 Cortext A-72 코어를 가진 ARM64 시스템에서 명령 캐시 타입을 인식하여 보여주는 로그이다.

[    0.000000] Detected VIPT I-cache on CPU0

 

참고

 

setup_nr_cpu_ids()

<kernel v5.0>

전역 변수 nr_cpu_ids에 최종 cpu 번호 + 1을 설정한다.

  • 일반적인 머신에서는 NR_CPUS값과 nr_cpu_ids 값이 동일하지만 NR_CPUS가 수천으로 설정이 되는 시스템에서는 실제 운영가능한 최고 cpu 번호를 알아내어 저장할 필요가 있다.

setup_nr_cpu_ids()

kernel/smp.c

/* 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;
}

전역 nr_cpu_ids 변수에 possible cpu id + 1 값이 담긴다.

  • nr_cpu_ids = find_last_bit(cpumask_bits(cpu_possible_mask),NR_CPUS) + 1;
    • 전역 nr_cpu_ids에 possible cpu id 중 가장 높은 cpu id + 1이 저장된다.
    • nr_cpu_ids는 아키텍처에 따라 먼저 설정되어 있을 수도 있다.

 

참고