结合源码的操作系统学习记录(2)--进程调度
-
调度程序
在 kernel/sched.c 中的 schedule 函数中实现。
首先扫描任务数组,在所有就绪态(TASK_RUNNING)的任务中找出时间滴答计数 counter 中找出值最大的那个任务(优先执行)。
如果此时所有就绪态进程的时间片都用完了,系统会根据没个进程的优先级(priority),对系统中所有的进程重新计算每个任务的时间片。
这样计算的话,当睡眠的进程被唤醒的时候,就会具有较高的时间片值,然后重复执行上述过程,最后调用 switch_to 执行进程切换。
void schedule(void) { int i,next,c; struct task_struct ** p; // 任务结构体指针 /* 检查 alarm,唤醒任何已经得到信号的可中断任务 */ for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) if (*p) { // 如果任务的 alarm 已经过期(alarm<jiffies),则在信号位图中指 SIGALRM 信号,然后清零 alarm 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; // 比较每个任务的 counter if ((*p)->state == TASK_RUNNING && (*p)->counter > c) c = (*p)->counter, next = i; } // 如果有 c 大于 0,也就是存在就绪态的任务,则跳出循环,执行任务切换 if (c) break; // 否则更新每一个任务的 counter,然后重新比较 for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) if (*p) (*p)->counter = ((*p)->counter >> 1) + (*p)->priority; } // 任务切换 switch_to(next); }
-
进程切换
在 include/asm/system.h 中的 switch_to() 宏中执行实际进程切换操作。
首先检查要切换到的进程是否是当前进行,不是的话执行后续操作。将全局变量 current 置为新任务的指针,然后跳转到新任务的 TSS 地址处,此时 CPU 会把其所有寄存器的状态保存到当前任务寄存器 TR 中 TSS 段选择符所指向的当前进程的 TSS 中,然后把新任务的 TSS 段选择符指向的新任务的 TSS 中的寄存器信息恢复到 CPU 中,开始执行新进程。
#define switch_to(n) {\ struct {long a,b;} __tmp; \ // ecx 是当前第 n 个进程对应的 pcb 首地址,通过比较 ecx 和 _current 判断下一个进程是不是当前进程 __asm__("cmpl %%ecx,_current\n\t" \ "je 1f\n\t" \ // 把第 n 个进程的 tss 复制到 __tmp.b "movw %%dx,%1\n\t" \ // current 指向 ecx,ecx 指向 task[n] "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])); \ }
-
终止进程
当一个进程结束后,内核会释放该进程所占用的系统资源,包括进程运行时打开的文件、申请的内存等。
exit 调用在 kernel/_exit.c/do_exit 中实现。函数首先释放进程占用内存,关闭进程打开的文件,对进程使用的文件系统的 i 节点进行同步。
int do_exit(long code) // code 是错误码 { int i; // 释放当前进程代码段和数据段所占的内存页 free_page_tables(get_base(current->ldt[1]),get_limit(0x0f)); free_page_tables(get_base(current->ldt[2]),get_limit(0x17)); // 如果该进程有子进程,则把子进程的父进程置为 1 号进程 for (i=0 ; i<NR_TASKS ; i++) if (task[i] && task[i]->father == current->pid) { task[i]->father = 1; // 如果子进程已经僵死(ZOMBIE)状态,则向进程 1 发送子进程终止信号 SIGCHLD if (task[i]->state == TASK_ZOMBIE) /* assumption task[1] is always init */ (void) send_sig(SIGCHLD, task[1], 1); } // 关闭当前进程打开的文件 for (i=0 ; i<NR_OPEN ; i++) if (current->filp[i]) sys_close(i); // 对文件系统进行同步操作 iput(current->pwd); current->pwd=NULL; iput(current->root); current->root=NULL; iput(current->executable); current->executable=NULL; // 如果该进程是一个 leader 并有控制终端,则释放该终端 if (current->leader && current->tty >= 0) tty_table[current->tty].pgrp = 0; // 如果进程上次用过协处理器,则将 last_task_used_math 置空 if (last_task_used_math == current) last_task_used_math = NULL; // 如果是 leader,中止所有相关进程 if (current->leader) kill_session(); // 设置当前进程为僵死状态,并向父进程发送 SIGCHLD current->state = TASK_ZOMBIE; current->exit_code = code; tell_father(current->father); // 重新调度 schedule(); return (-1); /* just to suppress warnings */ }
跟进 kill_session 中看一下具体的中止操作
static void kill_session(void) { // p 指针指向任务数组的最末端 struct task_struct **p = NR_TASKS + task; // 除 0 号进程外,如果进程的 session 等于当前进程的 session,就发送挂断进程信号 while (--p > &FIRST_TASK) { if (*p && (*p)->session == current->session) (*p)->signal |= 1<<(SIGHUP-1); } }
这里的会话(session)是进程组的集合,在 linux 中通过 sys_setsid 建立一个 session
int sys_setsid(void) { // 已经是 leader 进程并且不是超级用户则直接退出 if (current->leader && !suser()) return -EPERM; // 标记 leader current->leader = 1; // 设置 sessionid 和 组id 为该进程的 pid current->session = current->pgrp = current->pid; // 表示当前进程没有控制终端 current->tty = -1; return current->pgrp; }
跟进 tell_father,看看 tell 了些什么
static void tell_father (int pid) { int i; // 父进程向子进程发送 SIGCHLD if (pid) for (i = 0; i < NR_TASKS; i++) { if (!task[i]) continue; if (task[i]->pid != pid) continue; task[i]->signal |= (1 << (SIGCHLD - 1)); return; } /* if we don't find any fathers, we just release ourselves */ /* This is not really OK. Must change it to make father 1 */ printk ("BAD BAD - no father found\n\r"); release (current); // 如果没有找到父进程,则自己释放。 }
跟进释放操作 release
void release(struct task_struct * p) { int i; if (!p) return; // 找到要释放的任务 for (i=1 ; i<NR_TASKS ; i++) if (task[i]==p) { // 指控该任务向并释放相关内存页 task[i]=NULL; free_page((long)p); // 重新调度 schedule(); return; } panic("trying to release non-existent task"); }