进程同步与信号量:
如生活中的司机和售票员的合作,就体现了两个进程之间的合作
这里的代码就体现了两个进程之间的合作,他们使用共享数据来操作自身的停止和运行,当生产者判断counter为10,则进行停止,直到消费者进行了一次操作;当消费者判断counter为0,则进行停止,直到生产者中进行了一次操作,这样便实现了信号控制,但是这里的counter显然是需要互斥控制的。
使用上述代码,当生产者为多个的时候就出现问题了,当缓冲区满p1进行生产首先会阻塞,然后生产者p2进行生产也会阻塞,这时候counter的为5,且不会变换,消费者也只会进行一次counter-1的操作且释放一个生产者p1,这时候的counter为4了,这时候生产者p2就永远不会被释放了因为counter的值已经变为4了。主要就是用counter表示的二元关系已经不能实现多个生产者或多个消费者出现的情况。这时候我们就需要使用信号量的概念了
信号量需要在信号的基础上记录下一共阻塞了多少个进程,用于进行相应次数的唤醒操作
生产者生产一个进行-1,消费者消费一个进行+1
可以将sem看成一个空闲缓冲区数量,当c执行一次就+1,当p执行一次就-1,当sem为0表示缓冲区满了,当sem为负数表示有p在等待(也就是欠几个空闲缓存区)
生产者开始需要P(sem),当value执行了 – 操作,得到的数值小于0,表示仓库中有资源满了,自己就可以睡眠了,可以将CPU给其他进程使用,当大于0,表示仓库中还有消费者在等资源,这是否就应该继续生产
V(semaphore s){
s.value++;
if(s.value<=0){
wakeup(s.queue);
}
}
消费者当发现value++后得到的value的值小于等于0,这就表示value的值在没有++之前小于0,也就是有生产者在等待,这时候就使用wakeup来唤醒一个队列中的生产者
信号量的保护:
完整的信号量包括对于临界区的保护,也就是对于sem变量的修改同一时间只能有一个操作
通过加锁的方式,使得在加锁期间的时钟停止
需要保证每次只能有一个进程进入到临界区中,且每个需要进入临界区的进程都能得到进入
通过一个turn变量,当turn等于0进程0执行,当turn等于1进程1执行,且二者执行完都将turn翻转,这样我们可以推出当p0的临界区和p1的临界区同时执行的时候turn等于0且1,由此可以推出这个方法是正确的,这种方式就是值日的方式
除了值日的方式,我们还可以进行留便条的方式
我们使用留便条的方式可以得到上述代码,当对应的进程需要进行留便条就将自己的flag对应的值置为true,然后判断其他人的flag值,如果为true表示对方进入了临界区,但是这样的方式存在死锁问题
考虑上述执行序列存在死锁问题
这里主要是妻子发现丈夫没有留条且没有牛奶就去买,丈夫则是当发现妻子没有留条后进行等待,当等待完了发现还是没有牛奶就再去买,这样妻子就可以完整的运行不需要进行等待操作,可以随时remove自己的便条,这样丈夫就可以不需要已知等待
值日和标记的方法,flag表示自己的意愿,turn==0表示P0值日,turn ==1表示P1值日
当turn条件成立,则让指定进程执行,当turn不成立,但是对方去执行了空转;当自己执行完,flag置为false保证对方可以执行
这里的i指的就是一个进程的就绪队列号,第一个wiile循环主要是等待选号完成
这种算法易发生发生溢出且算法麻烦
多cpu不同的cpu的寄存器不同,这就不能保证临界区的保护,每条指令执行完,就进行一个是否需要中断的判断,cli指令就是将这个中断关掉,这是所有的中断都不发生,sti指令是将这个中断打开
使用一条指令来覆盖testandset中的代码
信号量的代码实现:
信号量是内核对象,通过对比名字是否相同,通过open打开信号量,再通过信号量的value值的大小,确定下一步动作
这里的ll_rw_block函数主要做的就是启动读磁盘功能,wait_on_buffer函数就是将自己阻塞也就是下面的lock_buffer的代码,去掉bh->b_lock=1这一行(不知道为啥要贴lock_buffer的代码在这里……),也就是bh如果有人访问就将自身阻塞(这个主要是sleep_on函数中做的),否则就继续执行
对sleep_on函数的分析:sleep_on函数创建了一个tmp变量,这个操作是在内核中完成的,所以tmp变量也是在内核栈中的变量(关于内核栈的内容可以看之前的关于进程切换的内容),p为一个二级指针,将p指向的地址写入到tmp中,然后将p的内容指向当前的pcb,也就是将自己放入到p的第一个,将之前的p的第一个放入到tmp中
当下一次进行读磁盘中断,将b_lock值变为0表示可以抢占bh了,然后wake_up中将队首pcb的状态变为0,表示进程位于等待状态(当下一次有调度到来,变量队列中的pcb就会发现这个进程为等待,就可能会调用它),当这个进程得到了cpu时间,那么程序就运行到了if(tmp)这一句了,也就是上一次切换后的下一句,也就是sleep_on的最后几句。最后将tmp指向的pcb的状态置为0,这样就导致了所有等待bh的进程都被唤醒了。
但是这里有些问题,因为调度的时间不是确定的,所以不能百分之百保证的当上一个进程被唤醒,这个进程不会立刻切换到下一个进程去唤醒下下个进程。
死锁:
二者相互等待对方放弃资源,所以造成了死锁
银行家算法的代码:https://blog.csdn.net/ychychych1/article/details/116456655
通用pc上都是通过忽略的方式,因为重启的代价小