程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

linux设备树-linux内核对设备树的处理

----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------

前面几节内容我们介绍了设备树的结构,以及在linux内核中移植设备树。这一节将对linux内核源码进行介绍,分析内核对设备的处理逻辑。

一、汇编阶段

1.1 uboot引导内核启动

linux设备树-linux内核设备树移植(一)中我们介绍了uboot引导linux内核的启动流程。uboot会为内核设置启动参数,最终并跳转到内核地址、启动内核。

uboot在启动内核时会设置三个参数,这三个参数会依次赋值给寄存器r0、r1、r2:

  • r0一般设置为0;
  • r1一般设置为machine id,在使用设备树的时候,这个参数就没有意义了;
  • r2一般设置为tag的开始地址,或者设置为dtb文件的开始地址;

当我们使用了设备树的时候,我们主要关注r2参数即可。

1.2 head.S

内核启动,首先执行的是arch/arm/kernel/head.S处的汇编代码,内核head.S主要做了以下工作:

  • 执行子程序__lookup_processor_type,使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息);
  • 执行子程序__vet_atags,判断是否存在可用的tag或dtb;
  • 执行子程序__create_page_tables:创建页表, 即创建虚拟地址和物理地址的映射关系;
  • 执行子程序__enable_mmu:使能MMU, 以后就要使用虚拟地址了;
  • 执行子程序__mmap_switched:__enable_mmu内部会调转到这里执行:

__mmap_switched子程序定义在是arch/arm/kernel/head-common.S文件中,该段子程序会调用C函数start_kernel。

以上的内核大概了解一下即可,这里不去分析具体源码了。head.S和head-common.S最终效果:

  • 把uboot传来的r1值,赋给了C变量 __machine_arch_type;
  • 把uboot传来的r2值,赋给了C变量__atags_pointer;
  • 然后执行C函数start_kernel。

二、内核启动

start_kernel函数位于init/main.c文件:

