start_kernel——setup_per_cpu_areas

原创 2015年07月07日 12:29:27

setup_per_cpu_areas是为了对内核的内存管理(mm)进行初始化而调用的函数之一。只在SMP系统中调用,UP中不执行任何操作。
setup_per_cpu_areas函数为SMP的每个处理器生成per-cpu数据。
per-cpu数据按照不同的CPU类别使用,以将性能低下引发的缓存一致性(cache coherency)问题减小到最小。per-cpu数据由各cpu独立使用,即使不锁也可访问,十分有效。
以下摘抄自:http://blog.csdn.net/yunsongice/article/details/5605239

每CPU变量主要是数据结构的数组,系统的每个CPU对应数组的一个元素。

一个CPU不应该访问与其他CPU对应的数组元素,另外,它可以随意读或修改它自己的元素而不用担心出现竞争条件,因为它是唯一有资格这么做的CPU。但是,这也意味着每CPU变量基本上只能在特殊情况下使用,也就是当它确定在系统的CPU上的数据在逻辑上是独立的时候。

每CPU的数组元素在主存中被排列以使每个数据结构存放在硬件高速缓存的不同行,因此,对每CPU数组的并发访问不会导致高速缓存行的窃用和失效(这种操作会带来昂贵的系统开销)。

虽然每CPU变量为来自不同CPU的并发访问提供保护,但对来自异步函数(中断处理程序和可延迟函数)的访问不提供保护,在这种情况下需要另外的同步技术。

此外,在单处理器和多处理器系统中,内核抢占都可能使每CPU变量产生竞争条件。总的原则是内核控制路径应该在禁用抢占的情况下访问每CPU变量。因为当一个内核控制路径获得了它的每CPU变量本地副本的地址,然后它因被抢占而转移到另外一个CPU上,但仍然引用原来CPU元素的地址,这是非常危险的。

init/main.c中的setup_per_cpu_areas调用arch/x86/kernel/setup_percpu.c

