一. 进程间通讯(IPC)
1. 什么是进程间通讯(IPC)?
多个进程之间进行的信息的传递与就换的过程。
2. 进程间通讯的目的?
2.1 数据通讯
2.2 事件通知
2.3 资源同步
2.4 进程控制
3. 进程间通讯方式:
1) 管道
2) 共享内存
3) 消息队列
4) 信号量
5) 信号
6) 套接字
4. 管道:
内核提供的具有读写两端的通讯通道;
特点: 1. 单向的,先进先出
2. 读取后的数据将不存在于管道中
3. 管道具有简单的流控制机制
4. 管道容量默认 64KB
4.1 无名管道 (匿名管道)
优点: 操作简单,创建管道就可以获取管道读写两端描述符
管道读写可借助 read write系统IO函数进行;
缺点: 1. 使用场景受限,只能应用于具有亲缘关系的进程之间;
2. 要完成完整的数据读写,一般要借助于两个管道;
3. 管道容易造成阻塞
pipe
头文件: #include <unistd.h>
函数原型: int pipe(int fd[2]);
函数功能: 创建匿名管道,获取读写描述符
函数参数: 用于获取读写描述符
函数返回值: 成功返回 -1;
失败 :返回-1. 错误码存放在 errno
4.2 有名管道 (命名管道)
创建管道的进程可以给管道命名(提供系统路径),以后对管道的操作,是通过
管道路径来实现;
管道路径是一个特殊的文件,和普通一样具有文件路径,访问属性,但不同处在于
管道文件是不能存储数据的,数据往往是存储在内存中的;
特点: 1. 对管道的访问要同时存在读和写进程,否则进程阻塞;
2. 如果存在读写进程完整打开命名管道,
读操作以以下方式进行:
1) 管道中无数据,读进程阻塞
2) 管道中有数据 不管数据大小是否满足读取的需要,则读操作都不会阻塞
写操作以以下方式进行:
1) 管道中无空间,写进程阻塞
2) 管道中有空间, 但不足以满足写入数据的需要,则写操作阻塞
中途有进程退出:
1) 退出的读进程,则写进程在不处理 SIGPIPE情况下,写进程会强制终止;
2) 退出的写进程,则读进程read 函数会返回0,但读进程不阻塞;
使用场景: 命名管道可以应用于所有的进程。
创建命名管道:
mkfifo
头文件: #include <sys/types.h>
#include <sys/stat.h>
函数原型: int mkfifo(const char* pathname,mode_t mode);
函数功能: 创建命名管道
函数参数: pathname:给创建的命名管道指定管道路径
mode: 指定管道文件的访问权限
函数返回值: 成功返回 -1;
失败 :返回-1. 错误码存放在 errno
5. 共享内存
共享内存是一块独立于进程地址空间之外,可供多个进程共同访问的物理内存。
优势: 1. 操作简单,效率高
2. 数据共享,可减少数据传递及数据拷贝
3. 容量大
缺点:
1. 共享内存存在同步问题,用户可利用信号量来提供同步机制。
共享内存访问步骤:
1. 申请共享内存;
shmget
头文件: #include <sys/ipc.h>
#include <sys/shm.h>
函数原型: int shmget(key_t key,size_t size,int shmflg);
函数功能: 创建或者获取共享内存
函数参数: key : 获取共享内存的关键字,
该关键字是创建共享内存时,由创建进程指定
key: 可取值为:
1. IPC_PRIVATE: 总是创建
2. ftok函数的返回值
size: 申请共享内存的大小
shmflg : 共享内存标志:
IPC_CREAT
IPC_EXCL
还可用于指定共享内存的权限
函数返回值: 成功 : 共享内存的标识符
失败 : -1
例子: shmget(key,1024,IPC_CREAT | IPC_EXCL | 0644)
2. 内存映射
shmat
头文件: #include <sys/ipc.h>
#include <sys/shm.h>
函数原型: void* shmat(int shmid,const void* shmaddr,int shmflg);
函数功能: 映射共享内存到进程地址空间
函数参数:
shmid : 共享内存的标识符,
shmaddr: 映射地址,NULL代表由系统决定
shmflg : 映射后内存操作方式:
SHM_RDONLY: 只读
其他读写, 往往写 0;
函数返回值: 成功 : 实际映射后的内存地址
失败 : (void*)-1
3. 地址空间访问
4. 解除映射
shmdt
头文件: #include <sys/ipc.h>
#include <sys/shm.h>
函数原型: int shmdt(const void* shmaddr);
函数功能: 映射共享内存到进程地址空间
函数参数:
shmaddr: 映射地址(shmat 返回值)
函数返回值: 成功 : 0
失败 : -1
5. 回收共享内存。
shmctl
头文件: #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: 共享内存信息结构体,用于存放获取到的,待设置的信息;
函数返回值: 成功 : 0
失败 : -1
延伸知识: IPC 标准:
1. SystemV AT&T
2. Posix IEEE
5. 信号量
作用: 信号量在进程间通讯中,并不是进行数据通讯,而是提供资源访问的同步机制;
信号量:内核提供的一种用于进程同步的一种机制,本质是一个非负的整数计数器,
特点是:当信号量计数为 < 0 时候,信号量会阻塞进程。
信号量PV原语(原子操作):
P操作原语: 信号量-1, 若对计数值为0的信号量做P操作,则进程阻塞
V操作原语: 信号量+1 V操作后,信号量计数值为>=0的时, 进程可被唤醒
SystemV 标准的信号量其实是一个信号量集合:
基本操作:
1. 创建或者获取信号量集合:
semget
头文件: #include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
函数原型: int semget(key_t key,int nsems,int semflg);
函数功能: 创建或者获取信号量集合
函数参数: key : 获取信号量集的关键字,
该关键字是创建信号量集时,由创建进程指定
key: 可取值为:
1. IPC_PRIVATE: 总是创建
2. ftok函数的返回值
nsems: 指定申请的信号量个数
semflg : 信号量集标志:
IPC_CREAT
IPC_EXCL
还可用于指定信号量集的权限
函数返回值: 成功 : 信号量集的标识符
失败 : -1
2. 信号量集合操作:
semop
头文件: #include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
函数原型: int semop(int semid, struct sembuf sops[], unsigned nsops);
函数功能: 对信号量集合中的信号量进行 PV操作
函数参数:
semid : 信号量集的描述符,
sops: struct sembuf 类型的数据,描述对信号量的具体操作
nsops : sops 数组元素的个数
函数返回值: 成功 : 0
失败 : -1
3. 信号量控制(回收)
semctl
头文件: #include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
函数原型: int semctl(int semid, int semnum, int cmd, ...);
函数功能: 对信号量集合进行控制
函数参数:
semid : 信号量集的描述符,
semnum: 信号量集中信号的编号
cmd : 控制方式:
IPC_RMID
SETVAL: 用于设置单个信号量的计数值
此次第四个参数为如下的自定义联合体
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) */
};
利用该联合体中 val 成员进行计数值的设置
函数返回值: 成功 : 0
失败 : -1
6. 消息队列(msgqueue)
消息队列是来实现进程间数据通讯的;
优点:不存在同步问题的,
缺点: 容量小。适合发送短数据
消息队列本质是一个消息链表,对于消息队列来说。存放在队列中的消息
需要指定消息类型,接收进程也是通过消息类型来接收发自不同进程的消息数据;
消息队列操作:
1. 创建消息队列:
msgget
头文件: #include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
函数原型: int msgget(key_t key,int msgflg);
函数功能: 创建或者获取消息队列
函数参数: key : 获取消息队列的关键字,
该关键字是创建消息队列时,由创建进程指定
key: 可取值为:
1. IPC_PRIVATE: 总是创建
2. ftok函数的返回值
msgflg : 消息队列标志:
IPC_CREAT
IPC_EXCL
还可用于指定信号量集的权限
函数返回值: 成功 : 消息队列的标识符
失败 : -1
2. 发送消息
msgsnd
头文件: #include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
函数原型: int msgsnd(int msgid,const void* msgp,size_t msgsz,int msgflg);
函数功能: 创建或者获取消息队列
函数参数: msgid : 消息队列的标识符,
msgp 指向消息缓冲区的指针,是一个用户自定义的结构体
struct msgbuf
{
long type;
char text[SIZE];
};
用于存放待发送的数据;
msgsz: 发送的消息实际长度
msgflg : 一般设为0
函数返回值: 成功 : 0
失败 : -1
3. 接收消息
msgrcv
头文件: #include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
函数原型: int msgrcv(int msgid,const void* msgp,size_t msgsz,int mtype,int msgflg);
函数功能: 创建或者获取消息队列
函数参数: msgid : 消息队列的标识符,
msgp 指向消息缓冲区的指针,是一个用户自定义的结构体
struct msgbuf
{
long type;
char text[SIZE];
};
用于存储接收到的消息数据
msgsz: 待接收的消息实际长度
mtype: 待接收的消息类型
msgflg : 一般设为0
函数返回值: 成功 : 返回实际接收到的消息长度
失败 : -1
4. 回收消息队列
msgctl
头文件: #include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
函数原型: int msgctl(int msgid,int cmd,struct msqid_ds *buf);
函数功能: 对消息队列进行控制
函数参数:
msgid : 消息队列的描述符,
cmd : 控制方式:
IPC_STAT 获取状态信息
IPC_SET 设置状态信息 权限
IPC_RMID 回收消息队列
函数返回值: 成功 : 0
失败 : -1
7. 信号(signal)
信号在进程间通讯中是实现事件通知和进程控制;
信号是系统在软件层,模拟实现中断自产生到被处理的整个流程。
信号的操作:
1. 发送信号
1.1 系统发送
1.2 进程给自身发送信号
raise
头文件: #include <signal.h>
#include <sys/types.h>
函数原型: int raise(int signum);
函数功能: 进程发送 signum 信号给自身进程
函数参数: signum : 信号编号,
函数返回值: 成功 : 0
失败 : -1 错误码存放在 errno
1.3 进程给其他进程发送信号 ***
kill
头文件: #include <signal.h>
#include <sys/types.h>
函数原型: int kill(pid_t pid,int signum);
函数功能: 进程发送 signum 信号给pid进程
函数参数: pid: 指定的进程ID
signum : 信号编号,
函数返回值: 成功 : 0
失败 : -1 错误码存放在 errno
1.4 进程发送信号同时携带数据
sigqueue
头文件: #include <signal.h>
#include <sys/types.h>
函数原型: int sigqueue(pid_t pid ,int signum,const union sigval value);
函数功能: 向pid进程发送signum 信号同时携带数据
函数参数: pid: 指定的进程ID
signum : 信号编号,
value: 信号携带的数据
union sigval
{
int sival_int;
void *sival_ptr;
};
函数返回值: 成功 : 0
失败 : -1 错误码存放在 errno
2. 信号处理
1.1 忽略
1.2 缺省
1.3 捕捉处理
signal
头文件: #include <signal.h>
#include <sys/types.h>
函数原型: sighandler_t signal(int signum,sighandler_t handler);
函数功能: 为signum 信号设置处理动作
函数参数: signum : 信号编号,
handler: 信号的处理动作
1. void handler(int s); 捕捉处理
2. SIG_IGN 忽略
3. SIG_DFL 默认
函数返回值: 成功 : 返回上一个处理函数的地址
失败 : SIG_ERR 错误码存放在 errno
sigaction
头文件: #include <signal.h>
#include <sys/types.h>
函数原型: int sigaction(int signum,const struct sigaction* act,struct sigaction* oldact);
函数功能: 为signum 信号设置处理动作
函数参数: signum : 信号编号,
act: 信号的处理动作
oldact : [OUT] 旧的处理方式,如果不关心可设为NULL
struct sigaction
{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags; //SA_SIGINFO
void (*sa_restorer)(void);
};
函数返回值: 成功 : 0
失败 : -1 错误码存放在 errno
1.4 屏蔽/阻塞
信号屏蔽: 临时不处理信号,但如果随后解除了屏蔽,信号还是要去响应处理的;
信号屏蔽不同于信号忽略
sigprocmask
头文件: #include <signal.h>
#include <sys/types.h>
函数原型: int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
函数功能: 屏蔽或解除屏蔽一个或者多个信号
函数参数: how: 操作方式
1. SIG_BLOCK 在已有屏蔽信号的基础上增加set中的屏蔽信号
2. SIG_SETMASK 解除已有屏蔽信号,增加set中的屏蔽信号
3. SIG_UNBLOCK 在已有屏蔽信号的基础上解除set中的屏蔽信号
set : 信号集合,
oldset : [OUT] 旧的屏蔽信号集合,如果不关心可设为NULL
函数返回值: 成功 : 0
失败 : -1 错误码存放在 errno
8. 定时器(timer)
定时器往往是应用于需要周期性处理的事务。
定时器是Linux系统提供的一个软件资源。
启动定时器方法:
alarm
头文件: #include <unistd.h>
函数原型: unsigned int alarm(unsigned int seconds);
函数功能: 设置定时器
函数参数: seconds: 定时时长(以秒为单位)
函数返回值: 成功 : 如果之前设置过定时器,返回上一个定时器遗留的时长
否则返回为0
ualarm
头文件: #include <unistd.h>
函数原型: useconds_t ualarm(useconds_t usecs, useconds_t interval);
函数功能: 设置定时器
函数参数: usecs: 初始定时时长 (以微秒为单位)
interval: 间隔时长 (以微秒为单位)
函数返回值: 成功 : 如果之前设置过定时器,返回上一个定时器遗留的时长
否则返回为0
请注意:usecs 和 interval 不能超过 1 秒钟时长