asmlinkage __visible void __init start_kernel(void)
{
        char *command_line;
        char *after_dashes;

        set_task_stack_end_magic(&init_task);
        smp_setup_processor_id();
        debug_objects_early_init();

        cgroup_init_early();

        local_irq_disable();
        early_boot_irqs_disabled = true;

        /*
         * Interrupts are still disabled. Do necessary setups, then
         * enable them.
         */
        boot_cpu_init();
        page_address_init();
        pr_notice("%s", linux_banner);
        setup_arch(&command_line);
        mm_init_cpumask(&init_mm);
        setup_command_line(command_line);
        setup_nr_cpu_ids();
        setup_per_cpu_areas();
        smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
        boot_cpu_hotplug_init();

        build_all_zonelists(NULL);
        page_alloc_init();

        pr_notice("Kernel command line: %s\n", boot_command_line);
        /* parameters may set static keys */
        jump_label_init();
        parse_early_param();
        after_dashes = parse_args("Booting kernel",
                                  static_command_line, __start___param,
                                  __stop___param - __start___param,
                                  -1, -1, NULL, &unknown_bootoption);
        if (!IS_ERR_OR_NULL(after_dashes))
                parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
                           NULL, set_init_arg);

        /*
         * These use large bootmem allocations and must precede
         * kmem_cache_init()
         */
        setup_log_buf(0);
        vfs_caches_init_early();
        sort_main_extable();
        trap_init();
        mm_init();

        ftrace_init();

        /* trace_printk can be enabled here */
        early_trace_init();

        /*
         * Set up the scheduler prior starting any interrupts (such as the
         * timer interrupt). Full topology setup happens at smp_init()
         * time - but meanwhile we still have a functioning scheduler.
         */
        sched_init();
        /*
         * Disable preemption - early bootup scheduling is extremely
         * fragile until we cpu_idle() for the first time.
         */
        preempt_disable();
        if (WARN(!irqs_disabled(),
                 "Interrupts were enabled *very* early, fixing it\n"))
                local_irq_disable();
        radix_tree_init();

        /*
         * Set up housekeeping before setting up workqueues to allow the unbound
         * workqueue to take non-housekeeping into account.
         */
        housekeeping_init();

        /*
         * Allow workqueue creation and work item queueing/cancelling
         * early.  Work item execution depends on kthreads and starts after
         * workqueue_init().
         */
        workqueue_init_early();

        rcu_init();

        /* Trace events are available after this */
        trace_init();

        if (initcall_debug)
                initcall_debug_enable();

        context_tracking_init();
        /* init some links before init_ISA_irqs() */
        early_irq_init();
        init_IRQ();
        tick_init();
        rcu_init_nohz();
        init_timers();
        hrtimers_init();
        softirq_init();
        timekeeping_init();

        /*
         * For best initial stack canary entropy, prepare it after:
         * - setup_arch() for any UEFI RNG entropy and boot cmdline access
         * - timekeeping_init() for ktime entropy used in rand_initialize()
         * - rand_initialize() to get any arch-specific entropy like RDRAND
         * - add_latent_entropy() to get any latent entropy
         * - adding command line entropy
         */
        rand_initialize();
        add_latent_entropy();
        add_device_randomness(command_line, strlen(command_line));
        boot_init_stack_canary();

        time_init();
        printk_safe_init();
        perf_event_init();
        profile_init();
        call_function_init();
        WARN(!irqs_disabled(), "Interrupts were enabled early\n");

        early_boot_irqs_disabled = false;
        local_irq_enable();

        kmem_cache_init_late();

        /*
         * HACK ALERT! This is early. We're enabling the console before
         * we've done PCI setups etc, and console_init() must be aware of
         * this. But we do want output early, in case something goes wrong.
         */
        console_init();
        if (panic_later)
                panic("Too many boot %s vars at `%s'", panic_later,
                      panic_param);

        lockdep_init();

        /*
         * Need to run this when irqs are enabled, because it wants
         * to self-test [hard/soft]-irqs on/off lock inversion bugs
         * too:
         */
        locking_selftest();

        /*
         * This needs to be called before any devices perform DMA
         * operations that might use the SWIOTLB bounce buffers. It will
         * mark the bounce buffers as decrypted so that their usage will
         * not cause "plain-text" data to be decrypted when accessed.
         */
        mem_encrypt_init();

#ifdef CONFIG_BLK_DEV_INITRD
        if (initrd_start && !initrd_below_start_ok &&
            page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
                pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
                    page_to_pfn(virt_to_page((void *)initrd_start)),
                    min_low_pfn);
                initrd_start = 0;
        }
#endif
        kmemleak_init();
        setup_per_cpu_pageset();
        numa_policy_init();
        acpi_early_init();
        if (late_time_init)
                late_time_init();
        sched_clock_init();
        calibrate_delay();
        pid_idr_init();
        anon_vma_init();
#ifdef CONFIG_X86
        if (efi_enabled(EFI_RUNTIME_SERVICES))
                efi_enter_virtual_mode();
#endif
        thread_stack_cache_init();
        cred_init();
        fork_init();
        proc_caches_init();
        uts_ns_init();
        buffer_init();
        key_init();
        security_init();
        dbg_late_init();
        vfs_caches_init();
        pagecache_init();
        signals_init();
        seq_file_init();
        proc_root_init();
        nsfs_init();
        cpuset_init();
        cgroup_init();
        taskstats_init_early();
        delayacct_init();

        poking_init();
        check_bugs();

        acpi_subsystem_init();
        arch_post_acpi_subsys_init();
        sfi_init_late();

        /* Do the rest non-__init'ed, we're now alive */
        arch_call_rest_init();
}
View Code

可以看出,start_kernel函数是整个Linux内核启动过程中的一个关键函数,它负责调用其他函数来逐步初始化各个子系统,最终启动用户空间。

start_kernel的调用过程如下:

2.1 setup_arch

