刘柳 + 《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 

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdGl0ZXIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">


进入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\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打印出新的东西时候,我都是鼠标移动提示)。这样所见即所得的方式希望读者喜欢。










继续我们的内核之旅,以上是追踪到 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

最新文章

  1. bottlepy template
  2. Effective STL(第7条)
  3. vim(5)vim下wimrc的配置,解决中文乱码问题
  4. shell 循环
  5. jquery修改a标签的href链接和文字
  6. prototype linkage can reduce object initialization time and memory consumption
  7. mysql通过binlog日志来恢复数据
  8. java数据结构之列表——ArrayList,LinkedList,比较
  9. 理解C#系列 / 核心C# / 编译参数
  10. lucene 实现word,pdf全文检索源码
  11. Delphi透明组件开发(去掉自己的csOpaque,去掉父控件的WS_CLIPCHILDREN,增加WS_EX_TRANSPARENT,截获WM_ERASEBKGND,然后在WM_DRAWITEM里画) good
  12. current online redo logfile 丢失的处理方法
  13. java String源码学习
  14. Java的标识符,数据类型与各种运算符
  15. AOJ/数据结构习题集
  16. intelj idea中JRebel激活
  17. hihocoder 1388 fft循环矩阵
  18. Vue(day5)
  19. 【Linux】Centos partition
  20. vim块编辑删除、插入、替换【转】

热门文章

  1. ZOJ3640-Help Me Escape
  2. tomcat 系统服务 outofmemory
  3. IBATIS动态SQL
  4. vim的漫漫长征路
  5. 创建出多个app
  6. 比较优势 - MBA智库百科
  7. Linux内核中的宏:__init and __exit
  8. 网页制作之html基础学习4-格式与布局
  9. CF#213DIV2:B The Fibonacci Segment
  10. 10-UIKit(UIDatePicker、UIPickerView、UIWebView、Storyboard)