进程的地址空间是相互独立的,因此进程之间交互数据必须需要专门的通信机制。

概述

Linux下的主要IPC手段:

  • 管道pipe。数据只能单方面流通, 只能在父子进程间进行。
  • 有名管道named pipe。半双工通信方式,可用于非父子进程通信。
  • 信号量semophore 进程间或不同进程间的同步手段。
  • 消息队列message queue。消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息量少,管道只能承载无格式字节流以及缓冲区大小受限的缺点。
  • 信号signal
  • 共享内存shared memory。 映射一段能被其他进程访问的内存。共享内存能被一个进程创建但是被多个进程访问。
  • 套接字socket。 可用于不同机器间的通信。

管道

管道是一个特殊的文件,这个文件只存在与内存中。创建管道时,系统为管道分配一个页面作为数据缓冲区,进行管道通信的两个进程通过读写这个缓冲区来实现通信。

dup与dup2

在子进程调用exec函数执行另外一个程序时,可以将子进程的文件描述符重定向到标准输入,新执行的程序能够从标准输入获取数据,实际上是从父进程获取输入数据。

1
2
3
4
5
#include <unistd.h>
// 返回文件描述符,为当前可用文件描述符的最小数值。
int dup(int oldfd);
// 利用参数newfd指定欲返回的文件描述符。如果newfd指定的文件描述符已经打开,先将其关闭,然后将oldfd指定的文件描述符赋值给该参数。
int dup2(int oldfd, int newfd);

有名管道

有名管道(named pipe或FIFO)。FIFO不同与管道之处在于它提供了一个路径名与之关联,以FIFO的形式存储与文件系统中。

基本使用

创建方式

可以用shell创建或者在程序中用系统函数创建。

1
2
3
4
5
6
7
8
9
10
11
#include <sys/types.h>
#include <sys/stat.h>

/*
path: 有名管道的路径名
mod: 模式
dev: 设备值
return: 成功返回0, 失败返回-1
*/
int mknod(const char *path, mode_t mod, dev_t dev);
int mkfifo(const char *path, mode_t mode);

读写方式

有名管道是一个硬盘上的文件,使用前需要先open()将其打开。

有名管道是存在与硬盘上的文件,而管道是存在与内存中的特殊文件

消息队列

基本概念

消息队列是存放在内核中的一个消息链表,每个消息队列用消息队列标识符标识。与管道不同的是消息队列存放在内核中,只有在内核重启或者显式的删除一个消息队列时,该消息队列才会被真正的删除。

数据结构

消息缓冲结构

向消息队列发送消息时,必须组成合理的数据 结构。Linux系统定义了一个模板数据结构msgbuf:

1
2
3
4
5
6
7
#include <linux/msg.h>
struct msgbuf {
// 消息类型
long mtype;
// 消息内容
char mtext[1];
}

msqid_ds内核数据结构

Linux中,每个消息队列都维护着一个结构体msqid_ds。该结构体保存着当前消息队列的状态信息。该结构体定义在linux/msg.h中,具体定义如下:

ipc_perm内核数据结构

ipc_perm保存着消息队列的一些重要信息,比如消息队列关联的键值,消息队列的用户ID、组ID。

创建与读写

创建消息队列

消息队列是随着内核的存在而存在的,每个消息队列在系统范围内对应唯一的键值。通过ftok函数获取该键值

1
2
3
#include <sys/types.h>
#include <sys/ipc.h>
ket_t ftok(const char *pathname, int proj_id);

读写消息队列

函数原型:

1
2
int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int mapflg);
int msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long int msgtyp, itn msgflg);

信号量

信号量大于等于0时表示可供并发进程使用的资源实体数;小于0时代表正在等待使用临界资源的进程数目。

数据结构

1
2
3
4
5
6
7
8
9
10
struct semid_ds {
struct ipc_perm sem_perm; // 对信号进行操作的许可权
__kernel_time_t sem_otime; // 对信号进行操作的最后时间
__kernel_time_t em_ctime; // 对信号进行修改的最后时间
struct sem *sembase; // 指向第一个信号
struct sem_queue sem_pending; // 等待处理的挂起操作
struct sem_undo *undo; // 撤销的请求
ushort sem_nsems; // 数组中的信号数目

}

共享内存

数据结构

共享内存是分配一块能被其他进程访问的内存。每个内存块在内核中维护这一个内部结构shmid_ds(和消息队列,信号量)一样,该结构定义在头文件linux/shm.h中。

1
2
3
4
5
struct shmid_ds {
struct ipc_perm shm_perm;
int shm_segsz;
******
}

共享内存区的创建

shmget来创建一个共享内存区。原型如下:

1
2
3
4
5
6
/*
key: ftok得到的键值
size: 以字节为单位指定内存的大小
shmflg: 操作标志位
*/
int shmget(key_t key, size_t size, int shmflg);

共享内存区的操作

在使用共享内存区之前,必须通过shmat将其附加到进程的地址空间。进程就与共享内存建立了连接。shmat调用成功后会返回一个指向共享内存区的指针,使用该指针即可访问共享内存区,如果失败返回-1.代码原型如下:

1
2
3
4
5
6
/*
shimid: shmget的返回值
shmaddr: 共享内存的附加点,一般设为空,由MMU自动管理
shmflg: 存取权限标志
*/
void* shmat(int shmid, const void *shmaddr, int shmflg);

当进程结束使用共享内存区,要通过函数shmdt断开与共享内存区的连接。该函数声明在sys/shm.h中代码原型如下:

1
int shmdt(const void *shmaddr);

Linux对共享内存区的控制是通过调用函数shmctl来完成的,该函数定义在头文件sys/shm.h中,原型代码如下:

1
2
3
4
5
6
/*
shimid: 共享内存区的标识符
buf:指向shmid_ds结构体的指针
cmd:操作标志位
*/
int shmctl(int shmid, int cmd, struct shimid_ds *buf);