start_kernel内部调用setup_arch函数,用于在内核启动期间对硬件进行初始化,包括设置 CPU、内存、设备树等等。该函数的实现是针对特定架构的,因此不同的架构会有不同的 setup_arch实现。在内核启动期间,setup_arch函数是被显式调用的,它的返回值是 0 或者一个错误码,用于指示初始化是否成功。

    setup_arch(&command_line);  // arch/arm/kernel/setup.c
        mdesc = setup_machine_fdt(__atags_pointer);  // arch/arm/kernel/devtree.c 
             early_init_dt_verify(phys_to_virt(dt_phys)  // 判断是否有效的dtb文件, drivers/of/fdt.c
                  initial_boot_params = params;
             mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);  // 找到最匹配的machine_desc, drivers/of/fdt.c
             early_init_dt_scan_nodes();
        machine_desc = mdesc;
    ......
2.1.1 DT_MACHINE_START

一个编译成uImage的内核镜像文件,可以支持多个单板,比如支持SMDK2440、SMDK2443、以及我们在linux设备树-linux内核设备树移植中加入的MINI2440 using devicetree。

这些板子的配置稍有不同,需要做一些单独的初始化,在内核里面,对于这些单板,都构造了一个machine_desc结构体。

比如我们新增的arch/arm/mach-s3c24xx/mach-smdk2440-dt.c文件:

static const char *const s3c2440_dt_compat[] __initconst = {
        "samsung,s3c2440",
        "samsung,mini2440",
        NULL
};

DT_MACHINE_START(S3C2440_DT, "Samsung S3C2440 (Flattened Device Tree)")
        /* Maintainer: Heiko Stuebner <heiko@sntech.de> */
        .dt_compat      = s3c2440_dt_compat,
        .map_io         = s3c2440_dt_map_io,
        .init_irq       = irqchip_init,
        .init_machine   = s3c2440_dt_machine_init,
MACHINE_END

宏DT_MACHINE_START定义在arch/arm/include/asm/mach/arch.h,如下:

#define DT_MACHINE_START(_name, _namestr)               \
static const struct machine_desc __mach_desc_##_name    \
 __used                                                 \
 __attribute__((__section__(".arch.info.init"))) = {    \
        .nr             = ~0,                           \
        .name           = _namestr,

#endif

这里attribute((section(“.arch.info.init”)))就是利用了编译器的特性,把machine_desc放到了.arch.info.init段。

2.1.2 of_flat_dt_match_machine

当我们的uboot不使用tag传参数,而使用dtb文件时,那么这时内核是如何选择对应的machine_desc呢?

在设备树文件的根节点里,有如下两行:

        model = "Samsung S3C2440 SoC";
        compatible = "samsung,s3c2440","samsung,mini2440";

这里的compatible属性声明想要什么machine_desc,属性值可以是一系列字符串,依次与machine_desc匹配。

内核最好支持samsung,s3c2440,如果不支持,再尝试是否支持samsung,mini2440。

of_flat_dt_match_machine函数就是通过遍历所有的machine_desc,找到与设备树中compatible最匹配的一个machine_desc,如果没有找到匹配的机器,则返回默认的机器指针;函数定义在drivers/of/fdt.c中;

/**
 * of_flat_dt_match_machine - Iterate match tables to find matching machine.
 *
 * @default_match: A machine specific ptr to return in case of no match.
 * @get_next_compat: callback function to return next compatible match table.
 *
 * Iterate through machine match tables to find the best match for the machine
 * compatible string in the FDT.
 */
const void * __init of_flat_dt_match_machine(const void *default_match,
                const void * (*get_next_compat)(const char * const**))
{
        const void *data = NULL;
        const void *best_data = default_match;
        const char *const *compat;
        unsigned long dt_root;
        unsigned int best_score = ~1, score = 0;

        dt_root = of_get_flat_dt_root();
        while ((data = get_next_compat(&compat))) {
                score = of_flat_dt_match(dt_root, compat);
                if (score > 0 && score < best_score) {
                        best_data = data;
                        best_score = score;
                }
        }
        if (!best_data) {
                const char *prop;
                int size;

                pr_err("\n unrecognized device tree list:\n[ ");

                prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
                if (prop) {
                        while (size > 0) {
                                printk("'%s' ", prop);
                                size -= strlen(prop) + 1;
                                prop += strlen(prop) + 1;
                        }
                }
                printk("]\n\n");
                return NULL;
        }

        pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());

        return best_data;
}

总结如下:

  • 设备树根节点的compatible属性列出了一系列的字符串,表示它兼容的单板名,从"最兼容"到次之;
  • 内核中有多个machine_desc,其中有dt_compat成员,,它指向一个字符串数组,,里面表示该machine_desc支持哪些单板;
2.1.3 early_init_dt_scan_nodes

early_init_dt_scan_nodes函数位于drivers/of/fdt.c文件:

void __init early_init_dt_scan_nodes(void)
{
        int rc = 0;

        /* Retrieve various information from the /chosen node */
        rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
        if (!rc)
                pr_warn("No chosen node found, continuing without\n");

        /* Initialize {size,address}-cells info */
        of_scan_flat_dt(early_init_dt_scan_root, NULL);

        /* Setup memory, calling early_init_dt_add_memory_arch */
        of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}

里面主要对三种类型的信息进行处理,分别是:

  • /chosen节点中bootargs属性:/chosen节点中bootargs属性就是内核启动的命令行参数,它里面可以指定根文件系统在哪里,第一个运行的应用程序是哪一个,指定内核的打印信息从哪个设备里打印出来;这里将 /chosen节点中bootargs属性的值, 存入全局变量boot_command_line
  • 根节点的 #address-cells 和 #size-cells属性:根节点的#address-cells和#size-cells属性指定属性参数的位数;存入全局变量: dt_root_addr_cells, dt_root_size_cells;
  • /memory中的reg属性:/memory中的reg属性指定了不同板子内存的大小和起始地址;
2.1.4 设备节点转换为device_node

我们先想一个问题,我们的uboot把设备树dtb文件随便放到内存的某一个地方就可以使用,为什么内核运行中,他不会去覆盖dtb所占用的那块内存呢?

在前面我们讲解设备树结构时,我们知道,在设备树文件中,可以使用/memreserve/指定一块内存,这块内存就是保留的内存,内核不会占用它。即使你没有指定这块内存,当我们内核启动时,他也会把设备树所占用的区域保留下来。

如下就是函数调用过程:

start_kernel // init/main.c
    setup_arch(&command_line);  // arch/arm/kernel/setup.c
        arm_memblock_init(mdesc);   // arch/arm/kernel/setup.c
            early_init_fdt_reserve_self();
                    /* Reserve the dtb region */
                    // 把dtb所占区域保留下来, 即调用: memblock_reserve
                    early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
                                      fdt_totalsize(initial_boot_params),
                                      0);           
            early_init_fdt_scan_reserved_mem();  // 根据dtb中的memreserve信息, 调用memblock_reserve
            
        unflatten_device_tree();    // arch/arm/kernel/setup.c
            __unflatten_device_tree(initial_boot_params, NULL, &of_root,
                        early_init_dt_alloc_memory_arch, false);            // drivers/of/fdt.c
                
                /* First pass, scan for size */
                size = unflatten_dt_nodes(blob, NULL, dad, NULL);
                
                /* Allocate memory for the expanded device tree */
                mem = dt_alloc(size + 4, __alignof__(struct device_node));
                
                /* Second pass, do actual unflattening */
                unflatten_dt_nodes(blob, mem, dad, mynodes);
                    populate_node
                        np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
                                    __alignof__(struct device_node));
                        
                        np->full_name = fn = ((char *)np) + sizeof(*np);
                        
                        populate_properties
                                pp = unflatten_dt_alloc(mem, sizeof(struct property),
                                            __alignof__(struct property));
                            
                                pp->name   = (char *)pname;
                                pp->length = sz;
                                pp->value  = (__be32 *)val;

