abbshr.github.io
abbshr.github.io copied to clipboard
Linux中鲜为人知的幕后工作者——Idle, Init & Scheduler
Idle进程和Init进程, 谁是老大?
感谢这学期开了一门嵌入式操作系统, 纠正了我一些认识上的误区.
当涂老师提到idle进程时, 我突然想起了init,
Init是所有进程的祖先, 它是内核创建的第一个进程...
记得我们学操作系统时水笔老范说了好几遍, 已经牢牢刻在脑海里了, 所以潜意识里仍是不假思索认为init是头子. 可是仔细看idle的作用以及地位, 貌似比init进程还有高, 这就让有点怀疑当初所学的东西是不是漏掉了什么...
确实当年学操作系统时漏掉了一些重要的东西, 今天和实验室的师兄们讨论了之后终于补回来了.
先从Linux启动流程说起...
当boot loader选定并加载一个内核后, 将计算机控制权交给加载的内核, 并创建一些系统函数. 当准备工作完成, 内核逻辑开始调用定义的start_kernal()
函数.
start_kernal()
函数的任务就是建立中断处理机制, 初始化内存管理的剩余部分, 初始化调度器, 初始化设备以及驱动等等. 最后调用rest_init()
函数创建init进程(pid 1), 并将(内核)自己做为idle进程(pid 0).
init进程由内核创建, 并在用户空间执行. 它在用户空间执行upstart服务(启动脚本), 创建非系统服务并调用login程序进行用户登录控制. 下面是init的代码:
static int init(void * unused)
{
lock_kernel();
do_basic_setup();
prepare_namespace();
/*
* Ok, we have completed the initial bootup, and
* we're essentially up and running. Get rid of the
* initmem segments and start the user-mode stuff..
*/
free_initmem();
unlock_kernel();
if (open("/dev/console", O_RDWR, 0) < 0) // stdin
printk("Warning: unable to open an initial console.\n");
(void) dup(0); // stdout
(void) dup(0); // stderr
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command)
execve(execute_command,argv_init,envp_init);
execve("/sbin/init",argv_init,envp_init);
execve("/etc/init",argv_init,envp_init);
execve("/bin/init",argv_init,envp_init);
execve("/bin/sh",argv_init,envp_init);
panic("No init found. Try passing init= option to kernel.");
}
init
函数最后执行了系统调用exec
, 将可作为init程序的二进制镜像加载到内存.
idle做什么?
现在来看看kernal space在系统启动过程中都干点什么.
the kernel looks for an init process to run, which (separately) sets up a user space and the processes needed for a user environment and ultimate login. The kernel itself is then allowed to go idle, subject to calls from other processes.
调用start_kernal()
函数这一阶段称作Kernel startup stage. 阶段最后才创建init进程. rest_init
代码如下:
rest_init() {
// init process, pid = 1
kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
unlock_kernel();
current->need_resched = 1;
// idle process, pid = 0
cpu_idle(); // never return
}
可见idle"进程"就是start_kernal
演变过来.
The startup function for the kernel (also called the swapper or process 0)
而idle的任务就是空转! 当系统中没有其他任何进程使用CPU时, 因为CPU也不能闲着呀, 所以这时调度器就把CPU控制权交给idle进程, idle进程的诞生是通过cpu_idle()
函数完成的, 而这个函数永远不会返回.:
/*
* The idle thread. There's no useful work to be
* done, so just try to conserve power and have a
* low exit latency (ie sit in a loop waiting for
* somebody to say that they'd like to reschedule)
*/
void cpu_idle (void)
{
/* endless idle loop with no priority at all */
init_idle();
current->nice = 20;
current->counter = -100;
while (1) {
void (*idle)(void) = pm_idle;
if (!idle)
idle = default_idle;
while (!current->need_resched)
idle();
schedule();
check_pgt_cache();
}
}
///////////////////////////////////////////////////////////////////////////////
void __init init_idle(void)
{
struct schedule_data * sched_data;
sched_data = &aligned_data[smp_processor_id()].schedule_data;
if (current != &init_task && task_on_runqueue(current)) {
printk("UGH! (%d:%d) was on the runqueue, removing.\n",
smp_processor_id(), current->pid);
del_from_runqueue(current);
}
sched_data->curr = current;
sched_data->last_schedule = get_cycles();
clear_bit(current->processor, &wait_init_idle);
}
///////////////////////////////////////////////////////////////////////////////
void default_idle(void)
{
if (current_cpu_data.hlt_works_ok && !hlt_counter) {
__cli();
if (!current->need_resched)
safe_halt();
else
__sti();
}
}
到这里我想你应该明白idle进程的由来了以及与init的关系了.
现在在linux下执行命令ps -eaf
, 查看一下:
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 17:48 ? 00:00:01 /sbin/init
root 2 0 0 17:48 ? 00:00:00 [kthreadd]
root 3 2 0 17:48 ? 00:00:00 [ksoftirqd/0]
root 5 2 0 17:48 ? 00:00:00 [kworker/0:0H]
root 7 2 0 17:48 ? 00:00:09 [rcu_sched]
pid为1的init进程的父进程pid为0 (第二个进程是内核进程的守护进程, 也是由内核创建), 也就是说忽略内核进程的情况下:
Init是所有进程的祖先, 它是内核创建的第一个进程
这句话是对的, init确实是所有用户级进程的祖先.
Scheduler由谁来调用?
其实最初讨论话题是由调度工作谁来做展开的. 现在如果你不知道上面的内容, 你怎么想?
我最开始头脑中有这么几个策略:
- 由中断处理程序(也就是内核)调用
schedule()
函数进行进程调度. - 由init进程负责调度管理.
- 由idle进程负责.
之所以会有后两个想法, 是因为我觉得每个进程都可以主动让出CPU控制权并调用schedule函数挑选就绪队列中的进程.
如果我清除的意识到init是user-space进程的话就直接排除掉了. 假设init负责了进程的调度, 那么首先由init → process_a, 如果process_a的时间片到了呢? 由于调度权在init进程, 所以这时没有任何用户空间进程可以执行, 于是idle进程篡位, 系统暂时进入空转, cpu_idle
函数通过调用schedule
函数才可以从就绪队列选择一个进程继续执行. 如果process_a正在执行时来了一个高优先级的进程呢? 中断之后由于没有调度器执行, 于是又进入idle的天下.
也就是说, 把调度权交给init完全就是废了, 一点用没有.
idle进程其实就是内核的一部分, 读一读它的源码你会发现它还负责调度工作:
while (1) {
void (*idle)(void) = pm_idle;
if (!idle)
idle = default_idle;
while (!current->need_resched)
idle();
schedule();
check_pgt_cache();
}
脑补一下这样一个场景: 某个进程由于某些原因放弃了CPU使用权. 由于idle进程(与其说是一个进程, 到不如说是部分内核代码, 本身没有什么就绪可言)是一个死循环, 检测到需要调度, 则调用schedule()
函数完成进程调度工作.
所以不把idle进程看做"进程"的一个原因可能是它是Scheduler吧.
但是呢, 这仅仅是现有操作系统的一种调度手段, 可并不代表这是唯一的调度手段. 可以脑洞大开, 让任一进程都可以作为调度器! 只要修改linux现有的代码, 当然, 那种情况就另当别论了.