共享内存与信号量
共享内存,指的是两个不相关的进程访问同一个逻辑内存,进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。如果我们不允许两个进程同时对共享内存进行读写操作,光靠共享内存的机制是做不到的。共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取,所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量。
共享内存的操作函数说明
1、创建共享内存(创建或者打开一块内存后,会返回该内存的索引,进程通过该索引可以对共享内存进行连接)
int shmget(key_t key, size_t size, int shmflg)创建共享内存
2、连接共享内存(不同的进程通过此函数,挂载到同一片内存上)
void *shmat(int shmid, const void *shmaddr, int shmflg);
3、解除共享内存的连接(当一个进程不需要共享内存的时候,就需要解除与该共享内存的连接。该函数并不删除所指定的共享内存区,而是将之前用shmat函数连接好的共享内存区脱离目前的进程。)
int shmdt(const void *shmaddr)
4、销毁共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
信号量的操作函数说明
说信号量之前,先提一句,信号量和信号是完全两个不同的东西,关于信号可以去看我这篇博客 ,点击进入
信号量就是用来解决进程间的同步与互斥问题的一种进程间通信机制。值得一提的是,二值信号量与自旋锁的效果差不多,但有一点需要注意是,信号量会引起进程的睡眠属于睡眠锁,不要在中断中用信号量,不要在持锁时,进行进程的切换!
1、信号量的建立(会返回一个信号标识符,用来指向这个信号量结构)
int semget(key_t key, int nsems, int semflg)
2、对信号量进行设置
int semctl(int semid, int semnum, int cmd, union semun arg)
struct semid_ds* buf;
unsigned short* array;
struct seminfo *buf;
} arg;
*/
3、信号量的P操作或V操作(可以简单的理解为,信号量的获取与释放,主要看各个val值,如果你把val设置为1 ,也就是资源只支持一个进程访问,该进程访问时进程P操作,会把val减1,当另一进程请求访问时,发现val已经等于0了,就会被堵塞)
int semop(int semid, struct sembuf *sops, unsigned nsops)
short val;
short flag;
*/
信号量配合共享内存应用
comm.h文件
#ifndef _COMM_H__
#define _COMM_H__
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
#define PROJ_mm 0x6667
int CreateShm(int size);
int DestroyShm(int shmid);
int GetShm(int size);
int init_sem(int semid, int num, int val);
int sem_p(int semid, int num) ;
int sem_p(int semid, int num) ;
int sem_v(int semid, int num) ;
#endif
comm.c
#include"comm.h"
static int CommShm(int size,int flags)
{
key_t key = ftok(PATHNAME,PROJ_ID);
if(key < 0)
{
perror("ftok");
return -1;
}
int shmid = 0;
if((shmid = shmget(key,size,flags)) < 0)
{
perror("shmget");
return -2;
}
return shmid;
}
int DestroyShm(int shmid)
{
if(shmctl(shmid,IPC_RMID,NULL) < 0)
{
perror("shmctl");
return -1;
}
return 0;
}
int CreateShm(int size)
{
return CommShm(size,IPC_CREAT | IPC_EXCL | 0666);
}
int GetShm(int size)
{
return CommShm(size,IPC_CREAT);
}
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int init_sem(int semid, int num, int val)
{
union semun myun;
myun.val = val;
if(semctl(semid, num, SETVAL, myun) < 0)
{
perror("semctl");
exit(1);
}
return 0;
}
int sem_p(int semid, int num)
{
struct sembuf mybuf;
mybuf.sem_num = num;
mybuf.sem_op = -1;
mybuf.sem_flg = SEM_UNDO;
if(semop(semid, &mybuf, 1) < 0){
perror("semop");
exit(1);
}
return 0;
}
int sem_v(int semid, int num)
{
struct sembuf mybuf;
mybuf.sem_num = num;
mybuf.sem_op = 1;
mybuf.sem_flg = SEM_UNDO;
if(semop(semid, &mybuf, 1) < 0){
perror("semop");
exit(1);
}
return 0;
}
client.c
#include"comm.h"
int main()
{
int semid ;
key_t sem_key;
if((sem_key = ftok(PATHNAME,PROJ_mm)) < 0){
perror("ftok failed .\n");
exit(-1);
}
semid = semget(sem_key, 0, IPC_CREAT);
int shmid = GetShm(4096);
sleep(1);
char *addr = shmat(shmid,NULL,0);
sleep(2);
int i = 0;
sem_p(semid,0);
while(i < 26)
{
addr[i] = 'A' + i;
i++;
addr[i] = 0;
sleep(1);
}
sem_v(semid,0);
shmdt(addr);
semctl (semid, 1, IPC_RMID, NULL);
DestroyShm(shmid);
sleep(2);
return 0;
}
server.c
#include"comm.h"
int main()
{
int semid ;
key_t sem_key;
if((sem_key = ftok(PATHNAME,PROJ_mm)) < 0){
perror("ftok failed .\n");
exit(-1);
}
semid = semget(sem_key,1,IPC_CREAT|IPC_EXCL|0666);
init_sem (semid, 0, 1);
int shmid = CreateShm(4096);
sleep(1);
char *addr = shmat(shmid,NULL,0);
sleep(2);
int i = 0;
getchar();
sem_p(semid,0);
while(i++ < 26)
{
printf("client# %s\n",addr);
sleep(1);
}
sem_v(semid,0);
shmdt(addr);
sleep(2);
return 0;
}
Makefile
all:server client
client:client.c comm.c
gcc -o $@ $^
server:server.c comm.c
gcc -o $@ $^
clean:
rm -f client server
不加信号量的实验结果如下
加上信号量的实验结果如下
总结 ,从这个两个结果可以看出,不加信号量的时候,读取数据和写入数据是宏观是同时进行的,会存在读取到的数据不完整的现象,而加上信号量后,读取数据进程,会等待数据全部写完后,在进行读取。保证了读到的数据的完整性和读写进程的同步性
|