标签:
计算机上的所有可运行的软件,通常包括操作系统,被组织成若干顺序进程(squential process),简称进程(process).一个进程就是一个正在运行的实例,包括程序计数器、寄存器和变量的当前值。从概念上说,每个程序拥有它自己的CPU.然而实际上是CPU在多个进程间切换.
在UNIX系统中,可以使用fork()系统调用创建系统调用.
进程的两个基本属性:
状态图:
进程状态切换的原因:
为了实现进程模型,操作系统维护着一张表格(一个结构数组),即进程表(process table),每个进程占用一个进程表项.进程表项也叫PCB(process control block),该表项包含了进程状态的重要信息,包括程序计数器,堆栈指针,内存分配状况,所打开的文件状态,账号和调度信息,以及其他在进程由运行态转换到就绪态或阻塞态时必须保存的信息,从而保证该进程一会能够再次启动,就好像从来没有被切换过一样.
线程是进程内一个相对独立的、可调度的执行单元。线程自己基本上不拥有资源,只拥有一点在运行时必不可少的资源(如程序计数器、一组寄存器和栈),但它可以与同属一个进程的其他线程共享进程拥有的全部资源。多线程是指一个进程中有多个线程,这些线程共享该进程资源。但是各线程自己堆栈数据不对其他线程共享。
用户级线程的优点:
用户级线程的缺点:
- 如何实现阻塞系统调用,因为这会停止所有的用户态指令.
- 如果发生缺页中断,由于操作系统不知道有其他线程存在,会阻塞到这个线程完成缺页中断,而不是去调度其他用户级线程
为了禁止两个进程同时进入临界区,软件算法或同步机构都应遵循以下准则:
几种互斥的方案
Peterson解法和TSL或XCHG解法都是正确的,但他们都有忙等待的缺点.
另外一种进程间通信原语,他们无法进入临界区时将被阻塞,而不是忙等待.最简单的是sleep和weakup.sleep是一个将引起调用进程阻塞的系统调用,即被挂起,直到另一个进程将其唤醒.weakup调用有一个参数是weakup调用将要唤醒的进程的地址.
代码如下:
#define N 100 /* number of slots in the buffer */
int count = 0; /* number of items in the buffer */
void producer(void)
{
int item;
while (TRUE) { /* repeat forever */
item = produce item( ); /* generate next item */
if (count == N) sleep( ); /* if buffer is full, go to sleep */
inser t item(item); /* put item in buffer */
count = count + 1; /* increment count of items in buffer */
if (count == 1) wakeup(consumer); /* was buffer empty? */
}
}
void consumer(void)
{
int item;
while (TRUE) { /* repeat forever */
if (count == 0) sleep( ); /* if buffer is empty, got to sleep */
item = remove item( ); /* take item out of buffer */
count = count ? 1; /* decrement count of items in buffer */
if (count == N ? 1) wakeup(producer); /* was buffer full? */
consume item(item); /* print item */
}
上面这段代码由于对count的操作不是原子操作,所以会导致多线程问题.这种方法并没有很好的解决这个问题.
信号量是一种新的变量类型,它使用一个整形变量来累计唤醒次数,供以后使用.
Dijkstra建议设立两种操作:down和up.对一个信号量进行down操作,则实际检查其值是否大于0,如果大于0就减一.如果为0,就睡眠.此时down操作暂未结束,只有另一个进程up时,操作系统才会选择一个进程进行up操作.
检查数值,修改变量值以及可能发生的睡眠操作都是用原子操作完成的.对信号量原子性的保护可以用之前提到的TSL和XCHG实现.
up操作对信号量的值增加1.如果一个或多个进程在该信号量上睡眠,无法完成一个先前的down操作,则系统选择一个完成down操作.于是,这种情况下,执行了一个up操作,但是信号量的值仍然是0,但是在其上睡眠的进程却少了一个.不会有进程因为执行up操作而阻塞.
信号量实现生产者消费者的代码:
#define N 100 /* number of slots in the buffer */
typedef int semaphore; /* semaphores are a special kind of int
semaphore mutex = 1; /* controls access to critical region */
semaphore empty = N; /* counts empty buffer slots */
semaphore full = 0; /* counts full buffer slots */
void producer(void)
{
int item;
while (TRUE) { /* TRUE is the constant 1 */
item = produce item( ); /* generate something to put in buffer *
down(&empty); /* decrement empty count */
down(&mutex); /* enter critical region */
inser t item(item); /* put new item in buffer */
up(&mutex); /* leave critical region */
up(&full); /* increment count of full slots */
}
}
void consumer(void)
{
int item;
while (TRUE) { /* infinite loop */
down(&full); /* decrement full count */
down(&mutex); /* enter critical region */
item = remove item( ); /* take item from buffer */
up(&mutex); /* leave critical region */
up(&empty); /* increment count of empty slots */
consume item(item); /* do something with the item */
}
}
信号量的一个简化版本称为互斥量
互斥量只有两个状态:解锁和加锁.常常使用一个整形量,0表示解锁,其他所有值表示加锁.当进程需要访问临界区时,它调用mutex_lock().当他出来时,它调用mutex_unlock().互斥量TSL实现代码如下:
mutex lock:
TSL REGISTER,MUTEX | copy mutex to register and set mutex to
CMP REGISTER,#0 | was mutex zero?
JZE ok | if it was zero, mutex was unlocked, so r
CALL thread yield | mutex is busy; schedule another thread
JMP mutex lock | tr y again
ok: RET | return to caller; critical region entered
mutex unlock:
MOVE MUTEX,#0 | store a 0 in mutex
RET | return to caller
mutex和enter_region的区别很明显:
enter_region()当测试不成功时就一直循环测试.而mutex会直接放弃时间片,让另一个进程得到调度,这样就避免了忙等待浪费资源.
Pthread提供许多可以用来同步线程的函数.其基本机制是使用一个可以被锁定和解锁的互斥量俩保护每个临界区.一个线程想进入临界区,它会先测试临界区有没有加锁,如果没有,就立即进入.如果加锁了,就阻塞直到解锁.如果多个互斥量等待同一个互斥量,就只允许一个线程复活.
线程调用 | 描述 |
---|---|
pthread_mutex_init | 创建一个互斥量 |
pthread_mutex_destroy | 撤销一个已存在的互斥量 |
pthread_mutex_lock | 获得一个锁或阻塞 |
pthread_mutex_trylock | 获得一个锁或失败 |
pthread_mutex_unlock | 释放一个锁 |
使用mutex和信号量可能会引发死锁问题.为了更易于编写正确的程序,Brinch Hansen 和 Hoare 提出了管程.管程中是一个由过程,变量及数据结构等组成的一个集合,它们组成一个特殊模块.
管程的一个很重要的特性就是任意时刻管程内只有一个活跃进程.
进入管程时的互斥由编译器进行负责,但通常的做法是用一个互斥量或二元信号量.因为编译器安排互斥是的出错的可能小的多.
java中使用管程解决生产者消费者的代码:
public class ProducerConsumer {
static final int N = 100; // constant giving the buffer size
static producer p = new producer( ); // instantiate a new producer thread
static consumer c = new consumer( ); // instantiate a new consumer thread
static our monitor mon = new our monitor( ); // instantiate a new monitor
public static void main(String args[ ]) {
p.star t( ); // star t the producer thread
c.star t( ); // star t the consumer thread
}
static class producer extends Thread {
public void run( ) { // run method contains the thread code
int item;
while (true) { // producer loop
item = produce item( );
mon.inser t(item);
}
}
private int produce item( ) { ... } // actually produce
}
static class consumer extends Thread {
public void run( ) { run method contains the thread code
int item;
while (true) { // consumer loop
item = mon.remove( );
consume item (item);
}
}
private void consume item(int item) { ... }// actually consume
}
static class our monitor { // this is a monitor
private int buffer[ ] = new int[N];
private int count = 0, lo = 0, hi = 0; // counters and indices
public synchronized void insert(int val) {
if (count == N) go to sleep( ); // if the buffer is full, go to sleep
buffer [hi] = val; // inser t an item into the buffer
hi = (hi + 1) % N; // slot to place next item in
count = count + 1; // one more item in the buffer now
if (count == 1) notify( ); // if consumer was sleeping, wake it up
}
public synchronized int remove( ) {
int val;
if (count == 0) go to sleep( ); // if the buffer is empty, go to sleep
val = buffer [lo]; // fetch an item from the buffer
lo = (lo + 1) % N; // slot to fetch next item from
count = count ? 1; // one few items in the buffer
if (count == N ? 1) notify( ); // if producer was sleeping, wake it up
return val;
}
private void go to sleep( ) { try{wait( );} catch(InterruptedException exc) {};}
}
}
几乎所有的进程I/O请求或计算都是交替突发的.
进程有IO密集型和计算密集型两种.
有关调度处理的一个关键问题是何时进行调度决策.
如果硬件时钟提供50HZ,60HZ或其他频率的周期性中断,可以在每个时钟中断或者在每k个中断时做出调度决策.
根据如何处理时钟中断,可以把调度算法分为两类.
不同调度算法有不同的调度策略,这也决定了调度算法对不同类型的作业影响不同。在选择调度算法时,必须考虑不同算法的特性。为了衡量调度算法的性能,人们提出了一些评价标准。
由于内核并不知道用户级线程的存在,所以内核会调度进程,在每个进程执行的时间片内,进程自由调度它的用户线程.
内核选择一个特定的线程运行.它不用考虑该线程属于哪个进程,不过如果有必要的话,它可以这么做.对被选择的线程赋予一个时间片,如果超过了时间片,就强制挂起该线程.
用户级线程和内核级线程的差距在于性能.用户级线程的线程切换需要少量的机器指令,而内核级线程需要完整的上下文切换,修改内存映像,是告诉缓存失效,这导致了若干数量级的延迟.
切换同一个进程的线程开销小于切换进程.切换进程之间的进程需要比切换同一个进程的线程多做一些工作.比如:修改内存映像,清除高速缓存
错误的解法:
#define N 5 /* number of philosophers */
void philosopher(int i) /* i: philosopher number, from 0 to 4 */
{
while (TRUE) {
think( ); /* philosopher is thinking */
take fork(i); /* take left fork */
take fork((i+1) % N); /* take right fork; % is modulo operator */
eat( ); /* yum-yum, spaghetti */
put fork(i); /* put left fork back on the table */
put fork((i+1) % N); /* put right fork back on the table */
}
}
当出现这样一种极端情况:每个哲学家都拿到了左边的叉子,尝试去拿右边的叉子.
这种情况下就出现了死锁
为了解决这个问题,可以让其中一位哲学家不先拿左边的,而是先拿右边的叉子.这样就不会出现死锁了.
下面这种解法,不仅没有死锁,而且获得了最大的并行度.
#define N 5 /* number of philosophers */
#define LEFT (i+N?1)%N /* number of i’s left neighbor */
#define RIGHT (i+1)%N /* number of i’s right neighbor */
#define THINKING 0 /* philosopher is thinking */
#define HUNGRY 1 /* philosopher is trying to get forks */
#define EATING 2 /* philosopher is eating */
typedef int semaphore; /* semaphores are a special kind of int */
int state[N]; /* array to keep track of everyone’s state */
semaphore mutex = 1; /* mutual exclusion for critical regions */
semaphore s[N]; /* one semaphore per philosopher */
void philosopher(int i) /* i: philosopher number, from 0 to N?1 */
{
while (TRUE) { /* repeat forever */
think( ); /* philosopher is thinking */
take forks(i); /* acquire two forks or block */
eat( ); /* yum-yum, spaghetti */
put forks(i); /* put both forks back on table */
}
}
void take forks(int i) /* i: philosopher number, from 0 to N?1 */
{
down(&mutex); /* enter critical region */
state[i] = HUNGRY; /* record fact that philosopher i is hungry */
test(i); /* tr y to acquire 2 forks */
up(&mutex); /* exit critical region */
down(&s[i]); /* block if forks were not acquired */
}
void put forks(i) /* i: philosopher number, from 0 to N?1 */
{
down(&mutex); /* enter critical region */
state[i] = THINKING; /* philosopher has finished eating */
test(LEFT); /* see if left neighbor can now eat */
test(RIGHT); /* see if right neighbor can now eat */
up(&mutex); /* exit critical region */
}
void test(i) /* i: philosopher number, from 0 to N?1 */
{
if (state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING) {
state[i] = EATING;
up(&s[i]);
}
}
算法使用一个state数组跟踪每个哲学家是在进餐、思考还是饥饿(正在试图拿叉子).一个哲学家只有在两个邻居都没有进餐时才允许进入到进餐状态.第i个哲学家的邻居则由宏LEFT和RIGHT定义.
该程序使用了一个信号量数组,每个信号量对应一个哲学家,这样在所需的叉子被占用时,想进餐的哲学家就被阻塞.
读者-写者问题为数据库建立了一个模型.考虑一个飞机订票系统,其中有许多竞争进程试图读写其中的数据.多个进程同时读数据库是可以接收的.但是只要有一个写者在写,那么其他进程都不能访问,即使读操作也不可以.
在该解法中,隐含着一个需要注解的条件.假设一个读者正在使用数据库,另一个读者来了.同时两个读者并不存在问题.第二个读者也允许进入.如果有第三个和更多的读者来了也同样允许.
现在假设一个写者到来.由于写者的访问时排他的,不能允许写者进入数据库,只能被挂起.只要还有一个读者在活动,就允许后续的读者进来.这种策略的结果是,如果有一个稳定的读者流,那么写者将永远得不到访问.
为了避免这种情况,可以稍微改变一下程序的写法:在一个读者到达,且一个写者在等待时,读者在写者之后被挂起,而不是立即允许进入.
typedef int semaphore; /* use your imagination */
semaphore mutex = 1; /* controls access to rc */
semaphore db = 1; /* controls access to the database */
int rc = 0; /* # of processes reading or wanting to */
void reader(void)
{
while (TRUE) { /* repeat forever */
down(&mutex); /* get exclusive access to rc */
rc = rc + 1; /* one reader more now */
if (rc == 1) down(&db); /* if this is the first reader ... */
up(&mutex); /* release exclusive access to rc */
read data base( ); /* access the data */
down(&mutex); /* get exclusive access to rc */
rc = rc ? 1; /* one reader fewer now */
if (rc == 0) up(&db); /* if this is the last reader ... */
up(&mutex); /* release exclusive access to rc */
use data read( ); /* noncritical region */
}
}
void writer(void)
{
while (TRUE) { /* repeat forever */
think up data( ); /* noncritical region */
down(&db); /* get exclusive access */
write data base( ); /* update the data */
up(&db); /* release exclusive access */
}
}
标签:
原文地址:http://blog.csdn.net/jly0612/article/details/51352145