Linux-0.11操作系统实验4理论-内核级线程实现

从fork中断开始

在这里插入图片描述
fork()中有中断,执行 INT 0x80时,CPU找到对应的内核栈,将 SS SP PC CS 压栈;接着执行_system_call

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

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

# system_call.s 程序
movl _current,%eax # 取当前任务(进程)数据结构地址%eax。
# 下面97-100 行查看当前任务的运行状态。如果不在就绪状态(state 不等于0)就去执行调度程序。
# 如果该任务在就绪状态但counter[??]值等于0,则也去执行调度程序。
cmpl $0,state(%eax) # state 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 # 如果时间片与eax相等,调度
ret_from_sys_call: # 中断返回,执行中断返回函数,从内核栈,切换到用户栈

两种情况都会调到reschedule,在其中调用_schedule函数发生调度,这里的ret_from_sys_call就是中断出口,会执行一系列pop,直到iret
在这里插入图片描述

切换五段论中的中间三段实现切换:schedule()

在这里插入图片描述
Linux 0.11 用 TSS切换,但也可以用栈切换,因为TSS中的信息可以写到内核栈。
实际上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。但是这种做法慢。

sched.c中调度函数代码实现如下:

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

切换五段论总结

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

sys_fork()详解

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

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

copy_peocess:

int copy_process (int nr, long ebp, long edi, long esi, long gs, long none,
	      long ebx, long ecx, long edx,
	      long fs, long es, long ds,
	      long eip, long cs, long eflags, long esp, long ss)

它复制系统进程信息(task[n])并且设置必要的寄存器,它还整个地复制数据段。参数来自于system_call和sys_fork压入栈的寄存器,保存父进程用户态的样子。
在这里插入图片描述
第一句申请内存用作PCB
esp0是内核栈,esp是用户栈
由tss.esp=esp可知,子进程和父进程共享用户栈

  p->tss.eip = eip;		// 指令代码指针。
  p->tss.eflags = eflags;	// 标志寄存器。
  p->tss.eax = 0;
  p->tss.ecx = ecx;
  p->tss.edx = edx;
  p->tss.ebx = ebx;
  p->tss.esp = esp;
  p->tss.ebp = ebp;
  p->tss.esi = esi;
  p->tss.edi = edi;
  p->tss.es = es & 0xffff;	// 段寄存器仅16 位有效。
  p->tss.cs = cs & 0xffff;
  p->tss.ss = ss & 0xffff;
  p->tss.ds = ds & 0xffff;
  p->tss.fs = fs & 0xffff;
  p->tss.gs = gs & 0xffff;
  p->tss.ldt = _LDT (nr);	// 该新任务nr 的局部描述符表选择符(LDT 的描述符在GDT 中)。
  p->tss.trace_bitmap = 0x80000000;

当INT 0x80结束返回时执行mov res,%eax ,又根据以上程序中 p->tss.eax = 0,产生了子进程和父进程返回值的差别(子进程的res=0,父进程的res≠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
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值