进程间通信之管道,消息队列,共享内存

Linux下进程间通信的几种主要手段:

  1. 管道:包括有名管道和无名管道;
  2. 信号(signal):用于通知接受进程有某事件发生,除了进程间通信,还可以发信号给进程本身;
  3. 消息队列:是消息的链接表;
  4. 共享内存:使得多个进程访问同一块共享内存。与信号量结合,实现进程间的同步与互斥;
  5. 信号量(semaphore):主要作为进程间或同一进程下不同线程间的同步手段;
  6. 套接字(socket):进程间通信机制,也可以不同机器间的进程间通信。

管道

从一个进程连接数据流到另一个进程时,使用管道(pipe)。通常是把一个进程的输出通过管道连接到另一个进程的输入。管道是半双工的,数据只能单向。

管道可分为有名管道和无名管道,

  • 有名管道:可以在任意进程进行;
  • 无名管道/匿名管道:只能在父子进程间通用 (亲缘关系的进程).

先看一下进程管道

#include <stdio.h>
FILE *popen(const char *command, const char *open_mode);
int pclose(FILE *stream_to_close);

popen函数允许一个程序将另一个程序作为新进场来启动,并可以传递数据给他或者通过它接收数据。

创建无名管道

#include <unistd.h>
int pipe( int fd[2] );
    //fd[0] r(读)     fd[1] w(写)

管道内部传输的数据是字节流,与TCP字节流的区别是,应用层程序能往一个TCP连接中写入多少字节的数据,取决于对方的接受通告窗口大小和本端的拥塞窗口大小;而管道本身有容量限制,自Linux 2.6.11内核起,管道容量的大小默认是65536.可通过fcntl函数修改管道容量。
F_SETPIPE_SZ操作是设置指定的管道的容量。/proc/sys/fs/pipe-size-max内核参数指定管道容量上限。F_GETPIPE_SZ操作是获取管道容量大小。

有名/命名管道:FIFO,在文件系统中以文件名的形式存在,但它的程序是mknod,
mknodfilenamep m k n o d f i l e n a m e p 或 : mkfifo filename

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

int mkfifo( const char * filename, mode_t mode );
int mknod( const char *filename, mode_t mode | S_IFIFO, (dev_t)0 );

由于命名管道出现在文件系统中,所以可以像文件一样在命令中使用。

  1. 使用open打开FIFO文件,但不能以O_RDWR模式打开FIFO文件进行读写操作。
    open(const char *path, O_RDONLY);
    //open将会阻塞,除非有一个进程以写方式打开同一个FIFO文件,否则不会返回。
    open(const char *path, O_WRONLY | O_NONBLOCK);//open调用立刻返回
  2. 不带O_NONBLOCK标志的O_RDONLY和O_WRONLY(读进程和写进程会同步)
  3. 带O_NONBLOCK标志的O_RDONLY和不带标志的O_WRONLY
    open(const char *path, O_RDONLY | NONBLOCK); //调用会成功并立刻返回
    open(const char *path, O_WRONLY);
    //open调用阻塞,知道一个进程一度方式打开同一个FIFO文件为止。
  4. 对FIFO进行读写操作
    对一个空的,阻塞的FIFO的read调用将等待,直到有数据可读时才继续指向。
    对一个空的,非阻塞的read调用将立刻返回。
    对一个完全阻塞FIFO的write调用将等待,直到数据可以被写入时才继续执行。

(当一个Linux进程被阻塞时,并不消耗CPU资源)

双向管道socketpair:

#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protcol, int fd[2] );
                //fd[0]是主线程,fd[1]子线程

双向管道不是管道,是两个套接字的封装。

管道数据存放地点

  • 管道文件(代码)——> 磁盘
  • 打开之后(数据)——>内存

消息队列

消息队列时在两个进程之间传递二进制数据块的一种方式。每个数据块都有特定的类型,接收方可以根据类型来有选择的接收数据,而不一定像管道和命名管道必须以先进先出的方式接收数据。
消息队列并未解决使用命名管道时遇到的一些问题,比如管道满时的阻塞问题。
与命名管道相比,少了在打开和关闭管道方面的复杂性,消息队列的优势在于,它独立于发送和接受进程而存在,消除了在同步命名管道的打开和关闭时可能产生的困难。

优势:

  • 可通过发送消息来几乎完全避免命名管道的同步和阻塞问题;
  • 可提前查看紧急消息。

    缺陷:

  • 每个数据块有最大长度的限制;

  • 系统中所有队列所包含的全部数据块的总长度也有上限。

Linux系统有两个宏定义,
MSGMAX:以字节为单位,一条消息的最大长度
MSGMNB:以字节为单位,一个队列的最大长度

限制
由于消息缓冲机制中所用的缓冲区为共用缓冲区,因此使用消息缓冲机制传送数据时,两通信进程必须满足如下条件:
(1)发送进程把写入消息的缓冲区挂入消息队列时,应禁止其他进程对消息队列的访问,否则,将引起消息队列的混乱。同理,当接收进程正从消息队列中取消息时,应禁止其他进程对该队列的访问。
(2)当缓冲区无消息时,接受进程不能接收任何消息;而发送进程是否可以发送消息,则只能由发送进程是否能够申请缓冲区决定。

创建和访问一个消息队列,

#include <sys/msg.h>
int msgget( key_t key, int msgflg );
成功返回一个正整数,即队列标识符,失败返回-1.
msgflg一般设置IPC_CREAT,如果已有会自动忽略。

把消息添加到消息队列中,

int msgsnd( int msqid, ocnst void *msg_ptr, size_t msg_sz, int msgflg );

函数成功返回0,消息数据的一份副本将被放到消息队列中;失败时返回-1.
msqid是由msgget函数返回的消息队列标识符
msg_ptr是一个指向准备发送消息的指针
msg_sz是msg_ptr指向的消息的长度。(长度不能不包含长整型类型成员变量的长度)
msgflg若为IPC_NOWAIT标志,将立刻返回,不发送消息且返回值为-1;
若为IPC_NOWAIT被清除,则发送进程将挂起以等待队列中腾出可用空间。

从一个消息队列中获取消息,

int msgrcv( int msqid, void *msg_ptr, size_t msg_sz, long int msgtype,
                                        int msgflg );

函数成功返回接收缓存区的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列总的对应消息。失败返回-1.

msqid由msgget返回的消息队列标识符
msg_ptr是一个指向准备接收消息的指针
msg_sz是msg_ptr指向的消息的长度
msgtype是一个长整数(接收优先级),0是获取队列中第一个可用消息;大于0是获取相同消息类型
的第一个消息;小于0是获取消息类型等于或小于msgtype的绝对值第一个消息。
msgflg若为IPC_NOWAIT标志,将立刻返回,返回值为-1;

若为IPC_NOWAIT被清除,则进程将挂起等待一条相应类型的消息到达。

操作函数:

int msgctl( int msqid, int command, struct msqid_ds *buf );

函数成功返回0,失败返回-1.
如果删除消息队列时,某个进程正在msgsnd或msgrcv函数中等待,这两个函数将失败。
command是将要采取的动作,如下图
这里写图片描述


共享内存

允许两个不相关的进程访问同一个逻辑内存。共享内存是由IPC为进程创建的一个特殊的地址范围,出现在该进程的地址空间中。其他进程可以访问共享内存中的地址空间中。如果某个进程向共享内存写入数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。
由于它未提供同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问,否则会产生竞态条件。一般是用共享内存来提供对大块内存区域的有效访问,同时通过传递小消息来同步对该内存的访问。

这里写图片描述

#include <sys/shm.h>

void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
int shmdt(const void *shm_addr);
int shmget(key_t key, size_t size, int shmflg);
int shm_open(const char* name, int oflag, mode_t mode );

shmget函数:创建共享内存,创建一段新的共享内存或获取一段已存在的共享内存。
int shmget (key_t key, size_t size, int shmflg);

    函数返回一个共享内存标识符(非负整数),用于后续的共享内存函数;失败返回-1.
    key有效地为共享内存段命名
    size以字节为单位指定需要共享的内存容量。
    这段共享内存的所有字节都被初始化为0.

