在使用共享存储区通信时会遇到当多人同时运行客户端代码通过共享存储区与服务器代码做应答时,共享存储区内容还没有来得及显示,就被恶意篡改的情况(上篇文章中有源码)。本文将通过Linux下的信号量机制(pv操作)解决这个问题。
PV操作的基本原理是在计算机操作系统课中学到的,这里不再详述。然而在Linux下PV操作基本编程是在下面的代码中学到的,现贴出来(注释是自己百度后添加的,里面很多参数的设置需要费一番功夫弄明白。):
//注意:在Linux下编程,中文注释有可能编译通不过。提示错误为:编码不对。(以下代码在加注释后便出现此问题,编译时去掉注释)
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>//signal的头文件。
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#define MY_SHMKEY 10071500 // need to change
#define MY_SEMKEY 10071500 // need to change
void sigend(int);//清除操作
int shmid, semid;
int main(void)
{
int *shmptr, semval, local;
struct sembuf semopbuf; //sembuf为信号量的数据结构。设置semopbuf中的值,通过semop指定,可改变当前指定信号量数据结构中的值。
if((shmid=shmget(MY_SHMKEY, sizeof(int), IPC_CREAT|IPC_EXCL|0666)) < 0)//IPC_CREAT如果不存在则创建,如果存在则打开。IPC_EXCL只有不存在的时候才创建,如果系统中一存在,则错误。二者同时使用保证此共享内存为新建的。0666为可读可写权限。
{ /* shared memory exists, act as client */
shmid=shmget(MY_SHMKEY, sizeof(int), 0666);//获取共享存储区的ID
semid=semget(MY_SEMKEY, 2, 0666);//获取信号量集ID
shmptr=(int *)shmat(shmid, 0, 0);
printf("Act as producer. To end, input 0 when prompted.\n\n");
printf("Input a number:\n");
scanf("%d", &local);
while( local )
{
semopbuf.sem_num=0;//指定操作信号量集中的第一个信号量
semopbuf.sem_op=-1;
semopbuf.sem_flg=SEM_UNDO;
semop(semid, &semopbuf, 1); //操作一个信号量。(p(s1))
*shmptr = local;
semopbuf.sem_num=1;
semopbuf.sem_op=1;
semopbuf.sem_flg=SEM_UNDO;
semop(semid, &semopbuf, 1); /* V(S2) */
printf("Input a number:\n");
scanf("%d", &local);
}
}
else /* acts as server */
{
semid=semget(MY_SEMKEY, 2, IPC_CREAT|0666);//创建信号量集(信号量被创建的情况有两种:1.键值为IPC_PRIVATE 2.指定一个系统中不存在的信号量集key值。同时标志中指定IPC_CREAT),2为信号量个数,即当前信号量集中有两个信号量
shmptr=(int *)shmat(shmid, 0, 0);//共享存储区挂接到shmptr上
semval=1;
semctl(semid, 0, SETVAL, semval);//SETVAL:设置信号量中一个单独的信号量的值。
semval=0;
semctl(semid, 1, SETVAL,semval); /* set S2=0 */
signal(SIGINT, sigend);//设置某一信号的对应动作.ctrl+c引发SIGINT,Kill命令引发SIGTERM
signal(SIGTERM, sigend);
printf("ACT CONSUMER!!! To end, try Ctrl+C or use kill.\n\n");
while(1)
{
semopbuf.sem_num=1;//指定信号量集中的第二个信号量
semopbuf.sem_op=-1;//如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。
semopbuf.sem_flg=SEM_UNDO;//SEM_UNDO保证程序异常结束时信号量的值回复到调用semop前的值。
semop(semid, &semopbuf, 1); /* P(S2) *///第三个参数指定本次操作的信号操作数量,必>1。
printf("Shared memory set to %d\n", *shmptr);
semopbuf.sem_num=0;
semopbuf.sem_op=1;
semopbuf.sem_flg=SEM_UNDO;
semop(semid, &semopbuf, 1); /* V(S1) */
}
}
}
void sigend(int sig)
{
shmctl(shmid, IPC_RMID, 0);
semctl(semid, IPC_RMID, 0);
exit(0);
}
下面是我的代码,在上次代码的基础上给Serve和Client做PV操作。
先建立一个信号量集(里面有两个信号量)。1号服务器使用(初始资源为0),2号客户端使用(初始资源为1)。先运行服务器程序,后运行客户端程序。交替对其做PV操作。(具体参加源程序)
memserve.c(服务器程序,按Ctrl+C结束):
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>//信号量头文件
#define SHM_Key 3001 //共享存储区Key
#define SEM_Key 4001 //信号量Key
void sigend(int);
union semun//此结构来自百度semctl.
{
int val;// value for SETVAL
};
int shmid,semid,*shmptr,val;//shmid放共享内存的ID,semid放信号量的ID
int main(void)
{
struct sembuf semaphore;//定义一个信号量
union semun semopts;//联合类型,在semctl中跟SETVAL配合使用,为信号量付初值
if((shmid=shmget(SHM_Key, sizeof(int), IPC_CREAT|IPC_EXCL|0666)) < 0)//IPC_EXCL保证此信号量被创建后不能在被第二次IPC_CREAT
printf("shmget error"), exit(1);
if((shmptr=(int *)shmat(shmid, 0, 0)) == (int *)-1)//
printf("shmat error"),exit(1);
if((semid=semget(SEM_Key,2,IPC_CREAT|0666))<0)//创建信号量集(2个)
printf("semget error"),exit(1);
semopts.val=0;
semctl(semid,0,SETVAL,semopts);//第一个信号量资源为0
semopts.val=1;
semctl(semid,1,SETVAL,semopts);//第二个信号量资源数为1
signal(SIGINT, sigend);//设置某一信号的对应动作.ctrl+c引发SIGINT,Kill命令引发SIGTERM
signal(SIGTERM, sigend);
printf("Serve start: \n");
while(1)
{
semaphore.sem_num=0;
semaphore.sem_op=-1;
semaphore.sem_flg=SEM_UNDO;//异常退出则撤销之前操作
semop(semid,&semaphore,1);//(p(s1))因其初始资源0,阻塞。
printf("Share Meomory's current value is %d\n",*shmptr);
semaphore.sem_num=1;
semaphore.sem_op=1;
semaphore.sem_flg=SEM_UNDO;
semop(semid,&semaphore,1);//v(s2)
}
}
void sigend(int sig)
{
shmctl(shmid, IPC_RMID, 0);
semctl(semid, IPC_RMID, 0);
exit(0);
}
memclient.c(客户端程序):
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>//信号量头文件
#define SHM_Key 3001 //共享存储区Key
#define SEM_Key 4001 //信号量Key
int main(void)
{
int shmid,*shmptr,semid;
struct sembuf semaphore;
shmid=shmget(SHM_Key, sizeof(int), 0666);
if((shmptr=(int *)shmat(shmid, 0, 0)) == (int *)-1) //第二个参数指定引入位置,0表示由内核决定引入位置
printf("shmat error"), exit(1);
semid=semget(SEM_Key,2,0666);
printf("Client start:\nInput a value :\n");
while(1)
{
semaphore.sem_num=1;
semaphore.sem_op=-1;
semaphore.sem_flg=SEM_UNDO;
semop(semid,&semaphore,1);//p(s2)
scanf("%d",shmptr);
semaphore.sem_num=0;
semaphore.sem_op=1;
semaphore.sem_flg=SEM_UNDO;
semop(semid,&semaphore,1);//v(s1)
}
}
运行结果如下: