Linux-IPC

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、信号、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC

匿名管道

  • 半双工
  • 父子进程间通信

命名管道

是一种特殊文件,文件类型是p,存放于文件系统中

1
2
3
anki@kong:~$ ls -l
total 124972
prwxrwxrwx 1 anki anki 0 Jan 29 22:54 ahhh
1
2
3
4
5
6
7
8
9
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
// 创建管道
// 成功返回0,出错返回-1
// pathname 管道名
// mode:和open中的mode相同
// O_NONBLOCK:只读open会阻塞等待写open,反之亦然
// O_NONBLOCK:
// 一个进程创建管道,然后open(pathname)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const char *filename = "/home/anki/ahhh";

pid_t pid;
char buff[20];
// 创建父子进程
if((pid = fork()) < 0) {
printf("Fork Error!\n");
} else if(pid > 0) {
// 父进程创建fifo,并同时写数据
int rs = mkfifo(filename, 0777);
if (rs<0 && errno != EEXIST) {
perror("create fifo error");
exit(1);
}
int fd = open(filename, O_WRONLY);
for (int i=0; i<10; i++) {
write(fd, "hello world", sizeof("hello world"));
sleep(1);
}
close(fd);
} else {
// 子进程打开文件,读数据
int fd=open(filename, O_RDONLY);
if (fd<0) {
perror("open fd failed");
exit(1);
}
for (int i=0; i<10; i++) {
int len=read(fd, buff, 20);
cout << len << ":" << buff << endl;
}
close(fd);
}

消息队列

  • 存放在内核,由一个标识符来标识

  • 特点

    • 面向记录,具有特定格式和特定优先级
    • 独立于进程,不会因进程结束而消失
    • 可以FIFO,也可以随机读取
1
2
3
4
5
6
7
8
9
#include <sys/msg.h>
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const char *filename = "/home/anki/ahhhh";

pid_t pid;

key_t k = ftok(filename, 10);
if (k<0) {
perror("ftok failed");
exit(-1);
}

if((pid = fork()) < 0) {
printf("Fork Error!\n");
} else if(pid > 0) {
int msgId = msgget(k, IPC_CREAT|0777);
if (msgId<0) {
perror("msgget failed");
exit(-1);
}
message mbuf;
mbuf.mtype = 110;

for (int i=0; i<10; i++) {
sprintf(mbuf.mtext, "hello world for %d\n", i);
msgsnd(msgId, &mbuf, sizeof(mbuf.mtext), 0);
}
} else {
int msgId = msgget(k, IPC_CREAT|0777);
if (msgId<0) {
perror("msgget failed");
exit(-1);
}
message mbuf;
msgrcv(msgId, &mbuf, 256, 110, 0);
cout << mbuf.mtype << ":" << mbuf.mtext << endl;
}

信号量

  • 特点
    • 初始>0
    • 主要用于进程间的同步/互斥,和共享内存合用可以实现进程间信息通讯
1
2
3
4
5
6
7
8
9
10
#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
// key: 不同进程通过key获得同一个信号量
// num_sems: 申请的信号量的数量
// flag:访问权限
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, union semun arg);

semop中,sembuf:

1
2
3
4
5
6
7
struct sembuf {
short sem_num; // 信号量组中对应的序号,0~num_sems-1
short sem_op; // 非零:信号量值在一次操作中的改变量,即所需要资源数,
// 为零:等待信号量值为0
short sem_flg; // IPC_NOWAIT:申请不到资源的话直接失败,返回错误 EAGAIN
// SEM_UNDO:进程在结束时,自动归还申请的信号量
}

semctl中,arg:

1
2
3
4
5
6
7

union semun {
short val;           // SETVAL用的值
struct semid_ds* buf; // IPC_STAT、IPC_SET用的semid_ds结构
unsigned short* array; // SETALL、GETALL用的数组值
struct seminfo *buf;   // 为控制IPC_INFO提供的缓存
} arg;

cmd:
| cmd | 解释 |
| - | - |
| IPC_STAT| 从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中|
| IPC_SET| 设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值|
| IPC_RMID| 从内核中删除信号量集合|
| GETALL| 从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中|
| GETNCNT| 返回当前等待资源的进程个数|
| GETPID| 返回最后一个执行系统调用semop()进程的PID|
| GETVAL| 返回信号量集合内单个信号量的值|
| GETZCNT| 返回当前等待100%资源利用的进程个数|
| SETALL| 与GETALL正好相反|
| SETVAL| 用联合体中val成员的值设置信号量集合中单个信号量的值|

共享内存

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <sys/shm.h>
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
// 当用shmget函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
// 当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
// shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。
int shmdt(void *addr);
// 控制共享内存的相关信息:成功返回0,失败返回-1
// shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

参考文章