进程通信
进程间通信(IPC)
进程之间交换数据的过程叫进程间通信
进程间通信方式
一、简单的进程间通信
- 命令行:父进程通过exec函数创建子进程时可以附加一些数据
- 环境变量:父进程通过exec函数创建子进程顺便传递一张环境变量表
- 信号:父子进程可以根据进程号相互发送信号,进程简单通信
- 命令行、环境变量只能单向传递,信号过于简单,且文件通信不能实时
- 文件:一个进程向文件中写入数据,另一个进程从文件中读取出来
二、传统进程间通信
管道(fifo)
管道是一种古老的通信的方式(基本上不再使用) 早期的管道是一种半双工,现在大多数是全双工
有名管道(以文件方式存在)
- 创建管道文件,命令:mkfifo + file
int mkfifo(const char *pathname, mode_t mode);
-
管道通信的编程模式 进程A 进程B 创建管道 mkfifo 打开管道 open 打开管道 写/读数据 read/write 写/读数据 关闭管道close 关闭管道 删除管道 unlink/remove
无名管道
(由内核帮助创建,只返回管道的文件描述符),看不到管道文件,只用于fork创建的父子进程之间
pipefd[0] 用来读数据
pipefd[1] 用来写数据
int pipe(int pipefd[2]);
三、XSI IPC进程间通信
- 每一个IPC对象都有一个IPC标识符(类似于文件描述符),IPC标识是一个非负的整数。IPC对象必须要先创建,创建后才能进程获取、设置、操作、删除。
- 创建IPC对象必须要提供一个键值(key_t),键值是创建、获取IPC对象的依据
- 产生键值的方法:
- 固定的字面值
- 使用函数计算
- 键值 = ftok(项目路径,项目id)
- 使用宏让操作系统随机分配,IPC_PRIATE(随机分配的键值)
必须把获取到IPC对象标识符记录下来,告诉其它进程
XSI可以创建的IPC对象:共享内存、消息队列、信号量
共享内存
- 由内存维护一个共享的内存区域,其他进程把自己的虚拟地址映射到这块内存,然后多个进程之间就可以共享这块内存
- 好处:不需要信息复制,是进程间通信最快的一种方式。但这种通信方式会面临同步的问题,需要与其他通信方式配合,最合适的搭配对象为信号。
共享内存的编程模式。进程之间要约定一个键值
进程A ->创建共享内存->加载共享内存->卸载共享内存->销毁共享内存
进程B -> 加载共享内存->卸载共享内存
- 1.创建共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
key:key值
size:共享内存的大小,尽量是4096的倍数,如果引用一个已存在的共享内存,则指定为0
shmflg:IPC_CREATIPC_EXCL
st_mode mode 权限
返回值:IPC对象标识符
- 2.加载共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:shmget()的返回值
shmaddr:进程提供的虚拟地址,如果为NULL,操作系统会自动选择一块地址映射
shmflg:
SHM_RND:当shmaddr为空时自动分配
SHM_RDONLY:限制内存的权限为只读
SHM_REMAP:映射已经存在的共享内存
返回值:映射后的虚拟内存地址(进程的虚拟地址与共享的内存映射)
- 3.卸载共享内存
int shmdt(const void *shmaddr);
返回值:成功为0,失败为-1
进程的虚拟地址与共享的内存取消映射关系
- 4.控制/销毁共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
cmd:
IPC_STAT:获取共享内存的属性
IPC_SET:设置共享内存的属性
IPC_RMID:删除共享内存
IPC_INFO:获取共享内存的信息(special Linux)
buf:记录共享内存属性的对象
消息队列
- 消息队列是一个由系统内核负责存储和管理、并通过IPC对象标识符获取的数据链表
- 1.创建或获取消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
msgflg:创建:IPC_CREAT|IPC_EXEC
获取:0
返回值:IPC对象标识符
- 2.发送一条消息到消息队列
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msqid:msgget的返回值
msgp:消息(消息类型+消息内容)的首地址
msgsz:消息内存的长度(不包括消息类型)
msgflg
- 3.从消息队列接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
msqid:msgget的返回值
msgp:接收消息的缓冲区地址
msgsz:要接受的消息长度
msgtyp:消息类型(包含在消息的前4个字节)
msgflg:MSG_NOWAIT:如果要接的消息不存在,则直接返回。(若不加则会阻塞等待)
MSG_NOERROR:当消息的实际长比msgsz(要接受的信号长度)还要长的话,则按照msgsz长度截取(若不加则会出错)
MSG_EXCEPT:从消息队列中接收第一个不是msgtyp类型的消息
- 4.控制/销毁消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
cmd
IPC_STAT:获取消息队列属性
IPC_SET:设置消息队列属性
IPC_RMID:删除消息队列
信号量
- 信号量(信号灯),可以当做进程与进程之间共享的全局变量,一般用来为共享的资源计数
信号量的使用方法
进程A:创建信号量,并设置初始值(设置资源的数)
进程B:获取信号量,查看信号量(查询剩余资源的数量),减少信号量(使用资源),增加信号量(资源使用完毕,归还)
- 当一个进程尝试减少信号量,如果不能减(代表资源使用完毕),则进程会进入等待状态(休眠),当信号量能够被减时(其他进程归还资源),进程会被唤醒
- 信号量编程模式
- 1.创建信号量或获取信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
nsems:信号量的数量
semflg:IPC_CREAT|IPC_EXEC|0644
返回值:成功返回信号量标识符,失败返回-1
- 2.增加或减少信号量
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid:信号量标识符,semget的返回值
struct sembuf{}
unsigned short sem_num; 信号量的编号
short sem_op; 对信号量的操作
(1)若sem_op为正,这对应于进程释放占用的资源数。sem_op值加到信号量的值上。(V操作)
(2)若sem_op为负,这表示要获取该信号量控制的资源数。信号量值减去sem_op的绝对值。(P操作)
(3)若sem_op为0,这表示调用进程希望等待到该信号量值变成0
short sem_flg; 操作权限 :IPC_NOWAIT 要接的消息不存在,直接返回(不阻塞)
nsops:sops数组中元素个数
- 3.控制/操作信号量
int semctl(int semid, int semnum, int cmd, ...);
semnum:信号量的编号
cmd
IPC_STAT 读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
IPC_SET 设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
IPC_RMID 将信号量集从内存中删除。
IPC_INFO 获取信号量的属性
GETALL 用于读取信号量集中的所有信号量的值。
GETNCNT 返回正在等待资源的进程数目。
GETPID 返回最后一个执行semop操作的进程的PID。
GETVAL 返回信号量集中的一个单个的信号量的值。
GETZCNT 返回这在等待完全空闲的资源的进程数目。
SETALL 设置信号量集中的所有的信号量的值。
SETVAL 设置信号量集中的一个单独的信号量的值。
IPC相关命令
ipcs -m 查看共享内存
ipcrm -m id 删除共享内存
ipcs -q 查看消息队列
ipcrm -q id 删除消息队列
ipcs -s 查看信号量
ipcrm -s id 删除信号量
四、网络进程间通信方式
就是不同机器的进程间通信方式,socket套接字。