共享内存 - 多进程编程(四)

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。

1、特点

  1. 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

  2. 因为多个进程可以同时操作,所以需要进行同步。

  3. 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

2、原型

1 #include 
2 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
3 int shmget(key_t key, size_t size, int flag);
4 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
5 void *shmat(int shm_id, const void *addr, int flag);
6 // 断开与共享内存的连接:成功返回0,失败返回-1
7 int shmdt(void *addr); 
8 // 控制共享内存的相关信息:成功返回0,失败返回-1
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

当用shmget函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。

当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。

shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。

shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。

3、例子

下面这个例子,使用了【共享内存+信号量+消息队列】的组合来实现服务器进程与客户进程间的通信。

  • 共享内存用来传递数据;
  • 信号量用来同步;
  • 消息队列用来 在客户端修改了共享内存后 通知服务器读取。

server.c

#include
#include
#include  // shared memory
#include  // semaphore
#include  // message queue
#include   // memcpy

// 消息队列结构
struct msg_form {
    long mtype;
    char mtext;
};

// 联合体,用于semctl初始化
union semun
{
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};

// 初始化信号量
int init_sem(int sem_id, int value)
{
    union semun tmp;
    tmp.val = value;
    if(semctl(sem_id, 0, SETVAL, tmp) == -1)
    {
        perror("Init Semaphore Error");
        return -1;
    }
    return 0;
}

// P操作:
//  若信号量值为1,获取资源并将信号量值-1
//  若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = -1; /*P操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V操作:
//  释放资源并将信号量值+1
//  如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = 1;  /*V操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}

// 删除信号量集
int del_sem(int sem_id)
{
    union semun tmp;
    if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
    {
        perror("Delete Semaphore Error");
        return -1;
    }
    return 0;
}

// 创建一个信号量集
int creat_sem(key_t key)
{
    int sem_id;
    if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
    {
        perror("semget error");
        exit(-1);
    }
    init_sem(sem_id, 1);  /*初值设为1资源未占用*/
    return sem_id;
}