可以看到,先把dtb中的memreserve信息告诉内核,把这块内存区域保留下来,不占用它。

然后解析dtb文件,转换成节点是device_node的树状结构,这里涉及两个结构体:

  • struct device_node:Linux内核使用device_node结构体来描述一个设备节点,此结构体定义在文件 include/linux/of.h 中;
  • struct property: Linux内核中使用结构体property表示节点属性,此结构体同样定义在文件include/linux/of.h中;

在dts文件里,每个大括号{}代表一个节点,比如根节点里有个大括号,对应一个device_node结构体。节点里面有各种属性,也可能里面还有子节点,所以它们还有一些父子关系。

根节点下的memory、chosen、uart、rtc等节点是并列关系,兄弟关系。对于父子关系、兄弟关系,在device_node结构体里有成员来描述这些关系。

2.2 arch_call_rest_init

start_kernel函数最后调用arch_call_rest_init函数,arch_call_rest_init函数是定义在init/main.c中的一个函数,用于调用没有被内核启动代码显式调用的初始化函数。

在内核启动过程中,当调用完所有的显式初始化函数后,arch_call_rest_init函数会被调用,以便调用其它的初始化函数,例如驱动程序注册、文件系统初始化、网络初始化等等。这些函数都由模块或子系统定义,并且它们的调用顺序可以在模块或子系统的 Makefile文件中进行配置。

需要注意的是,setup_arch主要负责硬件初始化,而arch_call_rest_init则主要负责软件初始化。它们的调用时机和作用不同,但都是Linux内核启动过程中不可或缺的环节。

在具体实现中,arch_call_rest_init函数会调用名为rest_init 的函数。而在 rest_init 函数中,会使用 fork系统调用创建两个新进程,分别为进程 1 和进程 2。其中:

  • 进程1会执行内核的初始化工作,比如初始化文件系统、内存管理等子系统;
  • 进程2则会负责启动 idle 进程并运行调度器;

在内核初始化完毕后,进程 1 会退出,而进程 2 则会成为系统中唯一的用户进程。

rest_init 函数位于init/main.c文件:

noinline void __ref rest_init(void)
{
        struct task_struct *tsk;
        int pid;

        rcu_scheduler_starting();
        /*
         * We need to spawn init first so that it obtains pid 1, however
         * the init task will end up wanting to create kthreads, which, if
         * we schedule it before we create kthreadd, will OOPS.
         */
        pid = kernel_thread(kernel_init, NULL, CLONE_FS);
        /*
         * Pin init on the boot CPU. Task migration is not properly working
         * until sched_init_smp() has been run. It will set the allowed
         * CPUs for init to the non isolated CPUs.
         */
        rcu_read_lock();
        tsk = find_task_by_pid_ns(pid, &init_pid_ns);
        set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
        rcu_read_unlock();

        numa_default_policy();
        pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
        rcu_read_lock();
        kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
        rcu_read_unlock();

        /*
         * Enable might_sleep() and smp_processor_id() checks.
         * They cannot be enabled earlier because with CONFIG_PREEMPT=y
         * kernel_thread() would trigger might_sleep() splats. With
         * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
         * already, but it's stuck on the kthreadd_done completion.
         */
        system_state = SYSTEM_SCHEDULING;

        complete(&kthreadd_done);

        /*
         * The boot idle thread must execute schedule()
         * at least once to get things moving:
         */
        schedule_preempt_disabled();
        /* Call into cpu_idle with preempt disabled */
        cpu_startup_entry(CPUHP_ONLINE);
}
View Code
2.2.1 kernel_init 

进程1执行的是kernel_init函数,kernel_init函数位于init/main.c文件:

