核心级线程实现实例(李治军操作系统课笔记5)

1、从中断开始

以fork()为例:
在这里插入图片描述
fork()中有中断,执行 INT 0x80时,CPU找到对应的内核栈,将 SS SP PC CS 压栈;注意ret=??处写的是INT 的下一句,也就是PC = mov res,%eax 。

切换五段论中的中断入口和出口

在这里插入图片描述
刚进入内核,_system_call将用户态信息压栈,这就是中断入口——建立内核栈和用户栈的关联)

在这里插入图片描述

movl _current,%eax  //取当前任务(进程)数据结构地址%eax。
//接下来查看当前任务的运行状态。如果不在就绪状态(state 不等于0)就去执行调度程序。
//如果该任务在就绪状态但counter[??]值(时间片)等于0,则也去执行调度程序。
cmpl $0,state(%eax)  //state(%eax)= state + %eax,eax = _current,_current = PCB,其实就是判断PCB是否是 0,0 表示就绪或执行,非0表示阻塞
jne reschedule  //如果是非0,就会发生调度
cmpl $0,counter(%eax)  //判断counter(%eax) 是否是0,eax = _current,counter是时间片
je reschedule    // 相等,调度
ret_from_sys_call:  // 中断返回,执行中断返回函数,从内核栈,切换到用户栈
reschedule:
pushl $ret_from_sys_call //将ret_from_sys_call 的地址入栈,,reschedule遇到 } 出栈,弹出ret_from_sys_call
jmp _schedule   //调用 schedule

在这里插入图片描述
这里的ret_from_sys_call就是中断出口

2、切换:schedule()

void schedule (void)
{
    //找到下一个线程的TCB next,切换到下一个线程
    ...
    switch_to (next);       // 切换到任务号为next 的任务,并运行之
}

switch_to():
在这里插入图片描述
实际上switch_to 通过 TSS 实现切换,如下图:
TSS(Task Struct Segment),任务结构段,一个TSS中有所有寄存器。
在这里插入图片描述
上图黄色的是现TSS,绿的是新TSS,下边 GDT(全局描述符表Global Descriptor Table)保存的是TSS的描述符粉色的是CPU当前的寄存器段信息,TR是一个类似于CS的选择子。切换就是将 现在CPU的寄存器信息写入当前线程的TSS中,TR指向新的TSS(n) 的段描述符,在GDT表中找到新的TSS,将新的TSS段内容中所有寄存器信息(包括ESP)覆盖进 CPU。
注:之前的说法是,在切换时找到下一个线程的PCB,由PCB找到esp完成栈的切换,而这里直接覆盖更新了esp,所以实际上TSS是PCB的一个子段。同时,可以看到EIP(cpu下一条指令地址)也被更新了,所以不需要再找到esp之后弹栈eip。但是这种做法慢。

3、总结

核心代码:INT ljmp IRET
五段论:
在这里插入图片描述


4、sys_fork()详细

_sys_fork:
……
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call _copy_process # 调用C 函数copy_process()(kernel/fork.c)。
……

作用:根据父进程,创建子进程,copy_press前将参数压栈,这些参数是父进程在用户态的样子

copy_peocess:

在这里插入图片描述
参数来自于system_call和sys_fork压入栈的寄存器,保存父进程用户态的样子。

在这里插入图片描述
第一句申请内存用作PCB
esp0是内核栈,esp是用户栈
由tss.esp=esp可知,子进程和父进程共享用户栈

在这里插入图片描述
此处p->tss.eax=0与下图结合
在这里插入图片描述
当INT 0x80结束返回时执行上面这句,产生了子进程和父进程返回值的差别(子进程的res=0,父进程≠0),所以能够让子进程运行自己的程序,如下:

if(!fork()) 
{
    //子进程执行
} else{
    //父进程执行
}

举例: shell 输入命令

int main(int argc, char * argv[])
{
    while(1)
    {
        scanf("%s", cmd);
        if(!fork())
        {
            exec(smd); // 执行子进程命令
        }
        wait(0); // 执行父进程命令,shell等待用户输入
    }
}

以这个例子介绍子进程如何执行自己的代码:
exec() 是一个系统调用,会执行 system_call

_system_call:
    push %ds ... %fs
    pushl %edx...
    call sys_execve
_sys_execve:
    lea EIP(%esp),%eax  
    pushl %eax
    call _do_execve

在这里插入图片描述
EIP = 0x1C是十进制的28,将%esp偏移28,由上图栈的构造可知是将eip的地址,也就是中断返回时要执行的下一句复制给eax,调用do_execve改变原本的父进程带来的eip。当子进程退出内核(通过IRET实现中断返回),回到用户态,就可以执行新的代码。

do_execve代码如下:

   int do_execve(* eip, ...)
    {
        p += change_ldt(...;
        eip[0] = ex.a_entry;// ex.a_entry是可执行程序入口地址,产生可执行文件时写入
        eip[3] = p; // eip[3]=esp+0x1C+0x0C,因为一个指针4个字节,有了自己的执行代码之后也要有自己的栈
        ...
    }

总结一下system_call的作用:
  • 调用sys_fork,调用 copy_process,父进程与子进程 内核栈不同,用户栈相同
  • 判断cmpl $0,state(%eax),非0表示阻塞,调用 reschedule 进程调度。reschedule 调用 schedule,schedule调用 switch_to(switch_to中ljmp实现长跳转,子进程将 TSS的内容复制到 CPU上,TSS图中粉色的部分)
  • iret 内核栈出栈
    子进程回到用户栈,执行的是 中断下边的一句代码:mov res, %eax ,res = %eax = 0
    父进程回到用户栈,执行的也是 中断下边的一句代码:mov res, %eax,父进程 eax != 0
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
操作系统 治军 pdf》是一本由作者治军撰写的关于操作系统的书籍的电子版PDF文件。操作系统是计算机系统中的一个核心组成部分,它负责管理和控制计算机的硬件和软件资源,使得计算机能够高效地运行各种应用程序。 在这本书中,治军详细介绍了操作系统的基本概念、原理和设计原则。他从操作系统的起源和发展历程出发,讲解了多道、分时和实时操作系统等不同类型的操作系统,并深入解析了其内核结构和功能。此外,书中还讨论了进程管理、内存管理、文件系统、输入输出控制等重要的操作系统主题,为读者提供了全面了解和深入学习的机会。 这本书的PDF版本使得读者可以更加便捷地获取其中的内容,无论是在电脑、平板还是手机上都可以进行阅读。通过阅读这本书,读者可以深入了解操作系统的基本原理和相关技术,有助于他们提升对计算机系统的理解和应用。对于学习计算机科学或相关专业的学生和从事软件开发工作的技术人员来说,这本书是一本宝贵的参考资料。 总之,《操作系统 治军 pdf》是一本全面介绍操作系统的书籍的电子版PDF文件,读者可以通过阅读它来深入了解操作系统的原理和技术。这本书的出现为学习和研究操作系统提供了便利,对于对计算机系统有兴趣的人士来说是一本值得阅读的优秀教材。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值