int main()
{
    key_t key;
    int shmid, semid, msqid;
    char *shm;
    char data[] = "this is server";
    struct shmid_ds buf1;  /*用于删除共享内存*/
    struct msqid_ds buf2;  /*用于删除消息队列*/
    struct msg_form msg;  /*消息队列用于通知对方更新了共享内存*/

    // 获取key值
    if((key = ftok(".", 'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }

    // 创建共享内存
    if((shmid = shmget(key, 1024, IPC_CREAT|0666)) == -1)
    {
        perror("Create Shared Memory Error");
        exit(1);
    }

    // 连接共享内存
    shm = (char*)shmat(shmid, 0, 0);
    if((int)shm == -1)
    {
        perror("Attach Shared Memory Error");
        exit(1);
    }


    // 创建消息队列
    if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
    {
        perror("msgget error");
        exit(1);
    }

    // 创建信号量
    semid = creat_sem(key);

    // 读数据
    while(1)
    {
        msgrcv(msqid, &msg, 1, 888, 0); /*读取类型为888的消息*/
        if(msg.mtext == 'q')  /*quit - 跳出循环*/
            break;
        if(msg.mtext == 'r')  /*read - 读共享内存*/
        {
            sem_p(semid);
            printf("%s\n",shm);
            sem_v(semid);
        }
    }

    // 断开连接
    shmdt(shm);

    /*删除共享内存、消息队列、信号量*/
    shmctl(shmid, IPC_RMID, &buf1);
    msgctl(msqid, IPC_RMID, &buf2);
    del_sem(semid);
    return 0;
}

client.c

#include
#include
#include  // shared memory
#include  // semaphore
#include  // message queue
#include   // memcpy

// 消息队列结构
struct msg_form {
    long mtype;
    char mtext;
};

// 联合体,用于semctl初始化
union semun
{
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};

// P操作:
//  若信号量值为1,获取资源并将信号量值-1
//  若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = -1; /*P操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V操作:
//  释放资源并将信号量值+1
//  如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = 1;  /*V操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}


int main()
{
    key_t key;
    int shmid, semid, msqid;
    char *shm;
    struct msg_form msg;
    int flag = 1; /*while循环条件*/

    // 获取key值
    if((key = ftok(".", 'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }

    // 获取共享内存
    if((shmid = shmget(key, 1024, 0)) == -1)
    {
        perror("shmget error");
        exit(1);
    }

    // 连接共享内存
    shm = (char*)shmat(shmid, 0, 0);
    if((int)shm == -1)
    {
        perror("Attach Shared Memory Error");
        exit(1);
    }

    // 创建消息队列
    if ((msqid = msgget(key, 0)) == -1)
    {
        perror("msgget error");
        exit(1);
    }

    // 获取信号量
    if((semid = semget(key, 0, 0)) == -1)
    {
        perror("semget error");
        exit(1);
    }

    // 写数据
    printf("***************************************\n");
    printf("*                 IPC                 *\n");
    printf("*    Input r to send data to server.  *\n");
    printf("*    Input q to quit.                 *\n");
    printf("***************************************\n");

    while(flag)
    {
        char c;
        printf("Please input command: ");
        scanf("%c", &c);
        switch(c)
        {
            case 'r':
                printf("Data to send: ");
                sem_p(semid);  /*访问资源*/
                scanf("%s", shm);
                sem_v(semid);  /*释放资源*/
                /*清空标准输入缓冲区*/
                while((c=getchar())!='\n' && c!=EOF);
                msg.mtype = 888;
                msg.mtext = 'r';  /*发送消息通知服务器读数据*/
                msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                break;
            case 'q':
                msg.mtype = 888;
                msg.mtext = 'q';
                msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                flag = 0;
                break;
            default:
                printf("Wrong input!\n");
                /*清空标准输入缓冲区*/
                while((c=getchar())!='\n' && c!=EOF);
        }
    }

    // 断开连接
    shmdt(shm);

    return 0;
}

注意:当scanf()输入字符或字符串时,缓冲区中遗留下了\n,所以每次输入操作后都需要清空标准输入的缓冲区。但是由于 gcc 编译器不支持fflush(stdin)(它只是标准C的扩展),所以我们使用了替代方案:

1 while((c=getchar())!='\n' && c!=EOF);

定义:
共享内存(Shared Memory)就是允许两个或多个进程访问同一个内存空间,是在多进程通信的最高效的方式。
操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其它的进程读到的数据也将会改变。由于共享内存会成为进程用户空间的一部分,所有这种通信方式不需要内核介入。
共享内存并未提供锁机制,也就是说,在某一个进程对共享内存的进行读写的时候,不会阻止其它的进程对它的读写,可能会出现数据的错乱。

函数:
1、必备头文件

#include
#include
1
2
2、shmget() 获取或创建共享内存

// 获取或者创建共享内存
int shmget(key_t key, size_t size, int shmflg);
- key是共享内存的键值,是一个整数,是共享内存在系统中的编号,不同共享内存的编号不同。一般使用十六进制。
- size表示待创建共享内存的大小,以字节为单位
- shmflg表示共享内存的访问权限,与文件的权限一样,
    0666 | IPC_CREAT 表示全部用户对它可读写
- 返回值:返回共享内存标识

3、shmat() 把共享内存连接到当前进程的地址空间

void *shmat(int shm_id, const void *shm_addr, int shmflg);
- shm_id表示由 shmget函数返回的 共享内存的标识号
- shm_addr指定共享内存连接到当前进程中的地址位置,通常为 NULL,表示让系统来选择共享内存的地址。
- shm_flg是一组标志位,通常为0
- 返回值:
    调用成功:返回一个指向共享内存第一个字节的指针
    调用失败:返回 -1


4、shmdt() 将共享内存从当前进程中分离,相当于 shmat()函数的反操作

int shmdt(const void *shmaddr);
- shmaddr是 shmat函数 返回的地址
- 返回值:
    调用成功:返回 0
    调用失败:返回 -1

5、shmctl() 删除共享内存int shmctl(int shm_id, int command, struct shmid_ds *buf);

- shm_id是共享内存的标识号
- command 填写 IPC_RMID
- buf 填写 0
- 返回值:
    调用成功:返回 0
    调用失败:返回 -1

使用步骤
调用 shmget() 函数创建一个新共享内存段或取得一个现有的共享内存段的标识号。返回后续可调用的需要用到的共享内存标识符。
在进程中使用 shmat() 函数附上共享内存段,即让该共享内存段成为调用进程的虚拟内存中的一部分(进程绑定共享内存段)
使用 shmat() 返回的内存地址指针,就可以在程序中对该共享内存段进行操作(写操作/读操作)。
调用 shmdt() 函数,让进程和共享内存段分离,进程无法再操作共享内存了。(只是分离,共享内存依然存在)
调用 shmctl() 函数,删除共享内存段,只有当与该共享内存段绑定好的进程都分离后,才会销毁,只有一个进程需要执行这一步。
举例
// write_shm.c

#include 
#include 
#include 
#include 
#include  

int main()
{
    int shmid; // 共享内存标识符
 
      // 创建共享内存,键值为0x5005,共1024字节。
      if ( (shmid = shmget((key_t)0x5005, 1024, 0666 | IPC_CREAT)) == -1)
      { 
        printf("shmat(0x5005) failed\n"); 
        return -1; 
    }

      char *ptext = NULL;   // 用于指向共享内存的指针
 
      // 将共享内存连接到当前进程的地址空间,由ptext指针指向它
      ptext = (char *)shmat(shmid, NULL, 0);
    
      // 操作本程序的ptext指针,就是操作共享内存
    char *str = "Hello world!";
      memcpy(ptext, str, strlen(str));
    
      // 把共享内存从当前进程中分离
      shmdt(ptext);

    return 0;
}

// read_shm.c

int main()
{
    int shmid; // 共享内存标识符
 
      // 创建共享内存,键值为0x5005,共1024字节。
      if ( (shmid = shmget((key_t)0x5005, 1024, 0666 | IPC_CREAT)) == -1)
      { 
        printf("shmat(0x5005) failed\n"); 
        return -1; 
    }

      char *ptext = NULL;   // 用于指向共享内存的指针
 
      // 将共享内存连接到当前进程的地址空间,由ptext指针指向它
      ptext = (char *)shmat(shmid, NULL, 0);
 
      // 操作本程序的ptext指针,就是操作共享内存
      printf("%s\n", ptext);
 
      // 把共享内存从当前进程中分离
      shmdt(ptext);
   
      // 删除共享内存
      if (shmctl(shmid, IPC_RMID, 0) == -1)
      { 
        printf("shmctl(0x5005) failed\n"); 
        return -1; 
    }
}

read_shm.c的输出结果:

Hello world!
1
可以看出,write_shm.c 申请共享内存,并写入数据,read_shm获取共享内存标识号,并绑定进程,读取该共享内存中的数据。

查看共享内存
ipcs -m # 查看现有的所有共享内存段

ipcrm -m shmid # 根据共享内存段的标识号,删除共享内存
————————————————
版权声明:本文为CSDN博主「HDD615」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Sir666888/article/details/125460499

一、进程间通信

        管道、信号:只能用于两个进程间进行通讯

        共享内存、消息队列、信号量、套接字

二、IPC通信

        1.相关知识

                1)IPC可用于多个进程间进行通讯,包括共享内存、消息队列、信号量。

                2)指令;

                        ipcs -m:查看共享内存

                        ipcs -q:查看消息队列

                        ipcs -s:查看信号量

                3)键值:用于标识共享内存、消息队列以及信号量集,确保唯一性。

                        ftok():键值的创造

                                头文件:

                                        #include

                                        #include

                                原型:key_t ftok(const char *pathname, int proj_id);

                                参数:

                                        const char *pathname:参考位置  (绝对路径)

                                        int proj_id:参考的id  (0-255)

                                返回值: 成功,返回键值(key_t)    失败,返回 -1

        2.共享内存

                1)什么是共享内存?

                        在操作系统内核开辟一个空间,每一个进程都可以将这个空间映射到自己的进程空间里面,多个进程可以共享这块内存,

对他的操作都可以立即实现。

                        范围:整个计算机的进程,都可以对它进行操作。

        2)过程

                创建共享内存

                进程映射到内存进行读写

                进程接触映射

                删除共享内存

        3)相关函数

                shmget():打开或者创建一块共享内存

                头文件:

                        #include

                        #include

                原型:int shmget(key_t key, size_t size, int shmflg);

                参数:

                        key_t key:键值

                        size_t size:共享内存的大小,以字节为单位

                        int shmflg:有则打开,没有则创建

                                                IPC_CREAT | 0777

                返回值: 成功,返回的是共享内存的id号 失败,返回 -1

                shmat():把一个共享内存映射到进程空间

                头文件:

                        #include

                        #include

                原型:void *shmat(int shmid, const void *shmaddr, int shmflg);

                参数:

                        int shmid, :共享内存的id

                        const void *shmaddr, :你要将共享内存映射的位置一般填写0,表示由系统分配一块,可用的地址空间

                        int shmflg:一般填写0 表示共享内存可读写

                返回值: 系统给分配的空间的首地址  失败返回-1

                shmdt():解除共享内存的映射

                头文件:

                        #include

                        #include

                原型:int shmdt(const void *shmaddr);

                参数:系统给分配的空间的首地址

                返回值:成功 0  失败 -1

                shmctl():删除共享内存

                头文件:

                        #include

                        #include

                原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);

                参数:

                        int shmid,:贡献内存的id

                        int cmd, :IPC_RMID  

                        struct shmid_ds *buf:NULL

                返回值:成功 0  失败 -1

下边是一个练习代码:

#include
#include
#include
#include
#include
 
int main()
 
{
    key_t ft;
    int shmid;
    ft = ftok("/home/whs/002/7.4",5);
    if(ft < 0)
    {
        perror("ftok");
        return -1;
    }
    printf("ft=%d\n",ft);
    shmid = shmget(ft,256,IPC_CREAT|0666); //创建共享内存
    if(shmid < 0)
    {
        perror("shmget");
        return -1;
    }
    void *p = shmat(shmid,0,0); //映射到进程空间,0,由系统分配;0,可读写
    strcpy(p,"zhongguowansui!");//写内容到共享内存
    shmdt(p);                   //解除映射
    return 0;
}
文章知识点与官方知识档案匹配,可进一步学习相关知识
————————————————
版权声明:本文为CSDN博主「编程小白菜123」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wanghongshuai1/article/details/125604916

你可能感兴趣的:(嵌入式,多进程,多线程,共享内存)