《Linux内核设计的艺术:图解Linux操作系统架构设计与实现原理》——3.2 内核第一次做进程调度

简介: 本节书摘来自华章计算机《Linux内核设计的艺术:图解Linux操作系统架构设计与实现原理》一书中的第3章,第3.2节,作者:新设计团队著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

3.2 内核第一次做进程调度

现在执行的是进程0的代码。从这里开始,进程0准备切换到进程1去执行。
在Linux 0.11的进程调度机制中,通常有以下两种情况可以产生进程切换。
1)允许进程运行的时间结束。
进程在创建时,都被赋予了有限的时间片,以保证所有进程每次都只执行有限的时间。一旦进程的时间片被削减为0,就说明这个进程此次执行的时间用完了,立即切换到其他进程去执行,实现多进程轮流执行。
2)进程的运行停止。
当一个进程需要等待外设提供的数据,或等待其他程序的运行结果……或进程已经执行完毕时,在这些情况下,虽然还有剩余的时间片,但是进程不再具备进一步执行的“逻辑条件”了。如果还等着时钟中断产生后再切换到别的进程去执行,就是在浪费时间,应立即切换到其他进程去执行。
这两种情况中任何一种情况出现,都会导致进程切换。
进程0角色特殊。现在进程0切换到进程1既有第二种情况的意思,又有怠速进程的意思。我们会在3.3.1节中讲解怠速进程。
进程0执行for(;;) pause( ),最终执行到schedule()函数切换到进程1,如图3-13所示。

image

pause函数的执行代码如下:

//代码路径:init/main.c:
    …
static inline _syscall0(int,fork)
static inline _syscall0(int,pause)
    …
void main(void)
{ 
    …
    move_to_user_mode();
    if (!fork()) {        /* we count on this going ok */
         init();
    }
    for(;;) pause();
}

pause()函数的调用与fork()函数的调用一样,会执行到unistd.h中的syscall0,通过int 0x80中断,在system_call.s中的call _sys_call_table(,%eax,4)映射到sys_pause( )的系统调用函数去执行,具体步骤与3.1.1节中调用fork()函数步骤类似。略有差别的是,fork()函数是用汇编写的,而sys_pause()函数是用C语言写的。
进入sys_pause()函数后,将进程0设置为可中断等待状态,如图3-13中第一步所示,然后调用schedule()函数进行进程切换,执行代码如下:

//代码路径:kernel/sched.c:
int sys_pause(void)
{
//将进程0设置为可中断等待状态,如果产生某种中断,或其他进程给这个进程发送特定信号…才有可能将
//这个进程的状态改为就绪态
       current->state= TASK_INTERRUPTIBLE;
       schedule();
       return 0;
}

在schedule()函数中,先分析当前有没有必要进行进程切换,如果有必要,再进行具体的切换操作。
首先依据task[64]这个结构,第一次遍历所有进程,只要地址指针不为空,就要针对它们的“报警定时值alarm”以及“信号位图signal”进行处理(我们会在后续章节详细讲解信号,这里先不深究)。在当前的情况下,这些处理还不会产生具体的效果,尤其是进程0此时并没有收到任何信号,它的状态是“可中断等待状态”,不可能转变为“就绪态”。
第二次遍历所有进程,比较进程的状态和时间片,找出处在就绪态且counter最大的进程。现在只有进程0和进程1,且进程0是可中断等待状态,不是就绪态,只有进程1处于就绪态,所以,执行switch_to(next),切换到进程1去执行,如图3-14中的第一步所示。
执行代码如下:

//代码路径:kernel/sched.c:
void schedule(void)
{
    int i,next,c;
    struct task_struct ** p;

/* check alarm, wake up any interruptible tasks that have got a signal */
    for(p= &LAST_TASK;p > &FIRST_TASK;--p)
         if (*p){
               if((*p)->alarm&&(*p)->alarm<jiffies){       //如果设置了定时或定时已过
                     (*p)->signal |= (1<<(SIGALRM-1));    //设置SIGALRM
                     (*p)->alarm= 0;            //alarm清零    
                     }
               if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
               (*p)->state==TASK_INTERRUPTIBLE)        //现在还不是这种情况
                     (*p)->state=TASK_RUNNING; 
         }

/* this is the scheduler proper: */

    while (1) {
         c= -1;
         next= 0;
         i= NR_TASKS;
         p= &task[NR_TASKS];
         while (--i) {
               if (!*--p)
                     continue;
               if ((*p)->state== TASK_RUNNING && (*p)->counter>c)//找出就绪态中
                               //counter最大的进程
                     c= (*p)->counter, next= i;
         }
         if (c) break;                        
         for(p= &LAST_TASK;p > &FIRST_TASK;--p)
               if (*p)
                     (*p)->counter= ((*p)->counter >> 1) +
                                (*p)->priority;//即counter= counter /2 + priority
    }
    switch_to(next);
}
//代码路径:include/sched.h:
    …