shmflg包含9个比特的权限标志,与创建文件的mode标志一样。
可通过设置权限,避免数据被其他用户修改。
shmget支持两个额外的标志:

  • SHM_HUGETLB,类似于mmap的MAP_HUGETLB标志,系统将使用“大页面”来为共享内存分配空间。
  • SHM_NORESERVE,类似于mmap的MAP_NORESERVE标志,不为共享内存保留交换分区(swap空间)。这样,当物理内存不足时,对该共享内存执行写操作将触发SIGSEGV信号。

共享内存被创建/获取之后,不能立即访问它,需要将共享内存连接到一个进程的地址空间中
void *shmat(int shm_id, const void *shm_addr, int shmflg);

shm_addr指定的是共享内存连接到当前进程中的地址位置。通常是一个空指针NULL,表示让系统来选择。

共享内存出现的地址。
函数成功返回一个指向共享内存第一字节的指针;失败返回-1.
shm_id是由shmget返回的共享内存标识符。
shmflg是一组位标志。一般使用SHM_RND(与shm_addr联合使用,用来控制共享内存连接的地址)
和SHM_RDONLY(使得连接的内存只读)。一般是让系统选择一个地址。

共享内存的读写权限由属主(创建者),访问权限和当前进程的属主决定。
当shmflg & SHM_RDONLY为true时的情况。此时即使共享内存的权限允许写操作,他都不能被写入。

shmdt:使用完共享内存之后,需要将共享内存从当前进程中分离

    函数成功返回0,失败时返回-1.

将共享内存分离并未删除它,只是使该共享内存对当前进程不在可用

控制函数
int shmctl(int shm_id, int command, struct shmid_ds *buf);
其中shmid_ds结构包含:
struct shmid_ds {
uid_t shm_perm.uid;
uid_t shm_perm.gid;
uid_t shm_perm.mode;
}

    函数成功返回0,失败返回-1.
    shm_id是shmget返回的共享内存标识符。
    buf是个指针,指向包含共享内存模式和访问权限的结构。

command是要采取的动作如下,
这里写图片描述

当试图删除一个正处于连接状态的共享内存段时,这个内存段还能继续使用,直到它从最后一个进程中分离为止。


IPC状态命令
$ ./ipcs -s 显示信号量状态

$ ./ipcrm -s 删除信号量

$ ipcs -m 显示共享内存状态

$ ipcrm -m 删除共享内存

ipcs -q 显示消息队列转台
ipcrm -q 删除一个消息队列


共享内存比管道和消息队列效率高的原因

共享内存是是进程间通信的一种方式。共享内存允许两个或多个进程访问同一块内存,就如同
malloc()函数向不同进程返回来指向同一个物理内存区域的指针。
因为所有进程共享同一块内存,共享内存在各种进程间通信方式中具有最高的效率。访问共享
来完成。同时它也避免了对数据的各种不必要的复制。

共享内存块提供了在任意数量的进程之间进行高效双向的通信机制。每个使用者都可以读取写入
等竞争状态的出现。不幸的是,Linux无法严格保证提供对共享内存块的独立访问,甚至是在通
共享内存块的进程之间必须协调使用同一个键值。

共享内存区时最快的可用IPC形式,一旦这样的内存区映射到共享它的进程的地址空间,这些进程
间数据的传递就不再通过执行任何进入内核的系统调用来传递彼此的数据,节省了时间。

——消息队列,FIFO,管道的消息传递方式一般为:

  1. 服务器得到输入
  2. 通过管道,消息队列写入数据,通常需要从进程拷贝到内核
  3. 客户从内核拷贝到进程
  4. 然后在从进程中拷贝到输出文件

——共享内存需要,

1.从输入文件到共享内存区
2.从共享内存区输出到文件
共享内存不涉及到内核的拷贝,时间消耗少。


参考资料:
《Linux程序设计 第4版》
《Linux高性能服务器编程》
参考博客:
http://blog.csdn.net/ttyue_123/article/details/52370676

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值