本文为大家呈现的代码可以实现进程的互斥。下面代码省去了pv操作的的具体定义以及union semun的定义(可参考前文)。本程序前半部分是一些初始化工作:生成key,创建信号量集,设置信号量初值。接下来的代码依次生成N个子进程,具体数目由运行时的参数决定。
现在我们要关注的是,这几个子进程如何互斥的访问临界区?这是本问要说明的重点。请先看本程序加PV操作和不加PV操作的结果:
edsionte@edsionte-laptop:~/code/IPC$ ./pv 3 ===process 2164 enter the critical section=== ===process:2164 is accessing================= ===process 2164 leave the critical section=== ===process 2165 enter the critical section=== ===process:2165 is accessing================= ===process 2165 leave the critical section=== ===process 2166 enter the critical section=== ===process:2166 is accessing================= ===process 2166 leave the critical section=== //不加PV ===process 2175 enter the critical section=== ===process 2176 enter the critical section=== ===process 2177 enter the critical section=== ===process:2177 is accessing================= ===process:2176 is accessing================= ===process:2175 is accessing================= ===process 2176 leave the critical section=== ===process 2177 leave the critical section=== ===process 2175 leave the critical section===
可以看到加入pv操作后,子进程在访问临界区时都不受子其他进程的影响。
可是仔细想一下,这个程序真的是在演示多个进程互斥访问同一个临界区吗?当然不是,其实从程序中的fork函数就可以发现。因为fork后的子进程代码段,数据段等都是父进程的副本,因此下面的程序根本不存在所谓的多个进程同时访问一个临界区。其实就是每个进程在唱自己的独角戏(各进程访问属于自己的临界区,一对一)而已。
但是,另一个事实又摆在我们面前:如果不加PV,那么很明显每个进程访问临界区又是交互运行的。其实我们可以这样想,对于单个子进程A而言,即便没有其他进程要竞争A进程要访问的那个临界区,那么A进程要也要装作周围有很多进程那样——在访问临界区的时候不受其他进程的干扰——只不过A进程现在只是独自一人去遵守那个规则而已。我们可以在V操作后再加一句话:
sleep(2); printf("===process %d is doing another thing=======\n",getpid());
再次运行,你可以发现这句话并不总是紧挨在临界区之后就被执行的,因为出了临界区,这几个子进程就是普通的交互运行而已。
所以,虽然这并不是我们以前熟悉的那个多进程互斥使用一个临界区的场景,但也可以体现进程对临界区的互斥访问。这样说来可能会有点绕,但是仔细想想也应该会理解。
那么如何实现多个进程互斥访问一个临界区?你也许会想到vfork函数,可是比较糟糕的是vfork后,父进程在子进程退出前总是阻塞,这样并不适合我们这里依次生成多个子进程的情况。如果还感到困惑,那么也没关系,不妨在学习了共享内存之后,再来理解本文。用共享内存才可以实现多个进程都访问一个内存区。
int main(int argc,char** argv) { int proj_id; int semid; union semun arg; pid_t pid; key_t key; int num; int i,j; if(argc!=2) { printf("error:%s num\n",argv[0]); return -1; } num=atoi(argv[1]); //create key proj_id=2; if((key=ftok(".",proj_id))==-1) { printf("generating IPC key failed\n"); return -1; } //create a semaphore set if((semid=semget(key,1,IPC_CREAT|0666))==-1) { printf("creating semaphore set failed\n"); return -1; } arg.val=1; if(semctl(semid,0,SETVAL,arg)==-1) { printf("set semval failed\n"); return -1; } for(i=0;i<\num;i++) { pid=fork(); if(pid<0) { printf("creating new process failed\n"); return -1; } else if(pid==0) { if((semid=semget(key,1,0))==-1) { printf("geting semid failed in the child process\n"); return -1; } p(semid,0); printf("===process %d enter the critical section===\n",getpid()); sleep(1); printf("===process:%d is accessing=================\n",getpid()); sleep(1); printf("===process %d leave the critical section===\n",getpid()); sleep(1); v(semid,0); return 0; } } for(i=0;i<\num;i++) { wait(NULL); } if(semctl(semid,0,IPC_RMID,0)==-1) { printf("remove the sem set failed\n"); return -1; } return 0; }