进程 和 线程都是动态概念
进程 = 资源(包括寄存器值,PCB,内存映射表)+指令序列
线程 = 指令序列
线程的资源是共享的
进程间的资源是独立隔离的,内存映射表不同,占用物理内存地址是分割的
线程的切换只是切换PC,切换了指令序列
进程的切换不仅切换PC,还包括切换资源,即切换内存映射表
用户级线程:调用yield函数,自己主动让出CPU,0.11内核看不见,内核只能看见所属进程而看不见用户线程,所以一个用户级线程需要等待,内核会切到别的进程上,不会切到该进程下的其他用户级线程!!
内核级线程:内核能看见内核级线程,一个线程需要等待,内核会切到所属进程下的其他内核级线程。
内核级线程:
只有内核级线程才能发挥多核性能,因为多核级线程共用一套MMU,即内存映射表,但是有多个CPU,可以一个CPU执行一个内核级线程
进程无法发挥多核性能,因为进程切换都得切MMU,即切换内存映射表,一个内核级线程的切换需要两套栈:用户栈+内核栈
系统调用中断过程:
1,INT中断自动压栈的有下一条指令,以及用户级线程SS:SP,就是下面五个参数
2,_system_call把寄存器保护压栈是压到内核栈中,需要手动压栈
3,系统调用,(有可能是_sys_fork,其实就是根据标号找到系统调用),结束之后继续执行,要执行reschedule,先push $ret_from_sys_call,让其在_schedule之后返回到ret_from_sys_call,_schedule为c函数,结束右括号会把ret_from_sys_call pop出来,返回到这里执行,即执行ret_from_sys_call;
4,在ret_from_sys_call中pop出_system_call是保存的寄存器内容,然后中断返回!!
5,中断返回是在最后,中断返回会把SS:SP以及用户态的下一条指令POP出来,即把5个寄存器pop出来!!!这样就会返回到用户栈,运行用户态的下一条指令!!!
代码:
reschedule:
pushl $ret_from_sys_call
jmp _schedule //如果用call就是先将下一条指令压栈,然后jmp,这样只能顺序往下走,但是用push+jmp则可以改变跳转地址!!
switch_to五段论
在“实地址模式”中,IRET 指令执行到中断程序或过程的远返回。在执行此操作的过程中,处理器从堆栈将返回指令指针、返回代码段选择器以及 EFLAGS 映像分别弹入 EIP、CS 以及 EFLAGS 寄存器,然后恢复执行中断的程序或过程。
这里要注意,中断出口这里已经经过了前面的switch_to,中断的iret已经不是原来的中断返回了,是切换后的新中断的执行返回!!这样返回以后就来到了引发该新中断的用户态代码来执行。
内核级线程切换实例
fork函数经典调用
if (!fork()) //关键在于 INT 80 后面的指令 mov res, %eax;父进程和子进程都会执行这个代码,但是%eax的值不一样
{
子进程
}
else
{
父进程
}
fork创建子进程时,copy_process复制父进程相应资源,申请内存空间,创建内核栈,创建用户栈。
eip[0] 赋值为新的程序的开始执行内存地址(可执行程序ELF结构里有entry地址),实际是赋值给esp+28地址处。
eip[3] 赋值为新的程序的用户栈,实际是赋值给esp+40地址处。
汇编调用c函数
实际上是把参数都压到栈里,然后c程序就可以调用,用call来调用
但是要注意c语言 调用结束后,要把栈里的参数删除,即addl指令,吧指针改一下,忽略那些参数
实际上是
push 参数
push 返回值
jmp 调用地址
c函数右括号会生成ret指令,会返回到返回地址
cpu调度算法
周转时间:从开始申请执行任务,到执行任务完成
响应时间:从开始申请执行任务到开始执行任务
三种调度方法:
调度算法:考虑优先级,任务类型,响应时间,周转时间,调度算法要简单些,内耗低。
1,先来先服务 平均周转时间可能会很长
2,短作业优先(SJF) 周期时间短,但是响应时间长,适用于后台程序,如gcc的编译,快点把整个程序编译完
3,时间片轮转(RR) 响应时间可以得到保证,nT,n为任务个数,T为时间片长度,适用于前台程序,IO操作多的
4,优先级轮转 固定优先级,可能会造成程序一直没法执行下去,需要动态调整优先级
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));
(*p)->alarm = 0;
}
if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
(*p)->state==TASK_INTERRUPTIBLE)
{
(*p)->state=TASK_RUNNING;
/*可中断睡眠 => 就绪*/
fprintk(3,"%d\tJ\t%d\n",(*p)->pid,jiffies);
}
}
/* 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)
c = (*p)->counter, next = i;
}
//首先找到counter最大并且TASK_RUNNING的进程作为调度进程
//counter 可以作为时间片和优先级的表达
if (c) break;
//如果所有进程的counter为0,则增加阻塞进程的counter
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) +
(*p)->priority;
}
/*编号为next的进程 运行*/
if(current->pid != task[next] ->pid)
{
/*时间片到时程序 => 就绪*/
if(current->state == TASK_RUNNING)
fprintk(3,"%d\tJ\t%d\n",current->pid,jiffies);
fprintk(3,"%d\tR\t%d\n",task[next]->pid,jiffies);
}
switch_to(next);
}
void do_timer(long cpl)
{
extern int beepcount;
extern void sysbeepstop(void);
if (beepcount)
if (!--beepcount)
sysbeepstop();
if (cpl)
current->utime++;
else
current->stime++;
if (next_timer) {
next_timer->jiffies--;
while (next_timer && next_timer->jiffies <= 0) {
void (*fn)(void);
fn = next_timer->fn;
next_timer->fn = NULL;
next_timer = next_timer->next;
(fn)();
}
}
if (current_DOR & 0xf0)
do_floppy_timer();
//时间中断,减少counter,近似RR轮转算法,考虑到SJF短左右优先
if ((--current->counter)>0) return;
current->counter=0;
if (!cpl) return;
schedule();
}