一步一步学linux操作系统: 33 进程间通讯_管道、消息队列、共享内存与信号量

管道模型

由于管道传递数据的单向性,管道又称为半双工管道

管道的这一特点决定了其使用的局限性,具有以下特点:
- 数据只能由一个进程流向另一个进程(其中一个读管道,一个写管道);如果要进行双工通信,需要建 立两个管道。
- 管道只能用于父子进程或者兄弟进程间通信。,也就是说管道只能用于具有亲缘关系的进程间通信。

管道分为两种类型:匿名管道、命名管道

匿名管道

意思就是这个类型的管道没有名字,用完了就销毁了。

Linux 命令:

 ps -ef | grep 关键字

这里面的竖线“|”就是一个管道。它会将前一个命令的输出,作为后一个命令的输入。

命名管道

这个类型的管道需要通过 mkfifo 命令显式地创建。

mkfifo hello

hello 就是这个管道的名称。管道以文件的形式存在。这个文件的类型是 p,就是 pipe 的意思

在这里插入图片描述
往管道里面写入东西

# echo "hello world" > hello

管道里面的内容没有被读出,这个命令就是停在这里的
在这里插入图片描述

读取管道里面的内容

# cat < hello 
hello world

一方面,管道里面的内容被读取出来,打印到了终端上;另一方面,echo 那个命令正常退出了

在这里插入图片描述

消息队列模型

管道将信息一股脑儿地从一个进程,倒给另一个进程不同,消息队列有点儿像邮件,发送数据时,会分成一个一个独立的数据单元,也就是消息体,每个消息体都是固定大小的存储块,在字节流上不连续。

msgget 函数

创建一个消息队列,参数 key,这是消息队列的唯一标识。
如何保持唯一性呢?
可以指定一个文件,ftok 会根据这个文件的 inode,生成一个近乎唯一的 key。只要在这个消息队列的生命周期内,这个文件不要被删除就可以了。只要不删除,无论什么时刻,再调用 ftok,也会得到同样的 key。
eg:
code来自 极客时间趣谈linux操作系统


#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>


int main() {
    int messagequeueid;
    key_t key;


    if((key = ftok("./1234", 1024)) < 0)
    {
        perror("ftok error");
        exit(1);
    }


    printf("Message Queue key: %d.\n", key);


    if ((messagequeueid = msgget(key, IPC_CREAT|0777)) == -1)
    {
        perror("msgget error");
        exit(1);
    }


    printf("Message queue id: %d.\n", messagequeueid);
}

在这里插入图片描述
System V IPC 体系有一个统一的命令行工具:ipcmk,ipcs 和 ipcrm 用于创建、查看和删除 IPC 对象。
ipcs -q 就能看到上面创建的消息队列对象
在这里插入图片描述

msgsnd 函数

发送信息,第一个参数是 message queue 的 id,第二个参数是消息的结构体,第三个参数是消息的长度,最后一个参数是 flag。IPC_NOWAIT 表示发送的时候不阻塞,直接返回。

eg:
code来自 极客时间趣谈linux操作系统
消息结构的定义

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

类型 type 和正文 text 没有强制规定,只要消息的发送方和接收方约定好即可。


#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <getopt.h>
#include <string.h>


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


int main(int argc, char *argv[]) {
  int next_option;
  const char* const short_options = "i:t:m:";
  const struct option long_options[] = {
    { "id", 1, NULL, 'i'},
    { "type", 1, NULL, 't'},
    { "message", 1, NULL, 'm'},
    { NULL, 0, NULL, 0 }
  };
  
  int messagequeueid = -1;
  struct msg_buffer buffer;
  buffer.mtype = -1;
  int len = -1;
  char * message = NULL;
  do {
    next_option = getopt_long (argc, argv, short_options, long_options, NULL);
    switch (next_option)
    {
      case 'i':
        messagequeueid = atoi(optarg);
        break;
      case 't':
        buffer.mtype = atol(optarg);
        break;
      case 'm':
        message = optarg;
        len = strlen(message) + 1;
        if (len > 1024) {
          perror("message too long.");
          exit(1);
        }
        memcpy(buffer.mtext, message, len);
        break;
      default:
        break;
    }
  }while(next_option != -1);


  if(messagequeueid != -1 && buffer.mtype != -1 && len != -1 && message != NULL){
    if(msgsnd(messagequeueid, &buffer, len, IPC_NOWAIT) == -1){
      perror("fail to send message.");
      exit(1);
    }
  } else {
    perror("arguments error");
  }
  
  return 0;
}

在这里插入图片描述

msgrcv 函数

收消息,第一个参数是 message queue 的 id,第二个参数是消息的结构体,第三个参数是可接受的最大长度,第四个参数是消息类型, 最后一个参数是 flag。IPC_NOWAIT 表示发送的时候不阻塞,直接返回。
eg:
code来自 极客时间趣谈linux操作系统


#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <getopt.h>
#include <string.h>


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


int main(int argc, char *argv[]) {
  int next_option;
  const char* const short_options = "i:t:";
  const struct option long_options[] = {
    { "id", 1, NULL, 'i'},
    { "type", 1, NULL, 't'},
    { NULL, 0, NULL, 0 }
  };
  
  int messagequeueid = -1;
  struct msg_buffer buffer;
  long type = -1;
  do {
    next_option = getopt_long (argc, argv, short_options, long_options, NULL);
    switch (next_option)
    {
      case 'i':
        messagequeueid = atoi(optarg);
        break;
      case 't':
        type = atol(optarg);
        break;
      default:
        break;
    }
  }while(next_option != -1);


  if(messagequeueid != -1 && type != -1){
    if(msgrcv(messagequeueid, &buffer, 1024, type, IPC_NOWAIT) == -1){
      perror("fail to recv message.");
      exit(1);
    }
    printf("received message type : %d, text: %s.", buffer.mtype, buffer.mtext);
  } else {
    perror("arguments error");
  }
  
  return 0;
}

在这里插入图片描述

共享内存模型

共享内存,顾名思义就是允许两个不相关的进程访问同一个物理内存。共享内存并未提供同步机制。

shmget函数

创建一个共享内存

int shmget(key_t key, size_t size, int flag);

第一个参数是 key,和 msgget 里面的 key 一样,都是唯一定位一个共享内存对象,也可以通过关联文件的方式实现唯一性。第二个参数是共享内存的大小。第三个参数如果是 IPC_CREAT,同样表示创建一个新的。

eg:


#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>


int main() {
    int sharememid;
    key_t key;


    if((key = ftok("./1234", 1024)) < 0)
    {
        perror("ftok error");
        exit(1);
    }


    printf("Share Men key: %d.\n", key);


    if ((sharememid = shmget(key, 1024, IPC_CREAT|0777)) == -1)
    {
        perror("shmget error");
        exit(1);
    }


    printf("Share Men id: %d.\n", sharememid);
}

在这里插入图片描述

shmat 函数

如果一个进程想要访问这一段共享内存,需要将这个内存加载到自己的虚拟地址空间的某个位置,通过 shmat 函数,就是 attach 的意思。其中 addr 就是要指定 attach 到这个地方。

通常的做法是将 addr 设为 NULL,让内核选一个合适的地址。返回值就是真正被 attach 的地方。

void *shmat(int shm_id, const void *addr, int flag);

shmdt 函数

如果共享内存使用完毕,可以通过 shmdt 解除绑定,然后通过 shmctl,将 cmd 设置为 IPC_RMID,从而删除这个共享内存对象。

int shmdt(void *addr); 
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

信号量

使得同一个共享的资源,同时只能被一个进程访问。在 System V IPC 进程间通信机制体系中,有信号量(Semaphore)。因此,信号量和共享内存往往要配合使用。

信号量其实是一个计数器,主要用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

信号量初始化为一个数值,来代表某种资源的总体数量

两种原子操作

  • P 操作
    申请资源操作
  • V 操作
    归还资源操作

semget 函数

创建一个信号量
第一个参数 key 同上,第二个参数 num_sems 不是指资源的数量,而是表示可以创建多少个信号量,形成一组信号量,也就是说,如果你有多种资源需要管理,可以创建一个信号量组。

 int semget(key_t key, int num_sems, int sem_flags);

semctl 函数

初始化信号量的总的资源数量,通过 semctl 函数。

第一个参数 semid 是这个信号量组的 id,第二个参数 semnum 才是在这个信号量组中某个信号量的 id,第三个参数是命令,如果是初始化,则用 SETVAL,第四个参数是一个 union。如果初始化,应该用里面的 val 设置资源总量。


int semctl(int semid, int semnum, int cmd, union semun args);


union semun
{
  int val;
  struct semid_ds *buf;
  unsigned short int *array;
  struct seminfo *__buf;
};

semop 函数

进行P 操作和V 操作

第一个参数还是信号量组的 id,一次可以操作多个信号量。第三个参数 numops 就是有多少个操作,第二个参数将这些操作放在一个数组中。

数组的每一项是一个 struct sembuf,里面的第一个成员是这个操作的对象是哪个信号量。第二个成员就是要对这个信号量做多少改变。当相应的资源数不能满足请求时,就要看 sem_flg ,


int semop(int semid, struct sembuf semoparray[], size_t numops);


struct sembuf 
{
  short sem_num; // 信号量组中对应的序号,0~sem_nums-1
  short sem_op;  // 信号量值在一次操作中的改变量
  short sem_flg; // IPC_NOWAIT, SEM_UNDO
}

信号

Linux系统中信号没有特别复杂的数据结构,就是用一个代号一样的数字。
Linux 提供了几十种信号,分别代表不同的意义。信号之间依靠它们的值来区分。
信号可以在任何时候发送给某一进程,进程需要为这个信号配置信号处理函数。

参考资料:

趣谈Linux操作系统(极客时间)链接:
http://gk.link/a/10iXZ
欢迎大家来一起交流学习

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值