zoukankan      html  css  js  c++  java
  • 动静结合学内核:linux idle进程和init进程浅析

    刘柳 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 + titer1@qq.com


    退休的贵族进程 0号进程


    全部进程的祖先叫做进程0
     在系统初始化阶段由start_kernel()函数从无到有手工创建的一个内核线程
     进程0最后的初始化工作创建init内核线程,此后执行cpu_idle,成为idle进程
    控制权的接力棒从bios-->bootloader-->idle,某种程度上说,就是完毕子系统初始化使命后,就退居二线了。
    0号进程一直处于皇宫“内核态”。没有出过宫“到用户态”。所谓贵族终身。

    0号进程的代码概要图

       字画的臭(逃)。主要意思是0号进程是这样串行产生的:
       start_kernel  -->rest_init --> cpu_idle_loop 


    进入idle loop的堆栈样本例如以下(堆栈调用图也能够从中画出来的)
    (gdb) bt
    #0  cpu_idle_loop () at kernel/sched/idle.c:201
    #1  cpu_startup_entry (state=<optimized out>) at kernel/sched/idle.c:274
    #2  0xc175d22d in rest_init () at init/main.c:418
    #3  0xc1a4bb59 in start_kernel () at init/main.c:680
    #4  0xc1a4b360 in i386_start_kernel () at arch/x86/kernel/head32.c:49
    #5  0x00000000 in ?? ()

    idle最核心的代码位置(前方高能。含有chinglish,蹩脚翻译,请大拿指点。仅仅翻译部分基本的)
    static void cpu_idle_loop(void)
    {
    	while (1) {
    		/*假设本架构以下有标示轮询poll的bit位,我们会保持不变??(理解:始终在这个循环里)
    		假设idle没有被调度,那么poll bit是被清空的
    		反过来说, 假设 设置了poll bit,那么need_resched将会保证cpu进行又一次调度。
    		*/
    		
    		__current_set_polling();
    		tick_nohz_idle_enter();
    
    		while (!need_resched()) {
    			check_pgt_cache();
    			rmb();
    
    			if (cpu_is_offline(smp_processor_id()))
    				arch_cpu_idle_dead();
    
    			local_irq_disable();
    			arch_cpu_idle_enter();
    
    			/*
    			在poll mode 中,我们会使能中断 和 自旋锁
    			同一时候 假设检測到唤醒(来自一些设备广播的),
    			我们将努力避免进入深度睡眠,由于我们知道 IPI (???)即将立即来到
    			*/
    			if (cpu_idle_force_poll || tick_check_broadcast_expired())
    				cpu_idle_poll();
    			else
    				cpuidle_idle_call();
    
    			arch_cpu_idle_exit();
    		}
    
    		/*
    		 * Since we fell out of the loop above, we know
    		 * TIF_NEED_RESCHED must be set, propagate it into
    		 * PREEMPT_NEED_RESCHED.
    		 *
    		 * This is required because for polling idle loops we will
    		 * not have had an IPI to fold the state for us.
    		 */
    		preempt_set_need_resched();
    		tick_nohz_idle_exit();
    		__current_clr_polling();
    
    		/*
    		 * We promise to call sched_ttwu_pending and reschedule
    		 * if need_resched is set while polling is set.  That
    		 * means that clearing polling needs to be visible
    		 * before doing these things.
    		 */
    		smp_mb__after_atomic();
    
    		sched_ttwu_pending();
    		schedule_preempt_disabled();
    	}
    }




    放 大杀器(o(^▽^)o,自大了吧),进入一个进入idle loop的现场追踪过程
    (初步制作gif,没有提示大家什么时候開始/结束,下次功力深入后将重制)。




    用户1号进程的前世今生


    进程1又称为init进程。是全部用户进程的祖先
    由进程0在start_kernel调用rest_init创建
    init进程PID为1,当调度程序选择到init进程时,init进程開始运行kernel_init ()函数
    init是个普通的用户态进程,它是Unix系统内核初始化与用户态初始化的接合点,它是全部用户进程的祖宗。在执行init曾经是内核态初始化,该过程(内核初始化)的最后一个动作就是执行/sbin/init可执行文件


    这段话是孟老师课件摘取,字字珠玑啊。
         所谓祖先,就是全部用户态进程都从这个进程fork出来。
         而init进程(pid=1)的产生也是第一个使用fork调用的函数。
         其它注意区分的是用户控件的/sbin/init在我们实验中指的是menuos编译出来的init.
    (pid!=1)

    首先来看微缩的start_kernel函数代码(情景分析):
    asmlinkage __visible void __init start_kernel(void)
    {
    	...
    	//初始化0号进程pcb
    		set_task_stack_end_magic(&init_task);
    	...
    		/* 当仅仅有一个CPU的时候这个函数就什么都不做。
    		可是假设有多个CPU的时候那么它就  
    		* 返回在启动的时候的那个CPU的号  
    		*/  
    	smp_setup_processor_id();
    
    	...
    
    	/* 关闭当前CPU的中断 */  
    	local_irq_disable();
    	early_boot_irqs_disabled = true;
    	
    	...
    
    	/* 初始化页地址,使用链表将其链接起来 */
    	page_address_init();
    	/* 显示内核的版本号信息 */  
    	pr_notice("%s", linux_banner);
    	/*  
    	* 每种体系结构都有自己的setup_arch()函数,是体系结构相关的,详细编译哪个  
    	* 体系结构的setup_arch()函数,由源代码树顶层文件夹下的Makefile中的ARCH变量  
    	* 决定  
    	*/  
    	setup_arch(&command_line);
    	
    	...
    	/* 打印Linux启动命令行參数 */ 
    	pr_notice("Kernel command line: %s
    ", 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();
    
    	...
    	/* 初始化hash表,便于从进程的PID获得相应的进程描写叙述符指针 */  
    	pidhash_init();
    	/* 虚拟文件系统的初始化 */ 
    	vfs_caches_init_early();
    	sort_main_extable();
    
    
    	/*  
    	* trap_init函数完毕对系统保留中断向量(异常、非屏蔽中断以及系统调用)               
    	* 的初始化,init_IRQ函数则完毕其余中断向量的初始化  
    	*/  
    	trap_init();
    	mm_init();
    
    	/* 进程调度器初始化 */ 
    	sched_init();
    
    	preempt_disable();
    	/* 检查中断是否已经打开,假设已经打开。则关闭中断 */  
    	if (WARN(!irqs_disabled(),
    		"Interrupts were enabled *very* early, fixing it
    "))
    		local_irq_disable();
    	...
    
    	/* init some links before init_ISA_irqs() */
    	early_irq_init();
    	init_IRQ();
    	tick_init();
    	rcu_init_nohz();
    	init_timers();
    	/* 对高精度时钟进行初始化 */  
    	hrtimers_init();
    	/* 初始化tasklet_softirq和hi_softirq */  
    	softirq_init();
    	timekeeping_init();
    	/* 初始化系统时钟源 */  
    	time_init();
    	sched_clock_postinit();
    	perf_event_init();
    	profile_init();
    	call_function_init();
    	WARN(!irqs_disabled(), "Interrupts were enabled early
    ");
    	early_boot_irqs_disabled = false;
    	local_irq_enable();
    
    	/* slab初始化 */  
    	kmem_cache_init_late();
    
    	/*  
    	* 初始化控制台以显示printk的内容,在此之前调用的printk  
    	* 仅仅是把数据存到缓冲区里  
    	*/  
    
    	console_init();
    	if (panic_later)
    		panic("Too many boot %s vars at `%s'", panic_later,
    		panic_param);
    
    	lockdep_info();
    
    	...
    
    	/*  
    	* CPU性能測试函数。能够计
    	算出CPU在1s内运行了多少次一个  
    	* 极短的循环。计算出来的值经过处理后得
    	到BogoMIPS值(Bogo是Bogus的意思),  
    	*/  
    	calibrate_delay();
    	pidmap_init();
    	...
    
    	/* 创建init进程 */  
    	rest_init();//66 analysis 0 #, never return ...
    }


         以下这个演示过程,给出了在start_kernel到rest_init的跟踪过程,(当中每次qume打印出新的东西时候,我都是鼠标移动提示)。这样所见即所得的方式希望读者喜欢。





    继续我们的内核之旅,以上是追踪到 rest_init,以下将从rest_init 到 kthread_init,
    图中,直接在init进程的函数段(kernel_init)中開始。
    (一些小插曲,本演示为了说明init进程最后变成了用户台进程,去查证了cs寄存器。只是source insight没有找到相应的bit,下次将会更新

    用户常见的是从用户态(博文下一轮更新将会说明)从核心态,
    这里init(pid=1)是从核心态变为用户态。一个比較核心的变化就是会把cs寄存器从核心段cs变为用户段cs
    从数字上来说,cs值从96(0x60)变为115(0x73)

    先看宏的解释:
    #define __USER_CS	(GDT_ENTRY_DEFAULT_USER_CS*8+3) //用户段cs 计算出来14*8+3 =0x73
    #define GDT_ENTRY_DEFAULT_USER_CS	14
    
    #define __KERNEL_CS	(GDT_ENTRY_KERNEL_CS*8)//核心段cs :0x60
    #define GDT_ENTRY_KERNEL_CS		(GDT_ENTRY_KERNEL_BASE+0)
    #define GDT_ENTRY_KERNEL_BASE		(12)


    再看切换的代码:为什么启动Init进程会涉及到start_thread,
    仅从调用图来看,丑图再现(逃。。)

    一言难尽。请看精彩的解释:原文再现

    这里直接定位到用户态切换的代码:
    start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)
    {
    	set_user_gs(regs, 0);
    	regs->fs		= 0;
    	regs->ds		= __USER_DS;
    	regs->es		= __USER_DS;
    	regs->ss		= __USER_DS;
    	regs->cs		= __USER_CS;
    	regs->ip		= new_ip;
    	regs->sp		= new_sp;
    	regs->flags		= X86_EFLAGS_IF;
    	/*
    	 * force it to the iret return path by making it look as if there was
    	 * some work pending.
    	 */
    	set_thread_flag(TIF_NOTIFY_RESUME);
    }

    上面的内容,一句话,说明Init进程怎样从核心态变成用户态的。
    最后我们在动态图里面把相关过程串起来吧。




    假设图看到,请点击这里:

    总结:



    整体来说,这里是差点儿各种子系统的诞生之地。这里牵一发,动全身。
    假设你在不同版本号内核比較start_kernel,就会发现非常大差异。

    idle进程,如标题所说,完毕重要子系统初始化。就退居二线。


    1号进程从0号进程fork出来。然后又切换到用户态,完毕控制权从核心态到用户态的转换,
    因此用户交互才干開始。



    使命。决定了一生。
    Linux进程如此,咋们的人生使命是?
    码农陷入了思索。。。     


    附录:

    题目自拟,内容环绕Linux内核的启动过程。即从start_kernel到init进程启动;
    博客中须要使用实验截图
    博客内容中须要细致分析start_kernel函数的运行过程
    总结部分须要阐明自己对“Linux系统启动过程”的理解,尤其是idle进程、1号进程是怎么来的。

    參考:
    http://book.51cto.com/art/201007/213598.htm

  • 相关阅读:
    HDU-5534-Partial Tree
    Dire Wolf HDU
    HDU 5119 Happy Matt Friends (14北京区域赛 类背包dp)
    4 Values whose Sum is 0 POJ
    Fliptile POJ
    Face The Right Way POJ
    【Selenium学习】解决chromedriver.exe' executable needs to be in PATH
    【Jenkins学习】修改jenkins显示为中文语言
    【Jenkins学习】Jenkins 批量删除历史构建
    【Tomcat学习】tomcat 日志详解
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/5152800.html
Copyright © 2011-2022 走看看