Linux进程间通讯(消息队列&共享内存)

本文介绍了消息队列和共享内存两种进程间通信机制的基本原理和使用方法,详细阐述了它们的数据结构、系统调用及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

消息队列:
  • 消息队列提供了一种从一个进程向另外一个进程发送“一块”数据的方法。
  • 每块数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型。
  • 消息队列的每块消息有最大长度的上限(MSGMAX),每个消息队列的总字节数也是有上限的(MSGMNB),系统上消息队列的总数也是有上限的(MSGMNI)。

内核为每个IPC对象维护一个结构体(struct ipc_perm),该结构存储了IPC对象的权限和所有者。在/usr/include/linux/ipc.h中。

struct ipc_perm
{
    __kernel_key_t  key;//调用xxxget()函数时的key,通常由ftok()函数产生
    __kernel_uid_t  uid;//所有者有效用户ID。
    __kernel_gid_t  gid;//所有者所属组的有效组ID。
    __kernel_uid_t  cuid;//创建者有效用户ID。
    __kernel_gid_t  cgid;//创建者所属组的有效组ID。
    __kernel_mode_t mode; //IPC对象的权限,类似于文件的权限。
    unsigned short  seq;//序列号。
};

消息队列结构(struct msgid_ds),在/usr/include/linux/msg.h中。

struct msqid_ds {
    struct ipc_perm msg_perm;   /*上一个结构体,存储IPC对象的权限和所有者*/
    struct msg *msg_first;      /* 队列中第一个未使用的消息*/
    struct msg *msg_last;       /* 队列中最后一个未使用的消息*/
    __kernel_time_t msg_stime;  /* 最后发送消息时间 */
    __kernel_time_t msg_rtime;  /* 最后接收消息时间 */
    __kernel_time_t msg_ctime;  /* 消息队列最后修改时间 */
    unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */
    unsigned long  msg_lqbytes; /* ditto */
    unsigned short msg_cbytes;  /* 消息队列上当前的字节数 */
    unsigned short msg_qnum;    /* 消息队列上的消息数 */
    unsigned short msg_qbytes;  /* 消息队列上最大字节数 */
    __kernel_ipc_pid_t msg_lspid;   /* 最后发送消息的进程pid */
    __kernel_ipc_pid_t msg_lrpid;   /* 最后接收消息的进程pid */
};

内核中消息队列模型:
这里写图片描述
下面介绍几个关于操作消息队列的系统调用:
创建/打开消息队列:

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

int msgget(key_t key, int msgflg);
  • 参数:
    • key:消息队列的关键字,通常由ftok()产生。
    • msgflg:类似于文件操作的mode参数。
  • 返回值:失败返回-1,并设置errno,成功返回消息队列标识符。

向消息队列中添加消息:

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

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  • 参数:
    • msqid:由msgget()返回的消息队列标识符。
    • msgp:指向要发送的消息,消息一般为struct msgbuf{}结构体,如下:
 truct msgbuf {
  long mtype;       /* message type, must be > 0 */
  char mtext[1];    /* message data 数组大小依据要发送或接收的数据大小可变*/
};
    • msgsz:要发送消息的大小(char mtext[]大小,不包括mtype)。
    • msgflg:控制当前消息队列满时将要发生的动作,msgflg=IPC_NOWAIT表示队列满时不等待,返回EAGAIN错误。
  • 返回值:
    成功返回0,失败返回-1,并设置errno。

从消息队列中取走消息:

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

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
    int msgflg);
  • 参数:
    • msqid:由msgget()返回的消息队列标识符。
    • msgp:指向存放接收到的消息的缓冲区。
    • msgsz:要接收消息的大小(char mtext[]大小,不包括mtype)
    • msgtyp:它可以实现简单的接收消息优先级:
      msgtyp == 0 返回队列第一条消息。
      msgtyp > 0 返回队列中第一条类型等于msgtyp的消息。
      msgtyp < 0 返回队列中满足类型小于等于msgtyp绝对值的所有消息中最小的一个消息。
    • msgflg:控制着当前消息队列中没有满足接收要求的消息时的动作,
  • 返回值:
    失败返回-1,并设置errno,成功返回接收到的字符个数。

