操作系统-进程通信
来源:互联网 发布:js设置隐藏属性 编辑:程序博客网 时间:2024/05/21 19:35
进程通信中的难点是对临界区的互斥访问,下面我们来看一下
忙等待的互斥
锁变量
其实我们很容易想到的解决方案就是加锁,
int lock,cnt;void solve_fork(){ while(lock==1); lock = 1; cnt++; printf("%d 进入了临界区\n",getpid()); lock = 0;}
同时我们也很容易发现这样是有问题的,两个进程同时判断lock,然后都加锁,然后同时进入临界区,其实用上面代码测试的话发现,基本上很多时候都是会同时访问临界区的。当然解决方案很简单,就是让判断和加锁变成一个原子操作,比如后面要说的信号量或者是用硬件加锁,硬件加锁类似于我们用的开关,可以用寄存器来实现,完全可行。
严格轮换法(自旋锁)
这个其实是对加锁的一个上述问题的一个改进,其原理就是每次两个进程去等待一个不同的值。
int lock,cnt;void solve_fork1(){ while(lock==1) {} cnt++; printf("%d 进入了临界区\n",getpid()); lock = 1;}void solve_fork2(){ while(lock == 0) {} cnt++; printf("%d 进入了临界区\n",getpid()); lock = 0;}
上面这样就实现了互斥访问不会出错,但是同样也是有问题的,首先他是通过忙等来解决问题的,其次,他这能解决两个进程竞争的情况。
Peterson解法
解法结果类似于上面的情况,但是思想不一样。可以研究一下
const int N = 2;int lock;int inter[N];void enter_region(int pro) // 传入参数0/1{ int other; other = 1 - pro; inter[pro] = 1; lock = pro; while(lock == pro && inter[other] == 1);}void leave_region(int pro){ inter[pro] = 0;}
非忙等待互斥
信号量
这个应该算是最常见的了,其原理上面锁变量说过了,这是把他变成了一个原子操作,但是怎么变成一个原子操作呢,操作系统内部是怎么实现这个原子操作呢?
其实上面介绍的方法都是可用的, 只是要解决忙等的情况,这里说一种操作系统实现的情况,对于多核的来说,操作系统使用TSL指令来实现原子操作,指令为 TSL RX LOCK
,即把lock的值copy到RX寄存器中,同时执行TSL的指令的CPU将锁住内存总线。防止其他CPU在这个指令结束之前访问。其实就是理解成上面说的开关。
这里还有一个问题就是忙等,这个可以用sleep和wakeup原语来解决。当另一个访问结束之后用wakeup原语激活当前进程。
当然除了信号量还有互斥信号量的概念,就是不用计数,只为0或1的信号量
管道程序
这个Linux用过的话应该不难理解,就是Linux上面的’ | ‘符号,可以理解成一个管道,我们把一个进程产生的数据作为另一个经常需要输入的数据,然后通过一个管道传递给第二个进程,Linux上面的实现是开一个管道,一边关闭读,另一边关闭写,然后进行通信就ok。
共享存储
其实就是给两个进程开一块能够同时访问的共享存储空间,然后到时候交替访问就可以实现,开启共享的系统调用时,分别用于开启和关闭共享存储空间 shmget(key_t key, size_t size, int flag)
shmdt(void *addr)
消息队列
把要通信的字段写入消息队列,然后另一个要用的进程去消息队列中取
经典IPC问题
哲学家就餐问题
5个哲学家要吃饭,一共有五个筷子在一个圆桌上,哲学家大多数时候在思考,饿了会吃饭,然后怎样设计一个方案使得不饥饿且能高效的吃饭。
首先看看如果每个哲学家饿了自由的去拿筷子吃饭会发生什么情况,假如他们先拿左边的筷子,拿到之后拿右边的,这样加入5个人同时饿了,然后去拿左边的筷子,然后成功,都去拿右边的筷子,都不成功,这样就发生死锁,没有变法进行下去了。
一个很直观的解决方案是,如果拿了左边筷子之后去拿右边筷子不能拿的话,就放下筷子,去等待一个随机数,然后在去拿,这样首先能够解决不发生死锁,但是由于是一个随机数,结果不可控,所以性能不够好。
其实更好的方案是,用and信号量,即把拿左边和右边当成是一个原子操作,释放的时候也一样,可优化点,在当前吃完之后,检查左边和右边是不是处于饥饿状态,需要的话就唤醒让吃饭.
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdint.h>#include <stdbool.h>#include <errno.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/ipc.h>#include <sys/sem.h>#include <sys/wait.h>#ifdef _SEM_SEMUN_UNDEFINEDunion semun{ int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf;};#endif#define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)intwait_1fork(int no,int semid){ //int left = no; //int right = (no + 1) % 5; struct sembuf sb = {no,-1,0}; int ret; ret = semop(semid,&sb,1); if(ret < 0) { ERR_EXIT("semop"); } return ret;}intfree_1fork(int no,int semid){ struct sembuf sb = {no,1,0}; int ret; ret = semop(semid,&sb,1); if(ret < 0) { ERR_EXIT("semop"); } return ret;}//这里表明叉子是一个临界资源#define DELAY (rand() % 5 + 1)//相当于P操作voidwait_for_2fork(int no,int semid){ //哲学家左边的刀叉号数 int left = no; //右边的刀叉 int right = (no + 1) % 5; //刀叉值是两个 //注意第一个参数是编号 struct sembuf buf[2] = { {left,-1,0}, {right,-1,0} }; //信号集中有5个信号量,只是对其中的 //资源sembuf进行操作 semop(semid,buf,2);}//相当于V操作voidfree_2fork(int no,int semid){ int left = no; int right = (no + 1) % 5; struct sembuf buf[2] = { {left,1,0}, {right,1,0} }; semop(semid,buf,2);}void philosophere(int no,int semid){ srand(getpid()); for(;;) { #if 1 //这里采取的措施是当两把刀叉都可用的时候 //哲学家才能吃饭,这样不相邻的哲学家就可 //吃上饭 printf("%d is thinking\n",no); sleep(DELAY); printf("%d is hungry\n",no); wait_for_2fork(no,semid);//拿到叉子才能吃饭 printf("%d is eating\n",no); sleep(DELAY); free_2fork(no,semid);//释放叉子 #else //这段代码可能会造成死锁 int left = no; int right = (no + 1) % 5; printf("%d is thinking\n",no); sleep(DELAY); printf("%d is hungry\n",no); wait_1fork(left,semid); sleep(DELAY); wait_1fork(right,semid); printf("%d is eating\n",no); sleep(DELAY); free_2fork(no,semid); #endif }} :intmain(int argc,char *argv[]){ int semid; //创建信号量 semid = semget(IPC_PRIVATE,5,IPC_CREAT | 0666); if(semid < 0) { ERR_EXIT("semid"); } union semun su; su.val = 1; int i; for(i = 0;i < 5;++i) { //注意第二个参数也是索引 semctl(semid,i,SETVAL,su); } //创建4个子进程 int num = 0; pid_t pid; for(i = 1;i < 5;++i) { pid = fork(); if(pid < 0) { ERR_EXIT("fork"); } if(0 == pid) { num = i; break; } } //这里就是哲学家要做的事情 philosophere(num,semid); return 0;}
生产者消费者问题
此问题关键在于,对缓冲区的访问是互斥的,所以需要一个信号量mutex,其次满了之后生产者不能放入产品,所以需要一个full变量,显示产品出来,空了之后消费者不能取,所以需要一个empty来保存还可以放入的物品数目,每次down的时候后操作mutex,释放的时候先释放mutex,可以尝试写一下代码。
- 操作系统:进程间通信
- 操作系统:进程通信
- 操作系统实验-进程通信
- 操作系统中的进程通信
- 操作系统进程间通信
- 【操作系统】进程间通信
- 【操作系统总结】进程通信
- 操作系统-进程通信
- 操作系统--进程通信
- 操作系统------进程间通信
- 操作系统 进程通信实验
- 操作系统:进程和进程通信
- 【现代操作系统】进程的通信
- 【操作系统】进程的通信方式
- 操作系统---进程/线程 间通信
- 操作系统---进程/线程 间通信
- 进程间的通信--------操作系统
- 操作系统 -- 进程间通信机制
- fileupload上传时传递参数
- Configuration with name 'default' not found
- Oracle表分区和索引分区汇总
- Cocoaposd安装与使用
- android状态栏实现沉浸式
- 操作系统-进程通信
- 消灭Bug!18款最佳的问题跟踪管理应用程序
- Android 获取SHA1值(简单粗暴)
- Android NDK JNI实现案例
- 粒子群算法(二)全局版本
- SpringMVC拦截器(资源和权限管理)
- ThreadPoolExecutor 学习
- 1083. List Grades (25)
- nginx配置详解