Linux进程间通信(消息队列/信号量+共享内存)

写在前面

不得不说,Deadline果真是第一生产力。不过做出来的东西真的是不堪入目,于是又花了一早上重写代码。


实验内容

进程通信的邮箱方式由操作系统提供形如 send()和 receive()的系统调用来支持,本实验要求学生首先查找资料了解所选用操作系统平台上用于进程通信的系统调用具体形式,然后使用该系统调用编写程序进行进程间的通信,要求程序运行结果可以直观地体现在界面上。在此基础上查找所选用操作系统平台上支持信号量机制的系统调用具体形式,运用生产者与消费者模型设计实现一个简单的信箱,该信箱需要有创建、发信、收信、撤销等函数,至少能够支持两个进程互相交换信息,比较自己实现的信箱与操作系统本身提供的信箱,分析两者之间存在的异同。

背景知识

  消息队列

  • 什么是消息队列
    消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
    Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。

  • Linux中如何使用消息队列
    Linux提供了一系列消息队列的函数接口来让我们方便地使用它来实现进程间的通信。它的用法与其他两个System V PIC机制,即信号量和共享内存相似。

    • msgget()函数
      该函数用来创建和访问一个消息队列。它的原型为:
      int msgget(key_t key, int msgflg);
      它返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1.
    • msgsnd()函数
      该函数用来把消息添加到消息队列中。它的原型为:
      int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
      如果调用成功,消息数据的一份副本将被放到消息队列中,并返回0,失败时返回-1.
    • msgrcv()函数
      该函数用来从一个消息队列获取消息,它的原型为:
      int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
      调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1。
    • msgctl()函数
      该函数用来控制消息队列,它与共享内存的shmctl函数相似,它的原型为:
      int msgctl(int msgid, int command, struct msgid_ds *buf);
      成功时返回0,失败时返回-1.

  信号量

  • 什么是信号量
    为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。
  • Linux的信号量机制
    Linux提供了一组精心设计的信号量接口来对信号量进行操作,它们不只是针对二进制信号量,下面将会对这些函数进行介绍,但请注意,这些函数都是用来对成组的信号量值进行操作的。它们声明在头文件sys/sem.h中。
    • semget()函数
      它的作用是创建一个新信号量或取得一个已有信号量,原型为:
      int semget(key_t key, int num_sems, int sem_flags);
      成功返回一个相应信号标识符(非零),失败返回-1.
    • semop()函数
      它的作用是改变信号量的值,原型为:
      int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
    • semctl()函数
      该函数用来直接控制信号量信息,它的原型为:
      int semctl(int sem_id, int sem_num, int command, ...);

  共享内存

  • 什么是共享内存
    顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc()分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
    特别提醒:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,例如前面说到的信号量。
  • 共享内存的使用
    与信号量一样,在Linux中也提供了一组函数接口用于使用共享内存,而且使用共享共存的接口还与信号量的非常相似,而且比使用信号量的接口来得简单。它们声明在头文件 sys/shm.h 中。
    • shmget()函数
      该函数用来创建共享内存,它的原型为:
      int shmget(key_t key, size_t size, int shmflg);
      成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.
    • shmat()函数
      第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:
      void *shmat(int shm_id, const void *shm_addr, int shmflg);
      成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.
    • shmdt()函数
      该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:
      int shmdt(const void *shmaddr);
      调用成功时返回0,失败时返回-1.
    • shmctl()函数
      与信号量的semctl()函数一样,用来控制共享内存,它的原型如下:
      int shmctl(int shm_id, int command, struct shmid_ds *buf);

  参考资料

  以上资料全部来源于以下网站:

  实验结果

  • 消息队列
    消息队列
  • 信号量+共享内存
    信号量+共享内存

  完整代码

  Linux-interProcessCommunication
  如果对你有帮助点个star吧(●'◡'●)

  总结

  • 不足
    • 没有图形化界面

    • 用信号量和共享内存实现的进程通信只能发送数字消息

    • 创建共享内存空间时,设置权限为了省事设置为0666( 每个进程可读和可写),应该要设置user只能读而不能写,other只能写而不能读

    • 消息队列没有测试msgrcv()函数通过改变msgtype参数来改变接收优先级。
      > msgtype 可以实现一种简单的接收优先级。如果msgtype为0,就获取队列中的第一个消息。如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。

    • ...

  • 两种方式实现进程间通信的异同
    • 异:消息队列不需要信号量来控制同步和互斥问题,并且可以很方便的改变接收优先级,而共享内存则只能简单的接收按时间排序的消息。
    • 同:一个进程接收到的消息都和另一个进程发送的相同。
  • 心得
    • 由于Linux提供的信号量接口函数都是针对一组信号量进行操作的,因此参数中大部分都需要指定对一组中的哪一个信号量进行操作
    • 共享内存只存放消息缓存区,至于信箱头的那些值仍然存放在各自进程中。

如有不足,欢迎指正!

posted @ 2018-06-02 13:55  闽A2436  阅读(3934)  评论(2编辑  收藏  举报