控制/删除消息队列:

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

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • 参数:
    • msqid:由msgget()返回的消息队列标识符。
    • cmd:将要进行的命令,有三种取值如下:
      IPC_STAT:将buf指向的struct msqid_ds结构体中的字段设置为消息队列的当前关联值。即取出消息队列的当前关联值。
      IPC_SET:在进程有足够的权限的条件下,将消息队列当前的关联值设置为buf指向的struct msqid_ds结构体中的各字段。
      IPC_RMID:删除消息队列。
    • buf:指向struct msqid_ds结构体的指针。
  • 返回值:
    成功返回0,失败返回-1,并设置errno。
    代码实例:
    comm.h
#ifndef __COMM_H_
#define __COMM_H_

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>

#define PATHNAME "."
#define PROJ_ID 0x6666

#define SERVER_TYPE 1
#define CLIENT_TYPE 2

struct msgbuf{
  long mtype;
  char mtext[1024];
};

int CreateMsgQueue();
int GetMsgQueue();
int DestroyMsgQueue(int msgid);
int SendMsg(int msgid,long who,char *msg);
ssize_t RecvMsg(int msgid,long recvType,char out[]);

#endif //__COMM_H_

comm.c

#include"comm.h"

//依据flag的不同,创建或者打开消息队列
static int CommMsgQueue(int flags){
  //获取关键字
  key_t _key = ftok(PATHNAME,PROJ_ID);
  if(_key == -1){
    perror("ftok");
    return -1; 
  }
  //产生或者打开消息队列
  int msgid = msgget(_key,flags);
  if(msgid == -1){
    perror("msgget");
    return -2; 
  }
  return msgid;
}
//创建消息队列
int CreateMsgQueue(){
  return CommMsgQueue(IPC_CREAT|IPC_EXCL|0666);
}
//打开消息队列
int GetMsgQueue(){
  return CommMsgQueue(IPC_CREAT);
}
//销毁消息队列
int DestroyMsgQueue(int msgid){
  int ret = msgctl(msgid,IPC_RMID,NULL);
  if(ret == -1){
    perror("msgctl");
  }
  return ret;
}
//发送数据
int SendMsg(int msgid,long who,char *msg){
  struct msgbuf buf;
  buf.mtype = who;
  strcpy(buf.mtext,msg);
  int ret = msgsnd(msgid,(void*)&buf,sizeof(buf.mtext),0);
  if(ret == -1){
    perror("msgsnd");
  }
  return ret;
}
//接收数据
ssize_t RecvMsg(int msgid,long recvType,char out[]){
  struct msgbuf buf;
  ssize_t ret = msgrcv(msgid,(void*)&buf,sizeof(buf.mtext),recvType,0);
  if(ret == -1){
    perror("msgrcv");
    return -1;
  }
  strcpy(out,buf.mtext);
  return 0;
}

client.c

#include"comm.h"

int main(){
  //获得消息队列
  int msgid = GetMsgQueue();
  char buf[1024];
  while(1){
    buf[0] = 0;
    printf("please Enter# ");
    fflush(stdout);
    //读标准输入
    ssize_t s = read(0,buf,sizeof(buf)-1);
    if(s > 0){ 
      buf[s] = '\0';
      //发送消息队列
      SendMsg(msgid,CLIENT_TYPE,buf);
      printf("send done...\n");
    }else{
      perror("read");
      break;
    }   
    //读消息队列
    RecvMsg(msgid,SERVER_TYPE,buf);
    printf("server #%s",buf);
  }
  return 0;
}

server.c

#include"comm.h"