// FIRST_TSS_ENTRY<<3是100000,((unsigned long) n)<<4,对进程1是10000
// _TSS(1)就是110000,最后2位特权级,左第3位GDT,110是6即GDT中tss0的下标
#define _TSS(n) ((((unsigned long) n)<<4) + (FIRST_TSS_ENTRY<<3))
    …
#define switch_to(n) {\            //参看2.9.1节
struct {long a,b;} __tmp; \        //为ljmp的CS、EIP准备的数据结构
__asm__("cmpl %%ecx,_current\n\t" \
         "je 1f\n\t" \            //如果进程n是当前进程,没必要切换,退出
         "movw %%dx,%1\n\t" \        //EDX的低字赋给*&__tmp.b,即把CS赋给.b
         "xchgl %%ecx,_current\n\t" \    //task[n]与task[current]交换
         "ljmp %0\n\t" \  // ljmp到__tmp,__tmp中有偏移、段选择符, 但任务门忽略偏移
         "cmpl %%ecx,_last_task_used_math\n\t" \//比较上次是否使用过协处理器    
         "jne 1f\n\t" \
         "clts\n" \                 //清除CR0中的切换任务标志
      "1:" \
         ::"m" (*&__tmp.a),"m" (*&__tmp.b), \    //.a对应EIP(忽略),.b对应CS 
         "d" (_TSS(n)),"c" ((long) task[n]));\//EDX是TSS n的索引号,ECX即task[n]
}

程序将一直执行到"ljmp %0nt" 这一行。ljmp通过CPU的任务门机制并未实际使用任务门,将CPU的各个寄存器值保存在进程0的TSS中,将进程1的TSS数据以及LDT的代码段、数据段描述符数据恢复给CPU的各个寄存器,实现从0特权级的内核代码切换到3特权级的进程1代码执行,如图3-14中的第二步所示。

image

接下来,轮到进程1执行,它将进一步构建环境,使进程能够以文件的形式与外设交互。
需要提醒的是,pause()函数的调用是通过int 0x80中断从3特权级的进程0代码翻转到0特权级的内核代码执行的,在_system_call中的call _sys_call_table (,%eax,4) 中调用sys_pause()函数,并在sys_pause( )中的schedule( )中调用switch( ),在switch( )中ljmp进程1的代码执行。现在,switch( )中ljmp后面的代码还没有执行,call _sys_call_table (,%eax,4) 后续的代码也还没有执行,int 0x80的中断没有返回。

相关文章
|
10月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
360 32
|
8月前
|
缓存 运维 前端开发
|
8月前
|
缓存 运维 前端开发
阿里云操作系统控制台:高效解决性能瓶颈与抖动之进程热点追踪
遇到“进程性能瓶颈导致业务异常”等多项业务痛点时,提供高效解决方案,并展示案例。
|
6月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
201 0
|
11月前
|
监控 搜索推荐 开发工具
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
1213 2
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
|
C语言 开发者 内存技术
探索操作系统核心:从进程管理到内存分配
本文将深入探讨操作系统的两大核心功能——进程管理和内存分配。通过直观的代码示例,我们将了解如何在操作系统中实现这些基本功能,以及它们如何影响系统性能和稳定性。文章旨在为读者提供一个清晰的操作系统内部工作机制视角,同时强调理解和掌握这些概念对于任何软件开发人员的重要性。
|
Linux 调度 C语言
深入理解操作系统:从进程管理到内存优化
本文旨在为读者提供一次深入浅出的操作系统之旅,从进程管理的基本概念出发,逐步探索到内存管理的高级技巧。我们将通过实际代码示例,揭示操作系统如何高效地调度和优化资源,确保系统稳定运行。无论你是初学者还是有一定基础的开发者,这篇文章都将为你打开一扇了解操作系统深层工作原理的大门。
161 4
|
存储 算法 调度
深入理解操作系统:进程调度的奥秘
在数字世界的心脏跳动着的是操作系统,它如同一个无形的指挥官,协调着每一个程序和进程。本文将揭开操作系统中进程调度的神秘面纱,带你领略时间片轮转、优先级调度等策略背后的智慧。从理论到实践,我们将一起探索如何通过代码示例来模拟简单的进程调度,从而更深刻地理解这一核心机制。准备好跟随我的步伐,一起走进操作系统的世界吧!
|
算法 调度 开发者
深入理解操作系统:进程与线程的管理
在数字世界的复杂编织中,操作系统如同一位精明的指挥家,协调着每一个音符的奏响。本篇文章将带领读者穿越操作系统的幕后,探索进程与线程管理的奥秘。从进程的诞生到线程的舞蹈,我们将一起见证这场微观世界的华丽变奏。通过深入浅出的解释和生动的比喻,本文旨在揭示操作系统如何高效地处理多任务,确保系统的稳定性和效率。让我们一起跟随代码的步伐,走进操作系统的内心世界。
165 2