进程的概念
进程是CPU上运行程序的抽象,每个进程有一个地址空间和控制线程,进程区别于程序,在于程序是静态的,进程是动态的,进程是系统进行资源分配和调度的基本单位。
有4种主要的事件导致进程被创建
(1)系统初始化;
(2)执行了正在运行的进程所调用的进程创建的系统调用(运行的进程通过系统调用请求创建新的进程);
(3)用户请求创建一个新进程;
(4)一个批处理作业的初始化。
进程的三种状态
(1)运行态,此时刻进程实际占用CPU;
(2)就绪态,万事具备,只欠CPU;
(3)阻塞态,等待外部事件发生,否则有了CPU也执行不了。
进程的实现
为了实现进程模型,操作系统维护者一张表格,成为进程表,每一个进程表项记录着进程的很多信息,包括:
(1)进程管理相关:寄存器,程序状态字,程序计数器,堆栈指针,进程状态,优先级,调度参数,进程ID,父进程,进程组,信号,进程开始时间,使用的CPU时间,下次报警时间等;
(2)存储管理相关:正文段指针,数据段指针,堆栈段指针;
(3)文件管理相关:根目录,工作目录,文件描述符,用户ID,组ID。
进程表存储在内存之中,正在运行进程的相关信息存储在CPU相应的进程之中。
线程
线程是程序执行的最小单元,多个线程共享同一个地址空间和其他资源。
用户级线程和内核级线程
用户级线程:
(1)线程在用户空间中,内核对线程一无所知,内核还是按照单线程进程的方式进行管理,因此明显的优点是用户级线程包可以在不支持线程的操作系统中使用;
(2)用户级的线程切换不需要陷入内核,比内核线程切换要快一个数量级;
(3)用户线程的另一个优点是,允许每个进程有自己定制的调度算法。
(1)一个线程被阻塞会导致所有的线程被阻塞;
(2)线程内部没有调度程序,除非一个线程主动让出CPU;
(3)CPU密集型而且不容易出现阻塞的应用,没有必要实现为多线程;
内核级线程:
(1)当一个线程阻塞时内核可以根据选择,运行另外的线程;
(2)内核级线程创建和撤销的成本比较大。
进程间通信
两个或多个进程读写某些共享的数据,最后的结果取决于进程运行的精确时序,称为竞争条件。
访问共享内存的片段称作临界区,只要两个进程不同时处于临界区,就能避免竞争条件。
一个好的解决竞争条件的方案,需要满足以下4个条件:
(1)任何两个进程不能同时处于临界区;
(2)不应对CPU速度和数量做任何假设;
(3)临界区外的进程不得阻塞其他进程;
(4)不得使进程无期限等待进入临界区。
解决竞争条件的几个方法
一、屏蔽中断
CPU只有在发生时间中断或其它中断时才会进行进程调度,如果在进程进入临界区后屏蔽中断,即可保证该进程一直运行直到出临界区,但是让用户来控制中断的方式不安全,另外对于多核系统而言,屏蔽单个CPU的中断不能保证运行在其他CPU上面的进程不访问临界区。
二、严格轮训
while(TRUE)
{
while(turn!=0)//循环等待
critical_region();
turn=1;
noncritical_region();
}
while(TRUE)
{
while(turn!=1)//循环测试等待
critical_region();
turn=0;
noncritical_region();
}
例如,当0号进程要进入临界区时,先判断临界区标识是否为0,为0才能进入,否则不停地测试等待;但是,考虑当0号进程进入临界区又完成任务出来后,1号进程没有进入临界区,由于第一次0号进程进去后自己把标识turn改为了1,此时0号进程也不能进入临界区。这种方法只能实现交替进炉临界区。
三、Peterson解法
#define FALSE 0;
#define TRUE 1;
#define N 2
int turn//现在轮到谁?
int intersted[N]//注册进程是否想进入临界区,初始化为0
void enter_region(int process)
{
int other=1-process;//另一进程
intersted[process]=TRUE;//表示想进入临界区
turn=process;//标志轮到谁
while(turn==process && intersted[other]==TRUE)//等待
}
void leave_region(int process)
{
intersted[process]=FALSE;
}
考虑当只有一个进程想进入临界区时,由于intersted[other]=FALSE,进程无需等待,直接进入临界区;当两个进程都想进入临界区时,考虑顺序依次为0,1,对于0号进程,他先把turn修改为0,但随后1号进程把该值改为1,因此0号进程不满足循环条件,可以直接进炉临界区,而1号进程需要等待。
四、TSL指令
硬件解法,读取Lock的值并修改该值为非0值,读和写的操作保证不可分割
逻辑为:
(1)读取锁变量的值,并修改该值为1;
(2)判断读取的值是否为0:是则直接进入临界区,否则重复(1),(2)。
五、信号量及PV操作
信号量是一个特殊变量,用于进程间传递消息的一个整数值,支持P操作和V操作。
P(s)
{
s.count--;
if(s.count<0)
{
执行P操作的进程被设置为阻塞状态;
将该进程插入相应的等待队列s.queue末尾;
重新调度;
}
}
V(s)
{
s.count++;
if(s.count<0)
{
唤醒相应等待队列中等待的一个进程;
改变其状态为就绪态,并将其插入就绪队列;
}
}
利用信号量处理互斥问题:
(1)初始化信号量s=1;
(2)在进入临界区前执行P操作,申请信号量;
(3)进入临界区
(4)离开临界区前执行V操作,释放信号量。
二元信号量主要用于解决互斥问题,多元信号量用于解决同步问题,可以用信号量解决生产者消费者问题
#define N 100
int mutex=1;
int full=0;
int empty=N;
void producer()
{
while(true)
{
produce_item(); //生产产品
P(&empty); //申请空格,不小于0才可以进行,否则阻塞
P(&mutex); //申请加锁
V(&mutex); //释放锁
V(&full); //full信号量加1
insert_item(); //放入产品
}
}
void customer()
{
while(true)
{
P(&full);//产品格子减一个;
P(&mutex);//申请加锁;
remove_item();
V(&empty)//空格数加1;
V(&mutex)//释放锁
consume_item()
}
}