一、常用函数

函数系列头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

ftok():

系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。

key_t ftok( char * fname, int id );//当成功执行的时候,一个key_t值将会被返回,否则 -1 被返回。

应用举例:

key_t key = ftok(".", 2);//通过ftok函数得到id值存在key中,下面创建共享内存时会用到

shmget() :

用于创建共享内存

int shmget(key_t key, size_t size, int shmflg); //成功返回共享内存的ID,出错返回-1

应用举例:

int shmid = shmget(key, 100, IPC_CREAT | IPC_EXCL | 0666);
//创建共享内存,成功返回共享内存的ID,出错返回-1,IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的共享内存,如果共享内存已存在,返回一个错误。

(1)第一个参数key是长整型(唯一非零),系统建立IPC通讯 ( 消息队列、 信号量和 共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到,由内核变成标识符,要想让两个进程看到同一个信号集,只需设置key值不变就可以。

(2)第二个参数size指定共享内存的大小,它的值一般为一页大小的整数倍(未到一页,操作系统向上对齐到一页,但是用户实际能使用只有自己所申请的大小)。

(3)第三个参数shmflg是一组标志,创建一个新的共享内存,将shmflg 设置了IPC_CREAT标志后,共享内存存在就打开。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的共享内存,如果共享内存已存在,返回一个错误。一般我们会还或上一个文件权限

shmctl():

用于操作共享内存

int shmctl(int shm_id, int cmd, struct shmid_ds *buf); //成功返回0,出错返回-1

(1)第一个参数,shm_id是shmget函数返回的共享内存标识符。

(2)第二个参数,cmd是要采取的操作,它可以取下面的三个值 :    

  • IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。    

  • IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值    

  • IPC_RMID:删除共享内存段

(3)第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。 shmid_ds结构至少包括以下成员

struct shmid_ds
{
    uid_t shm_perm.uid;
    uid_t shm_perm.gid;
    mode_t shm_perm.mode;
};    

shmat():

用于创建共享存储段之后,将进程连接到它的地址空间

void *shmat(int shm_id, const void *shm_addr, int shmflg); //成功返回指向共享存储段的指针,出错返回-1

应用举例:

void *pshm = shmat(shmid, 0, 0);//挂接操作,成功返回指向共享存储段的指针,出错返回-1
if (*(int *)pshm == -1) {//查看挂接是否成功如果出错返回-1,报错
    printf("shmat error!\n");
    exit(0);
}

(1)第一个参数,shm_id是由shmget函数返回的共享内存标识。

(2)第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。

(3)第三个参数,shm_flg是一组标志位,通常为0

shmdt():

用于分离操作

int shmdt(const void *shmaddr); //成功返回0,出错返回-1
//addr参数是以前调用shmat时的返回值

二、函数实践

在设计实验中,运用共享内存的四个主要函数shmaget函数,shmat函数,shmdt函数,shmctl函数之后,读出传送数据的文件可以视为服务器read.c,传送数据相当于客户端文件write.c,实现进程之间的相互通信。

write.c程序段(将数据写入共享空间):

//write.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <string.h>
#include <signal.h>
struct Msg {
    int flag;//0为读,1为写
    char content[104];
};
int shmid;
void *pshm;
void Handle(int s) {//监听函数
    if (s == 2)
    {
        shmdt(pshm);
        shmctl(shmid, IPC_RMID, 0);
        exit(0);
    }
}
int main() {
    signal(2, Handle);//按ctrl+c键退出时会处理这个消息,进行共享内存卸载、删除操作,最后exit(0)退出程序

    key_t key = ftok(".", 2);//通过ftok函数得到id值存在key中

    shmid = shmget(key, 100, IPC_CREAT | IPC_EXCL | 0666);/*创建共享内存,成功返回共享内存的ID,出错返回-1,IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的共享内存,如果共享内存已存在,返回一个错误。*/

    pshm = shmat(shmid, 0, 0);//挂接操作,成功返回指向共享存储段的指针,出错返回-1

    if (*(int *)pshm == -1) {//查看挂接是否成功如果出错返回-1,报错
        printf("shmat error!\n");
        exit(0);
    }
    memset(pshm, 0, 100);//初始化
    struct Msg *msg = (struct Msg *)pshm;
    msg->flag = 1;
    while (1) {
        if (msg->flag == 1) {//当为1时写消息,此时读文件中不能操作此共享内存
            printf("请输入内容至共享内存:");
            scanf("%s", msg->content);
            msg->flag = 0;//当写消息后flag置为0,让读文件开始执行读操作,此时写文件不能进行写操作
        }
        else {
            sleep(1);
        }
    }
    return 0;
}

read.c程序段(将数据同步从共享空间读出):

//read.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <string.h>
#include <signal.h>
struct Msg {
    int flag;
    char content[104];
};
int main() {

    key_t key = ftok(".", 2);
    int shmid = shmget(key, 0, 0);//通过ftok函数得到id值存在key中

    void *pshm = shmat(shmid, 0, 0);//挂接操作,成功返回指向共享存储段的指针,出错返回-1

    if (*(int *)pshm == -1) {//查看挂接是否成功如果出错返回-1,报错
        printf("shmat error!\n");
        exit(0);
    }

    struct Msg * msg = (struct Msg *)pshm;
    while (1) {
        if (msg->flag == 0) {//当为0时读消息,此时写文件中不能操作此共享内存
            printf("从共享内存收到 : %s\n", msg->content);
            msg->flag = 1;
        }
        else {
            sleep(1);
        }
    }

    return 0;
}

运行结果与分析:

在程序运行中,同时打开两个终端,分别运行write和read程序,便能发现write将内容写入共享内存,而同时read进程自行同步将共享内存中的数据读出,进而实现进程通信 。

设计的这个实验的优点是方便简单,效率非常的高,有着很高的灵活性。但若有多个进程同时读取,可能会出现错误,解决该问题需要有一种进程同步的机制,或者借助其他手段来进行进程间的同步工作,将在接下来的学习中会进一步解决。

三、参考博客与资料

sky_Mata 的 进程间通信方式——共享内存: https://blog.csdn.net/skyroben/article/details/72625028
清清飞扬 的 linux ftok()函数: https://www.cnblogs.com/joeblackzqq/archive/2011/05/31/2065161.html

02-10 21:27