void __init setup_per_cpu_areas(void)
{
    unsigned int cpu;
    unsigned long delta;
    int rc;

    printk(KERN_EMERG "NR_CPUS:%d nr_cpumask_bits:%d nr_cpu_ids:%d nr_node_ids:%d\n",
        NR_CPUS, nr_cpumask_bits, nr_cpu_ids, nr_node_ids);

    /*
     * Allocate percpu area.  Embedding allocator is our favorite;
     * however, on NUMA configurations, it can result in very
     * sparse unit mapping and vmalloc area isn't spacious enough
     * on 32bit.  Use page in that case.
     */
#ifdef CONFIG_X86_32
    if (pcpu_chosen_fc == PCPU_FC_AUTO && pcpu_need_numa())
        pcpu_chosen_fc = PCPU_FC_PAGE;
#endif
    rc = -EINVAL;
    if (pcpu_chosen_fc != PCPU_FC_PAGE) {
        const size_t dyn_size = PERCPU_MODULE_RESERVE +
            PERCPU_DYNAMIC_RESERVE - PERCPU_FIRST_CHUNK_RESERVE;
        size_t atom_size;

        /*
         * On 64bit, use PMD_SIZE for atom_size so that embedded
         * percpu areas are aligned to PMD.  This, in the future,
         * can also allow using PMD mappings in vmalloc area.  Use
         * PAGE_SIZE on 32bit as vmalloc space is highly contended
         * and large vmalloc area allocs can easily fail.
         */
#ifdef CONFIG_X86_64
        atom_size = PMD_SIZE;
#else
        atom_size = PAGE_SIZE;
#endif
        rc = pcpu_embed_first_chunk(PERCPU_FIRST_CHUNK_RESERVE,
                        dyn_size, atom_size,
                        pcpu_cpu_distance,
                        pcpu_fc_alloc, pcpu_fc_free);
        if (rc < 0)
            pr_warning("%s allocator failed (%d), falling back to page size\n",
                   pcpu_fc_names[pcpu_chosen_fc], rc);
    }
    if (rc < 0)
        rc = pcpu_page_first_chunk(PERCPU_FIRST_CHUNK_RESERVE,
                       pcpu_fc_alloc, pcpu_fc_free,
                       pcpup_populate_pte);
    if (rc < 0)
        panic("cannot initialize percpu area (err=%d)", rc);

    /* alrighty, percpu areas up and running */
    delta = (unsigned long)pcpu_base_addr - (unsigned long)__per_cpu_start;
    for_each_possible_cpu(cpu) {
        per_cpu_offset(cpu) = delta + pcpu_unit_offsets[cpu];
        per_cpu(this_cpu_off, cpu) = per_cpu_offset(cpu);
        per_cpu(cpu_number, cpu) = cpu;
        setup_percpu_segment(cpu);
        setup_stack_canary_segment(cpu);
        /*
         * Copy data used in early init routines from the
         * initial arrays to the per cpu data areas.  These
         * arrays then become expendable and the *_early_ptr's
         * are zeroed indicating that the static arrays are
         * gone.
         */
#ifdef CONFIG_X86_LOCAL_APIC
        per_cpu(x86_cpu_to_apicid, cpu) =
            early_per_cpu_map(x86_cpu_to_apicid, cpu);
        per_cpu(x86_bios_cpu_apicid, cpu) =
            early_per_cpu_map(x86_bios_cpu_apicid, cpu);
#endif
#ifdef CONFIG_X86_32
        per_cpu(x86_cpu_to_logical_apicid, cpu) =
            early_per_cpu_map(x86_cpu_to_logical_apicid, cpu);
#endif
#ifdef CONFIG_X86_64
        per_cpu(irq_stack_ptr, cpu) =
            per_cpu(irq_stack_union.irq_stack, cpu) +
            IRQ_STACK_SIZE - 64;
#endif
#ifdef CONFIG_NUMA
        per_cpu(x86_cpu_to_node_map, cpu) =
            early_per_cpu_map(x86_cpu_to_node_map, cpu);
        /*
         * Ensure that the boot cpu numa_node is correct when the boot
         * cpu is on a node that doesn't have memory installed.
         * Also cpu_up() will call cpu_to_node() for APs when
         * MEMORY_HOTPLUG is defined, before per_cpu(numa_node) is set
         * up later with c_init aka intel_init/amd_init.
         * So set them all (boot cpu and all APs).
         */
        set_cpu_numa_node(cpu, early_cpu_to_node(cpu));
#endif
        /*
         * Up to this point, the boot CPU has been using .init.data
         * area.  Reload any changed state for the boot CPU.
         */
        if (!cpu)
            switch_to_new_gdt(cpu);
    }

    /* indicate the early static arrays will soon be gone */
#ifdef CONFIG_X86_LOCAL_APIC
    early_per_cpu_ptr(x86_cpu_to_apicid) = NULL;
    early_per_cpu_ptr(x86_bios_cpu_apicid) = NULL;
#endif
#ifdef CONFIG_X86_32
    early_per_cpu_ptr(x86_cpu_to_logical_apicid) = NULL;
#endif
#ifdef CONFIG_NUMA
    early_per_cpu_ptr(x86_cpu_to_node_map) = NULL;
#endif

    /* Setup node to cpumask map */
    setup_node_to_cpumask_map();

    /* Setup cpu initialized, callin, callout masks */
    setup_cpu_local_masks();
}

打印Log

NR_CPUS:4 nr_cpumask_bits:4 nr_cpu_ids:4 nr_node_ids:1