int main(){
  //创建消息队列
  int msgid = CreateMsgQueue();
  char buf[1024];
  while(1){
    buf[0] = 0;
    //读取消息队列
    RecvMsg(msgid,CLIENT_TYPE,buf);
    printf("client# %s",buf);
    printf("please Enter# ");
    fflush(stdout);
    //读标准输入
    ssize_t s = read(0,buf,sizeof(buf)-1);
    if(s > 0){ 
      buf[s] = '\0';
      //发送消息队列
      SendMsg(msgid,SERVER_TYPE,buf);
      printf("send done...\n");
    }else{
      break;
    }
  }
  //销毁消息队列
  DestroyMsgQueue(msgid);
  return 0;
}

Makefile

.PHONY:all
all:client server

client:client.c comm.c
        gcc $^ -o $@
server:server.c comm.c
        gcc $^ -o $@

.PHONY:cl
cl:
        rm -f client server

结果演示:
消息队列
注:以Ctrl+c关闭程序时,第二次运行前需要ipcrm命令删除上一次创建的消息队列。
ipcs&ipcrm
ipcs:

  • 说明:ipcs命令往标准输出写入一些关于活动进程间通讯设施的信息,如没有指定任何标志,则输出当前活动消息队列、共享内存段、信号量数组。
  • 选项:
    • q:输出消息队列。
    • m:输出共享内存段。
    • s:信号量。
  • 示例:
[DELL@localhost lesson9]$ ipcs -q

--------- 消息队列 -----------
键        msqid      拥有者  权限     已用字节数 消息      
0x6602268c 98304      DELL       666        0            0           

[DELL@localhost lesson9]$ 

ipcrm

  • 说明:ipcrm用于删除进程间通讯设施。用法与ipcs类似,不过要在ipcrm命令后面指定要删除通讯设施的标识符(xxxid)。
  • 示例:
[DELL@localhost lesson9]$ ipcs -q

--------- 消息队列 -----------
键        msqid      拥有者  权限     已用字节数 消息      
0x6602268c 98304      DELL       666        0            0           

[DELL@localhost lesson9]$ ipcrm -q 98304
[DELL@localhost lesson9]$ ipcs -q

--------- 消息队列 -----------
键        msqid      拥有者  权限     已用字节数 消息      

[DELL@localhost lesson9]$ 

共享内存:

共享内存是最快的IPC通讯形式,一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再通过内核,即进程不需要再调用系统调用进入内核来传递信息,但共享内存没有内置同步与互斥操作。

共享内存模型:
这里写图片描述
共享内存结构(shmid_ds{}),在/usr/include/linux/shm.h中:

struct shmid_ds {
    struct ipc_perm     shm_perm;   /* 保存权限和所有者 */
    int         shm_segsz;  /* 共享内存大小 */
    __kernel_time_t     shm_atime;  /* 最后一个进程映射此共享内存时间 */
    __kernel_time_t     shm_dtime;  /* 最后一个进程分离该共享内存的时间 */
    __kernel_time_t     shm_ctime;  /* 最后改变该共享内存的时间 */
    __kernel_ipc_pid_t  shm_cpid;   /* 创建该共享内存进程pid */
    __kernel_ipc_pid_t  shm_lpid;   /* 最后操作该共享内存的pid */
    unsigned short      shm_nattch; /* 映射该共享内存的进程总数 */
    unsigned short      shm_unused; /* compatibility */
    void            *shm_unused2;   /* ditto - used by DIPC */
    void            *shm_unused3;   /* unused */
};

下面介绍关于共享内存的几个系统调用:
创建共享内存:

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

int shmget(key_t key, size_t size, int shmflg);
  • 参数:
    • key:关键字,通常由ftok()函数产生。
    • size:共享内存大小。
    • shmflg:类似于文件操作的mode
  • 返回值:
    失败返回-1,并设置errno,成功返回共享内存标识符。

映射共享内存:

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

