进程通信
(1)进程的同步与互斥
一般来说同步反映了进程之间的协作性质,往往指有几个进程共同完成一个任务时在时间次序上的某种限制,进程相互之间各自的存在及作用,通过交换信息完成通信。如接力比赛中一组队员使用接力棒等。
进程互斥体现了进程之间对资源的竞争关系,这时进程相互之间不一定清楚其它进程情况,往往指多个任务多个进程间的通讯制约,因而使用更广泛。如打篮球时双方挣抢篮板球等。
(2)临界区
并发进程中与共享变量有关的程序段定义为临界区。进入临界区的准则是:①一次只准一个进程进入临界区;②本进程结束负责通知下一进程;③进程调度,不能阻塞。
(3)原语
原语是不可中断的过程。
·加锁/开锁(LOCK/UNLOCK)原语
优点是实现互斥简单;缺点是效率很低。
·信号量(Semaphore)及PV操作
PV操作能够实现对临界区的管理要求。它由P操作原语和V操作原语组成,对信号量进行操作,具体定义如下:
P(S):①将信号量S的值减1,即S=S-1;
②如果S>0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。
V(S):①将信号量S的值加1,即S=S+1;
②如果S<=0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。
信号量的数据结构为一个值和一个指针,指针指向等待该信号量的下一个进程。信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量;当它的值小于0时,其绝对值表示等待使用该资源的进程个数。注意信号量的值仅能由PV操作来改变。
一般来说,信号量S0时,S表示可用资源的数量。执行一次P操作意味着请求分配一个单位资源,因此S的值减1;当S<0时,表示已经没有可用资源,请求者必须等待别的进程释放该类资源,它才能运行下去。而执行一个V操作意味着释放一个单位资源,因此S的值加1;若S0,表示有某些进程正在等待该资源,因此要唤醒一个等待状态的进程,使之运行下去。
消息缓冲通信原语
高级通信原语,用于一组信息发送(Send)与读取(Read)。
生产者-消费者问题:
在这个问题上主要考虑的是:缓冲区满或缓冲区空以及竞争条件(race condition)。以下是一个含竞争条件的生产者-消费者问题实例。
#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) {
produce_item(&item);
if (count==N) sleep();
enter_item(item);
count=count+1;
if (count==1) wakeup(consumer);
}
}
void comsumer(void) {
int item;
while(TRUE) {
if(count==0) sleep();
remove_item(&item);
count=count-1;
if (count==N-1) wakeup(producer);
consume_item(item);
}
}
信号量的概念首先由E.W.Dijkstra在1965年提出的。semaphore(信号量)是一个大于等于零的整型变量。 对信号量有两个原子操作:-和+,DOWN()和UP(),SLEEP()和WAKEUP(),P()和V(),Wait() 和Signal()。虽然它们名字都不一样,可意思都是相同的,拿down和up操作来说明。 DOWN(t)操作 递减信号量t的值:先检查t是否大于0,若t大于0则t减去1;若t的值为0,则进程进入睡眠状态,而此时的DOWN操作并没有结束。这是原子操作,用来保证一旦一个信号量的操作开始,则在操作完成或阻塞之前别的进程不允许访问该信号量。 UP(t)操作 递增信号量t的值:如果一个或多个进程在该信号量t上睡眠,则无法完成一个先前的DOWN操作,则由系统选择其中的一个并允许起完成它的DOWN操作。因此,对一个有进程在起上睡眠的信号量执行一次UP操作以后,该信号量的值仍旧是0,但在其上睡眠的进程却少了一个。递增信号量的值和唤醒一个进程的操作也是原子操作,不可分割的。这样做保证不会有进程因为执行UP操作而被阻塞。
多线程程序中最简单也是最常用的同步机制要算是mutex(互斥量)。
一个mutex提供两个基本操作:DOWN和UP。一旦某个线程调用了DOWN,其它线程再调用DOWN时就会被阻塞。当这个线程调用UP后,刚才阻塞在DOWN里的线程中,会有一个且仅有一个被唤醒。换句话说,对于一个给定的mutex,只有一个线程可以在DOWN和UP调用之间获取处理器时间。这里的mutex 用来保护访问的临界区,避免数据出现竞争条件。
#include<sys/types.h>
#include<linux/sem.h>
#include<linux/shm.h>
#include<unistd.h>
#include<stdio.h>
#include<errno.h>
#include<time.h>
#define MAXSHM 5 //定义缓冲区数组的下标变量个数
/* 定义3个信号量的内部标识 */
int fullid;
int emptyid;
int mutexid;
/* 主函数 */
int main()
{
/* 定义2个共享内存的ID */
int arrayid;
int getid;
/* 定义共享内存虚拟地址 */
int *array;
int *get;
/* 创建共享内存 */
arrayid=shmget(IPC_PRIVATE,sizeof(int) *MAXSHN,IPC_CREAT|0666);
getid=shmget(IPC_PRIVATE,sizeof(int),IPC_CREAT|0666);
/* 初始化共享内存 */
array=(int *) shmat(arrayid,0,0);
get=(int *) shmat(getid,0,0);
*get=0;
/* 定义信号量数据结构 */
struct sembuf P,V;
union semun arg;
/* 创建信号量 */
fullid=semget(IPC_PRIVATE,1,IPC_CREAT|0666);
emptyid=semget(IPC_PRIVATE,1,IPC_CREAT|0666);
mutexid=semget(IPC_PRIVATE,1,IPC_CREAT|0666);
/*初始化信号量 */
arg.val=0; //初始时缓冲区中无数据
if(semctl(fullid,0,SETVAL,arg)==-1)
perror("semctl setval error");
arg.val=MAXSHM; //初始时缓冲区中有5个空闲的数组元素
if(semctl(emptyid,0,SETVAL,arg)==-1)
perror("semctl setval error");
arg.val=1; //初始时互斥信号为1,允许一个进程进入
if(semctl(mutexid,0,SETVAL,arg)==-1)
perror("semctl setval error");
/* 初始化 P V操作 */
P.sem_num=0;
P.sem_op=-1;
P.sem_flg=SEM_UNDO;
V.sem_num=0;
V.sem_op=1;
V.sem_flg=SEM_UNDO;
/* 生产者进程 */
if(fork()==0)
{
int i=0;
int set=0;
while(i<10)
{
semop(emptyid,&P,1); //对 emptyid执行P操作
semop(mutexid,&P,1); //对 mutexid执行 P操作
array[set%MAXSHM]=i+1;
printf("Producer put number %d to No.%d\n",array[set%MAXSHM],set%MAXSHM);
set++; //写计数加1
semop(mutexid,&V,1); //对mutexid执行 V 操作
semop(fullid,&V,1); //对fullid执行 V 操作
i++;
}
sleep(3); //SLEEP 3秒,等待消费者进程执行完毕
printf("Poducer if over\n");
exit(0);
}
else
{
/* 消费者A进程 */
if(fork()==0)
{
while(1)
{
if(*get==10)
break;
semop(fullid,&P,1); //对fullid执行 P 操作
semop(mutexid,&P,1); //对mutexid执行 P 操作
printf("The ConsumerA get number from No.%d\n",(*get)%MAXSHM);
(*get)++; //读计数加1
semop(mutexid,&V,1); //对mutexid执行 V 操作
semop(emptyid,&V,1); //对fullid执行 V 操作
sleep(1);
}
printf("ConsunerA is over\n");
exit(0);
}
else
{
/*消费者B进程 */
if(fork()==0)
{
while(1)
{
if(*get==10)
break;
semop(fullid,&P,1); //对fullid执行 P 操作
semop(mutexid,&P,1); //对mutexid执行 P 操作
printf("The ConsumerA get number from No.%d\n",(*get)%MAXSHM);
(*get)++; //读计数加1
semop(mutexid,&V,1); //对mutexid执行 V 操作
semop(emptyid,&V,1); //对emptyid执行 V 操作
sleep(1);
}
printf("ConsunerB is over\n");
exit(0);
}
}
}
/* 父进程返回后回收3个子进程 */
wait(0);
wait(0);
wait(0);
/* 断开并撤消2个共享内存 */
shmdt(array);
shmctl(arrayid,IPC_RMID,0);
shmctl(get);
shmctl(getid,IPC_RMID,0);
/* 撤消3个信号量集 */
semctl(emptyid,IPC_RMID,0);
semctl(fullid,IPC_RMID,0);
semctl(mutexid,IPC_RMID,0);
exit(0);
}
主要函数及数据结构:
signal();//安装信号用。signalaction 也可以,但好像太繁,小程序不用
sigxxxset();//信号集操作
sigprocmask();//设置信号集
sigsuspend();//等待相关信号
代码:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void wake(){ return;}//do nothing but return immediately to wake up the process
int main()
{
sigset_t set;
pid_t pid;
int i;
sigfillset(&set);
sigdelset(&set,SIGINT);
sigprocmask(SIG_SETMASK,&set,NULL);//for user control
if( (pid=fork() ) ==0 )//child
{
sigdelset(&set,SIGUSR2);
signal(SIGUSR2,wake);//install the child handler
sleep(1);
puts("This is child. I am going to wake parent first.");
for( i=1;i<=4;i++)
{
kill(getppid(),SIGUSR1);//send singal to parent
sigsuspend(&set);//waiting for parent's signal
puts("Parent wake me up!");
/*
critical code
*/
printf("Child: i=%d.\n",i);
}
_exit(0);
}
//parent
sigdelset(&set,SIGUSR1);
signal(SIGUSR1,wake);
puts("I am the parent!");
for( i=1;i<=4;i++)
{
sigsuspend(&set);//waiting for child's signal
puts("Child wake me up!");
/*
critical code
*/
printf("Parent: i=%d.\n",i);
kill(pid,SIGUSR2);//sending child signal
}
return 0;
}
运行结果:
I am the parent!
This is child. I am going to wake parent first.
Child wake me up!
Parent: i=1.
Parent wake me up!
Child: i=1.
Child wake me up!
Parent: i=2.
Parent wake me up!
Child: i=2.
Child wake me up!
Parent: i=3.
Parent wake me up!
Child: i=3.
Child wake me up!
Parent: i=4.
Parent wake me up!
Child: i=4.
解释:
一开始把信号集填满并设置是为了避免没必要的信号的干扰。并且,在父子进程还没运行到要接受信号的地方时,互相受影响。当然,你如果把这一步省了你就会看到,运行结果不像你想像的那样了。
更复杂的延伸。利用信号进行数学计算控制。实质上,这里可以搞并行计算的(smp required)。但只是为了简单,所以没有写成并行而已。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <signal.h>
#include <math.h>
double ac(double);
double bc(double,double);
double cc(double,double);
double dc(double,double);
void wake(){return;}
int main()
{
pid_t pid;
key_t key=ftok("/dev/shm/",3);
int shmid;
sigset_t set;
typedef struct
{
double a;
double b;
double c,d;
}data;
data* ptr;
sigfillset(&set);
sigdelset(&set,SIGINT);
sigprocmask(SIG_SETMASK,&set,NULL);
if( (shmid=shmget(key,sizeof(data),IPC_CREAT|0666)) == -1 )
{
perror("shmget fail");
exit(1);
}
if( (ptr=(data*)shmat(shmid,NULL,0)) == NULL)
{
perror("shmat fail");
exit(1);
}
ptr->a=1.0;
ptr->b=2.0;
if( (pid=fork() ) ==0)
{
sigdelset(&set,SIGUSR2);//parent will wake me up through this signal
signal(SIGUSR2,wake);
puts("\
Hello, this is the child! I will be sleeping\n\
until the parent finish the 1st part calculation.\n\
Then he will wake me up. Now, I go to bed!");
sigsuspend(&set);//waiting for parent's signal, because parent will send the first signal
ptr->b=bc(ptr->a,ptr->b);
puts("2nd part finished!");//actually, you don't need this.
kill(getppid(),SIGUSR1);//tell parent that he has done the jub
sigsuspend(&set);//waiting for the parent's wakeup again.
ptr->d=dc(ptr->b,ptr->c);
puts("4th part finished!");//actually, you don't need this.
puts("OK, the child has done his job!");
shmdt(ptr);
kill(getppid(),SIGUSR1);
_exit(0);
}
// parent now
sigdelset(&set,SIGUSR1);
signal(SIGUSR1,wake);
puts("Hello, this is parent!");
sleep(1);//let the user to read clearly
ptr->a=ac(ptr->a);//calculate the first part
puts("first part finished!");//actually, you don't need this.
kill(pid,SIGUSR2);
sigsuspend(&set);
ptr->c=cc(ptr->a,ptr->b);
puts("3rd part finished!");//actually, you don't need this.
kill(pid,SIGUSR2);
sigsuspend(&set);
puts("OK, all the caculation has been done. The result is:");
printf("a=%f, b=%f, c=%f, d=%f.\n",ptr->a,ptr->b,ptr->c,ptr->d);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
double ac(double x)
{
return (sin(x)+cos(x)+tan(x) );
}
double bc(double x,double y)
{
return (tan(x)+sin(y)+tan(y) );
}
double cc(double x,double y)
{
return (atan(x)+tan(x)+cos(y) );
}
double dc(double x,double y)
{
return ( atan(x*y) );
}
运行结果:
[sody@longtem os]$ ./a.out
Hello, this is the child! I will be sleeping
until the parent finish the 1st part calculation.
Then he will wake me up. Now, I go to bed!
Hello, this is parent!
first part finished!
2nd part finished!
3rd part finished!
4th part finished!
OK, the child has done his job!
OK, all the caculation has been done. The result is:
a=2.939181, b=-1.480964, c=1.127340, d=-1.031139.