IPC_简单应用共享内存实现进程间通信

 --------参考文献   W.Richard Stevens, Stephen A.Rago.UNIX环境高级编程[M].北京:人民邮电出版社,2014.6:459-462.

目录

一、 共享内存

1.1 定义

1.2 方案设计

1.3 相关知识

二、 代码实现

2.1 信号量部分

2.2 写进程(留心)

2.3 读进程


一、 共享内存

1.1 定义

共享存储允许两个或多个进程共享一个给定的存储区。因为数据不需要在客户进程和服务器进程之间复制。所以这是一种最快的IPC。在多个进程之间同步访问一个给定的存储区。若服务器进程正在将数据放入共享存储区,则在它做完这一操作之前,客户进程不应该去读取这些数据。通常,信号量用于同步共享存储访问。

1.2 方案设计

多个进程将同一文件映射到它们的地址空间。内核为每一个共享存储段维护着一个结构:

struct shmid_ds{
    struct ipc_perm     shm_perm;    //权限控制。
    size_t              shm_segsz;   //size of segement in bytes.字节数。
    pid_t               shm_lpid;    //pid of last shmop().    最后执行shmop()的进程ID。
    pid_t               shm_cpid;    //pid of creator.创建该共享内存的进程ID。
    shmatt_t            shm_nattch;  //number of current attaches.现有链接数。
    time_t              shm_time;   //last_attach time.     最后被链接的时间。
    time_t              shm_dtime;   //last-detach time.    最后被删除链接的时间。
    time_t              shm_ctime;   //last-change time.    最后修改时间。
    .
    .
    . 
}
图1 读写进程方案设计

 

1.3 相关知识

shmget 获得一个共享内存标识符。是创建一个新的共享存储段(关键字key没有对应的标识符ID)还是引用一个现有的共享存储段(关键字key有对应的标识符ID)。

#include<sys/shm.h>

int shmget(key_t key, size_t size, int flag);
//返回值:若成功,返回共享存储ID;若出错,返回-1。
  • key 关键字。
  • size 共享存储段的长度,以字节为单位。实现通常将其向上取整为系统页长的整数倍。如应用指定的size 值并非系统页长的整数倍,那么最后一页的余下部分是不可使用的(浪费)。如果在创建一个新段(通常在服务器进程中),则必须指定其size 。如果引用一个现存的段(一个客户进程),则将size指定为0。当创建一个新段时,段内的内容初始化为0。
  • flag (幻数),进行一些操作。

semctl 函数包含了多种信号量操作。

#include<sys/shm.h>

int semctl(int shmid, int cmd, struct shmid_ds *buf);
//返回值:若成功,返回0;若出错,返回-1。
  • shmid 共享存储ID。
  • cmd (幻数)IPC_STAT、IPC_SET、IPC_RMID、SHM_LOCK、SHM_UNLOCK。
  • buf 指针。

shmat 函数将shmid连接到它的地址空间中。

#include<sys/shm.h>

void* shmat(int shmid, const void* addr, int flag);
//返回值:若成功,返回指向共享存储段的指针;若出错,返回-1。返回-1很令人费解,看看2.2写进程就明白了。
  • shmid 共享存储ID。
  • addr 不展开说,推荐使用NULL(连接到内核选择第一个可用地址上)。
  • flag (幻数),进行一些操作。 

二、 代码实现

2.1 信号量部分

//semaphore.h    头文件

#ifndef SEMAPHORE_H
#define SEMAPHORE_H
#include<sys/sem.h>	//semget() semctl() semop() 

typedef union semun
{
    int val;	//for SETVAL
    struct semid_ds *buf;   //for IPC_STAT and IPC_SET
    unsigned short *array;  //for GETALL and SETALL
}semun;

int sem_init(int key);    //初始化信号量集
void sem_p(int semid, int semnum);    //p操作,申请临界资源
void sem_v(int semid, int semnum);    //v操作,归还临界资源
void sem_del(int semid, int semnum);    //删除信号量

#endif
//semaphore.c    源文件

#include"semaphore.h"

int sem_init(int key)        //初始化信号量集
{
    int semid = semget((key_t)key, 0, 0666);
    if(-1 == semid)
    {
	semid = semget((key_t)key, 1, 0666|IPC_CREAT);
	semun un;
	un.val = 1;	//初始资源数目为1,因为临界资源是一块共享内存区域
	semctl(semid, 0, SETVAL, un);	//un.val
    }
    return semid;
}

