Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linux中鲜为人知的幕后工作者——Idle, Init & Scheduler #43

Open
abbshr opened this issue Apr 11, 2015 · 0 comments
Open

Linux中鲜为人知的幕后工作者——Idle, Init & Scheduler #43

abbshr opened this issue Apr 11, 2015 · 0 comments

Comments

@abbshr
Copy link
Owner

abbshr commented Apr 11, 2015

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由谁来调用?

其实最初讨论话题是由调度工作谁来做展开的. 现在如果你不知道上面的内容, 你怎么想?

我最开始头脑中有这么几个策略:

  1. 由中断处理程序(也就是内核)调用schedule()函数进行进程调度.
  2. 由init进程负责调度管理.
  3. 由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现有的代码, 当然, 那种情况就另当别论了.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant