system V IPC进程间通信机制一网打尽

简介: system V IPC进程间通信机制一网打尽

必备IPCS命令解析

  • ipcs  

功能 : 查看 system V IPC进程间通信设施的信息

常用选项

-a : 查看所有通信设施信息, 不加选项默认-a

-m : 指定查看共享内存

-q  : 指定查看消息队列

-s   : 指定查看信号量

  • ipcrm  
  • 功能:删除System V进程间通信(IPC)对象和关联的数据结构

使用方式:  ipcrm 选项 id号

常用选项

-m : 删除共享内存

-q : 删除消息队列

-s : 删除信号量

eg : 指定删除shmid = 18 和  msqid = 8的 IPC对象.

Linux IPC消息队列

消息队列提供了一个进程向另外一个进程传输一个数据块的方法


消息队列优势: 对比管道通信来看


避免了管道通信的同步阻塞问题    eg : 读进程从管道中读取数据, 但是管道中并没有数据可读, 此刻读进程就需要同步阻塞等待写进程写入数据.

消息队列不必像管道那样先进先出的接收数据了, 消息队列可以根据类型(type值)来有选择地接收数据

  • msgget

函数功能: 创建一个全局消息队列IPC对象.   (内核数据结构)


参数分析:  key 唯一标识, 唯一标识一个全局的消息队列.   msgflg指定权限


返回值: 成功返回一个正整数, 代表消息队列的句柄, 失败返回 -1,并且设置errno


插一个权限介绍:     r : 读权限    w : 可写权限   X : 可执行权限.   所谓的9位权限标识位, 就是针对: 文件拥有者, 文件拥有组人员, 其他人员的访问权限的限制

  • msgsnd

函数功能:发送一条消息到消息队列中

参数分析:

msqid 之前msgget返回的消息队列句柄


msgp 指针, 指向待发送的一条消息结构体, 结构体形式如下


msgsz  消息正文的存储空间, 最多可以存下msgsz个字节的正文


msgflg 控制发送消息的形式  eg: 常用的 IPC_NOWAIT 表示非阻塞发送, 意思就是说正常没有设置非阻塞的情况下, 如果消息队列满了, 就会阻塞等待, 但是设置了IPC_NOWAIT则会立刻返回, 并且设置errno = EAGAIN

struct msgbuf {
    long mtype;       /* message type, must be > 0 */
    char mtext[1];    /* message data */
};
//我们必须按照这个形式设置msgbuff

返回值:成功返回 0, 失败返回 -1 并且设置errno

  • msgrcv

函数功能:从消息队列中接收一条消息

参数分析:

分析:


msqid 之前msgget返回的消息队列句柄


msgp 指针, 指向待发送的一条消息结构体, 结构体形式如下


msgsz  消息正文的存储空间, 最多可以存下msgsz个字节的正文


msgflg 控制接收消息的形式  eg: 常用的 IPC_NOWAIT 表示非阻塞接收, 意思就是说正常没有设置非阻塞的情况下, 如果消息队列没有消息, 就会阻塞等待, 但是设置了IPC_NOWAIT则会立刻返回, 并且设置errno = ENOMSG

msgtyp 指定接受哪种类型的消息:

  1. 等于0,指定接收消息队列第一条消息
  2. 大于0,指定接收消息队列中第一条类型等于msgtyp的消息
  3. msgtyp 小于0, 读取消息队列中第一个类型值比msgtyp的绝对值小的消息

返回值:  成功返回接收的消息正文长度, 失败返回 -1 并且设置 errno

  • msgctl

函数功能: 控制消息队列, 对于消息队列IPC进行设置, 操作

参数分析:

  1. msqid 之前msgget返回的消息队列句柄
  2. cmd 对于消息队列的操作, 一般用的最多是 IPC_RMID,删除消息队列标识符

返回值:成功返回 0, 失败返回 -1 并且设置 errno

实际案例, 父子进程之间通信, 父进程向消息队列中写入消息, 子进程按照消息的类型接收消息

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <sys/wait.h>
#define ERR_EXIT(m) \
    do { perror(m); exit(EXIT_FAILURE); } while (0);
#define TEXTSIZE 1024
//消息结构体
typedef struct msgbuff {
  long int type;
  char  text[TEXTSIZE];
} msgbuff;
int main(int argc, char* argv[]) {
  if (2 != argc) {
    fprintf(stderr, "usage: %s <filepath>", argv[0]);
    exit(EXIT_FAILURE);
  }
  key_t key = ftok(argv[1], 'q');
  pid_t pid = fork();
  int msgid;
  if (pid == -1) {
    ERR_EXIT("fork");
  }
  if (pid == 0) {
    //son process
    //获取消息队列
    msgid = msgget(key, 0666 | IPC_CREAT);
    msgbuff msg;
    //然后接收类型为1的消息并且打印
    int read_size = msgrcv(msgid, &msg, TEXTSIZE, 1, 0);
    if (-1 == read_size) {
      ERR_EXIT("msgrcv");
    }
    printf("type: %d, text: %s\n", msg.type, msg.text);
    memset(&msg, 0, sizeof(msg));
    //再接收一下类型为2的消息并且打印
    read_size = msgrcv(msgid, &msg, TEXTSIZE, 2, 0);
    if (-1 == read_size) {
      ERR_EXIT("msgrcv");
    }
    printf("type: %d, text: %s\n", msg.type, msg.text);
  } else {
    //fa process
    //创建消息队列
    msgid = msgget(key, 0666 | IPC_CREAT);
    //创建消息结构体
    msgbuff msg1, msg2;
    memset(&msg1, 0, sizeof(msg1));
    memset(&msg1, 0, sizeof(msg2));
    msg1.type = 1;
    memcpy(msg1.text, "hello msg queue I am type1", strlen("hello msg queue I am type1"));
    msgsnd(msgid, &msg1, TEXTSIZE, 0);
    msg2.type = 2;
    memcpy(msg2.text, "hello msg queue, I am type2", strlen("hello msg queue, I am type2"));
    msgsnd(msgid, &msg2, TEXTSIZE, 0);
    waitpid(pid, NULL, 0);
      if (-1 == msgctl(msgid, IPC_RMID, NULL)) {
          ERR_EXIT("msgctl");
          exit(1);
      }
  }
  return 0;
}

Linux IPC信号量

  • 理解信号量

信号量是一种资源, 临界资源, 是为了多进程间或者多线程间的同步互斥问题而存在的.


信号量本身也是一个IPC对象,是临界共享资源, 利用它使得多进程或者多线程之间可以实现通信.


信号量本身就是具有原子性的计数器, 意思就是说对于信号量的操作是自带原子性的, 就不用担心多进程或者多线程互斥写操作的问题了.


何为同步问题: 同步, 两个事件之间具有同步的性质, 意味着两个事件之间是存在条件约束的, 是有等待关系的.    eg  B 的要发生必须先发生 A ,否则B只能挂起等待,  则称 A, B是同步的


Linux最常见的同步问题: 条件变量, 满足一个条件, 才能执行一个操作. 在满足条件之前挂起等待, 条件的满足其实依赖的是 另一个事件的发生.


通信的时候通信双方也必须保持同步,比如说共享内存就需要借助信号量这样的同步机制,维护通信双方进程之间的同步.


何为互斥问题:    多个进程或者线程对于同一份资源进行写操作, 写操作一定是互斥的, 即为同一时刻只能允许一个进程或者线程对这份资源进行一个写操作.

  • semget

函数功能:创建或者获取一个信号量集合IPC对象

参数分析:

  1. key :标识全局信号量集合
  2. nsems: 表示要创建/获取的信号量集中信号量的数目,如果是创建信号量,这个值必须指定。如果是获取已经存在的信号量,可以把它设置成0.
  3. semflg:指定权限

插一个权限介绍:     r : 读权限    w : 可写权限   X : 可执行权限.   所谓的9位权限标识位, 就是针对: 文件拥有者, 文件拥有组人员, 其他人员的访问权限的限制

返回值:成功返回信号集合句柄, 失败返回 -1 并且设置errno

  • semop

函数功能:对于信号量资源进行 P V 操作,改变信号量的值

参数分析:

semid 之前semget返回的信号量集合句柄


nsops:nsops参数指定要执行的操作个数,即是sops数组中元素的个数,semop对数组sops中的每个元素按照数组顺序依次执行操作,并且这个过程是原子操作。


sops 结构体指针, 指向struct sembuf 数组首地址 结构体定义如下

