当前位置: 技术问答>linux和unix
关于操作系统进程调度
来源: 互联网 发布时间:2016-05-19
本文导语: 操作系统的主要功能之一是处理机分配,就是将某个进程分配到处理上运行,我的疑问是操作系统是在处理机上运行的,操作系统如何将进程分配到处理机上的啊,操作系统将进程分配到处理机上后,操作系统是啥状...
操作系统的主要功能之一是处理机分配,就是将某个进程分配到处理上运行,我的疑问是操作系统是在处理机上运行的,操作系统如何将进程分配到处理机上的啊,操作系统将进程分配到处理机上后,操作系统是啥状态,操作系统是阻塞?(单处理机系统)
|
进程切换过程
从一个进程的上下文切换到另一个进程的上下文,因为其发生频率很高,所以通常都是调度器效率高低的关键。在Linux中,这一功能是以一段经典的汇编代码实现的,此处就着力描述这段代码。
这段名为switch_to()的代码段在schedule()过程中调用,以一个宏实现:
/* 节选自[include/asm-i386/system.h] */
#define switch_to(prev,next,last) do {
asm volatile("pushl %%esint"
"pushl %%edint"
"pushl %%ebpnt" 保存esi、edi、ebp寄存器
"movl %%esp,%0nt" esp保存到prev->thread.esp中
"movl %3,%%espnt" 从next->thread.esp恢复esp
"movl $1f,%1nt" 在prev->thread.eip中保存"1:"的跳转地址,当prev被再次切换到的时候将从那里开始执行
"pushl %4nt" 在栈上保存next->thread.eip,__switch_to()返回时将转到那里执行,即进入next进程的上下文
"jmp __switch_ton" 跳转到__switch_to(),进一步处理(见下)
"1:t"
"popl %%ebpnt"
"popl %%edint"
"popl %%esint" 先恢复上次被切换走时保存的寄存器值,再从switch_to()中返回。
:"=m" (prev->thread.esp), %0
"=m" (prev->thread.eip),%1
"=b" (last) ebx,因为进程切换后,恢复的栈上的prev信息不是刚被切换走的进程描述符,因此此处使用ebx寄存器传递该值给prev
:"m" (next->thread.esp), %3
"m" (next->thread.eip), %4
"a" (prev), "d" (next), eax,edx
"b" (prev)); ebx
} while (0)
进程切换过程可以分成两个阶段,上面这段汇编代码可以看作第一阶段,它保存一些关键的寄存器,并在栈上设置好跳转到新进程的地址。第二阶段在switch_to()中启动,实现在__switch_to()函数中,主要用于保存和更新不是非常关键的一些寄存器(以及IO操作许可权映射表ioperm)的值:
unlazy_fpu(),如果老进程在task_struct的flags中设置了PF_USEDFPU位,表明它使用了FPU,unlazy_fpu()就会将FPU内容保存在task_struct::thread中;
用新进程的esp0(task_struct::thread中)更新init_tss中相应位置的esp0;
在老进程的task_struct::thread中保存当前的fs和gs寄存器,然后从新进程的task_struct::thread中恢复fs和gs寄存器;
从新进程的task_struct::thread中恢复六个调试寄存器的值;
用next中的ioperm更新init_tss中的相应内容
switch_to()函数正常返回,栈上的返回地址是新进程的task_struct::thread::eip,即新进程上一次被挂起时设置的继续运行的位置(上一次执行switch_to()时的标号"1:"位置)。至此转入新进程的上下文中运行。
从一个进程的上下文切换到另一个进程的上下文,因为其发生频率很高,所以通常都是调度器效率高低的关键。在Linux中,这一功能是以一段经典的汇编代码实现的,此处就着力描述这段代码。
这段名为switch_to()的代码段在schedule()过程中调用,以一个宏实现:
/* 节选自[include/asm-i386/system.h] */
#define switch_to(prev,next,last) do {
asm volatile("pushl %%esint"
"pushl %%edint"
"pushl %%ebpnt" 保存esi、edi、ebp寄存器
"movl %%esp,%0nt" esp保存到prev->thread.esp中
"movl %3,%%espnt" 从next->thread.esp恢复esp
"movl $1f,%1nt" 在prev->thread.eip中保存"1:"的跳转地址,当prev被再次切换到的时候将从那里开始执行
"pushl %4nt" 在栈上保存next->thread.eip,__switch_to()返回时将转到那里执行,即进入next进程的上下文
"jmp __switch_ton" 跳转到__switch_to(),进一步处理(见下)
"1:t"
"popl %%ebpnt"
"popl %%edint"
"popl %%esint" 先恢复上次被切换走时保存的寄存器值,再从switch_to()中返回。
:"=m" (prev->thread.esp), %0
"=m" (prev->thread.eip),%1
"=b" (last) ebx,因为进程切换后,恢复的栈上的prev信息不是刚被切换走的进程描述符,因此此处使用ebx寄存器传递该值给prev
:"m" (next->thread.esp), %3
"m" (next->thread.eip), %4
"a" (prev), "d" (next), eax,edx
"b" (prev)); ebx
} while (0)
进程切换过程可以分成两个阶段,上面这段汇编代码可以看作第一阶段,它保存一些关键的寄存器,并在栈上设置好跳转到新进程的地址。第二阶段在switch_to()中启动,实现在__switch_to()函数中,主要用于保存和更新不是非常关键的一些寄存器(以及IO操作许可权映射表ioperm)的值:
unlazy_fpu(),如果老进程在task_struct的flags中设置了PF_USEDFPU位,表明它使用了FPU,unlazy_fpu()就会将FPU内容保存在task_struct::thread中;
用新进程的esp0(task_struct::thread中)更新init_tss中相应位置的esp0;
在老进程的task_struct::thread中保存当前的fs和gs寄存器,然后从新进程的task_struct::thread中恢复fs和gs寄存器;
从新进程的task_struct::thread中恢复六个调试寄存器的值;
用next中的ioperm更新init_tss中的相应内容
switch_to()函数正常返回,栈上的返回地址是新进程的task_struct::thread::eip,即新进程上一次被挂起时设置的继续运行的位置(上一次执行switch_to()时的标号"1:"位置)。至此转入新进程的上下文中运行。
|
进程A在运行的时候,OS并不是无事可做,在那闲着的!A需要的内存空间,文件读写都是通过系统调用
,由OS在底层来实现的,别忘了操作系统的五大管理功能,A是不能直接越过OS内核直接读写硬件的!
针对楼主的问题,如果当前没有用户进程在运行,默认会执行系统的一个空闲进程,通常叫Idle进程,这个进程
通常什么都不做,仅仅是维护一个计数器而已...
,由OS在底层来实现的,别忘了操作系统的五大管理功能,A是不能直接越过OS内核直接读写硬件的!
针对楼主的问题,如果当前没有用户进程在运行,默认会执行系统的一个空闲进程,通常叫Idle进程,这个进程
通常什么都不做,仅仅是维护一个计数器而已...
|
并不只是时钟中断会发生调度,任何一个系统调用进入了内核态就可能会发生调度了,比如write, read
|
如果没有任何系统调用,OS什么也不做,直到时间片用来,OS重新调度
|
有内存与外存就绪状态
|
进程A运行完是以什么为标志呢?main()函数返回了,这也就到系统内核去了。所以OS又得到了控制权, 可以重新找一个新的就绪程序运行。
|
进入内存进入就绪状态
得到处理机进入运行状态
得到处理机进入运行状态
|
OS为了标识进程,定义了一个叫 进程控制块 的数据结构,通常简称为PCB或TCB,在linux 0.11里面的定义如下:
2.6版本的内核里,这个结构的定义很长,就不贴了,有兴趣自己下载内核源码看吧。
然后还有几个list的列表,里面分别保存着当前处于不同状态的进程。当发生进程切换的时候(由多个可能的条
件触发),会首先修改当前这个进程的PCB内部的某些数据,然后将它丢到相应的list里面去,比如挂起队列等。
然后会搜索当前处于就绪状态的队列,找到队列里面处于第一个结点的进程,然后运行它...
大概的过程就是这样,其中内部的例如寄存器的压栈和恢复,内存区域的设置等细节就不说,楼主自己看书吧...
2.6版本的内核里,这个结构的定义很长,就不贴了,有兴趣自己下载内核源码看吧。
struct task_struct {
/* these are hardcoded - don't touch */
long state; /* -1 unrunnable, 0 runnable, >0 stopped */
long counter;
long priority;
long signal;
struct sigaction sigaction[32];
long blocked; /* bitmap of masked signals */
/* various fields */
int exit_code;
unsigned long start_code,end_code,end_data,brk,start_stack;
long pid,father,pgrp,session,leader;
unsigned short uid,euid,suid;
unsigned short gid,egid,sgid;
long alarm;
long utime,stime,cutime,cstime,start_time;
unsigned short used_math;
/* file system info */
int tty; /* -1 if no tty, so it must be signed */
unsigned short umask;
struct m_inode * pwd;
struct m_inode * root;
struct m_inode * executable;
unsigned long close_on_exec;
struct file * filp[NR_OPEN];
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
struct desc_struct ldt[3];
/* tss for this task */
struct tss_struct tss;
};
然后还有几个list的列表,里面分别保存着当前处于不同状态的进程。当发生进程切换的时候(由多个可能的条
件触发),会首先修改当前这个进程的PCB内部的某些数据,然后将它丢到相应的list里面去,比如挂起队列等。
然后会搜索当前处于就绪状态的队列,找到队列里面处于第一个结点的进程,然后运行它...
大概的过程就是这样,其中内部的例如寄存器的压栈和恢复,内存区域的设置等细节就不说,楼主自己看书吧...
|
楼主的意思是进程的切换是由操作系统(os)做的,假设进程A被调到到处理机上运行,进程A在处理机上运行时,os在做什么?
|
我的理解是:程序并不是连续执行的, 在每个时钟中断后执行中断代码, 然后完成操作系统的调度功能
还请高手传道
还请高手传道
|
。。没懂楼主说啥
|
是先进入就绪态才有可能被重新装载内存。