赞
踩
有4种事件导致进程的创建:
新进程都是由一个已存在的进程执行了一个用于创建进程的系统调用而创建的。在UNIX中,只有一个系统调用可以创建新进程:fork。在调用fork之后,这两个进程拥有相同的存储映像、同样的环境字符串和相同的打开文件。通常,子进程接着执行execve或一个类似的系统调用,以修改其存储映像并执行一个新的程序。
进程创建之后,父进程和子进程各自拥有不同的地址空间。
进程在操作系统中存在三种状态:
为了实现进程模型,操作系统维护着一张表,称为进程表(process table)。每个进程占用一个进程表项,这个进程表项即进程控制块(PCB)。PCB包含了进程状态的重要信息,包括寄存器、程序计数器、程序状态字、堆栈指针、进程状态、内存分配状况、所打开文件的状态、账号和调度信息等。
中断向量包含中断服务程序的入口地址。进程中断时,中断硬件将程序计数器、程序状态字,还有一些寄存器等压入堆栈,计算机随即跳转到中断向量指向的地址。
当进程的I/O时间占比越大,多道程序设计的道数应该越大,以提高CPU的利用率。
理解进程的一个角度是,用某种方法把相关的资源集中在一起。进程有存放程序正文和数据及其他资源的地址空间。这些资源包括打开的文件、子进程等。把他们都放到进程中可以更容易管理。
进程拥有一个执行的线程,通常简写为线程。在线程中有一个程序计数器,用来记录接着要执行哪一条指令。线程拥有寄存器,用来保存线程当前的工作变量。线程还有一个堆栈,用来记录执行历史,其中每一帧保存了一个已调用但是还没有返回的过程。尽管线程必须在某个进程中执行,但是线程和它的进程是不同的概念,可以分别处理。进程用于把资源集中在一起,线程则是在CPU上被调度执行的实体。
每个进程有自己的地址空间、全局变量、打开文件、子进程等,每个线程则有自己的程序计数器、寄存器、堆栈、状态。
把整个线程包放在用户空间中,内核对线程包一无所知。每个进程需要有其专用的线程表,用来跟踪该进程中的线程。
用户级线程的优点:
用户级线程的问题:
在内核中有用来记录系统所有线程的线程表。当某个线程希望创建一个新线程或撤销一个已有线程时,它进行一个系统调用,这个系统调用通过对线程表的更新完成线程创建或撤销工作。
内核的线程表保存了每个线程的寄存器、状态和其他信息。这些信息是传统内核所维护的每个单线程进程信息的子集。
使用内核级线程,然后将用户级线程与某些或者全部内核线程多路复用起来。
两个或多个进程读写某些共享数据,而最后结果取决于进程运行的精确时序(运行结果和运行时序相关),这样的情况称为竞争条件(race condition)。
我们需要使用互斥(mutual exclusion),确保当一个进程在使用一个共享变量或文件时,其他进程不能做同样的操作。我们把对共享内存进行访问的程序片段称作临界区域(critical region)或临界区(critical section)。
对于一个好的解决方案,需要满足一下4个条件:
在单处理系统中,最简单的方法是使每个进程在刚进入临界区后立即屏蔽所有中断,并在就要离开之前再打开中断。这个方案并不好,因为把屏蔽中断的权力交给用户进程是不明智的。另一方面,对于内核来说,当它在更新变量或列表的几条指令期间将中断屏蔽是很方便的。
屏蔽中断对于操作系统本身而言是一项很有用的技术,但对于用户进程则不是一种合适的通用互斥机制。
一个共享锁变量,其初始值为0,当一个进程想进入临界区时,他先测试这把锁,如果该锁的值为0,则该进程将其设置为1并进入临界区。若这把锁的值已经为1时,则该进程将等待直到其值变为0。这种方法会导致竞争条件,因为检查并设置锁变量这个操作不是原子性的。
设置整型变量turn,初始值为0,用于记录轮到哪个进程进入临界区。
while(true) {
while(turn != 0);
critical_region();
turn = 1;
noncritical_region();
}
连续测试一个变量直到某个值出现为止,称为忙等待(busy waiting)。
只有在有理由认为等待时间非常短的情形下才使用忙等待。用于忙等待的锁,称为自旋锁(spin lock)。
当轮到进程0进入临界区,但是进程0正在执行非临界区的代码,这时如果进程1想要进入临界区也不得不等待,这违背了条件3:临界区外运行的进程不得阻塞其他进程。
#define FALSE 0 #define TRUE 1 #define N 2 int turn; //轮到谁进入临界区 int interested[N]; void enter_region(int process) { int other; other = 1 - process; interested[process] = TRUE; //标识该进程希望进入临界区 turn = process; while(turn == process && interested[other] == TRUE); //当轮到自己,但是对方希望进入临界区时等待 } void leave_region(int process) { interested[process] = FALSE; }
这是一个需要硬件支持的方案。许多计算机都有下面一条指令:
TSL RX, LOCK
这条指令称为测试并加锁(test and set lock),它将一个内存字lock读到寄存器中RX中,然后在该内存地址上存一个非零值。该操作是不可分割的,即该指令结束之前其他处理器均不允许访问该内存字,执行TSL指令的CPU将锁住内存总线,以防止其他CPU在本指令结束之前访问内存。
一个可替代TSL的指令是XCHG,它原子性地交换两个位置的内容。
忙等待的方法浪费CPU,并且,如果处于忙等待的进程优先级较高,CPU一直被该进程占用,进入临界区的低优先级的进程就永远无法离开临界区,这种情况被称为优先级反转问题(priority inversion problem)。
现在考察几条进程间通信原语,他们在无法进入临界区时阻塞,而不是忙等待。sleep是一个将引起调用进程阻塞的系统调用;wakeup调用有一个参数,即要被唤醒的进程。
以上方法,对于某些执行时序,生产者和消费者都会睡眠,这就导致了竞争条件的出现。
引入一个变量类型,称为信号量(semaphore)一个信号量的取值可为0或正值。
可以对信号量进行两种操作:P,V。这两种操作的特点是:检查数值、修改变量值以及可能发生的睡眠或唤醒操作作为一个单一的、不可分割的原子操作。
P对信号量的值减1,代表消费了资源;V对信号量的值增1,代表提供了资源。
该方法使用了三个信号量:full,用来记录充满的缓冲槽数目;empty,用来记录空的缓冲槽数目;mutex,用来确保生产者和消费者不会同时访问缓冲区。
注意P(empty)和P(mutex)的顺序(图中down(&empty)和down(&mutex))不能互换:如果先取得互斥信号量,发现空的缓冲槽数目为0,则生产者进入睡眠。这时虽然有满的缓冲槽,但是消费者不能取得互斥信号量,于是消费者也进入睡眠。这就导致了死锁。
同理,P(full)和P(mutex)也不能互换。但是两个V操作可以互换。
信号量的另一种用途是用于实现同步(synchronization)。信号量full和empty用来保证某种顺序的事件发生或不发生。在本例中,它们能保证缓冲区满的时候让生产者停止运行,缓冲区空的时候让消费者停止运行。
PV操作在同步互斥的应用总结:在同步问题中,若某个行为要用到某种资源,则在这个行为前面P这种资源一下;若某个行为会提供某种资源,则在这个行为后面V这种资源一下。
管程是一组数据及定义在这组数据之上的对这组数据的操作组成的软件模块,这组操作能初始化并改变管程中的数据和同步进程。
进程按照它们请求CPU的顺序使用CPU。通常适用于批处理系统。
CPU每次选取预计运行时间最短的进程运行。
既可用于作业调度,又可用于进程调度。
在作业调度中,从后备作业队列选择优先级最高的一个或几个作业,将他们调入内存,分配必要的资源,创建进程并放入就绪队列;在进程中,优先级调度算法每次从就绪队列选择优先级最高的进程,将处理机分配给它。
主要用于作业调度。每次进行作业调度时,先计算后备作业队列中每个作业的响应比,从中选出响应比最高的作业投入运行。
响应比=(等待时间+要求服务时间) / 要求服务时间
适用于分时系统。系统将所有就绪进程排成一个队列,算法按照FCFS的原则,但每个进程只能运行一个时间片,如100ms。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。