操作系统实践(9)——进程、多进程、系统调用、进程调度

这一章,与前面提到的特权级转移以及中断处理机制相关的知识关系很大。

首先画个图了解下这几个概念的关系以及相互作用:

进程1

以前一直很想知道系统调用是怎么实现的,看完书才明白系统调用跟普通的中断处理程序实现基本是相似的,可见中断处理程序是本章重点中的重点。

梳理一下书中的思路:
1.首先实现一个简单的进程,注册一个时钟中断处理程序,在进程执行的过程中发生时钟中断,执行中断处理程序,返回进程被中断的地方继续执行。
2.在1的基础上,再增加一个进程,每次发生中断,切换到另一个进程执行,而且保证进程能正确的在被中断的地方恢复执行。
3.总结一下增加一个进程需要做哪些修改。
3.实现一个简单的系统调用,进程调用系统调用后,能正确返回进程并继续执行。
4.修改一下时钟中断,对进程增加类似优先级的标识,根据进程的优先级,实现简单的调度程序。

1. 简单的进程

大概的执行过程为:执行进程,进行特权级转移,切换到中断处理程序,恢复进程。这里的关键点就是,怎么保存进程的状态,以及怎么恢复进程继续执行。
这里记录进程信息的结构为:

typedef struct s_stackframe {   /* proc_ptr points here                         
        u32     gs;
        u32     fs; 
        u32     es;
        u32     ds;
        u32     edi;
        u32     esi;
        u32     ebp;
        u32     kernel_esp;
        u32     ebx;
        u32     edx;
        u32     ecx;
        u32     eax;
        u32     retaddr;
        u32     eip;
        u32     cs;
        u32     eflags;
        u32     esp;
        u32     ss;
}STACK_FRAME;


typedef struct s_proc {
        STACK_FRAME                     regs;                  
        u16                             ldt_sel;
        DESCRIPTOR                      ldts[LDT_SIZE];
        u32                             pid;
        char                            p_name[16];
}PROCESS;

画个图,有个形象的了解:

进程2

注意图中的”STACK_FRAME A”部分和”STACK_FRAME B部分” 。
进程执行时,发生时钟中断,”STACK_FRAME B”部分被push进内核栈,开始执行中断处理程序,而”STACK_FRAME A”部分则是中断处理程序需要保存到进程头部结构的部分。当中断处理程序处理结束,想返回进程时,则需要找到进程的头部结构,然后跳到”STACK_FRAME B”部分,iretd完成进程的恢复。

这里最需要关注的就是栈的切换,进程执行->中断->恢复进程执行 这个循环过程,栈的变化如下图所示:
进程3

2. 多进程

从上面简单的进程实现中可以看到,执行一个进程最重要的,就是要找到这个进程的头部结构。在时钟中断处理程序中,在保存好之前进程的状态后,就可以根据自定义一些策略,选择接下来执行哪个进程。
在书中的例子中,在全局定义了一个变量 p_proc_ready,指向接下来要执行的进程的结构 头部。有了这个指针,如果要从内核切换到进程,可以直接把esp移动到STACK_FRAME B的部分,执行一系列push操作,把STACK_FRAME B入栈,执行iretd执行完成从内核到进程的跳转。然后设置好tss,使发生时钟中断时,esp指向被中断的进程的STACK_FRAME B部分,然后执行一系列push操作把STACK_FRAME A部分保存到进程的结构中,这样就保存了进程的状态。接着修改p_proc_ready使其指向不同的进程结构的头部,重复上面的过程,即实现多进程的切换。

3. 系统调用

这个与上述的时钟中断有些类似,只不过这里时自定义一个中断号,在IDT中注册一个handler,进程如果需要执行系统调用,这执行 int n进行。书中的系统调用,参数使用eax传递,调用的返回结果,也是通过eax传递。

4. 进程调度

这个是个比较大的话题,以后再做详细的研究。书中的调度相对简单,每个进程的结构,增加一个优先级属性,根据优先级属性分配不同的时间片。


遇到的问题

1. 在进程的结构中, 中间的kernel_esp,popad为什么会忽略掉?

好吧,popad指令就是这么设计的,为了防止修改当前的esp。
IF OperandSize = 32 (* instruction = POPAD *)
THEN
EDI ¬ Pop();
ESI ¬ Pop();
EBP ¬ Pop();
increment ESP by 4 (* skip next 4 bytes of stack *)
EBX ¬ Pop();
EDX ¬ Pop();
ECX ¬ Pop();
EAX ¬ Pop();
ELSE (* OperandSize = 16, instruction = POPA *)
DI ¬ Pop();
SI ¬ Pop();
BP ¬ Pop();
increment ESP by 2 (* skip next 2 bytes of stack *)
BX ¬ Pop();
DX ¬ Pop();
CX ¬ Pop();
AX ¬ Pop();
FI;
也就是说,popad指令,会忽略esp的恢复。

2. 内核中写的一些c代码,局部变量放在哪里?

代码跟踪的结果显示,C语言的局部变量,是放在进程的栈里。

3. leave指令

通常在进入函数中时有两条命令,如下:
push ebp ; 保存上一个函数的栈帧基地址
mov ebp,esp ; 设置新的函数栈帧基地址
在返回函数前通常有如下两条指令:
mov esp,ebp ; 将当前函数栈帧基地址保存到esp中
pop ebp ; 恢复上一个函数的栈帧基地址
Intel又设计了两条指令来简化上面的两个步骤,那就是ENTER和LEAVE指令。
leave指令就相当于 mov esp,ebp 和 pop ebp 两条指令的执行效果。而ENTER指令要麻烦一点。enter指令也有一个先天上的不足,那就是速度慢,这里不去了解,一般编译器生成代码时很少使用enter指令,倒是LEAVE指令经常被用到。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值