注意,下文提到的任务和进程是指同一个概念
1、 相关数据结构和寄存器
a、 任务控制块:
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;//任务的可执行文件i节点
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;
};
此结构保存了当前任务的相关数据,用于操作系统对其进行管理。
b、 任务状态段
struct tss_struct {
long back_link; /* 16 high bits zero */
long esp0;
long ss0; /* 16 high bits zero */
long esp1;
long ss1; /* 16 high bits zero */
long esp2;
long ss2; /* 16 high bits zero */
long cr3;//页目录基地址寄存器
long eip;
long eflags;
long eax,ecx,edx,ebx;
long esp;
long ebp;
long esi;
long edi;
long es; /* 16 high bits zero */
long cs; /* 16 high bits zero */
long ss; /* 16 high bits zero */
long ds; /* 16 high bits zero */
long fs; /* 16 high bits zero */
long gs; /* 16 high bits zero */
long ldt; /* 16 high bits zero *///任务的ldt段选择符。
long trace_bitmap; /* bits: trace 0, bitmap 16-31 */
struct i387_struct i387;
};
此结构保存了任务的处理器状态,指定了构成任务执行空间的各个段
c、 任务结构指针数组
struct task_struct * task[NR_TASKS] = {&(init_task.task), };
一共可以管理64个任务(NR_TASKS = 64)
d、 任务寄存器
TR(任务寄存器)中存放着16位的TSS段选择符及当前任务TSS段的整个描述符(TR的不可见部分)。这些信息是从GDT中当前任务的TSS描述符中复制过来的。在系统初始化期间加载一次TR寄存器的初值(如,任务0的TSS段选择符),随后在系统运行期间,TR内容会在任务切换时自动地被改变。
2、 任务状态
TASK_RUNNING:任务正在运行或已准备就绪
TASK_INTERRUPTIBLE:任务处于可中断等待状态
TASK_UNINTERRUPTIBLE:任务处于不可中断等待状态
TASK_ZOMBIE:任务处于僵死状态,已经停止运行,但父进程还没发信号
TASK_STOPPED:进程已停止
3、 任务的创建
当前任务通过fork()系统调用,首先在任务数组task[NR_TASKS]中寻找一个空项,并取得进程号(任务号,即在任务数组中的位置0 <= n <= NR_TASKS),然后复制当前的任务控制块到新的任务控制块中,并对新任务控制块中的内容进行一些修改,包括根据任务新任务号计算新任务的ldt选择符(new->tss.ldt),把运行时间片数设置为其优先级等。然后根据新任务的任务号、task.tss的地址和task.ldt的地址在GDT中设置新任务TSS段和LDT段描述符项,并置新任务为就绪态。
当新的任务创建成功后,便可通过execve系统调用加载新的可执行文件执行。调用execve时并没有真正把可执行文件加载到内存中,只是把可执行文件头部信息读到高速缓冲块中,然后修改当前任务的任务控制块中相关数据。接着把调用execve系统调用进程在堆栈上的中断返回地址修改为新的可执行文件的执行入口地址。等中断返回执行新的可执行文件指令时,系统会产生缺页异常,于是异常服务程序便会从硬盘上加载该指令所在页到内存页中,并进行物理地址到虚拟地址的映射,在页目录和页表中添加相应的项。这样做可避免在执行新的可执行文件前大量的硬盘I/O操作,提高了创建进程的效率。
4、 退出任务
通过调用系统调用exit可退出本进程,也可通过向其进程发送信号SIGKILL,不过这使得程序没有任何机会进行清理工作。在调用exit退出进程时,会释放其所占用的一切资源,并通知其父进程,然后执行任务调度,运行其他进程。
5、 任务调度
调度时,首先唤醒获得信号的处于可中断等待状态的任务,然后在任务数组task[NR_TASKS]中查找处于就绪态的任务(任务号),在所有就绪太任务中所剩时间片最多的任务将被切换运行。如果有处于运行态的任务,但所有任务的时间片都用完了,则重新每一个任务设置时间片,并重新寻找任务用于切换。如果是没有任务处于运行态,则切换到任务0运行。
注意,任务0是个空闲(idle)任务,只有当没有其他任务可以运行时才调用它。它不能被杀死,也不能睡眼。
切换任务时,首先检查新任务是不是当前任务,若是则什么也不做就退出。若不是则根据任务号计算出在GDT中新任务的TSS段描述符的选择符(16位,偏移量),然后把当前任务设置为新任务,并通过ljmp指令,跳转到新任务的TSS段选择符,从而造成任务切换到该TSS对应的进程中。
6、总结
任务的管理主要依赖于任务控制块(任务描述符)和任务状态段这两个数据结构,其中任务控制块主要用于系统对任务的操作,而任务状态段主要用于任务切换时CPU进行状态保存。