JackCin's blog JackCin's blog
首页
  • 页面

    • Html
    • CSS
  • 核心

    • JavaScript基础
    • JavaScript高级
  • 框架

    • Vue
  • jQuery
  • Node
  • Ajax
Linux
  • 操作系统
  • 数据结构与算法
  • 51单片机
  • CC2530
  • 网站
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

JackCin

前端小菜鸡(✪ω✪)
首页
  • 页面

    • Html
    • CSS
  • 核心

    • JavaScript基础
    • JavaScript高级
  • 框架

    • Vue
  • jQuery
  • Node
  • Ajax
Linux
  • 操作系统
  • 数据结构与算法
  • 51单片机
  • CC2530
  • 网站
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Linux系统编程入门

  • Linux 多进程开发

    • 进程概述
    • 进程状态转换
    • 进程创建
    • 进程控制
    • 进程间通信
    • 进程间通信补充
      • 一、进程间通信
        • 1.进程见通信概述
        • 2.Linux下进程间通信概述
        • 3.IPC 对象
        • 4.查看 IPC 对象
      • 二、共享内存
        • 1. 共享内存概述
        • 2. 共享内存实现
        • 3.共享内存函数调用流程
        • 3、共享内存函数
        • 3.1 shmget()
        • 3.2 shmat()
        • 3.3 shmdt()
        • 3.4 shmctl()
        • 3.5 代码
      • 三、消息队列
        • 1、消息队列概念
        • 2、消息队列模型
        • 3、消息队列流程
        • 4、消息队列函数
        • 4.1 msgget()
        • 4.2 msgsnd()
        • 4.3 msgrcv()
        • 4.4 msgctl()
        • 4.5 代码
      • 四、信号灯/量
        • 1、 临界资源
        • 2、信号灯
        • 3、 System V 信号灯
        • 4、二值、计数信号灯
        • 5、P\V 操作
        • 6、信号灯函数
        • 6.1 semget()
        • 6.2 semctl()
        • 6.3 semctl()
        • 7、信号灯函数使用
        • 8、信号量与共享内存
      • 五、进程间通信方式比较
    • 信号
    • 共享内存
    • 守护进程
  • Linux 多线程开发

  • Linux 网络编程

  • Linux
  • Linux 多进程开发
JackCin
2023-09-12
目录

进程间通信补充

# 进程间通信

# 一、进程间通信

# 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) 进程间通信,提 供了各种进程间通信的方法

561bdc422193dc78c48a4020a9b14dd

举例:

IPC对象例子

  • 🟩可以不要,然后直接将 shmget 函数里的第一个参数写为 IPC_PRIVATE 这个宏

# 4.查看 IPC 对象

  • 1.ipcs命令用于查看系统中的IPC对象

      1. ipcs –m 共享内存
      1. ipcs –s 信号量
      1. ipcs –q 消息队列
  • 2.ipcrm命令用于删除系统中的IPC对象

    ipcrm –m id

  • ❗❗❗ 创建的IPC对象如果不删除的话会一直保留在系统中

    • 比如:我们成功创建了一次信号集,然后要进行修改(比如我们创建的信号集里只有1个信号灯,然后我们要改为两个),那我们需要先将之前创建的信号集删除

ipcs-查看ipc对象

# 二、共享内存

# 1. 共享内存概述

  • 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数 据的拷贝
  • 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其 映射到自己的私有地址空间
  • 进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
  • 由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

# 2. 共享内存实现

  1. 创建/打开共享内存
  2. 映射共享内存,即把指定的共享内存映射到进程的地址空间 用于访问
  3. 撤销共享内存映射
  4. 删除共享内存对象

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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
// 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

# 四、信号灯/量

# 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

# 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

# 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

# 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
// 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
// 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

# 8、信号量与共享内存

信号量与共享内存

# 五、进程间通信方式比较

通信方式 特点
signal 信号捕捉 唯一的异步通信方式
msg 消息队列 常用于cs模式中, 按消息类型访问 ,可有优先级
shm 共享内存 效率最高(直接访问内存) ,需要同步、互斥机制
sem 信号灯/量 配合共享内存使用,用以实现同步和互斥
pipe 匿名管道 具有亲缘关系的进程间,单工,数据在内存中
fifo 有名管道 可用于任意进程间,双工,有文件名,数据在内存
编辑 (opens new window)
上次更新: 2023/09/13, 12:29:52
进程间通信
信号

← 进程间通信 信号→

最近更新
01
51单片机及补充知识
09-13
02
独立按键
09-13
03
LCD1602液晶显示器
09-13
更多文章>
Theme by Vdoing | Copyright © 2019-2023 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式