Linux进行进程调度一般有两种情况
1.允许进程运行的时间结束
2.进程的运行停止 比如当进程阻塞时需要等待外设提供数据 或者等待其他程序的运行结果 这时就会进行进程调度
这里是执行for(;;) pause(); 最终执行到schedule()函数切换到其他进程运行 这里切换到进程1
Linux创建了进程1 后开始进行进程调度
static inline _syscall0(int,pause)
void main(void) /* This really IS void, no error here. */
{
...
if (!fork()) { /* we count on this going ok */
init();
}
/*
* NOTE!! For any other task 'pause()' would mean we have to get a
* signal to awaken, but task0 is the sole exception (see 'schedule()')
* as task 0 gets activated at every idle moment (when no other tasks
* can run). For task0 'pause()' just means we go check if some other
* task can run, and if not we return here.
*/
for(;;) pause();
...
}
接下来是循环的运行pause() 函数 pause()也是系统调用 系统调用的过程和fork open类似 都是通过调用unistd.h的syscall0 通过int 0x80中断 在system_call.s的call _sys_call_table(,%eax,4)映射到sys_pause()的系统调用函数去执行
//unistd.h
int pause(void);
#define __NR_pause 29
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
替换后就是
int pause(void)
{
long __res; \
__asm__ volatile ("int $0x80"
: "=a" (__res)
: "0" (__NR_pause));
if (__res >= 0)
return (int) __res;
errno = -__res;
return -1;
}
这里int 0x80后就去system_call.s去执行调用 最后执行sys_pause()函数
int sys_pause(void)
{
current->state = TASK_INTERRUPTIBLE;//设置进程0为可中断等待状态
schedule(); //切换进程
return 0;
}
void schedule(void)
{
int i,next,c;
struct task_struct ** p;
/* check alarm, wake up any interruptible tasks that have got a signal */
//遍历所有进程 判断报警定时值alarm和信号位图进行处理 这里不会产生什么效果
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;
}
/* 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) //找出处于就绪态的进程和counter最大的那个
c = (*p)->counter, next = i;
}
if (c) break;
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) +
(*p)->priority;
}
switch_to(next); //切换到进程1
}
#define FIRST_TSS_ENTRY 4
#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
//FIRST_TSS_ENTRY<<3也就是100000 (unsigned long) n)<<4)对进程1是10000 所以_TSS(1)就是110000
//最后两位是特权级00表示内核特权级 第三位0表示GDT 110表示GDT的第六项 也即tss0的下标
#define switch_to(n) {\
struct {long a,b;} __tmp; \
__asm__("cmpl %%ecx,_current\n\t" \
"je 1f\n\t" \
"movw %%dx,%1\n\t" \
"xchgl %%ecx,_current\n\t" \
"ljmp %0\n\t" \
"cmpl %%ecx,_last_task_used_math\n\t" \
"jne 1f\n\t" \
"clts\n" \
"1:" \
::"m" (*&__tmp.a),"m" (*&__tmp.b), \
"d" (_TSS(n)),"c" ((long) task[n])); \
}
了解switch_to函数的话可以去看这篇博客讲的很清楚 我就不再重复一遍
http://blog.csdn.net/smallmuou/article/details/6837087
最重要的是ljmp %0\n\t这句 ljmp通过cpu的任务门机制从未实际使用任何任务门,将cpu的各个寄存器值保存在进程0的tss中,讲进程1的tss数据以及LDT的代码段,数据段描述符数据恢复给CPU的各个寄存器,实现从特权0的内核代码切换到3特权级的进程1代码执行。