核心问题,进程和线程的管理。
支持并发进程的基本要求是加强互斥的能力。一个进程被授予互斥能力时,在其活动期间,具有排斥所以其他进程的能力。
支持互斥的硬件机制,操作系统或编译器支持的互斥解决方案,最后,信号量,管程和消息传递。
术语介绍:
原子操作:指令操作期间不会被别的进程打断。
临界区域:一段访问公共区域的代码,一个进程在执行这段代码,别的进程就不能执行。
这里指的是代码。
死锁:两个或两个以上的进程等待其他进程做完某事而不能继续执行。
活锁:
互斥:多个进程共同访问同一个临界区域,只能一个进程访问。
竞争条件:多个进程访问同一共享数据,结果依赖于执行的相对时间。
饥饿:进程长时间无法获得调度器的调度。
进程间的资源竞争 互斥:会导致死锁,饿死。
进程间通过共享合作 通过一个参数,一个文件做共享对象,以实现互斥,做到临界区数据的保护
互斥的要求:
1、一次只能一个进程进入临界区
2、非临界区进程不能干涉其他进程。退出临界区必须释放资源和上述的共享对象
3、不能出现饿死或死锁。(关闭中断,内核做schedule,优先级反转)
4、没有进程在临界区时,别的进程需要立刻能进入。
5、支持多处理器(多处理都能访问共享对象做互斥)
6、进程在临界区处理的时间需要短。
中断禁止,专用机器指令
两个或多个进程可以通过简单的信号进行合作。以做到两个进程各自的某段代码顺序执行。一个执行semSignal,一个执行semWait。
1、信号量初始化为0或1
2、进程执行semWait时,会将信号量减1,如果信号量小于0,则该进程阻塞。如果大于等于0,进程排入就绪队列。
3、当进程执行semSignal时,会将信号量加1。如果小于或等于0,则从阻塞队列里取出一个进程。
信号量负值的大小,代表有多少进程在阻塞队列中。
通过信号量做到互斥,避免多个进程同时进入到某一个临界区域。
semWait
临界区域
semSignal
信号量的操作必须是原子操作,可以通过软件的算法Dekker/Peterson。或者硬件来实现compare&swap指令。
单处理器系统,可以禁止中断,避免信号量的操作被中断打断。
https://blog.csdn.net/qq_35212671/article/details/52713822
消息传递的原语是,send(dest, message),receive(source, message)
发送分阻塞和非阻塞,接收也分阻塞和非阻塞。常见的是非阻塞发送,阻塞接收。需要实现消息应答机制。
非阻塞发送还要考虑,错误会导致重复的消息发送。
利用消息传递和信箱的方式,可以做到进程间互斥。进程阻塞接受box中的消息,得到消息后,执行临界区代码。
完成后,将邮件还给邮箱。
p(int i)
message msg;
while(true){
receive(box, msg)
临界区
send(box, msg)
}
parbegin(p(1), p(2), p(3), p(4), p(5), p(6), p(7))
有1条消息,只会发送给一个进程,其他进程被阻塞。
有一块共享的数据区域,需要满足如下条件:
1、任意进程都可以读取,相互之间不需要互斥
2、只能允许一个进程写入,写入时,其他进程不能读取内容。
读写者问题和一般互斥问题,生产者和消费的区别:
一般互斥问题,读取者效率低,读取和写入者都需要做互斥。
生产者和消费者都需要对队列做读写,无法像读取者那样,不修改共享区域内容
读优先的方式,会导致将数据的权限一直保持给读取者,结果会引发写入者饿死的情况。
Page165
void read(){
semWait(x)
readcount++
if(readcount == 1)
semWait(wsem)
semSignal(x)
READ_UINT()
semWait(x)
readcount--
if(radcount == 1)
semWait(wsem)
semSignal(x)
}
void write(){
semWait(wsem)
WRITE_UINT()
semSignal(wsem)
}
另外一种解决办法,当写进程尝试写入时,就不会让新的读进程进入。
void reader(){
semWait(z)
semWait(rsem)
semWait(x)
read_counter++
if(counter==1)
semWait(wsem)
semSignal(x)
semSignal(rsem)
semSignal(z)
READ_UINT()
semWait(x)
read_counter--
if(counter==0)
semSignal(wsem)
semSignal(x)
}
void writer(){
semWait(y)
write_counter++
if(write_count==1)
semWait(rsem)
semSignal(y)
semWait(wsem)
WRITE_UINT()
semSignal(wsem)
semWait(y)
write_counter--
if(write_count==0)
semSignal(rsem)
semSignal(y)
}
更好的方式,是采用消息传递的方式来达到写入者优先。由控制者决定写入和控制的权限。
进程互斥和同步可以通过信号量,信箱的方式去实现。
竞争进程 | 合作进程 |
---|---|
不知道对方存在 | 知道对方存在 |
列出与竞争进程相关的三种控制问题:
第一个是互斥,因为竞争进程不知道对方的存在,对于临界区域需要做到互斥。
互斥带来的2个问题是,分别是死锁和饿死。
死锁是当两个进程分别各占有1个资源,都在等对方释放另一个资源,导致都无法执行。
饿死是由于优先级的关系,低优先级的进程始终得不到运行的问题。
列出互斥的要求
一次只有1个进程可以进入到临界区域。
一个在非临界区域停止的进程不能干涉其他进程,否则会导致死锁。
不能出现等待临界区域的进程无限延迟。
没有进程在临界区时,需要进入必须立刻可以进入。
进程执行速度和支持多核。
进程在临界区的时间是有限的。
信号量上可以执行什么操作
一个信号量初始化为一个非负数
semWait可以使信号量减1,如果非负,即可得到执行,如果负数,阻塞进程
semSignal可以使信号量加1,如果值小于或等于0,被阻塞的得到执行
9.二元信号量和一般信号量的区别:
二元信号量初始化为0或1
semWaitB操作,如果为0,进程受阻,如果为1,变为0,执行该进程
semSignal操作,队列是否有阻塞的进程,有就执行,没有就把值改为1
10.强信号量和弱信号量的区别
先进先出的从阻塞队列中取出进程,是强信号量。
没有规定进程dequeue的顺序,是弱信号量,会出现饥饿的情况。
5.1
5.4 让2个进程以A,B,A,A的顺序运行:
#include
#include
#include
#include
int a=0;
int b=0;
sem_t *sem_a;
sem_t *sem_b;
void * thread1(void*arg)
{
printf("a\n");
sem_wait(sem_a);
//sleep(2);
printf("a2\n");
sem_post(sem_b);
return 0;
}
void * thread2(void*arg)
{
printf("b\n");
sem_wait(sem_b);
//sleep(2);
printf("b2\n");
sem_post(sem_a);
sem_post(sem_a);
return 0;
}
int main()
{
pthread_mutex_t m_j;
int r,r1,r2,r3;
pthread_t thread_1;
pthread_t thread_2;
pthread_t thread_3;
pthread_t thread_4;
pthread_mutex_init(&m_j,0 );
sem_a = sem_open("/mysem_a", O_CREAT, S_IRUSR|S_IWUSR, 0);
sem_b = sem_open("/mysem_b", O_CREAT, S_IRUSR|S_IWUSR, 0);
printf("thread create\n");
r = pthread_create(&thread_1,NULL,thread1,NULL);
r1= pthread_create(&thread_2,NULL,thread1,NULL);
r2= pthread_create(&thread_3,NULL,thread1,NULL);
r3= pthread_create(&thread_4,NULL,thread2,NULL);
sleep(2);
sem_post(sem_a);
sleep(5);
sem_unlink("/mysem_a");
sem_unlink("/mysem_b");
return 0;
}
5.5 忙等待的含义,避免忙等待的另一种技术
忙等待是为了满足某一条件,进程持续执行测试变量的指令来得到访问权限。
另一种技术是阻塞式等待,可以释放当前CPU使用权,由调度器来分配cpu时间。
会耗费CPU在进程切入切出的时间。