动静结合学内核: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号进程的代码概要图
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdGl0ZXIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
(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();
}
}

用户1号进程的前世今生
进程1又称为init进程。是全部用户进程的祖先由进程0在start_kernel调用rest_init创建init进程PID为1,当调度程序选择到init进程时,init进程開始运行kernel_init ()函数init是个普通的用户态进程,它是Unix系统内核初始化与用户态初始化的接合点,它是全部用户进程的祖宗。在执行init曾经是内核态初始化,该过程(内核初始化)的最后一个动作就是执行/sbin/init可执行文件
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\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(); ...
/* 初始化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\n"))
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\n");
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打印出新的东西时候,我都是鼠标移动提示)。这样所见即所得的方式希望读者喜欢。

这里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)
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进程如此,咋们的人生使命是?
码农陷入了思索。。。
附录:
博客中须要使用实验截图
博客内容中须要细致分析start_kernel函数的运行过程
总结部分须要阐明自己对“Linux系统启动过程”的理解,尤其是idle进程、1号进程是怎么来的。
动静结合学内核:linux idle进程和init进程浅析的更多相关文章
- 从linux看Android之一--init进程
准备环境: 熟悉linux环境和shell脚本 用SSHDROID和XShell搭建android的命令行环境(帮助找到熟悉的linux界面,因为android删除了很多标准linux平台上很多的sh ...
- 内核启动流程3--Busybox的init进程
Busybox是用来制作文件系统的一个工具集,可以用来替换GNU fileutils shellutils等工具集,它为各种小型的或者嵌入式系统提供了比较完全的工具集. 它提供的核心程序中包括了用户空 ...
- (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- [Linux] 孤儿进程与僵尸进程[总结]
转载: http://www.cnblogs.com/Anker/p/3271773.html 1.前言 之前在看<unix环境高级编程>第八章进程时候,提到孤儿进程和僵尸进程,一直对这两 ...
- 第一次作业:基于Linux操作系统深入源码进程模型分析
1.Linux操作系统的简易介绍 Linux系统一般有4个主要部分:内核.shell.文件系统和应用程序.内核.shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序.管理文件并使 ...
- [linux]孤儿进程与僵尸进程
转载自:http://www.cnblogs.com/Anker/p/3271773.html 一.前言 之前在看<unix环境高级编程>第八章进程时候,提到孤儿进程和僵尸进程,一直对这两 ...
- Linux下1号进程的前世(kernel_init)今生(init进程)----Linux进程的管理与调度(六)
前面我们了解到了0号进程是系统所有进程的先祖, 它的进程描述符init_task是内核静态创建的, 而它在进行初始化的时候, 通过kernel_thread的方式创建了两个内核线程,分别是kernel ...
- 二十三、Linux 进程与信号---进程链和进程扇、守护进程和孤儿进程以及僵尸进程
23.1 进程链和进程扇 23.1.1 概念 进程链:一个父进程构建出一个子进程,子进程再构建出子子进程,子子进程构建出子子子进程.... 这种就为进程链 进程扇:一个父进程构建出多个子进程,子进程都 ...
- Linux init进程学习 转
http://oss.org.cn/kernel-book/ch13/13.6.1.htm init进程的建立 Linux将要建立的第一个进程是init进程,建立该进程是以调用kernel_threa ...
随机推荐
- Week2(9月16日):动手做个简单的例子
Part I:提问 =========================== 1.什么是ASP.NET MVC? 2.MVC的英文? 3.什么是模型? 4.什么是控制器? 5.什么是视图? 6.ASP ...
- Ajax辅助方法
目前为止,我们已经考察了如何编写客户端JavaScript代码,以发送并接受服务器的数据.然而,在使用ASP.NET MVC时,还有另一种方法可用来执行Ajax调用,这就是Ajax辅助方法. Ajax ...
- c# 数据库编程(通过SqlCommand 执行DML语句)
原来一直是java,python等语言,最近用c#语言,并编写数据库访问代码.使用了之后,这里总结下,分享下c#如何操作数据库. 在java等其它语言中,有一套标准的api来完成数据库访问,并且一般都 ...
- Flask web开发 处理Session
本文我们在上篇文章<Flask web开发 处理POST请求(登录案例)>的基础上,来讲述Flask对session的支持. 在上面案例上,我们需要修改和新增如下功能 1.登录成功后的 ...
- c++ namespace命名空间详解
What is a namespace? A namespace defines an area of code in which all identifiers are guaranteed to ...
- 【Linux驱动器】Linux-2.6.20.4内核移植
最近一段时间以来一直学习TQ2440内核开发板移植.嫁接驱动器. 真诚地相信这方面的知识有很大的困难,.但有一种观点认为,从看,难度越大,的提升空间的能力更大! ! 1.解压源代码 从Internet ...
- 安装Devstack的DNS问题
所谓的OpenStack一键安装,省去了敲键盘的麻烦,但是卡在中间出了问题也是比较尴尬的 在公司内安装经常会出现卡在下载软件的地方,有时候还会出错 trick就是换一个US的dns,比如8.8.8.8
- 编程算法 - 扑克牌的顺子 代码(C)
扑克牌的顺子 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 从扑克牌中随机抽取5张牌, 推断是不是一个顺子, 即这5张牌是不是连续的. 2~1 ...
- char 与 varchar 不同,造成的麻烦
就是因为他们的不同,造成我一小天的麻烦,就是取得不了正确的结果,后来经原同事提醒,终于找到了原因,但是还有点没看懂,所以又找了个网上的经验,贴进来,以备以后再查. --简单的存储过程 create p ...
- 我的Python成长之路---第二天---Python基础(7)---2016年1月9日(晴)
再说字符串 一.字符串的编码 字符串的编码是个很令人头疼的问题,由于计算机是美国人发明的,他们很理所当然的认为计算机只要能处理127个字母和一些符号就够用了,所以规定了一个字符占用8个比特(bit)也 ...