static int __ref kernel_init(void *unused)
{
        int ret;

        kernel_init_freeable();
        /* need to finish all async __init code before freeing the memory */
        async_synchronize_full();
        ftrace_free_init_mem();
        free_initmem();
        mark_readonly();

        /*
         * Kernel mappings are now finalized - update the userspace page-table
         * to finalize PTI.
         */
        pti_finalize();

        system_state = SYSTEM_RUNNING;
        numa_default_policy();

        rcu_end_inkernel_boot();

        if (ramdisk_execute_command) {
                ret = run_init_process(ramdisk_execute_command);
                if (!ret)
                        return 0;
                pr_err("Failed to execute %s (error %d)\n",
                       ramdisk_execute_command, ret);
        }

        /*
         * We try each of these until one succeeds.
         *
         * The Bourne shell can be used instead of init if we are
         * trying to recover a really broken machine.
         */
        if (execute_command) {
                ret = run_init_process(execute_command);
                if (!ret)
                        return 0;
                panic("Requested init %s failed (error %d).",
                      execute_command, ret);
        }
        if (!try_to_run_init_process("/sbin/init") ||
            !try_to_run_init_process("/etc/init") ||
            !try_to_run_init_process("/bin/init") ||
            !try_to_run_init_process("/bin/sh"))
                return 0;

        panic("No working init found.  Try passing init= option to kernel. "
              "See Linux Documentation/admin-guide/init.rst for guidance.");
}
View Code

函数调用流程:

kernel_init
    kernel_init_freeable();
        do_basic_setup();
            do_initcalls();
                for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
                    do_initcall_level(level);  // 比如 do_initcall_level(3)
                        for (fn = initcall_levels[3]; fn < initcall_levels[3+1]; fn++)
                            do_one_initcall(initcall_from_entry(fn));  // 就是调用"arch_initcall_sync(fn)"中定义的fn函数

do_initcall_level 是 Linux 内核中负责执行注册的初始化函数的函数之一。在内核启动过程中,各个子系统需要进行初始化,而这些初始化函数是通过调用 module_init、subsys_initcall、device_initcall 等宏定义的函数实现的。

这些宏实际上是向内核注册相关的初始化函数,然后将其存放到一个特定的段中,例如 __initcall_start 到 __initcall_end。

在内核初始化期间,do_initcall_level 函数按照预设的顺序(由参数 level 指定)执行这些初始化函数:

leveltatic initcall_entry_t *initcall_levels[] __initdata = {
        __initcall0_start,
        __initcall1_start,
        __initcall2_start,
        __initcall3_start,
        __initcall4_start,
        __initcall5_start,
        __initcall6_start,
        __initcall7_start,
        __initcall_end,
};

/* Keep these in sync with initcalls in include/linux/init.h */
static const char *initcall_level_names[] __initdata = {
        "pure",
        "core",
        "postcore",
        "arch",
        "subsys",
        "fs",
        "device",
        "late",
};

static void __init do_initcall_level(int level)
{
        initcall_entry_t *fn;

        strcpy(initcall_command_line, saved_command_line);
        parse_args(initcall_level_names[level],
                   initcall_command_line, __start___param,
                   __stop___param - __start___param,
                   level, level,
                   NULL, &repair_env_string);

        trace_initcall_level(initcall_level_names[level]);
        for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
                do_one_initcall(initcall_from_entry(fn));
}
View Code

该函数会检查 __initcall_start 到 __initcall_end 这个段中的所有函数,并将标记了 __initcall 的函数按照指定的级别(level)依次执行。

每个级别都以 __initcall[0-7]_start 标记开头,并且在 System.map 文件中可以看到相应的条目,比如:

c06e687c T __initcall0_start
c06e687c t __initcall_ipc_ns_init0
c06e6880 t __initcall_init_mmap_min_addr0
c06e6884 t __initcall_net_ns_init0
c06e6888 T __initcall1_start
c06e6888 t __initcall_ptrace_break_init1
c06e688c t __initcall_s3c2442_core_init1
c06e6890 t __initcall_s3c2440_core_init1
c06e6894 t __initcall_samsung_gpiolib_init1
c06e6898 t __initcall_wq_sysfs_init1
c06e689c t __initcall_ksysfs_init1
......
c06e6940 t __initcall_of_platform_default_populate_init3s
c06e6944 T __initcall4_start
c06e6944 t __initcall_topology_init4
......

System.map 是在 Linux 内核编译过程中生成的一个文件。它是内核的一个符号表,用于记录内核二进制映像中各个符号(变量、函数等)的地址,以便调试时能够快速定位到这些符号。通过 System.map 文件,开发人员可以查询内核二进制映像中每个符号的地址,以及符号所属的模块或函数名等信息。 

2..2.2 of_platform_default_populate_init

在System.map中可以找到__initcall_of_platform_default_populate_init3s,也就是在执行do_initcall_level(3)时,会执行到of_platform_default_populate_init函数。

of_platform_default_populate_init函数定义在drivers/of/platform.c:

static int __init of_platform_default_populate_init(void)
{
        struct device_node *node;

        if (!of_have_populated_dt())
                return -ENODEV;

        /*
         * Handle certain compatibles explicitly, since we don't want to create
         * platform_devices for every node in /reserved-memory with a
         * "compatible",
         */
        for_each_matching_node(node, reserved_mem_matches)
                of_platform_device_create(node, NULL, NULL);

        node = of_find_node_by_path("/firmware");
        if (node) {
                of_platform_populate(node, NULL, NULL, NULL);
                of_node_put(node);
        }

        /* Populate everything else. */
        of_platform_default_populate(NULL, NULL, NULL);

        return 0;
}
arch_initcall_sync(of_platform_default_populate_init);
View Code

可以看到of_platform_default_populate_init初始化函数是通过arch_initcall宏定义的实现的。

在这个函数内部,它会遍历设备节点树,检查设备节点上是否存在compatible属性,如果存在,就会为这个设备节点分配一个platform_device结构体,并将这些platform_device注册到Linux内核中,以便与其它驱动程序进行匹配和绑定。

并非所有的device_node都会转换为platform_device,只有以下的device_node会转换;

(1) 该节点必须含有compatible属性;根节点是例外的,生成platfrom_device时,即使有compatible属性也不会处理。

(2) 根节点的子节点(节点必须含有compatible属性);

(3) 含有特殊compatible属性的节点的子节点(子节点必须含有compatible属性),这些特殊的compatible属性为:

"simple-bus","simple-mfd","isa","arm,amba-bus "

比如dts文件配置如下:

   / {
         mytest {
             compatible = "mytest", "simple-bus";
             mytest@0 {
                   compatible = "mytest_0";
             };
         };
         i2c {
             compatible = "samsung,i2c";
             at24c02 {
                   compatible = "at24c02";                      
             };
         };
         spi {
             compatible = "samsung,spi";              
             flash@0 {
                   compatible = "winbond,w25q32dw";
                   spi-max-frequency = <25000000>;
                   reg = <0>;
                 };
         };
     };

其中:

  • /mytest会被转换为platform_device, 因为它兼容"simple-bus",它的子节点/mytest/mytest@0 也会被转换为platform_device;
  • /i2c节点一般表示I2C控制器,,它会被转换为platform_device, 在内核中有对应的platform_driver;/i2c/at24c02节点不会被转换为platform_device,它被如何处理完全由父节点的platform_driver决定,一般是被创建为一个i2c_client;

  • 类似的也有/spi节点,,它一般也是用来表示SPI控制器,它会被转换为platform_device,在内核中有对应的platform_driver;/spi/flash@0节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个spi_device;

参考文章

[1]linux设备树学习笔记(二、linux内核对设备树的处理)(部分转载)

posted @ 2023-04-23 23:02  大奥特曼打小怪兽  阅读(743)  评论(1编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步