struct sembuf
{
  unsigned short int sem_num;/* semaphore number */
  short int sem_op; /* semaphore operation */
  short int sem_flg;/* operation flag */
};
  1. sem_flg 可选值是 IPC_NOWAIT, SEM_UNDO,IPC_NOWAIT指,无论信号量集操作是否成功,semop调用都立刻返回。并且设置errno = EAGAIN , SEM_UNDO含义是,当进程退出时,取消正在进行的semop操作      

返回值:成功返回 0, 失败返回 -1 并且设置 errno

  • semctl

函数功能:对于指定信号量IPC进行操作

参数分析:

semid 之前semget返回的信号量集合句柄


semnum 指定操作信号量在信号量集合中的编号, (The semaphores in a set are numbered starting at 0. 也就是说集合中的信号量编号从0开始.


cmd 对指定信号量执行的操作


有的命令需要传入第4个参数,这个参数类型由用户定义,但是,内核给出了它的固定形式

 union semun {
     int              val;    /* Value for SETVAL */
     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
     unsigned short  *array;  /* Array for GETALL, SETALL */
     struct seminfo  *__buf;  /* Buffer for IPC_INFO
                             (Linux-specific) */
 };

cmd常用列举:

  1. SETVAL 将信号量的semval值设置为semun.val
  2. IPC_RMID 立即移除信号量集,唤醒所有等待信号量集的进程

返回值:成功返回 0, 失败返回 -1 并且设置 errno

实际案例:利用信号量实现一个简单的父子进程之间的同步等待, 通信, 也就是利用P V实现了跟锁一样的效果, 实现了父子进程同步打印, 不会出现乱入(汇编指令中间重入)问题, 保证原子性操作

#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <sys/wait.h>
#define ERR_EXIT(m) \
    do { perror(m); exit(EXIT_FAILURE); } while (0);
//联合结构体
union semun {
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
};
int initsem(key_t key) {
    int semid = -1;
    //创建一个信号量集合IPC, 其中包含一个信号量
    if (-1 == (semid = semget(key, 1, 0666 | IPC_CREAT))) {
        ERR_EXIT("semget");
    }
    //设置信号量val
    union semun sem_un;
    sem_un.val = 1;
    if (-1 == semctl(semid, 0, SETVAL, sem_un)) {
        ERR_EXIT("semctl");
    }
    return semid;
}
//销毁
void destorysem(int semid) {
    if (-1 == semctl(semid, 0, IPC_RMID)) {
        ERR_EXIT("semctl destorysem");
    }
}
void P(int semid) {
    //因为信号量集合中就只是一个
    //所以sembuf无需设置为vector
    struct sembuf op;//操作
    op.sem_num = 0;
    op.sem_op = -1;// - 1
    op.sem_flg = SEM_UNDO;
    if (-1 == semop(semid, &op, 1)) {
        ERR_EXIT("semop P");
    }
}
void V(int semid) {
    struct sembuf op;//操作
    op.sem_num = 0;
    op.sem_op = 1;// + 1
    op.sem_flg = SEM_UNDO;
    if (-1 == semop(semid, &op, 1)) {
        ERR_EXIT("semop V");
    }
}
int main(int argc, char* argv[]) {
    if (2 != argc) {
        fprintf(stderr, "usage: %s <filepath>", argv[0]);
        exit(EXIT_FAILURE);
    }
    //1.获取唯一标识key
    key_t key = ftok(argv[1], 'a');
    //2. 创建信号量集合
    int semid = initsem(key);
    int cnt = 10;
    pid_t pid = fork();
    if (pid == -1) {
        ERR_EXIT("fork");
    }
    if (pid == 0) {
        //son process
      while (cnt > 0) {
         // P(semid);
          printf("I am son process\n");
          sleep(1);
          //V(semid);
          cnt -= 1; 
      }
    } else {
      //fa process
      while (cnt > 0) { 
        //P(semid);
        printf("I am fa process\n");
        sleep(1);
        //V(semid);
        cnt -= 1;
      }
      waitpid(pid, NULL, 0);
      destorysem(semid);
    }
    return 0;
}

Linux IPC共享内存

共享内存是最快的IPC机制, 因为它不涉及任何的数据传输. 而是采取利用一块公共物理内存映射, 挂载到进程地址空间, 使得多个进程对于同一块物理内存可视的方法实现通信.


但是也存在一定的弊端, 就是无法支持同步, 我们就需要借助其他通信机制手段来同步进程对共享内存的访问


eg : 我 A 进程对于共享内存写入了数据, 需要跟 B 进程通信, 建立同步, 可以这个时候存在一个很大的问题, B 进行如何可以得知 A 进行已经写入了数据了.    此时我们就需要借助semaphore来实现同步.       ---   这一点很重要, 理解为何使用共享内存就需要配合其他通信机制来完成同步, 因为共享内存本身就不支持同步访问. 不同于信号量是本身支持原子操作的.

  • shmget

功能:申请共享内存

shmflag : 9位权限标志位:    我们通常使用  0x666  |  IPC_CREAT | IPC_EXCL

插一个权限介绍:     r : 读权限    w : 可写权限   X : 可执行权限.   所谓的9位权限标识位, 就是针对: 文件拥有者, 文件拥有组人员, 其他人员的访问权限的限制

  • shmat

功能:将物理内存连接,挂载,关联到进程地址空间中去


shmaddr :NULL 自动选取连接地址,         不为NULL, 人为指定到希望挂载的地址上


我们还是填写 NULL


Return Val : 返回一个ptr, 指向共享物理内存的首地址. 虚拟空间中的连接首地址

  • shmdt

功能:解除挂载, 将共享内存和当前进程进行脱离.


shmaddr :  之前 shmat 的 return val, 共享内存映射在虚拟地址空间上的首地址.


Return Val:          success return 0     failure return -1

  • shmctl

功能:对于共享内存IPC进行操作

cmd:  IPC_RMID  删除共享内存

buf 传 NULL 即可

实际案例:利用共享内存实现 父子进程之间的 通信, 父进程向共享内存中写入数据, 子进程在写入时候同步打印写入的数据即可.

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#define ERR_EXIT(m) \
    do { perror(m); exit(EXIT_FAILURE); } while (0);
#define SHMSIZE 1024
//联合结构体
union semun {
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
};
int initsem(key_t key) {
  int semid = -1;
  if (-1 == (semid = semget(key, 1, 0666 | IPC_CREAT))) {
    ERR_EXIT("semid");
  }
  //设置信号量集合中编号为0号的信号量val 为 2
  union semun sem_un;
  sem_un.val = 1;
  if (-1 == semctl(semid, 0, SETVAL, sem_un)) {
    ERR_EXIT("semop set val");
  }
  return semid;
}
void destorysem(int semid) {
  if (-1 == semctl(semid, 0, IPC_RMID)) {
    ERR_EXIT("semctl destorysem");
  }
} 
void P(int semid) {
  struct sembuf op;
  op.sem_num = 0;
  op.sem_op = -1;
  op.sem_flg = SEM_UNDO;
  if (-1 == semop(semid, &op, 1)) {
    ERR_EXIT("semop P");
  }
}
void V(int semid) {
  struct sembuf op;
  op.sem_num = 0;
  op.sem_op = 1;
  op.sem_flg = SEM_UNDO;
  if (-1 == semop(semid, &op, 1)) {
    ERR_EXIT("semop V");
  }
} 
void waitforzero(int semid) {
  struct sembuf op;
  op.sem_num = 0;
  op.sem_op = 0;
  op.sem_flg = SEM_UNDO;
  if (-1 == semop(semid, &op, 1)) {
    ERR_EXIT("semop zero");
  }
} 
int main(int argc, char* argv[]) {
  if (2 != argc) {
    fprintf(stderr, "usage: %s <filepath>", argv[0]);
    exit(EXIT_FAILURE);
  }
  key_t key1 = ftok(argv[1], 'c');
  key_t key2 = ftok(argv[1], 'C');
  //创建信号量集合IPC
  int semid = initsem(key1);
  int shmid = -1;
  pid_t pid = fork();
  if (pid < 0) {
    ERR_EXIT("fork");
  }
  if (pid == 0) {
    //son process
    waitforzero(semid);
    if (-1 == (shmid = shmget(key2, SHMSIZE, 0666 | IPC_CREAT))) {
      ERR_EXIT("shmget");
    }
    void* p = shmat(shmid, NULL, 0);
        if ((void*)-1 == p)
        {
            ERR_EXIT("shmat");
        }
        char buf[SHMSIZE] = {0};
        memcpy(buf, p, SHMSIZE);
        printf("%s\n", buf);
        //卸载共享内存
        if (-1 == shmdt(p))
        {
            ERR_EXIT("shmdt in parent");
        }
    V(semid);//为父进程归还这个资源.
  } else {
    // fa process
    if (-1 == (shmid = shmget(key2, SHMSIZE, 0666 | IPC_CREAT))) {
      ERR_EXIT("shmget");
    }
    void* p;
    p = shmat(shmid, NULL, 0);
    if ((void*)-1 == p)
        {
            ERR_EXIT("shmat");
        }
    char buff[SHMSIZE];
    printf("请输入要传输给子进程的数据\n");
    scanf("%s", buff);
    memcpy(p, buff, strlen(buff));
    P(semid);
    waitpid(pid, NULL, 0);
    //通信完成, 销毁所有IPC
    destorysem(semid);
    //卸载内存, 销毁shmid共享内存
    if (-1 == shmdt(p)) {
      ERR_EXIT("shmdt");
    }
    if (-1 == shmctl(shmid, IPC_RMID, NULL)) {
      ERR_EXIT("shmctl destoryshm");
    }
  }
  return 0;
}
相关文章
|
1月前
|
消息中间件 存储 Linux
|
2月前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
41 0
Linux c/c++之IPC进程间通信
|
2月前
|
消息中间件 存储 网络协议
操作系统的心脏:深入理解进程间通信(IPC)机制
在现代计算机系统中,操作系统扮演着至关重要的角色,而进程间通信(IPC)作为操作系统的核心功能之一,极大地影响着系统的性能和稳定性。本文将通过浅显易懂的语言,详细探讨进程间通信的基本原理、主要类型及其实际应用,旨在为读者提供一个清晰且全面的理解和认识。 ##
176 1
|
3月前
|
人工智能 Kubernetes 算法
探究操作系统的心脏——进程管理机制
本文深入探讨了操作系统核心组件之一——进程管理机制。进程管理作为操作系统的基础功能,负责协调和控制计算机系统内运行的所有进程,确保系统资源的有效分配与利用。通过详细介绍进程的定义、状态转换、调度算法以及多线程技术等关键概念,本文揭示了进程管理如何支撑起整个操作系统的运行框架,并保障用户任务的顺利执行。同时,文章还讨论了现代操作系统在进程管理方面的创新与挑战,为读者提供了一个全面而深入的理解视角。
49 1
|
3月前
|
算法 调度 UED
探索操作系统的心脏——进程管理机制
本文将深入探讨操作系统中至关重要的部分——进程管理机制。我们将从基本概念入手,逐步解析进程的定义、状态及其在操作系统中的角色。随后,我们会详细讨论进程调度算法,包括先来先服务、短作业优先、时间片轮转和优先级调度等,分析它们的优势与应用情景。最后,通过实例展示这些算法在实际系统运作中的运用,帮助读者更好地理解进程管理的核心原理。
|
3月前
|
消息中间件 Python
深入理解操作系统的进程间通信(IPC)机制
本文将探讨操作系统中的核心概念——进程间通信(IPC),揭示其在系统运作中的重要性及实现方式。通过分析不同类型的IPC手段,如管道、信号、共享内存等,帮助读者更好地理解操作系统的内部工作原理及其在实际应用中的表现。
187 1
|
3月前
|
消息中间件 存储 大数据
深入理解操作系统中的进程间通信(IPC)机制
本文旨在探讨操作系统中进程间通信(IPC)的核心机制与其重要性。通过对不同IPC手段如管道、信号、消息队列及共享内存等的详细解析,揭示它们如何高效地促进进程间的信息交换与同步。文章不仅阐述各种IPC技术的实现原理,还探讨了它们在实际系统应用中的场景与优化策略,为系统开发者提供全面而深入的理解。
|
3月前
|
消息中间件 程序员 数据处理
探究操作系统中的进程间通信(IPC)机制及其在现代软件开发中的应用
本文深入探讨了操作系统中的核心概念——进程间通信(IPC),揭示了其在现代软件开发中的关键作用。通过对各种IPC机制如管道、消息队列、共享内存等的详细分析,本文旨在为读者提供一个清晰的理解框架,帮助他们掌握如何在实际应用中有效利用这些技术以实现进程间的协同工作。此外,文章还将探讨IPC在高并发环境下的性能优化策略,以及如何避免常见的IPC编程错误。通过结合理论与实践,本文不仅适合希望深入了解操作系统原理的技术人员阅读,也对那些致力于提升软件质量和开发效率的程序员具有重要参考价值。
69 0
|
5月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
5月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
192 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)