每CPU变量的声明和普通变量的声明一样,主要的区别是使用了attribute((section(PER_CPU_BASE_SECTION sec)))来指定该变量被放置的段中,普通变量默认会被放置data段或者bss段中。
看到这里有一个问题:如果我们只是声明了一个变量,那么如果有多个副本的呢?奥妙在于内核加载的过程。
一般情况下,ELF文件中的每一个段在内存中只会有一个副本,而.data..percpu段再加载后,又被复制了NR_CPUS次,一个每CPU变量的多个副本在内存中是不会相邻。
分配内存以及复制.data.percup内容的工作由pcpu_embed_first_chunk来完成,这里就不展开了。__per_cpu_offset数组中记录了每个CPU的percpu区域的开始地址。我们访问每CPU变量就要依靠__per_cpu_offset中的地址。“(摘录自:http://blog.csdn.net/fengtaocat/article/details/7078472)
PAGE_SIZE的大小为4K,实现代码如下:
include/asm/page_types.h

/* PAGE_SHIFT determines the page size */
#define PAGE_SHIFT  12
#define PAGE_SIZE   (_AC(1,UL) << PAGE_SHIFT)

include/uapi/linux/const.h

/* Some constant macros are used in both assembler and
 * C code.  Therefore we cannot annotate them always with
 * 'UL' and other type specifiers unilaterally.  We
 * use the following macros to deal with this.
 *
 * Similarly, _AT() will cast an expression with a type in C, but
 * leave it unchanged in asm.
 */

#ifdef __ASSEMBLY__
#define _AC(X,Y)    X
#define _AT(T,X)    X
#else   //__ASSEMBLY__ not defined
#define __AC(X,Y)   (X##Y)
#define _AC(X,Y)    __AC(X,Y)
#define _AT(T,X)    ((T)(X))
#endif

“某些常量宏会同时出现被c和asm引用,而c与asm在对立即数符号的处理上是不同的。asm中通过指令来区分其操作数是有符号还是无符号,而不是通过操作数。而c中是通过变量的属性,而不是通过操作符。c中如要指名常量有无符号,必须为常量添加后缀,而asm则通过使用不同的指令来指明。如此,当一个常量被c和asm同时包含时,必须作不同的处理。故AFLAGS中将添加一项D__ASSEMBLY__,来告知预处理器此时是asm”(http://my.oschina.net/u/930588/blog/134751)

原理已经很清楚,具体代码不再分析了。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

setup_per_cpu_areas 函数

setup_per_cpu_areas是为了对内核的内存管理(mm)进行初始化而调用的函数之一。只在SMP系统中调用,UP中不执行任何操作。  setup_per_cpu_areas函数为SMP的每个...

per_cpu_pageset 之一(init/main.c start_kernel初始化)

pcp 初始化(之一)         所谓的pcp是每cpu页框高速缓冲,由数据结构struct per_cpu_pageset描述,包含在内存域struct zone中。在内核中,系统会经常请求和...

(二)start_kernel分析二---之setup_arch()函数分析

start_kernel分析 如果以为到了c代码可以松一口气的话,就大错特措了,linux的c也不比汇编好懂多少,相反到掩盖了汇编的一些和机器相关的部分,有时候更难懂。其实作为编写操作系统的c代码,...

start_kernel->setup_arch->paging_init->memtable_init-->create_mapping

/* * Create the page directory entries and any necessary * page tables for the mapping specif...

start_kernel->setup_arch->paging_init->bootmem_init() 之 4

/* * Reserve the various regions of node 0 */ static __init void reserve_node_zero(unsigne...

start_kernel->setup_arch->paging_init->memtable_init

/* * Setup initial mappings.  We use the page we allocated for zero page to hold * the mappin...

ARM Linux启动流程分析——start_kernel前启动阶段(汇编部分)

本文整理了ARM Linxu启动流程的第二阶段——start_kernel前启动阶段(汇编部分),内核版本为3.12.35。我以手上的树莓派b(ARM11)为平台示例来分析Linux内核在自解压后到跳...

<Linux>Linux内核启动分析(二)——start_kernel

在前一篇对head.S的分析中,我们知道内核启动的第一阶段的是处理u-boot传进来的机器id,由此来判断内核是否支持这个CPU以及该单板。而head.S最后跳到start_kernel这函数中作进一...

Linux Per-CPU Data

Linux Per-CPU Data Reasons for Using Per-CPU Data There are a couple benefits to using per-CPU data....
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)