kernel 启动流程之 【设备驱动加载】 学习笔记
2016-10-21 18:00
645 查看
先上总流程图,一图胜千言!
点击查看大图
head.S 初始化完成后跳转到 start_kernel 入口:
START => 源码分析:
这里要干的事情非常多而复杂,如果需要全部理解整个过程的话需要强大的知识背景做支撑以及对内核有着深入的理解才行,基于学习的循序渐进的考量,这里我们重点关注device driver的启动加载流程机制.
小结:
从上面注释可以看到 start_kernel 函数干的事情非常多(这里不一一罗列了),而且几乎每一个子函数包含的内容都很复杂需要非常深厚的操作系统、计算机结构和数据结构等知识积累才能分析清楚,而这里我们感兴趣和重点关注跟我们实际工作关系最密切的设备驱动的加载过程。
rest_init 分析.
如下图:内核0号进程fork出1号跟2号进程,1号进程演变成init进程,2号进程就是kthread进程,后面内核空间所有新进程都由2号进程fork出来。
用户空间所有新进程都是1号进程的子孙:
小结:1、首先0号进程fork出1号init进程,执行设备驱动初始化和拉起第一个用户空间程序init;2、接着0号进程fork出2号内核线程(kthreadd)作为内核的守护进程;3、然后0号进程进入idle循环.
下面的 do_inicall_level 函数很有趣也比较关键, 我们知道,设备驱动入口都是类似这样写的:module_init(fn),而这个module_init宏 其实就是这里的level 6, 以此为例,内核机制和编译系统机制提供实现将这个函数指针fn加到kernel内存布局的
".init.data" section(段)地址空间中并依次增长,直到添加完所有同level的的设备入口fn到该内存段中,然后再下一个 level的操作...这里实现机制涉及到kernel内存布局相关, 需要参考vmlinux.ld.S文件分析. 所以下面代码中的initcall_levels是一个指针数组, 数组成员也是一个指针数组,数组内容是对应level的函数指针, 所以这里要干的事情就是依次取出各个level的函数指针fn执行(*fn)回调, 也就是实现了依次遍历各个level的驱动程序。
验证推测的最好办法就是加log测试,下面是kernel log输出结果,包括fn的函数名和level:
现在我们已经实现了从start_kernel到回调驱动入口代码的流程分析,还有比较关心的就是从kernel_init到具体某一个驱动的probe函数的整个代码流程是怎么样的(深入了解这个流程对于debug有实际意义),
通常做法有两种方式,一种就是继续跟代码,一步一步的看逻辑找答案,另外一种比较简单而实用的方式就是使用调试手段加dump_stack(),把call stack打印出来就清楚明了了, 我们的做法是两者结合,这样是最清楚不过的了。
先来看简单实用的方式,下面以
battery_meter_init 代码为例,看从模块入口到probe的代码路径是怎么样的:
编译-烧机-抓开机log:
从上面 backtrace看整个代码调用流程就很清晰了,所以这种方式其实是非常实用而重要的debug手段!
下面进入源码分析,验证上面的过程:
继续看平台驱动注册:
这里就是关键地方了!
驱动注册基本流程:
1、 通过平台驱动通用接口注册platform_drv_xx的callback;
2、 使用driver_find查询当前drv是否已添加到总线集合drivers_kset中,
如果已存在则返回退出;
3、 创建驱动内核对象, 并添加到内核对象层级中, 将knode_bus链接总线链表bus->p->klist_drivers
中.
4、 创建驱动属性组;
5、 注册完成, kobject_uevent发送KOBJ_ADD通知.
点击查看大图
head.S 初始化完成后跳转到 start_kernel 入口:
kernel-3.18/init/main.c:505:asmlinkage __visible void __init start_kernel(void)
START => 源码分析:
这里要干的事情非常多而复杂,如果需要全部理解整个过程的话需要强大的知识背景做支撑以及对内核有着深入的理解才行,基于学习的循序渐进的考量,这里我们重点关注device driver的启动加载流程机制.
asmlinkage __visible void __init start_kernel(void) { char *command_line; char *after_dashes; /* * Need to run as early as possible, to initialize the * lockdep hash: */ /* 有些体系结构有自己的start_kernel入口,这里保证只初始化一次 系统哈希表chainhash_table */ lockdep_init(); set_task_stack_end_magic(&init_task); /* 获取当前执行cpu的id */ smp_setup_processor_id(); /* 对象调试支持初始化 */ debug_objects_early_init(); /* * Set up the the initial canary ASAP: */ /* 初始化栈canary值,canary值用于防止栈溢出攻击,这里不太明白详细...*/ boot_init_stack_canary(); /* cgrop :将一组任务在一个或多个子系统中与一组参数关联,机制有点复杂... */ cgroup_init_early(); /* 关闭中断,因为很多初始化的工作不能被中断 */ local_irq_disable(); early_boot_irqs_disabled = true; /* * Interrupts are still disabled. Do necessary setups, then * enable them */ /* 获取当前cpu id,激活之 */ boot_cpu_init(); /* 高端内存相关,建立内核映射所需的散列表 */ page_address_init(); pr_notice("%s", linux_banner); /* 体系结构相关初始化,没种体系结构都有特有的初始化入口 */ setup_arch(&command_line); /* cpu屏蔽位清零 */ mm_init_cpumask(&init_mm); /* 将命令行参数保存到 static_command_line 中 */ setup_command_line(command_line); setup_nr_cpu_ids(); setup_per_cpu_areas(); smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */ /* 建立系统内存页区(zone)链表 */ build_all_zonelists(NULL, NULL); /* cpu热拔插相关, 这名字取得... */ page_alloc_init(); pr_notice("Kernel command line: %s\n", boot_command_line); /* 解析启动命令行参数 */ parse_early_param(); after_dashes = parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, -1, -1, &unknown_bootoption); if (!IS_ERR_OR_NULL(after_dashes)) parse_args("Setting init args", after_dashes, NULL, 0, -1, -1, set_init_arg); /* 执行跳转指令? */ jump_label_init(); /* * These use large bootmem allocations and must precede * kmem_cache_init() */ /* 设置 log输出缓冲buf */ setup_log_buf(0); /* 初始化和分配pid散列表 */ pidhash_init(); /* 创建虚拟文件系统(vfs)需要各种数据结构的缓存 */ vfs_caches_init_early(); /* 内核异常表排序 */ sort_main_extable(); /* 异常捕获设置初始化,跟体系结构相关,arm架构的实现是空函数 */ trap_init(); /* 内核内存分配器初始化,初始化slab机制分配器和vmalloc机制 */ mm_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(); /* 为 idr(一种将一个整数ID号和一个指针关联在一起的机制) 机制创建cache */ idr_init_cache(); /* rcu(read-copy-update, 内核锁机制一类)机制初始化 */ rcu_init(); /* 上下文tracking机制 ? 该机制被 CONFIG_CONTEXT_TRACKING_FORCE 包住了 */ context_tracking_init(); /* 为内核基数树算法分配内存,运用于内存页查找 */ radix_tree_init(); /* init some links before init_ISA_irqs() */ /* 初始化体系结构相关irq,创建irq描述符,插入到基数属链表 irq_desc_tree 中管理*/ early_irq_init(); init_IRQ(); /* 时钟相关初始化*/ tick_init(); rcu_init_nohz(); init_timers(); hrtimers_init(); softirq_init(); timekeeping_init(); time_init(); /* 进程调度时钟初始化 */ sched_clock_postinit(); /* cpu 性能相关monitor */ perf_event_init(); /* gdb等debug工具设置相关 */ profile_init(); /* smp下跨cpu的函数传递初始化 */ call_function_init(); WARN(!irqs_disabled(), "Interrupts were enabled early\n"); early_boot_irqs_disabled = false; /* 使能中断 */ local_irq_enable(); /* slab 分配器后期初始化 */ 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(); /* 检查异常记录信息,如果存在异常,走内核panic流程 */ if (panic_later) panic("Too many boot %s vars at `%s'", panic_later, panic_param); /* printk 输出相关信息 */ lockdep_info(); /* * 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(); /* 容器组的页面内存分配 ? */ page_cgroup_init(); page_ext_init(); /* debug 相关*/ debug_objects_mem_init(); /* 内存leak监视 */ kmemleak_init(); setup_per_cpu_pageset(); numa_policy_init(); if (late_time_init) late_time_init(); sched_clock_init(); calibrate_delay(); /* 进程pid 映射表初始化 */ pidmap_init(); anon_vma_init(); acpi_early_init(); /* 创建内核进程分配的cache */ thread_info_cache_init(); cred_init(); /* fork 机制初始化 */ fork_init(totalram_pages); /* 创建进程需要的slab缓存 */ proc_caches_init(); buffer_init(); /* 内核安全架构初始化 */ key_init(); security_init(); /* kgdb 在线调试相关支持*/ dbg_late_init(); /* vfs所需要的slab缓存 */ vfs_caches_init(totalram_pages); /* 为 sigqueue_cachep 创建slab缓存 */ signals_init(); /* rootfs populating might need page-writeback */ /* 内存页写回机制初始化 */ page_writeback_init(); /* proc 文件系统 */ proc_root_init(); cgroup_init(); cpuset_init(); taskstats_init_early(); delayacct_init(); check_bugs(); acpi_subsystem_init(); sfi_init_late(); if (efi_enabled(EFI_RUNTIME_SERVICES)) { efi_late_init(); efi_free_boot_services(); } /* trace 初始化 */ ftrace_init(); /* 创建2号内核线程kthreadd,初始化device driver,拉起1号进程init等 */ rest_init(); }
小结:
从上面注释可以看到 start_kernel 函数干的事情非常多(这里不一一罗列了),而且几乎每一个子函数包含的内容都很复杂需要非常深厚的操作系统、计算机结构和数据结构等知识积累才能分析清楚,而这里我们感兴趣和重点关注跟我们实际工作关系最密切的设备驱动的加载过程。
rest_init 分析.
static noinline void __init_refok rest_init(void) { int pid; ... // 由0号进程fork出1号进程进入kernel_init,拉起第一个用户空间程序init. kernel_thread(kernel_init, NULL, CLONE_FS); numa_default_policy(); // 创建2号内核线程. 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(); // 等待kthreadd初始化完成。 complete(&kthreadd_done); init_idle_bootup_task(current); schedule_preempt_disabled(); // 0 号进程最终成为idle运行。 cpu_startup_entry(CPUHP_ONLINE); }
如下图:内核0号进程fork出1号跟2号进程,1号进程演变成init进程,2号进程就是kthread进程,后面内核空间所有新进程都由2号进程fork出来。
用户空间所有新进程都是1号进程的子孙:
小结:1、首先0号进程fork出1号init进程,执行设备驱动初始化和拉起第一个用户空间程序init;2、接着0号进程fork出2号内核线程(kthreadd)作为内核的守护进程;3、然后0号进程进入idle循环.
static int __ref kernel_init(void *unused) { int ret; // 该函数内包含了初始化各种设备驱动程序等操作. kernel_init_freeable(); ... if (execute_command) { ret = run_init_process(execute_command); if (!ret) return 0; pr_err("Failed to execute %s (error %d). Attempting defaults...\n", execute_command, ret); } // 依次找然后执行init程序. 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; // 如果从上面路径都没有找到init程序就直接kernel panic了. panic("No working init found. Try passing init= option to kernel. " "See Linux Documentation/init.txt for guidance."); }
static noinline void __init kernel_init_freeable(void) { /* * 这里需要等kthreadd初始化完成才可以继续干活,否则会报内核 Oops. */ wait_for_completion(&kthreadd_done); ... // 进入各种基础初始化 do_basic_setup(); ... }
static void __init do_basic_setup(void) { cpuset_init_smp(); usermodehelper_init(); shmem_init(); // 驱动数据结构初始化申请内存,注册内核对象 driver_init(); init_irq_proc(); do_ctors(); usermodehelper_enable(); // 执行设备驱动入口回调 do_initcalls(); random_int_secret_init(); }
// 依次遍历各个levnel的驱动程序,level越小越先执行。 static void __init do_initcalls(void) { int level; for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) do_initcall_level(level); }
下面的 do_inicall_level 函数很有趣也比较关键, 我们知道,设备驱动入口都是类似这样写的:module_init(fn),而这个module_init宏 其实就是这里的level 6, 以此为例,内核机制和编译系统机制提供实现将这个函数指针fn加到kernel内存布局的
".init.data" section(段)地址空间中并依次增长,直到添加完所有同level的的设备入口fn到该内存段中,然后再下一个 level的操作...这里实现机制涉及到kernel内存布局相关, 需要参考vmlinux.ld.S文件分析. 所以下面代码中的initcall_levels是一个指针数组, 数组成员也是一个指针数组,数组内容是对应level的函数指针, 所以这里要干的事情就是依次取出各个level的函数指针fn执行(*fn)回调, 也就是实现了依次遍历各个level的驱动程序。
static void __init do_initcall_level(int level) { initcall_t *fn; ... /* 依次遍历各个level的驱动程序 */ for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++) { pr_err("xxx## %s, %d ,%d: %pf \n", __func__,__LINE__,level, *fn); // 可以验证打印出来看看是什么. do_one_initcall(*fn); } } // 每个数组成员也是一个指针数组,而成员的数组成员是函数指针,__initdata表明了所在的内存段位置. static initcall_t *initcall_levels[] __initdata = { __initcall0_start, __initcall1_start, __initcall2_start, __initcall3_start, __initcall4_start, __initcall5_start, __initcall6_start, __initcall7_start, __initcall_end, };
int __init_or_module do_one_initcall(initcall_t fn) { int count = preempt_count(); int ret; char msgbuf[64]; ... // 这个还用的着注释么?. ret = fn(); ... return ret; }
验证推测的最好办法就是加log测试,下面是kernel log输出结果,包括fn的函数名和level:
Line 2376: [ 0.199826] <2>.(2)[1:swapper/0]xxx## do_initcall_level, 883 ,0: init_mmap_min_addr Line 2378: [ 0.200871] <2>.(2)[1:swapper/0]xxx## do_initcall_level, 883 ,0: mtk_arch_reset_init Line 2386: [ 0.204162] <2>.(2)[1:swapper/0]xxx## do_initcall_level, 883 ,0: init_cpufreq_transition_notifier_list Line 2388: [ 0.205416] <2>.(2)[1:swapper/0]xxx## do_initcall_level, 883 ,0: net_ns_init Line 2390: [ 0.206843] <2>.(2)[1:swapper/0]xxx## do_initcall_level, 883 ,1: vfp_init Line 2398: [ 0.209921] <2>.(2)[1:swapper/0]xxx## do_initcall_level, 883 ,1: ptrace_break_init ... Line 2468: [ 0.264062] <2>.(2)[1:swapper/0]xxx## do_initcall_level, 883 ,2: atomic_pool_init Line 2472: [ 0.266331] <2>.(2)[1:swapper/0]xxx## do_initcall_level, 883 ,2: bdi_class_init ... Line 2524: [ 0.319510] <1>.(1)[1:swapper/0]xxx## do_initcall_level, 883 ,3: gate_vma_init Line 2526: [ 0.320495] <1>.(1)[1:swapper/0]xxx## do_initcall_level, 883 ,3: customize_machine ... Line 3236: [ 0.829585] <0>.(0)[1:swapper/0]xxx## do_initcall_level, 883 ,4: proc_schedstat_init Line 3238: [ 0.830644] <0>.(0)[1:swapper/0]xxx## do_initcall_level, 883 ,4: pm_sysrq_init ... Line 3522: [ 1.005405] <1>.(2)[1:swapper/0]xxx## do_initcall_level, 883 ,4: wireless_nlevent_init Line 3524: [ 1.008609] <2>.(2)[1:swapper/0]xxx## do_initcall_level, 883 ,4: activity_stats_init ... Line 4136: [ 1.542339] <3>.(3)[1:swapper/0]xxx## do_initcall_level, 883 ,6: register_pmu_driver Line 4142: [ 1.569623] <1>.(1)[1:swapper/0]xxx## do_initcall_level, 883 ,6: kallsyms_init Line 4144: [ 1.570621] <1>.(1)[1:swapper/0]xxx## do_initcall_level, 883 ,6: audit_init ... Line 4474: [ 1.763312] <1>.(1)[1:swapper/0]xxx## do_initcall_level, 883 ,6: ltr553_init Line 4482: [ 1.767457] <1>.(1)[1:swapper/0]xxx## do_initcall_level, 883 ,6: epl_sensor_init Line 4492: [ 1.771665] <1>.(1)[1:swapper/0]xxx## do_initcall_level, 883 ,6: bma222_init Line 4498: [ 1.774798] <1>.(1)[1:swapper/0]xxx## do_initcall_level, 883 ,6: akm09911_init Line 4500: [ 1.776511] <1>.(1)[1:swapper/0]xxx## do_initcall_level, 883 ,6: hall_init ... Line 6436: [ 2.805940] <2>.(3)[1:swapper/0]xxx## do_initcall_level, 883 ,7: init_machine_late ...
现在我们已经实现了从start_kernel到回调驱动入口代码的流程分析,还有比较关心的就是从kernel_init到具体某一个驱动的probe函数的整个代码流程是怎么样的(深入了解这个流程对于debug有实际意义),
通常做法有两种方式,一种就是继续跟代码,一步一步的看逻辑找答案,另外一种比较简单而实用的方式就是使用调试手段加dump_stack(),把call stack打印出来就清楚明了了, 我们的做法是两者结合,这样是最清楚不过的了。
先来看简单实用的方式,下面以
battery_meter_init 代码为例,看从模块入口到probe的代码路径是怎么样的:
static int battery_meter_probe(struct platform_device *dev) { int ret_device_file = 0; char *temp_strptr; battery_log(BAT_LOG_CRTI, "------- battery_meter_probe!! -------\n"); dump_stack(); ...
编译-烧机-抓开机log:
[ 2.322439] <1>.(1)[1:swapper/0]------- battery_meter_probe!! ------- [ 2.323246] <1>-(1)[1:swapper/0]CPU: 1 PID: 1 Comm: swapper/0 Tainted: G W 3.18.35+ #4 [ 2.324357] Backtrace: [ 2.324676] <1>-(1)[1:swapper/0][<c010badc>] (dump_backtrace) from [<c010bc7c>] (show_stack+0x18/0x1c) [ 2.325832] r6:c103d790 r5:ffffffff r4:00000000 r3:00000000 [ 2.326547] <1>-(1)[1:swapper/0][<c010bc64>] (show_stack) from [<c0a92d58>] (dump_stack+0x90/0xa4) [ 2.327669] <1>-(1)[1:swapper/0][<c0a92cc8>] (dump_stack) from [<c07e1f2c>] (battery_meter_probe+0x40/0x22c) [ 2.328889] r8:c1080b7c r7:c1080b7c r6:c044fcfc r5:c12f5318 r4:c1080a38 r3:00000000 [ 2.329866] <1>-(1)[1:swapper/0][<c07e1eec>] (battery_meter_probe) from [<c03c4354>] (platform_drv_probe+0x38/0x90) [ 2.331162] r7:c1080b7c r6:fffffdfb r5:c1080a48 r4:ffffffed [ 2.331877] <1>-(1)[1:swapper/0][<c03c431c>] (platform_drv_probe) from [<c03c29e4>] (driver_probe_device+0x1d8/0x43c) [ 2.333195] r7:c115017c r6:c10a7c38 r5:c1080a48 r4:c1150170 [ 2.333908] <1>-(1)[1:swapper/0][<c03c280c>] (driver_probe_device) from [<c03c2c90>] (__device_attach+0x48/0x4c) [ 2.335171] r10:00000000 r9:00000000 r8:00000000 r7:00000000 r6:c03c2c48 r5:c1080a48 [ 2.336147] r4:c1080b7c [ 2.336470] <1>-(1)[1:swapper/0][<c03c2c48>] (__device_attach) from [<c03c129c>] (bus_for_each_drv+0x60/0x94) [ 2.337701] r5:c1080a48 r4:00000000 [ 2.338154] <1>-(1)[1:swapper/0][<c03c123c>] (bus_for_each_drv) from [<c03c2dac>] (device_attach+0x80/0x88) [ 2.339363] r6:c1040f40 r5:c1080a7c r4:c1080a48 [ 2.339945] <1>-(1)[1:swapper/0][<c03c2d2c>] (device_attach) from [<c03c1524>] (bus_probe_device+0x8c/0xb0) [ 2.341154] r6:c1040f40 r5:c1080a48 r4:c1080a50 r3:df166000 [ 2.341870] <1>-(1)[1:swapper/0][<c03c1498>] (bus_probe_device) from [<c03bf2a4>] (device_add+0x43c/0x558) [ 2.343068] r6:c1080a48 r5:c1040e40 r4:c1080a50 r3:00000001 [ 2.343784] <1>-(1)[1:swapper/0][<c03bee68>] (device_add) from [<c03c4a04>] (platform_device_add+0xd0/0x264) [ 2.345004] r9:de089bc0 r8:c1080bd4 r7:c1080bd4 r6:c1080a48 r5:c1080a38 r4:00000000 [ 2.345979] <1>-(1)[1:swapper/0][<c03c4934>] (platform_devdev) from [<c03c2dd8>] (driver_attach+0x24/0x28) [ 2.359715] r6:c1040f40 r5:de087b80 r4:c1080bd4 [ 2.360298] <1>-(1)[1:swapper/0][<c03c2db4>] (driver_attach) from [<c03c17f0>] (bus_add_driver+0x15c/0x218) [ 2.361516] <1>-(1)[1:swapper/0][<c03c1694>] (bus_add_driver) from [<c03c3750>] (driver_register+0x80/0x100) [ 2.362735] r7:df04a038 r6:c0f39e6c r5:00000000 r4:c1080bd4 [ 2.363450] <1>-(1)[1:swapper/0][<c03c36d0>] (driver_register) from [<c03c4e54>] (__platform_driver_register+0x5c/0x64) [ 2.364789] r5:00000000 r4:c12f5318 [ 2.365244] <1>-(1)[1:swapper/0][<c03c4df8>] (__platform_driver_register) from [<c0f39ee0>] (battery_meter_init+0x74/0xc4) [ 2.366627] <1>-(1)[1:swapper/0][<c0f39e6c>] (battery_meter_init) from [<c0f00e74>] (do_one_initcall+0x140/0x200) [ 2.367901] r5:c0f62538 r4:c0f62538 [ 2.368354] <1>-(1)[1:swapper/0][<c0f00d34>] (do_one_initcall) from [<c0f010a0>] (kernel_init_freeable+0x16c/0x20c) [ 2.369649] r10:c0f62d6c r9:00000141 r8:c0f00600 r7:c10efb80 r6:c0f62d60 r5:c0f6c278 [ 2.370625] r4:00000006 [ 2.370951] <1>-(1)[1:swapper/0][<c0f00f34>] (kernel_init_freeable) from [<c0a8bd78>] (kernel_init+0x10/0x100)
从上面 backtrace看整个代码调用流程就很清晰了,所以这种方式其实是非常实用而重要的debug手段!
下面进入源码分析,验证上面的过程:
static int __init battery_meter_init(void) { int ret; // 注册到平台设备 ret = platform_device_register(&battery_meter_device); if (ret) { return ret; } // 注册到平台驱动 ret = platform_driver_register(&battery_meter_driver); if (ret) { return ret; } return 0; }
继续看平台驱动注册:
// 发现这个其实是一个宏定义 #define platform_driver_register(drv) \ __platform_driver_register(drv, THIS_MODULE) int __platform_driver_register(struct platform_driver *drv, struct module *owner) { // 标记为:THIS_MODULE drv->driver.owner = owner; // 总线类型 drv->driver.bus = &platform_bus_type; // drv->driver 赋值,下面的 platform_drv_probe 其实就是最终需要call的接口! if (drv->probe) drv->driver.probe = platform_drv_probe; if (drv->remove) drv->driver.remove = platform_drv_remove; if (drv->shutdown) drv->driver.shutdown = platform_drv_shutdown; return driver_register(&drv->driver); } int driver_register(struct device_driver *drv) { int ret; struct device_driver *other; // 异常判断,如果依次直接kernel panic. BUG_ON(!drv->bus->p); if ((drv->bus->probe && drv->probe) || (drv->bus->remove && drv->remove) || (drv->bus->shutdown && drv->shutdown)) printk(KERN_WARNING "Driver '%s' needs updating - please use " "bus_type methods\n", drv->name); /* 首先去drivers_kset的对象list中查是否存在相同名字的内核对象,如果存在就 输出错误提示信息。 */ other = driver_find(drv->name, drv->bus); if (other) { // 这个log信息可以用于驱动debug检查。 printk(KERN_ERR "Error: Driver '%s' is already registered, " "aborting...\n", drv->name); return -EBUSY; } // 如果是新的driver则加入到总线中. ret = bus_add_driver(drv); ... }
int bus_add_driver(struct device_driver *drv) { struct bus_type *bus; struct driver_private *priv; int error = 0; bus = bus_get(drv->bus); if (!bus) return -EINVAL; // debug辅助信息 pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name); // 使用kmalloc申请内存,初始化为0,属于线性映射内存 priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) { error = -ENOMEM; goto out_put_bus; } // 注册到driver内核对象链表. klist_init(&priv->klist_devices, NULL, NULL); priv->driver = drv; drv->p = priv; priv->kobj.kset = bus->p->drivers_kset; error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, "%s", drv->name); // 如果注册失败,直接退出. if (error) goto out_unregister; // 添加到klist_drivers list 中管理. klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); if (drv->bus->p->drivers_autoprobe) { // attach. error = driver_attach(drv); if (error) goto out_unregister; } ... }
int driver_attach(struct device_driver *drv) { /* 通过drv找到dev然后传入__driver_attach执行,这里使用了内核中最 普遍的获取容器数据结构实例的指针操作. */ return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); }
static int __driver_attach(struct device *dev, void *data) { struct device_driver *drv = data; // 如果dev跟drv不匹配,返回. if (!driver_match_device(drv, dev)) return 0; // 如果存在父设备,则需要拿到两把锁 if (dev->parent) /* Needed for USB */ device_lock(dev->parent); device_lock(dev); // 匹配到,执行driver的probe if (!dev->driver) driver_probe_device(drv, dev); // 执行完释放锁. device_unlock(dev); if (dev->parent) device_unlock(dev->parent); return 0; }
这里就是关键地方了!
int driver_probe_device(struct device_driver *drv, struct device *dev) { int ret = 0; // 如果没注册dev返回错误 if (!device_is_registered(dev)) return -ENODEV; // 有用的debug信息 pr_debug("bus: '%s': %s: matched device %s with driver %s\n", drv->bus->name, __func__, dev_name(dev), drv->name); // pm runtime需要保证不挂起. pm_runtime_barrier(dev); // 看名字就知道这里就是要干最终的call驱动的probe了? ret = really_probe(dev, drv); pm_request_idle(dev); return ret; }
static int really_probe(struct device *dev, struct device_driver *drv) { int ret = 0; int local_trigger_count = atomic_read(&deferred_trigger_count); // 原子计数器,保证原子操作 atomic_inc(&probe_count); // 有用的debug信息 pr_debug("bus: '%s': %s: probing driver %s with device %s\n", drv->bus->name, __func__, drv->name, dev_name(dev)); WARN_ON(!list_empty(&dev->devres_head)); dev->driver = drv; ... // 执行probe: if (dev->bus->probe) { TIME_LOG_START(); ret = dev->bus->probe(dev); TIME_LOG_END(); bootprof_probe(ts, dev, drv, (unsigned long)dev->bus->probe); if (ret) goto probe_failed; } else if (drv->probe) { TIME_LOG_START(); /* 这里执行最新的probe,这个drv->probe是不是好像哪里见过??? 没错,这里执行的就是开头驱动注册时候传入的 platform_drv_probe ! 兜兜转转一大圈又绕回去了! */ ret = drv->probe(dev); ... } ... // 如果执行失败,移除dev,释放资源. probe_failed: devres_release_all(dev); driver_sysfs_remove(dev); dev->driver = NULL; dev_set_drvdata(dev, NULL); .... } 继续分析: static int platform_drv_probe(struct device *_dev) { // 又见 container_of 机制!内核中链表和指针的运用非常之巧妙而高效. struct platform_driver *drv = to_platform_driver(_dev->driver); struct platform_device *dev = to_platform_device(_dev); int ret; ... ret = dev_pm_domain_attach(_dev, true); if (ret != -EPROBE_DEFER) { // 真正执行驱动的probe入口!!! ret = drv->probe(dev); if (ret) dev_pm_domain_detach(_dev, true); } ... }
驱动注册基本流程:
1、 通过平台驱动通用接口注册platform_drv_xx的callback;
2、 使用driver_find查询当前drv是否已添加到总线集合drivers_kset中,
如果已存在则返回退出;
3、 创建驱动内核对象, 并添加到内核对象层级中, 将knode_bus链接总线链表bus->p->klist_drivers
中.
4、 创建驱动属性组;
5、 注册完成, kobject_uevent发送KOBJ_ADD通知.
相关文章推荐
- kernel 启动流程之 【设备驱动加载】 学习笔记_好
- kernel 启动流程之 【设备驱动加载】 学习笔记
- kernel 启动流程之 【head.S】 学习笔记
- kernel 启动流程之 【head.S】 学习笔记
- kernel 启动流程之 【head.S】 学习笔记
- linux网络设备应用与驱动编程学习笔记(1)——应用编程(套接字)
- [OpenSolaris][kernel]Solaris内核加载设备驱动过程
- linux字符设备驱动学习笔记2
- 设备驱动学习笔记(1)----设备和模块的分类
- input子系统学习笔记三 驱动的分层及设备驱动层实现原理
- 驱动学习笔记2-用程序加载NT驱动程序
- linux字符设备驱动学习笔记1
- [OpenSolaris][kernel]Solaris内核加载设备驱动过程
- Android 中的WiFi学习笔记(转载)----WIFI启动 代码流程走读---网络连接流程
- 块设备驱动学习笔记(二)——实例模板
- wince驱动学习笔记(vs2005实现流驱动动态加载与卸载 2)
- Linux网络设备驱动学习笔记(-)
- Windows内核学习笔记(一)--浅谈驱动对象、设备对象与请求
- OGRE 学习笔记(启动详解 + 实体的加载与显示)
- Android 中的WiFi学习笔记(转载)----WIFI启动 代码流程走读---网络连接流程