meminfo初始化
"init=/init\0" \
"mem=256M\0" \
"mpurate=800\0" \
……2.kernel中用parse_args解析此bootarg,具体可参考另一份笔记Linux启动参数“init=xxx”分析parse_args -> parse_one -> unknown_bootoption -> obsolete_checksetup -> obs_kernel_param.setup_func = early_mem3.kernel中设置对应bootarg的处理函数early_param("mem", early_mem); -> __setup_param(str, fn, fn, 1) ->#define __setup_param(str, unique_id, fn, early) \static const char __setup_str_##unique_id[] __initconst \
__aligned(1) = str; \
static struct obs_kernel_param __setup_##unique_id \
__used __section(.init.setup) \
__attribute__((aligned((sizeof(long))))) \= { __setup_str_##unique_id, fn, early }4.early_mem函数static int __init early_mem(char *p){
static int usermem __initdata = 0;
unsigned long size, start;
char *endp;
/*
* If the user specifies memory size, we
* blow away any automatically generated
* size.
*/
if (usermem == 0) {
usermem = 1;
meminfo.nr_banks = 0;
}/** defined in arch/arm/plat-omap/include/plat/memory.hstart = PHYS_OFFSET;
* PHYS_OFFSET = UL(0x80000000)
*/
size = memparse(p, &endp);
if (*endp == '@')
start = memparse(endp + 1, NULL);
arm_add_memory(start, size);
return 0;
}最后遗留一个问题,假如有几块内存需要处理怎么办?没关系,Linux提供了对应的机制。就是__tagtable(ATAG_MEM, parse_tag_mem32);这是一张表格,代表了一组ATAG_MEM param,用parse_tag_mem32来处理。这一组param同样也是在u-boot阶段就加入了。即:do_bootm_linux -> setup_memory_tags (u-boot)只不过在D7800中CONFIG_NR_DRAM_BANKS=1(configs/d7800.h)。static void setup_memory_tags (bd_t *bd){int i;for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {params->hdr.tag = ATAG_MEM;params->hdr.size = tag_size (tag_mem32);params->u.mem.start = bd->bi_dram[i].start;params->u.mem.size = bd->bi_dram[i].size;params = tag_next (params);}}meminfo中的内存地址全部是真正的物理地址。
USB驱动程序
vmalloc分析
工作队列work queue解析
cmwq 的实现遵循了以下几个原则:
- 与原有的工作队列接口保持兼容,cmwq 只是更改了创建工作队列的接口,很容易移植到新的接口(__create_workqueue -> alloc_workqueue)。
- 工作队列共享 per-CPU 的线程池,提供灵活的并发级别而不再浪费大量的资源。
- 自动平衡工作者线程池和并发级别,这样工作队列的用户不再需要关注如此多的细节。
create_workqueue (create_singlethread_workqueue) -> alloc_workqueue -> __alloc_workqueue_key struct workqueue_struct*alloc_workqueue(char *name, unsigned int flags, int max_active); |
其中:
name:为工作队列的名字,而不像 2.6.36 之前实际是为工作队列服务的内核线程的名字。
flag 指明工作队列的属性,可以设定的标记如下:
- WQ_NON_REENTRANT:默认情况下,工作队列只是确保在同一 CPU 上不可重入,即工作项不能在同一 CPU 上被多个工作者线程并发执行,但容许在多个 CPU 上并发执行。但该标志标明在多个 CPU 上也是不可重入的,工作项将在一个不可重入工作队列中排队,并确保至多在一个系统范围内的工作者线程被执行。
- WQ_UNBOUND:工作项被放入一个由特定 gcwq 服务的未限定工作队列,该客户工作者线程没有被限定到特定的 CPU,这样,未限定工作者队列就像简单的执行上下文一般,没有并发管理。未限定的 gcwq 试图尽可能快的执行工作项。
- WQ_FREEZEABLE:可冻结 wq 参与系统的暂停操作。该工作队列的工作项将被暂停,除非被唤醒,否者没有新的工作项被执行。
- WQ_MEM_RECLAIM:所有的工作队列可能在内存回收路径上被使用。使用该标志则保证至少有一个执行上下文而不管在任何内存压力之下。
- WQ_HIGHPRI:高优先级的工作项将被排练在队列头上,并且执行时不考虑并发级别;换句话说,只要资源可用,高优先级的工作项将尽可能快的执行。高优先工作项之间依据提交的顺序被执行。
- WQ_CPU_INTENSIVE:CPU 密集的工作项对并发级别并无贡献,换句话说,可运行的 CPU 密集型工作项将不阻止其它工作项。这对于限定得工作项非常有用,因为它期望更多的 CPU 时钟周期,所以将它们的执行调度交给系统调度器。
max_active:决定了一个 wq 在 per-CPU 上能执行的最大工作项。比如 max_active 设置为 16 表示一个工作队列上最多 16 个工作项能同时在 per-CPU 上同时执行。当前实行中,对所有限定工作队列,max_active 的最大值是 512,而设定为 0 时表示是 256;而对于未限定工作队列,该最大值为:MAX[512,4 * num_possible_cpus() ],除非有特别的理由需要限流或者其它原因,一般设定为 0 就可以了。
在之前的代码中,一些用户依赖于 ST 中的严格执行顺序,这种行为在 cmwq 中可以将 max_active 设为 1,flag 设置为 WQ_UNBOUND 来获得相同的行为
cmwq 本质上是提供了一个公共的内核线程池的实现,其接口基本上和以前保持了兼容,只是更改了创建工作队列的函数的后端,它实际上是将工作队列和内核线程的一一绑定关系改为由内核来管理内核线程的创建,因此在 cmwq 中创建工作队列并不意味着一定会创建内核线程。
1. gcwq封装了线程池,worker封装了工作者线程,work就是工作者线程真正在处理的事务。2. 针对每一个CPU,内核用一个数据结构global_cwq来表示cmwq,即gcwq,还有一个未和CPU绑定的gcwq,所以总共gcwq的数量为CPU数目+1.3. gcwq中有三个重要的list,分别是: 1) 管理空闲worker的idlelist; 2)管理正在工作的worker的busy_hash; 3) 以及本CPU上等待处理的work列表worklist4. worker才是Linux调度器进行调度的基本单位,worker有一个成员task_struct,调度器利用此结构来调度worker_thread,决定他何时休眠,合适唤醒
1. ST (Single Thread):只在创建它的CPU上有cwq与之对应2. MT (Multi Thread):在每个active CPU上均有cwq与之对应,对应到每个CPU上,有一个cpu_workqueue_struct与之对应,即cwqwq与cwq的对应关系是在__alloc_workqueue_key中实现的:
work的处理流程如下图所示:1. 通过alloc_cwqs创建一组per-CPU变量2. 用一个for_each_cwq_cpu循环遍历所有CPU,并为每个CPU的cwq结构初始化,指定cwq->gcwq和cwq->wq
work在各个list之间的转换的关系如下图所示:
work如何处理: process_scheduled_work()
queue_work如果不指定cpu id的话,会默认调用get_cpu()获得当前thread所在的CPU号(也就是queue_work所在的CPU,或者说提交work所在的CPU),并将work加到CPU对应的gcwq的worklist上,以待进一步处理。1. 在busy_hash table中找,是否有thread正在处理当前事务。如果有,则将本事务转移到那个worker thread的scheduled列表上,以保证同一个work不在同一CPU的不同thread上并发。2. 配置worker->current_work = work, worker->current_cwq = cwq, 记录此时在处理的是哪个workqueue上的哪个work。由于此时gcwq->lock是锁上的,所以和其他本地thread同步没有问题3. 记录CPU number到work->data,从scheduled列表中删除该work4. 处理gcwq有GCWQ_HIGHPRI_PENDING标志的情况,加入下一个work仍然是高优先级,则去叫醒idle的worker来处理,如果没有则继续。保证在有高优先级pending的时候,尽量多的worker参与处理。5. 解锁gcwq->lock6. 为work清除pending标志,开始真正的处理该work,调用work->func7. 锁住gcwq->lock8. 做一些完成时的更新:1) 从busy_hash中删除该worker; 2) 将current_work和current_cwq赋为NULL
内核同步-spin_lock系列
类似spin_lock_irq,但会记住调用此函数之前的中断状态(是否使能),在释放锁时进行恢复
三种环境:会在请求锁的时候,禁止软中断(bh = bottom half,底半部)
优先级依次增高,即后者能将前者打断,所以在低优先级环境中请求共享资源时,需要屏蔽掉高优先级运行,可以调用相应的spin_lock函数来达到这个目的。如果不这样做,就会发生死锁。进程上下文,软中断,硬中断
*注:spin_lock_irq包含了local_irq_disable(禁止中断),preempt_disable(禁止内核抢占),以及spin_lock本身的操作。举例:A进程用spin_lock获得了某一资源,还未释放,此时中断来了,进入中断处理流程,若此时需要操作共享资源,则必须要用spin_lock请求锁,但这时假如进程A和中断处理流程在同一CPU上,则A没有机会再释放锁,导致中断处理流程中死锁。所以,进程A中必须要在获取锁之前屏蔽中断(只需屏蔽本地中断即可,因为假如中断处理流程在另外一个CPU上时,换一种说法,就是另外一个CPU产生了中断,其实和本CPU没有关系,进程A的操作不受影响,可以顺利释放锁)。因为在不同的CPU上出现中断不会导致
进程A的状态被设为TASK_INTERRUPT,只是换出。当中断处理程序忙等被换出后,进程A还是有机会
获得CPU,执行并退出临界区。
Linux启动流程
__HEAD // 0xc000_8000
ENTRY(stext)
……
ENDPROC(stext)
Linux启动参数“init=xxx”分析
params数组以tag为ATAG_NONE的tag作结束。params其实就是一组struct tag,定义为:->……->setup_commandline_tag (将环境变量bootargs放入params数组,其tag为ATAG_CMDLINE)
struct tag {
struct tag_header hdr;
union { // 各种tag……struct tag_cmdline cmdline;……}u;}
在setup_arch(&command_line);中引用parse_tags(tags),以tag.hdr.size = 0为循环终止条件,遍历params区域(即struct tag数组),并通过该param的tag(ATAG_xxx)在.taglist.init区域中寻找合适的处理函数。对于ATAG_CMDLINE,其对应的处理函数即为parse_tag_cmdline,即将tag->u.cmdline.cmdline copy到default_command_line数组中,在setup_arch函数中,就是from数组。MACHINE_START(D7800, "HSM D7800 Board")/* Maintainer: Syed Mohammed Khasim - Texas Instruments */.boot_params = 0x80000100, // 与UBOOT一致.map_io = omap3_map_io,.reserve = omap_reserve,.init_irq = d7800_init_irq,.init_machine = d7800_init,.timer = &omap_timer,MACHINE_END
对于.taglist.init的配置,是通过引用一系列的如下的宏来实现的tag->u.cmdline.cmdline => default_command_line => boot_command_line => comdline_p => command_line => static_command_line (setup_command_line中实现)
__tagtable(ATAG_CORE, parse_tag_core);__tagtable(ATAG_MEM, parse_tag_mem32);__tagtable(ATAG_CMDLINE, parse_tag_cmdline);这些宏其实就是将一组struct tagtable放到.taglist.init区域中,此区域可以用__taglist_begin, __taglist_end来表示
__stop___param - __start___param,
&unknown_bootoption);
1)如果有此定义,则直接调用该param的kernel_param_ops(例如param_ops_bool = {.set = param_set_bool; .get = param_get_bool})2)如果没有定义,则调用handle_unknown,即unknown_bootoption
3)如果param在__setup_start~__setup_end中仍然没有对应的处理的话,则会放到envp_init中unknown_bootoption -> obsolete_checksetup(param)obsolete_checksetup函数会遍历__setup_start~__setup_end区域,假如在此区域中找到param对应的obs_kernel_param,则调用该obs_kernel_param的setup_func(其实init参数就是在此时被处理的)。
* obsolete是过时的意思,从中可以看出linux中认为param参数是以后主要使用的启动参数传递方式,将慢慢摒弃__setup的形式,通过module_param的方式可以在启动参数中为驱动的参数赋值,这种赋初值的方式将成为主流
* 关于__setup_start~__setup_end区域的初始化,参考以下内容
__initconst: __section(.init.rodata),将该变量放入名字为“.init.rodata”的段中
__used:告诉编译器无论 GCC 是否发现这个函数的调用实例,都要使用这个函数。这对于从汇编代码中调用 C 函数有帮助
__attribute__:gcc的标志,为函数和变量设置属性,以增强编译的功能,可用编译时参数WALL打开
1.mdesc = setup_machine(machine_arch_type);所获得的machine_desc没有fixup成员。其定义在arch/arm/mach-omap/board-d7800.c中2.依上,则char *from = default_command_line = CONFIG_CMDLINE = "root=/dev/mmcblk0p2 rootwait console=ttyO2,115200"3.strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);4.strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);5.*cmdline_p = cmd_line; parse_early_param();
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
DECLARE_GLOBAL_DATA_PTR
因为在u-boot中,这个指针使用一个固定的寄存器来保存它,所以即使是局部变量,但是它在所有使用到它的地方都是一样的值,这个值在board_init_f中初始化,指向CFG_GBL_DATA_ADDR
Linux物理内存管理