1. 进程
一个正在执行的程序实例
2. 进程模型
概念上每个进程拥有自己的虚拟CPU,实际上真正的CPU在各进程之间来回切换(多道程序设计)
3. 创建进程
1) 系统初始化
2) 执行了正在运行的进程所调用的进程创建系统调用fork()
3) 用户请求创建一个新进程
4) 一个批处理作业的初始化
4. 进程终止
1) 正常退出(自愿的)
2) 出错退出(自愿的)
3) 严重错误(非自愿)
4) 被其他进程杀死(非自愿)
5. 进程的层次结构
UNIX:进程与其子进程构成一个进程组
WINDOWS:没有层次概念,父进程用句柄控制子进程
6. 进程状态
运行、阻塞、就绪
1) 运行->阻塞:进程为等待输入而阻塞
2) 运行->就绪:调度程序选择另一个进程
3) 就绪->运行:调度程序选择这个进程
4) 阻塞->就绪:出现有效输入
其他状态:挂起、终止
7. 进程的实现
进程表表项(进程控制块)包括与进程管理、存储管理、文件管理有关的信息。
中断向量:中断发送时,保存状态后,计算机跳转到中断向量所指示位置完成操作。
多道程序设计中的CPU利用率=1-p^n
(n进程数,p用于I/O操作的时间与等待时间的时间比)
8. 线程(迷你进程)
9. 进程与线程内容的比较
进程: 地址空间 全局变量 打开文件清单 子进程 即将发生的报警 信号与信号处理程序 账号信息 | 线程: 程序计数器 寄存器 堆栈 状态 |
一组线程共享资源
两种主要方法实现进程包:在用户空间中和在内核中(另有混合实现)
10. 内核级线程的调度程序激活机制
目的:模拟内核级线程的功能,提供(在用户空间才能实现的)更好的性能和更大的灵活性,避免了不必要的用户空间/内核空间转换,提高效率。
内核给每个进程安排一定数量的虚拟处理器,将线程分配的处理器上
11. 弹出式线程
分布式系统中另一种处理消息到来的方式,一个消息的到达导致系统创建一个处理该消息的程序。
12. 进程间通信
竞争条件:两个或多个进程读写共享数据,最后结果取决于进程运行的精确时序
临界区:对共享内存进行访问的程序片段
避免竞争条件需满足以下4个条件:
1) 任何两个进程不能同时处于临界区
2) 不应对CPU数量和速度做任何假设
3) 临界区外运行的进程不得阻塞其他进程
4) 不得使进程无限期等待进入临界区
13. 实现忙等待的互斥的方案
1) 屏蔽中断
2) 锁变量
3) 严格轮换法(忙等待)
4) Peterson解法
5) TSL指令(需要硬件支持,“测试并加锁”指令)
14. 信号量
用一个整型变量累计唤醒次数
P/down:大于0,减1;为0,将进程睡眠
V/up:增1。
整个操作是不可分割的原子操作
15. 使用信号量解决“生产者-消费者问题”
(1)含有严重竞争条件的生产者-消费者问题(下面代码)
对count的访问未加限制。
有可能出现以下情况:缓冲区为空,消费者刚刚读取count的值发现它为0。此时调度程序把 CPU切换给了生产者。生产者想缓冲区中加入一个数据项,count加1,并且它推断由于刚才count为0,所以消费者一定在睡眠,所以调用 wakeup来唤醒消费者。但消费者并未睡眠,所以wakeup信号丢失。当消费者下次运行时,它将测试先前读到的count值,发现为0,于是睡眠。生 产者迟早会填满整个缓冲区,然后睡眠。最后两个进程都将永远睡眠下去。
#define N 100 /*缓冲区中的槽数目*/
int count = 0; /*缓冲区中的数据项数目*/
void producer(void)
{
int item;
while(TRUE)
{
item=produce_item();
if(count==N) sleep();
insert_item(item);
count=count+1;
if(count==1) wakeup(consumer); /*count==1说明生产之前count==0,consumer进程应该在睡眠,遂唤醒它*/
}
}
void consumer(void)
{
int item;
while(TRUE)
{
if(count==0) sleep();
item=remove_item();
count=count-1;
if(count==N-1) wakeup(producer); /*count==N-1说明消费之前缓冲区满了,生产者应该在睡眠,遂唤醒*/
consume_item(item);
}
}
(2)使用信号量解决“生产者-消费者问题”
信号量mutex用于互斥,保证任意时刻只有一个进程读写缓冲区和相关变量。互斥是避免混乱所必需的操作。
信号量full和empty用于实现同步,保证某种事件的顺序发生或不发生。它们保证当缓冲区满的时候生产者停止运行,以及当缓冲区空的时候消费者停止运行。
#define N 100 /*缓冲区中的槽数目*/
typedef int semaphore; /*信号量是一种特殊的整型数据*/
semaphore mutex = 1; /*控制对临界区的访问*/
semaphore empty = N; /*计数缓冲区的空槽数目*/
semaphore full=0; /*计数缓冲区的满槽数目*/
void producer(void)
{
int item;
while(TRUE)
{
item=produce_item();
down(&empty); /*空槽数目减一*/
down(&mutex); /*进入临界区*/
insert_item(item);
up(&mutex); /*离开临界区*/
up(&full); /*将满槽的数目加1*/
}
}
void consumer(void)
{
int item;
while(TRUE)
{
down(&full);
down(&mutex);
item=remove_item();
up(&mutex);
up(&empty);
consume_item(item);
}
}
16. 互斥量
一个二进制位(解锁和加锁),是信号量的简化版本。
17. 利用线程解决“生产者-消费者问题”
#include <stdio.h>
#include <pthread.h>
#define MAX 10 //需要生产的数量
pthread_mutex_t the_mutex;
pthread_cond_t condc, condp;
int buffer = 0;//生产者、消费者使用的缓冲区
void *producer(void *ptr)
{
int i;
for(i=1; i<=MAX; i++)
{
pthread_mutex_lock(&the_mutex); //互斥使用缓冲区
while(buffer !=0) pthread_cond_wait(&condp, &the_mutex);
printf("procucer produce item %d\n",i);
buffer = i; //将数据插入缓冲区
pthread_cond_signal(&condc);//唤醒消费者
pthread_mutex_unlock(&the_mutex);//释放缓冲区
}
pthread_exit(0);
}
void *consumer(void *ptr)
{
int i;
for(i=1; i<=MAX; i++)
{
pthread_mutex_lock(&the_mutex);//互斥使用缓冲区
while(buffer ==0) pthread_cond_wait(&condc, &the_mutex);
printf("consumer consume item %d\n",i);
buffer = 0;//从缓冲区中取出数据
pthread_cond_signal(&condp);//唤醒生产者
pthread_mutex_unlock(&the_mutex);//释放缓冲区
}
pthread_exit(0);
}
int main(int argc, char *argv[])
{
pthread_t pro, con;
pthread_mutex_init(&the_mutex, 0);
pthread_cond_init(&condc,0);
pthread_cond_init(&condp,0);
pthread_create(&con, 0, consumer, 0);
pthread_create(&pro, 0, producer, 0);
pthread_join(pro,0);
pthread_join(con,0);
pthread_cond_destroy(&condc);
pthread_cond_destroy(&condp);
pthread_mutex_destroy(&the_mutex);
return 0;
}
18. 其它同步机制
(1)管程
高级同步原语,一个管程是一个有过程、变量及数据结构等组成的一个集合,组成一个特殊模块或软件包。
任一时刻管程中只能有一个进程。
(2)消息传递
(3)屏障:除非所有的进程都就绪准备下一阶段,否则任何进程都不能进入下一阶段。
19. 调度
选择下一个占用资源的进程。
何时调度:
1) 创建新进程后
2) 一个进程退出时
3) 一个进程阻塞时
4) 发生I/O中断时
5) 时钟中断时
调度算法的目标:公平、策略强制性、平衡
调度算法的分类:抢占式/非抢占式
三种环境:
1) 批处理(吞吐量、周转时间、CPU利用率)
2) 交互式(响应时间、均衡性)
3) 实时(满足截止时间、可预测性)
20. 调度算法
批处理:
1)先来先服务
2)最短作业优先
3)最短剩余时间优先
交互式:
1)轮转调度(分时间片,每个进程在一个时间片内运行)
2)优先级调度
3)多级队列(前两种结合)
4)最短进程优先(老化算法估计运行时间aT0+(1-a)T1,如a=1/2,时间序列为T0,T0/2+T1/2,T0/4+T1/4+T2/2,三轮过后T0在新的估计值中所占的比重下降到1/8)
5)保证调度
6)彩票调度
7)公平分享调度(保证用户分配到的CPU时间的公平)
实时:
硬实时/软实时
周期性/非周期性
21. 哲学家就餐问题的一种解法
#define N 5 /*哲学家数目*/
#define LEFT (i-1+N)%N /*i的左邻号码*/
#define RIGHT (i+1)%N /*i的右邻号码*/
#define THINKING 0 /*哲学家正在思考*/
#define HUNGRY 1 /*哲学家想取得叉子*/
#define EATING 2 /*哲学家正在吃面*/
typedef int semaphore; /*信号量是一个特殊的整型变量*/
int state[N]; /*记录每个人状态的数组*/
semaphore mutex = 1; /*临界区互斥*/
semaphore s[N]; /*每个哲学家一个信号量*/
void philosopher(int i) { /*i:哲学家号码,从0到N-1*/
while(TURE){ /*无限循环*/
think(); /*哲学家正在思考*/
take_forks(i); /*需要两只叉子,或者阻塞*/
eat(); /*进餐*/
put_forks(i); /*把两把叉子同时放回桌子*/
}
}
void take_forks(int i) { /*i:哲学家号码,从0到N-1*/
down(&mutex); /*进入临界区*/
state[i]= HUNGRY; /*记录下哲学家i饥饿的事实*/
test(i); /*试图得到两只叉子*/
up(&mutex); /*离开临界区*/
down(&s[i]); /*如果得不到叉子就阻塞*/
}
void put_forks(int i) { /*i:哲学家号码,从0到N-1*/
down(&mutex); /*进入临界区*/
state[i]= THINKING; /*哲学家进餐结束*/
test(LEFT); /*看一下左邻居现在是否能进餐*/
test(RIGHT); /*看一下右邻居现在是否能进餐*/
up(&mutex); /*离开临界区*/
}
void test(i) { /*i:哲学家号码,从0到N-1*/
if(state[i]== HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING) {
state[i]= EATING;
up(&s[i]);
}
}
22. 读者写者问题的一种解法
typedef int semaphore; /*定义整型的信号量*/
semaphore mutex = 1; /*为控制对全局量rc的访问而设置的信号量*/
semaphore db = 1; /*为控制对数据库的访问而设置的信号量*/
int rc = 0; /*rc是正在读或者等待读的进程数,初值为0*/
void reader(void)
{
while(TRUE) { /*设置无限循环方式*/
down(&mutex); /*获取对rc变量的互斥访问权*/
rc= rc + 1; /*这时又多了一个读进程*/
if(rc == 1) down(&db); /*若是第一个读者,则允许访问*/
up(&mutex); /*释放对rc的互斥操作*/
reader_data_dase(); /*完成数据访问操作*/
down(&mutex); /*获取对rc变量的互斥访问权*/
rc= rc - 1; /*这时减少了一个读者进程*/
if(rc == 0) up(&db);/*若是最后一个读者,可允许写*/
up(&mutex); /*释放对rc的互斥访问*/
use_data_read(); /*非临界区操作,使用读出数据*/
}
}
void writer(void)
{
while(TRUE) { /*设置无限循环方式*/
think_up_data(); /*非临界区操作,准备希望修改的数据*/
down(&db); /*获取数据库互斥访问权*/
writer_data_dase; /*更新数据库消息*/
up(&db); /*释放数据库互斥访问权*/
}
}