《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的中断没有返回。

相关文章
|
7月前
|
安全 网络协议 Linux
深入理解Linux内核模块:加载机制、参数传递与实战开发
本文深入解析了Linux内核模块的加载机制、参数传递方式及实战开发技巧。内容涵盖模块基础概念、加载与卸载流程、生命周期管理、参数配置方法,并通过“Hello World”模块和字符设备驱动实例,带领读者逐步掌握模块开发技能。同时,介绍了调试手段、常见问题排查、开发规范及高级特性,如内核线程、模块间通信与性能优化策略。适合希望深入理解Linux内核机制、提升系统编程能力的技术人员阅读与实践。
679 1
|
7月前
|
监控 Ubuntu Linux
什么Linux,Linux内核及Linux操作系统
上面只是简单的介绍了一下Linux操作系统的几个核心组件,其实Linux的整体架构要复杂的多。单纯从Linux内核的角度,它要管理CPU、内存、网卡、硬盘和输入输出等设备,因此内核本身分为进程调度,内存管理,虚拟文件系统,网络接口等4个核心子系统。
457 0
|
7月前
|
Web App开发 缓存 Rust
|
7月前
|
Ubuntu 安全 Linux
Ubuntu 发行版更新 Linux 内核,修复 17 个安全漏洞
本地攻击者可以利用上述漏洞,攻击 Ubuntu 22.10、Ubuntu 22.04、Ubuntu 20.04 LTS 发行版,导致拒绝服务(系统崩溃)或执行任意代码。
|
7月前
|
Ubuntu Unix Linux
操作系统的最强入门科普(Unix/Linux篇)
下期文章,小枣君会重点聊聊Windows和macOS那条线。敬请关注! 如果大家觉得文章不错,还请帮忙多多转发!谢谢!
|
安全 Linux 数据安全/隐私保护
Vanilla OS:下一代安全 Linux 发行版
【10月更文挑战第30天】
971 0
Vanilla OS:下一代安全 Linux 发行版
|
运维 自然语言处理 Ubuntu
OS Copilot-操作系统智能助手-Linux新手小白的福音
OS Copilot 是阿里云推出的一款操作系统智能助手,专为Linux新手设计,支持自然语言问答、辅助命令执行和系统运维调优等功能。通过简单的命令行操作,用户可以快速获取所需信息并执行任务,极大提升了Linux系统的使用效率。安装步骤简单,只需在阿里云服务器上运行几条命令即可完成部署。使用过程中,OS Copilot不仅能帮助查找命令,还能处理文件和复杂场景,显著节省了查找资料的时间。体验中发现,部分输出格式和偶尔出现的英文提示有待优化,但整体非常实用,特别适合Linux初学者。
562 10
|
弹性计算 自然语言处理 Ubuntu
OS Copilot-操作系统智能助手-Linux新手小白的福音
OS Copilot是由阿里云推出的操作系统智能助手,专为Linux新手设计,支持自然语言问答、辅助命令执行等功能,极大提升了Linux系统的使用效率。用户只需通过简单的命令或自然语言描述问题,OS Copilot即可快速提供解决方案并执行相应操作。例如,查询磁盘使用量等常见任务变得轻松快捷。此外,它还支持从文件读取复杂任务定义,进一步简化了操作流程。虽然在某些模式下可能存在小问题,但总体上大大节省了学习和操作时间,提高了工作效率。
464 2
OS Copilot-操作系统智能助手-Linux新手小白的福音
|
弹性计算 运维 Ubuntu
os-copilot在Alibaba Cloud Linux镜像下的安装与功能测试
我顺利使用了OS Copilot的 -t -f 功能,我的疑惑是在换行的时候就直接进行提问了,每次只能写一个问题,没法连续换行更有逻辑的输入问题。 我认为 -t 管道 功能有用 ,能解决环境问题的连续性操作。 我认为 -f 管道 功能有用 ,可以单独创建可连续性提问的task问题。 我认为 | 对文件直接理解在新的服务器理解有很大的帮助。 此外,我还有建议 可以在非 co 的环境下也能进行连续性的提问。
296 7