进程与线程的关系和区别
进程是正在运行的程序,按照进程中的线程数量划分,进程分为单线程进程和多线程进程。线程就是执行流,平时我们所写的程序,如果其中是“显示”创建线程,它就属于单线程进程。所以存粹的进程实际上就是单线程进程。线程是后来才提出的概念,本来都是单线程进程只有一个执行流。后来一个进程中多了执行流,就把这些执行流叫做线程。线程实质上是一段引导处理器执行的、具有能动性的代码。
线程必然属于某一个进程,线程要运行必须要有相应的资源,而进程就是这个资源的提供者,所以线程存在于进程中。所以进程=线程+资源。
进程拥有整个地址空间,其中包括各种资源,而进程中的所有线程共享同一个地址空间。只有线程才具有能动性,它才是处理器的执行单元,因此它是调度器眼中的调度单元。进程只是一个资源整合体,它将进程中所有线程运行时用到的资源收集在一起,真正上处理器运行的其实是线程,进程中的线程才是一个个的执行实体、执行流。
总结:线程是具有能动性、执行力、独立的代码块。进程=线程+资源。执行流、调度单元、运行实体都是针对线程而言的,线程才是解决问题的思路、步骤,只有它才能上处理器运行。进程和线程的区别是线程没有自己独享的资源,因此没有自己的地址空间,它要依附在进程的地址空间中从而借助进程的资源运行。说白了就是线程没有自己的页表,而进程有。
进程、线程的状态
操作系统把执行过程所经历的不同阶段按状态分为:阻塞态、就绪态、运行态。状态描述的是执行流,也就说状态是描述线程的。我们一般说的进程状态都是指的是单线程的进程。
PCB
操作系统为每一个进程都提供了一个PCB:程序控制块,它记录着与此进程相关的信息:进程状态、优先级、PID等等。
每个进程都有自己的PCB,所有的PCB都放在一张表格中维护,这就是进程表。调度器根据这个表来选择处理器上运行的进程。我们设计的系统中PCB只占一页:4K。
实现线程的两种方式--内核或用户进程
线程要么在0特权级的内核空间中实现,要么在3特权级的用户空间实现。
我们的系统在内核中实现。
中断栈
功能:因为栈是在PCB中的,当发生中断时,通过要设置中断栈,将寄存器都备份到中断栈中。调度单元调度每个线程是根据每个线程的时间片来调度的,每一次时钟中断线程的时间片减1,减到0时就调用调度函数将 当前线程换下处理器,将下一个线程换上处理器,重新开始中断然后减1。
struct intr_stack {
uint32_t vec_no; // kernel.S 宏VECTOR中push %1压入的中断号
uint32_t edi;
uint32_t esi;
uint32_t ebp;
uint32_t esp_dummy; // 虽然pushad把esp也压入,但esp是不断变化的,所以会被popad忽略
uint32_t ebx;
uint32_t edx;
uint32_t ecx;
uint32_t eax;
uint32_t gs;
uint32_t fs;
uint32_t es;
uint32_t ds;
/* 以下由cpu从低特权级进入高特权级时压入 */
uint32_t err_code; // err_code会被压入在eip之后
void (*eip) (void);
uint32_t cs;
uint32_t eflags;
void* esp;
uint32_t ss;
};
注意顺序:结构体先声明的元素地址位比较低。我们要根据实际的压栈情况来设置元素。
线程栈
一个线程的运行的目的就是执行这个线程的代码,执行代码就要有代码的地址,所以线程栈的功能:记录这个线程执行的代码地址。当首次调度这个线程时候,线程栈记录的是该线程中代码中的第一个要执行的函数地址,当不是第一次执行时候,里面记录的是上次被换下线程时候执行到的地址,即 jmp intr_exit 这行代码的地址,永远都是,因为被换下时是在shedule() 中执行到 switch to 函数被换下的,这个函数的下一条代码句子要