void sem_p(int semid, int semnum)        //p操作,申请临界资源
{
    struct sembuf buf;
    buf.sem_num = semnum;
    buf.sem_op = -1;        //资源总数减1
    buf.sem_flg = SEM_UNDO;    //进程结束时会自动将申请的资源归还。
    semop(semid, &buf, 1);
}

void sem_v(int semid, int semnum)        //v操作,归还临界资源
{
    struct sembuf buf;
    buf.sem_num = semnum;
    buf.sem_op = 1;        //资源总数加1
    semop(semid, &buf, 1);
}

void sem_del(int semid, int semnum)
{
    semctl(semid, 0, IPC_RMID);        //删除信号量集合
}

2.2 写进程(留心

自主实现的sem_p函数体内,到底是否指定SEM_UNDO(幻数),是需要一个小心处理的问题。经过分析,我们发现确实需要指定SEM_UNDO标志。读进程和写进程实现时——使用sem_p和sem_v将临界区域保护起来,如果读进程执行了p操作,突然崩了,临界资源(共享内存)将得不到释放,写进程就会永远处于阻塞的状态。

信号量本质就是放在内核中的计数器,只起到标志作用。这就好比有人在图书馆用书占了一个座位,但是一天到晚都没有去过(不文明的行为)。如果有同学想要坐那个位子,发现有人;实际相当于没人。信号量有时也会如此,刚将资源分配给一个进程,做了标记,进程崩了,来不及归还资源;这时候信号量应该自己自动处理

void sem_p(int semid, int semnum)        //p操作,申请临界资源
{
    struct sembuf buf;
    buf.sem_num = semnum;
    buf.sem_op = -1;        //资源总数减1
    buf.sem_flg = SEM_UNDO;    //进程结束时会自动将申请的资源归还。
    semop(semid, &buf, 1);
}
#include<stdio.h>   //printf()
#include<string.h>  //memset()
#include<sys/shm.h>		//shmid() shmat()
#include<assert.h>    //assert()
#include"semaphore.h"        //引入稍前提到的头文件

int main()
{
    int shmid = shmget((key_t)1996, 512, 0666|IPC_CREAT);  //将标识符转换成共享内存内核ID  
    assert(-1 != shmid);
    
    char* ptr = (char*)shmat(shmid, 0, 0);    //申请共享内存
    assert((char*)-1 != ptr);
    memset(ptr, 0, 512);
    
    int semid = sem_init(100);	//创建信号量集或者引用现有信号量集

    while(1)
    {
	sem_p(semid, 0);	/*申请资源,因为资源总数只有1,故而只能一个进程独享*/
	
	printf("please input: ");
	fflush(stdout);	//将提示语句从缓冲区中刷出来
	memset(ptr, 0, 512);
	
	fgets(ptr, 128, stdin);
	ptr[strlen(ptr) - 1] = '\0'; /*一次读入的有效字符只有127个,因为结束符的关系*/
	
	sem_v(semid, 0);	//归还资源

	if(0 == strcmp(ptr, "end"))	//输出end字符串时,循环结束
	    break;
    }

    shmdt(ptr);    //释放该块共享内存区域
    sem_del(semid);    //删除信号量集
    return 0;
}

2.3 读进程

#include<sys/shm.h> //shmget() shmdt() shmat()
#include<string.h>  //memset()
#include<assert.h>  //assert()
#include<stdio.h>   //printf()
#include"semaphore.h"

int main()
{
    int shmid = shmget((key_t)1996, 512, 0666|IPC_CREAT);	/*将标识符转换成共享内存内核ID。*/
    assert(-1 != shmid);

    char* ptr = (char*)shmat(shmid,0 , 0);	//申请共享内存
    assert((char*)-1 != ptr);
    memset(ptr, 0, 512);
    
    int semid = sem_init(100);		//申请信号量集或者引用现有信号量集

    while(1)
    {
	sem_p(semid, 0);	/*申请资源(共享内存),因为只有1个,故特定时刻只能1个进程独占。*/

	if(0 != strcmp(ptr, "end"))	//打印输内存中不是end的字符串
	    printf("%s\n", ptr);

	sem_v(semid, 0);		//归还资源(共享内存)。
	if(0 == strcmp(ptr, "end"))	//如果内存中的字符串是end,循环结束。
	    break;
    }
    shmdt(ptr);	//释放申请的共享内存区域。
    sem_del(semid);    //删除信号量集。
    return 0;
}
图2 进程写端生成可执行文件

 

图3 进程写端生成可执行文件

 

图4 初始运行

 

图5 查看申请的信号量集合在内核中的ID

 

图6 运行示意图

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值