进程间通信补充
# 进程间通信
# 一、进程间通信
# 1.进程见通信概述
- UNIX平台进程通信方式
- 早期进程间通信方式
- 管道、有名管道和信号
- AT&T的贝尔实验室 •
- 对Unix早期的进程间通信进行了改进和扩充,形成了“system V IPC”,其通信进 程主要局限在单个计算机内
- BSD(加州大学伯克利分校的伯克利软件发布中心)
- 形成了基于**套接字(socket)**的计算机之间的进程间通信机制
- 早期进程间通信方式
- Linux继承了上述所有的通信方式
# 2.Linux下进程间通信概述
- 常用的进程间通信方式
- 传统的进程间通信方式
- 无名管道(pipe)、有名管道(fifo)和信号(signal)
- System V IPC对象
- 共享内存(share memory)、消息队列(message queue)和信号灯 (semaphore)
- BSD
- 套接字(socket)
- 传统的进程间通信方式
# 3.IPC 对象
- IPC(Inter-Process Communication) 进程间通信,提 供了各种进程间通信的方法
举例:
- 🟩可以不要,然后直接将
shmget
函数里的第一个参数写为IPC_PRIVATE
这个宏
# 4.查看 IPC 对象
1.ipcs命令用于查看系统中的IPC对象
ipcs –m
共享内存
ipcs –s
信号量
ipcs –q
消息队列
2.ipcrm命令用于删除系统中的IPC对象
ipcrm –m id
❗❗❗ 创建的IPC对象如果不删除的话会一直保留在系统中
- 比如:我们成功创建了一次信号集,然后要进行修改(比如我们创建的信号集里只有1个信号灯,然后我们要改为两个),那我们需要先将之前创建的信号集删除
# 二、共享内存
# 1. 共享内存概述
- 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数 据的拷贝
- 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其 映射到自己的私有地址空间
- 进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
- 由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等
# 2. 共享内存实现
- 创建/打开共享内存
- 映射共享内存,即把指定的共享内存映射到进程的地址空间 用于访问
- 撤销共享内存映射
- 删除共享内存对象
# 3.共享内存函数调用流程
# 3、共享内存函数
# 3.1 shmget()
- 创建/打开共享内存
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
- int shmget(key_t key, int size, int shmflg);
-参数
- key: IPC_PRIVATE 或 ftok的返回值
- size:共享内存区大小
- shmflg:同open函数的权限位,也可以用8进制表示法
- 返回值
- 成功:共享内存段标识符
- 出错:-1
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 3.2 shmat()
- 将共享内存与当前进程相关联
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
- void *shmat(int shmid, const void *shmaddr, int shmflg);
- 参数
- shmid:要映射的共享内存区标识符
- shmaddr:将共享内存映射到指定地址(若为NULL,则表示由系统自动完成映射)
- shmflg :
- SHM_RDONLY:共享内存只读
- 默认0:共享内存可读写
- 返回值
- 成功:映射后的地址
- 出错:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 3.3 shmdt()
- 将当前进程与共享内存间脱离关联
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
- int shmdt(const void *shmaddr);
- 参数 shmaddr:共享内存映射后的地址
- 返回值
- 成功:0
- 出错:-1
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 3.4 shmctl()
- 操控共享内存
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 参数
- shmid:要操作的共享内存标识符
- cmd :
- IPC_STAT (获取对象属性)
- IPC_SET (设置对象属性)
- IPC_RMID (删除对象)
- buf : 指定IPC_STAT/IPC_SET时用以保存/设置属性
- 返回值
- 成功:0
- 出错:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3.5 代码
# 三、消息队列
# 1、消息队列概念
- 消息队列是IPC对象的一种
- 消息队列由消息队列ID来唯一标识
- 消息队列就是一个消息的列表。用户可以在消息队列中 添加消息、读取消息等。
- 消息队列可以按照类型来发送/接收消息
# 2、消息队列模型
# 3、消息队列流程
# 4、消息队列函数
# 4.1 msgget()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int flag);
- 参数
- key:和消息队列关联的key值
- flag:消息队列的访问权限
- IPC_CREAT :如果消息队列对象不存在,则创建之,否则则进行打开操作;
- IPC_EXCL:和IPC_CREAT 一起使用(用”|”连接),如果消息对象不存在则创建之,否则产生一个错误并返回。
- 返回值
- 成功:消息队列ID
- 出错:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 4.2 msgsnd()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t size, int flag);
- 参数
- msqid:消息队列的ID
- msgp:指向消息的指针。常用消息结构msgbuf如下:
- struct msgbuf{
long mtype; //消息类型
char mtext[N] //消息正文
};
- size:❗❗❗发送的消息正文的字节数
- flag:
- IPC_NOWAIT 消息没有发送完成函数也会立即返回。
- 0:直到发送完成函数才返回
- 返回值
- 成功:0
- 出错:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 4.3 msgrcv()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag);
- 参数
- msqid:消息队列的ID
- msgp:接收消息的缓冲区
- size:要接收的消息的字节数(包括了消息类型和正文)
- msgtype:
- 0:接收消息队列中第一个消息。
- 大于0:接收消息队列中第一个类型为msgtyp的消息.
- 小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。
- flag:
- 0:若无消息函数会一直阻塞
- IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。
- 返回值
- 成功:接收到的消息的长度
- 出错:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 4.4 msgctl()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
- 参数
- msqid:消息队列的队列ID
- cmd:
- IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中。
- IPC_SET:设置消息队列的属性。这个值取自buf参数。
- IPC_RMID:从系统中删除消息队列。
- buf:消息队列缓冲区
- 返回值
- 成功:0
- 出错:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 4.5 代码
// msq_client.c
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#define QUEUE_MSG_LEN 256
#define PROJ_ID 'g'
#define PATH_NAME "/app"
#define SERVER_MSG 1
#define CLIENT_MSG 2
/*message data structure */
struct msg
{
long type;
long msgtype;
unsigned char text[QUEUE_MSG_LEN];
};
int main(void)
{
int qid;
int msglen;
int i=0;
struct msg msg_buf;
/* get a message queue */
key_t msgkey;
if ((msgkey = ftok(PATH_NAME, PROJ_ID)) == -1) //�õ�ID��
{
perror("ftok error!\n");
exit(1);
}
if ((qid = msgget(msgkey, IPC_CREAT | 0666)) == -1)
{
perror("msgget error!\n");
exit(1);
}
while (1)
{
/*receive message from message queue with SERVER_MSG type */
if (msgrcv(qid, &msg_buf, sizeof(struct msg) - sizeof(long), SERVER_MSG, 0) == -1)
{
perror("Server msgrcv error!\n");
exit(1);
}
printf("server rcv : %ld: %s\n",msg_buf.msgtype,msg_buf.text);
#if 1
printf("client send: ");
/*get string from terminal & fill up message data structure */
fgets(msg_buf.text, QUEUE_MSG_LEN, stdin);
if (strncmp("exit", msg_buf.text, 4) == 0)
{
break;
}
msg_buf.text[strlen(msg_buf.text) - 1] = '\0';
msg_buf.type = CLIENT_MSG;
msg_buf.msgtype = i++;
/*send message to message queue with CLIENT_MSG type */
if (msgsnd(qid, &msg_buf, strlen(msg_buf.text) + sizeof(long), 0) == -1)
{
perror("client msgsnd error!\n");
exit(1);
}
#endif
}
exit(0);
}
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// msq_server.c
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/stat.h>
#define QUEUE_MSG_LEN 256
#define PROJ_ID 'g'
#define PATH_NAME "/app"
#define SERVER_MSG 1
#define CLIENT_MSG 2
/*message data structure */
struct msg
{
long type;
long msgtype;
unsigned char text[QUEUE_MSG_LEN];
};
int main(void)
{
/*message data structure */
struct msg msg_buf;
int qid;
int msglen;
int i=0;
/*get message queue */
key_t msgkey;
if ((msgkey = ftok(PATH_NAME, PROJ_ID)) == -1)
{
perror("ftok error!\n");
exit(1);
}
if ((qid = msgget(msgkey, IPC_CREAT | 0666)) == -1)
{
perror("msgget error!\n");
exit(1);
}
while (1)
{
printf("server send: ");
/*get string from terminal & fill up message data structure */
fgets(msg_buf.text, QUEUE_MSG_LEN, stdin);
if (strncmp("exit", msg_buf.text, 4) == 0)
{
msgctl(qid, IPC_RMID, NULL);
break;
}
msg_buf.text[strlen(msg_buf.text) - 1] = '\0';
msg_buf.type = SERVER_MSG;
msg_buf.msgtype = i++;
/*send message to message queue with SERVER_MSG type */
if (msgsnd(qid, &msg_buf, sizeof(struct msg) - sizeof(long), 0) == -1)
{
perror("Server msgsnd error!\n");
exit(1);
}
#if 1
/*receive message from message queue with CLIENT_MSG type */
if (msgrcv(qid, &msg_buf, sizeof(struct msg) - sizeof(long), CLIENT_MSG, 0) == -1)
{
perror("Server msgrcv error!\n");
exit(1);
}
printf("server rcv: %d: %s\n",msg_buf.msgtype,msg_buf.text);
#endif
}
exit(0);
}
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 四、信号灯/量
# 1、 临界资源
- 一次只允许一个进程使用的资源称为临界资源;
- 临界资源并不全是
硬件
或是软件
,而是两者都能作为临界资源。 - 比如硬件的有:
- 打印机、磁带机等;
- 软件有:
- 消息缓冲队列、变量、数组、缓冲区等;
- 临界资源并不全是
- 临界区(critical region)
- 访问共享变量的程序代码段称为临界区,也称为临界段(critical section);
- 进程互斥
- 两个或两个以上的进程不能同时进入关于同一组共享变量的临界区,否可 可能会发生与时间有关的错误,这种现象称为进程互斥;
# 2、信号灯
- 信号灯(semaphore),也叫信号量。它是不同进程间或 一个给定进程内部不同线程间同步的机制。
- 信号灯种类:
- posix有名信号灯(可用于线程、进程同步)
- posix基于内存的信号灯(无名信号灯)
- System V信号灯(IPC对象)
# 3、 System V 信号灯
- System V的信号灯是一个或者多个信号灯的一个集合。 其中的每一个都是单独的计数信号灯。
- 而Posix信号灯指的是单个计数信号灯
- System V 信号灯由内核维护
# 4、二值、计数信号灯
- 二值信号灯:
- 值为0或1。与互斥锁类似,资源可用时值为1,不可用时值为 0。
- 计数信号灯:
- 值在0到n之间。用来统计资源,其值代表可用资源数
# 5、P\V 操作
- 通常把信号量操作抽象成PV操作
- P :等待操作是等待信号灯的值变为大于0,然后将其减1
- V :释放操作则相反,用来唤醒等待资源的进程或者线程
# 6、信号灯函数
# 6.1 semget()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
- 参数
- key:和信号灯集关联的key值
- nsems: 信号灯集中包含的信号灯数目
- semflg:信号灯集的访问权限,通常为IPC_CREAT | 0666
- 返回值
- 成功:信号灯集ID
- 出错:-1
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 6.2 semctl()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
- int semctl ( int semid, int semnum, int cmd…/*union semun arg*/);
- 参数
- semid:信号灯集ID
- semnum: 要修改的信号灯编号
- cmd:
- GETVAL:获取信号灯的值
- SETVAL:设置信号灯的值
- IPC_RMID:从系统中删除信号灯集合
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;
- 返回值
- 成功:0
- 出错:-1错误原因存于errno中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 6.3 semctl()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop ( int semid, struct sembuf *opsptr, size_t nops);
- 参数
- semid:信号灯集ID
- struct sembuf {
short sem_num; // 要操作的信号灯的编号
short sem_op; // 0 : 等待,直到信号灯的值变成0
// 1 : 释放资源,V操作
// -1 : 分配资源,P操作
short sem_flg; // 0, IPC_NOWAIT, SEM_UNDO
};
- nops: 要操作的信号灯的个数
- 返回值
- 成功:0
- 出错:-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 7、信号灯函数使用
// sem.h
#ifndef __SEM_H__
#define __SEM_H__
#if 0
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
#endif
int init_sem(int semid, int num, int val)
{
union semun myun;
myun.val = val;
if(semctl(semid, num, SETVAL, myun) < 0)
{
perror("semctl");
exit(1);
}
return 0;
}
int sem_p(int semid, int num)
{
struct sembuf mybuf;
mybuf.sem_num = num;
mybuf.sem_op = -1;
mybuf.sem_flg = SEM_UNDO;
if(semop(semid, &mybuf, 1) < 0)
{
perror("semop");
exit(1);
}
return 0;
}
int sem_v(int semid, int num)
{
struct sembuf mybuf;
mybuf.sem_num = num;
mybuf.sem_op = 1;
mybuf.sem_flg = SEM_UNDO;
if(semop(semid, &mybuf, 1) < 0)
{
perror("semop");
exit(1);
}
return 0;
}
#endif
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// sem_client.c
#include <sys/types.h>
#include <linux/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "sem.h"
int main(void)
{
key_t key; //sem key
int semid, semval;
//get semaphore
if((key = ftok("/app",'i')) <0)
{
perror("ftok");
exit(1);
}
printf("key = %x\n",key);
if((semid = semget(key, 1, 0666)) < 0)
{
perror("semget");
exit(1);
}
//get semaphore value every 1 seconds
while (1)
{
if ((semval = semctl(semid, 0, GETVAL, 0)) == -1)
{
perror("semctl error!\n");
exit(1);
}
if (semval > 0)
{
printf("Still %d resources can be used\n", semval);
}else
{
printf("No more resources can be used!\n");
//break;
}
sleep(1);
}
exit(0);
}
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// sem_server.c
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <linux/sem.h>
#include <stdio.h>
#include <errno.h>
#include "sem.h"
#define MAX_RESOURCE 10
int main(void)
{
key_t key_info;
int semid;
//get semaphore
if ((key_info = ftok ("/app", 'i')) < 0)
{
perror ("ftok info");
exit (-1);
}
printf("key is %d\n", key_info);
if ((semid = semget (key_info, 1, IPC_CREAT | IPC_EXCL |0666)) < 0)
{
if (errno == EEXIST)
{
semid = semget (key_info, 1, 0666);
}
else
{
perror ("semget");
exit (-1);
}
}
else
{
init_sem (semid, 0, 1);
}
//substract 1 every 3 seconds until semaphore value is -1
while (1)
{
//p
printf("p\n");
sem_p (semid, 0);
//进入临界区
sleep(4);
//V
printf("v\n");
sem_v (semid, 0);
sleep(3);
}
exit(0);
}
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 8、信号量与共享内存
# 五、进程间通信方式比较
通信方式 | 特点 |
---|---|
signal 信号捕捉 | 唯一的异步通信方式 |
msg 消息队列 | 常用于cs模式中, 按消息类型访问 ,可有优先级 |
shm 共享内存 | 效率最高(直接访问内存) ,需要同步、互斥机制 |
sem 信号灯/量 | 配合共享内存使用,用以实现同步和互斥 |
pipe 匿名管道 | 具有亲缘关系的进程间,单工,数据在内存中 |
fifo 有名管道 | 可用于任意进程间,双工,有文件名,数据在内存 |
编辑 (opens new window)
上次更新: 2023/09/13, 12:29:52