void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 参数:
    • shmid:由shmget()返回的标识符。
    • shmaddr:指定的映射的地址。
      1、为NULL时,由内核决定映射地址。
      2、不为NULL且shmflg不为SHM_RND时,则以shmaddr为映射地址。
      3、不为NULL且shmflg为SHM_RND时,则shmaddr向下取最近的SHMLBA整数倍处,即shmaddr = shmaddr - (shmaddr%SHMLBA)。
    • shmflg:两个可能取值。
      1、SHM_RND:配合shmaddr一块使用。
      2、SHM_RDONLY:映射的共享内存以只读方式操作。
  • 返回值:
    成功返回映射后的首地址,失败返回-1,并设置errno。

分离共享内存:

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

int shmdt(const void *shmaddr);
  • 参数:
    • shmaddr:由shmat()函数返回的指针。
  • 返回值:
    成功返回0,失败返回-1,并设置errno。

控制/删除共享内存:

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

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 参数:
    • shmid:由shmget()返回的标识符。
    • cmd:将要进行的命令,有三种取值如下:
      IPC_STAT:将buf指向的struct shmid_ds结构体中的字段设置为共享内存的当前关联值。即取出共享内存的当前关联值。
      IPC_SET:在进程有足够的权限的条件下,将共享内存当前的关联值设置为buf指向的struct shmid_ds结构体中的各字段。
      IPC_RMID:删除共享内存。
    • buf:指向struct shmid_ds结构体的指针。
  • 返回值:
    成功返回0,失败返回-1,并设置errno。

实例代码:

comm.h

#ifndef __COMM_H_
#define __COMM_H_

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

#define PATHNAME "."
#define PROJ_ID 0x666

//创建共享内存
int CreateShm(size_t size);
//销毁共享内存
int DestroyShm(int shmid);
//获取共享内存
int GetShm(size_t size);

#endif //__COMM_H_

comm.c

#include"comm.h"

//依据flags的不同,创建或者打开共享内存。
static int CommShm(size_t size,int flags){
  key_t _key = ftok(PATHNAME,PROJ_ID);
  if(_key == -1){
    perror("ftok");
    return -1; 
  }
  int shmid = shmget(_key,size,flags);
  if(shmid == -1){
    perror("shmget");
    return -2; 
  }
  return shmid;
}

int CreateShm(size_t size){
  return CommShm(size,IPC_CREAT|IPC_EXCL|0666);
}
int DestroyShm(int shmid){
  int ret = shmctl(shmid,IPC_RMID,NULL);
  if(ret == -1){
    perror("shmctl");
  }
  return ret;
}
int GetShm(size_t size){
  return CommShm(size,IPC_CREAT);
}

client.c

#include"comm.h"

int main(){
  //获得共享内存
  int shmid = GetShm(4096);
  sleep(1);
  //将共享内存映射到进程的虚拟地址空间中。
  char *addr = shmat(shmid,NULL,0);
  sleep(2);
  int i = 0;
  while(i < 26){
    addr[i] = 'A'+i;
    i++;
    addr[i] = 0;
    sleep(1);
  }
  //分离共享内存
  shmdt(addr);
  sleep(2);
  return 0;
}

server.c

#include"comm.h"


int main(){
  //创建共享内存
  int shmid = CreateShm(4096);
  //将共享内存映射到进程的虚拟地址空间。
  char *addr = shmat(shmid,NULL,0);
  sleep(2);
  int i = 0;
  while(i++ < 26){
    printf("client# %s\n",addr);
    sleep(1);
  }
  //分离共享内存
  shmdt(addr);
  sleep(2);
  //删除共享内存。
  DestroyShm(shmid);
  return 0;
}

Makefile

.PHONY:all
all:client server

client:client.c comm.c
        gcc $^ -o $@
server:server.c comm.c
        gcc $^ -o $@


.PHONY:cl
cl:
        rm -f server client

结果演示:

这里写图片描述
注:以Ctrl+c关闭程序时,第二次运行前需要ipcrm命令删除上一次创建的共享内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值