一. 进程1的创建
进程1是由进程0创建的,创建过程如下:
main() { fork() } ---> _syscall0(int,fork) ---> int 0x80中断进入系统调用(特权级由3切换到0) ---> _sys_call --->sys_fork() ---> find_empty_process()为进程申请一个可用的进程号pid ---> copy_process(),在该函数中初始化创建的进程1的TSS结构,将tss段插入到gdt描述符表中,并设置好TSS的段基址和段长度。
进程1的创建主要是通过copy_process()函数来完成,copy_process()函数主要完成以下任务:
(1)调用get_free_page()函数,在主内存申请一个空闲页面,并将申请的空闲页面清零,用于进程1的task_struct及内核栈。
(2)将父进程0的task_struct的内容复制给进程1的task_struct
(3)为进程1的task_struct、tss做个性化设置:特别要注意,将进程1的tss.eip设置为创建过程中int 0x80中断程序执行时,保护现场压入栈中的EIP了,这一点会影响后面轮转后,进程1的执行。
(4)为进程1创建第一个页表,将进程0的页表项内容赋给这个页表
(5)进程1共享进程0的文件
(6)设置进程1的GDT项
(7)最后将进程1设置为就绪状态,使其可以参与进程间的轮转
代码如下:
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
long ebx,long ecx,long edx,
long fs,long es,long ds,
long eip,long cs,long eflags,long esp,long ss)
//参数是int 0x80、system_call、sys_fork调用时多次累积压栈的结果
{
struct task_struct *p;
int i;
struct file *f;
p = (struct task_struct *) get_free_page();
if (!p)
return -EAGAIN;
task[nr] = p;
//将父进程的task_struct内容拷贝到子进程
*p = *current; /* NOTE! this doesn't copy the supervisor stack */ //curent是task_struct类型,并未包含task_union联合体中的stack部分,此处的赋值相当于拷贝内容
//设置子进程
p->state = TASK_UNINTERRUPTIBLE;
p->pid = last_pid;
p->father = current->pid;
p->counter = p->priority;
p->signal = 0;
p->alarm = 0;
p->leader = 0; /* process leadership doesn't inherit */
p->utime = p->stime = 0;
p->cutime = p->cstime = 0;
p->start_time = jiffies;
//设置子进程的TSS,所用到的数据都是前面程序压栈形成的参数
p->tss.back_link = 0;
p->tss.esp0 = PAGE_SIZE + (long) p; //esp0是内核栈指针
p->tss.ss0 = 0x10; //0x10,段选择符:0特权级、GDT、数据段
p->tss.eip = eip; //EIP指向_syscall0(int,fork)调用中的if(__res>0),会影响后面进程切换后(switch_to(n)执行完ljmp %0)的程序的执行
p->tss.eflags = eflags;
p->tss.eax = 0; //决定main()函数中if(!fork())后面的分支语句
p->tss.ecx = ecx;
p->tss.edx = edx;
p->tss.ebx = ebx;
p->tss.esp = esp;
p->tss.ebp = ebp;
p->tss.esi = esi;
p->tss.edi = edi;
p->tss.es = es & 0xffff;
p->tss.cs = cs & 0xffff;
p->tss.ss = ss & 0xffff;
p->tss.ds = ds & 0xffff;
p->tss.fs = fs & 0xffff;
p->tss.gs = gs & 0xffff;
p->tss.ldt = _LDT(nr); //挂接子进程的LDT
p->tss.trace_bitmap = 0x80000000;
if (last_task_used_math == current)
__asm__("clts ; fnsave %0"::"m" (p->tss.i387));
if (copy_mem(nr,p)) { //设置子进程的代码段、数据段及创建、复制子进程的第一个页表
task[nr] = NULL;
free_page((long) p);
return -EAGAIN;
}
for (i=0; i<NR_OPEN;i++)
if (f=p->filp[i])
f->f_count++;
if (current->pwd)
current->pwd->i_count++;
if (current->root)
current->root->i_count++;
if (current->executable)
current->executable->i_count++;
set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss)); //FIRST_TSS_ENTRY = 4对应GDT表中TSS0
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
p->state = TASK_RUNNING; /* do this last, just in case */
return last_pid;
}
其中copy_process()输入参数都是之前入栈的数据,按照顺序如下:
1)内联函数_syscall0(int,fork) ---> 执行int 0x80中断服务程序:为中断返回保护现场,CPU硬件自动将ss esp eflags cs eip压栈
2)_system_call ---> sys_fork():程序将ds es fs edx ecx ebx压栈
3)sys_fork() ---> copy_process(): 将gs esi edi ebp eax压栈,其中eax将会作为nr
二. 进程1的轮转
进程跳转流程:
当进程1创建完成后会回到main()中,执行main()函数中的pause()函数 --- >_syscall0(int,pause) --->int 0x80中断进入系统调用 ---> _sys_call --->sys_pause() ---> schedule() --- switch_to() 完成进程切换。
下面解析一下进程调度部分的代码:
引用两篇文章:http://blog.csdn.net/fukai555/article/details/41912735
http://blog.csdn.net/smallmuou/article/details/6837087
//代码路径:/kernel/sched.c
void schedule(void)
{
int i,next,c;
struct task_struct ** p;
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) //LAST_TASK = task[63]: task[63]是struct task_struct*类型
if (*p) {
if ((*p)->alarm && (*p)->alarm < jiffies) { //如果设置了定时或定时时间片已过
(*p)->signal |= (1<<(SIGALRM-1)); //设置SIGALRM:SIGALRM是定时器终止时发送给进程的信号
(*p)->alarm = 0;
}
if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
(*p)->state==TASK_INTERRUPTIBLE)
(*p)->state=TASK_RUNNING;
}
while (1) {
c = -1;
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];
while (--i) { //找出counter值最大的,state为TASK_RUNNING的进程
if (!*--p)
continue;
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i;
}
if (c) break;
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) + //设置进程初始时时间片大小为: 进程priority值(初始时counter = 0)
(*p)->priority;
}
switch_to(next);
}
//代码路径:/include/sched.h
#define FIRST_TSS_ENTRY 4
#define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3)) //生成TSS的段选择符
#define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))
#define switch_to(n) {\
struct {long a,b;} __tmp; \ //为ljmp的CS、EIP准备的数据结构
__asm__("cmpl %%ecx,_current\n\t" \ //比较进程n与当前进程
"je 1f\n\t" \ //如果相等,就没必要切换,退出
"movw %%dx,%1\n\t" \ //把edx低字部分赋给tmp.b
"xchgl %%ecx,_current\n\t" \ //task[n]与task[current]交换
"ljmp %0\n\t" \ //
"cmpl %%ecx,_last_task_used_math\n\t" \ //比较上次是否使用过协处理器
"jne 1f\n\t" \
"clts\n" \ //清除CR0中的任务切换标志
"1:" \
::"m" (*&__tmp.a),"m" (*&__tmp.b), \ //a对应EIP,b对应CS
"d" (_TSS(n)),"c" ((long) task[n])); \ //edx是TSSn的索引号,ecx是task[n]
}
说明:
(1)_TSS(n): 生成TSS的段选择符
TI=0 :表示段描述符在GDT中
TI=1 :表示段选择符在LDT中
RPL:表示优先级
(2)GDT表
(3)进程切换过程:switch_to(n)
首先判断传递进来的任务号n是否是当前任务号,如果是就直接跳转到1,跳出;否则,将current指向task[n]。在将current指向task[n]之前将edx的低字部分即task[n]的TSS的段选择符赋给__tmp.b的低16位,然后将current指向task[n],之后执行ljmp %0\n\t,按照语法格式理解相当于 ljmp *__tmp.a,也就是跳转到地址&__tmp.a中包含的48bit逻辑地址(因为使用内存操作数实现长跳转时:ljmp *mem48,其中内存位置mem48处存放目标逻辑地址: 高16bit存放的是seg_selector,低32bit存放的是offset)。
ljmp %0
在保护模式下CPU进行长跳转,如果发现段选择符指向TSS段,那么CPU将会忽略偏移,自动将TSS段的内容加载到当前CPU寄存器中,比如eax,ldr,cs,ss,esp,eip等,其实就是104字节的TSS结构的所有成员变量赋值给CPU寄存器(注意这里是ss0和esp0),从而任务切换到该TSS对应的进程。再次强调这个过程是自动完成的。
注意,执行完ljmp %0后,后面的 cmpl %%ecx,_last_task_used_math还没执行,int 0x80中断没有返回,CPU就开始执行进程1了。现在CPU中的EIP是copy_process()中为进程1的tss.eip,里面的值是:内联函数_syscall0(int,fork) ---> 执行int 0x80中断服务程序:为中断返回保护现场,CPU硬件自动将ss esp eflags cs eip压栈,时的值,因此进程1接下来会去执行fork中int 0x80中断返回时下一行代码,即if(__res>=0),所以之后将从这行代码开始执行,不会像创建进程1那样原路返回。