进程间通信:数据传输、资源共享、事件通知、进程控制。
Linux系统下的ipc
早期unix系统 ipc:管道(数据传输)、信号(事件通知)、fifo(数据传输)。
system-v ipc(贝尔实验室):system-v 消息队列(数据传输、进程控制)、system-v 信号量(资源共享、进程控制)、system-v 共享内存(数据传输效率较高)
socket ipc(BSD)
posix ipc(IEEE):posix 消息队列(数据传输、进程控制)、posix 信号量(资源共享、进程控制)、posix 共享内存(数据传输)
有名管道:用于无父子关系的进程间通信。无父子关系的进程可将信息发送到某个有名管道中,并通过管道名读取消息
无名管道:用于具有父子关系的进程间通信。
信号:用于通知其他进程有何事件发送。此外,进程可以向自身发送信号,还可以获得Linux内核发出的信号。
消息队列:克服了信号的数据结构过于简单的问题,同时也解决了管道数据流无格式和缓冲区长度受限等问题。规定了每个进程的权限,避免了仿冒信息的出现。
信号量:用于解决进程的同步和相关资源抢占而设计的。
共享内存:让多个进程访问同一个内存空间,适合于数据量极大和数据结构极为复杂的进程间通信。但这种方式牺牲了系统的安全性,所有通常与其他进程间通信形式混合使用,并避免以根用户权限执行。
套接字:Linux下的程序能快速移植到其它类UNIX平台上。
无名管道pipe函数
//所需头文件
#include <unistd.h>
//函数原型
int pipe(int pipefd[2]); //pipefd[0]读数据,pipefd[1]写数据
//返回值
成功:0
失败:-1
是一个没有名字的特殊文件,无法使用open函数,但可以使用close函数。只能通过子进程继承文件描述符的形式来使用。读操作时管道如果为空,会阻塞进程,直到有数据写入管道。写操作时管道如果满了,会阻塞进程,直到管道读取数据。当数据被读取后,这些数据将自动被管道清除。所有文件描述符被关闭之后,无名管道被销毁。
有名管道mkfifo函数
//所需头文件
#include <sys/types.h>
#include <sys/state.h>
//函数原型
int mkfifo(const char *filename,mode_t mode); //文件名,权限
//返回值
成功:0
失败:-1
有文件名,可以使用open函数打开,任意进程间数据传输。读操作时管道如果为空,会阻塞进程,直到有数据写入管道。写操作时管道如果满了,会阻塞进程,直到管道读取数据。当数据被读取后,这些数据将自动被管道清除。wtite具有“原子性”。
信号:软件模拟中断,进程接受信号后做出相应响应
显示信号类型:kill -l
pkill 进程名:杀死进程
硬件产生信号:执行非法指令、访问非法内存、驱动程序等
软件产生信号:控制台(Ctrl+C中断信号、Ctrl+L退出信号、Ctrl+Z停止信号)、kill命令、程序调用kill()函数
信号的处理方式:
忽略:进程当信号从来没有发生过
捕获:进程会调用相应的处理函数,进行相应的处理
默认:使用系统默认处理方式来处理信号
常见信号名 | 信号编号 | 产生原因 | 默认处理方式 |
SIGHUP | 1 | 关闭终端 | 终止 |
SIGINT | 2 | Ctrl+C | 终止 |
SIGQUIT | 3 | Ctrl+\ | 终止+转储 |
SIGABRT | 6 | abort() | 终止+转储 |
SIGPE | 8 | 算术错误 | 终止 |
SIGKILL | 9 | kill -9 pid | 终止,不可捕获/忽略 |
SIGUSR1 | 10 | 自定义 | /忽略 |
SIGSEGV | 11 | 段错误 | 终止+转储 |
SIGUSR2 | 12 | 自定义 | /忽略 |
SIGALRM | 14 | alarm() | 终止 |
SIGTERM | 15 | kill pid | 终止 |
SIGCHLD | 17 | (子)状态变化 | /忽略 |
SIGTOP | 19 | Ctrl+Z | 暂停,不可捕获/忽略 |
signal函数
//所需头文件
#include <signal.h>
//函数原型
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler); //要设置的信号,SIG_IGN、SIG_DFL、void (*sighandler_t)(int)
//返回值
成功:上一次设置的handler
失败:SIG_ERR
kill函数
//所需头文件
#include <sys/types.h>
#include <signal.h>
//函数原型
int kill(pid_t pid,int sig); //PID,信号类型
//返回值
成功:0
失败:1
raise函数
//所需头文件
#include <signal.h>
//函数原型
int raise(int sig);
//返回值
成功:0
失败:1
信号集处理函数:屏蔽信号集、非处理信号集
屏蔽信号集:手动/自动屏蔽某些信号
非处理信号集:信号如果被屏蔽,则记录在未处理未处理信号集。非实时信号(1~31),不排队,只留一个;实时信号(34~64),排队,保留全部。
信号集相关API | 描述 | 参数 |
int sigempty(sigset_t *set) | 将信号集初始化为0 | |
int sigfillset(sigset_t *set) | 将信号集初始化为1 | |
int sigaddset(sigset_t *set,int signum) | 将信号集某一位设置为1 | |
int sigdelset(sigset_t *set,int signum) | 将信号集某一位设置为0 | |
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset) | 使用设置好的信号集去修改信号屏蔽集 | how:SIG_BLOCK-屏蔽某个信号(屏蔽集|set)、SIG_UNBLOCK-打开某个信号(屏蔽集&~set)、SIG~SETMASK-屏蔽集=set oldset:保存旧的屏蔽集的值,NULL表示不保存 |
system-v 消息队列
system-v ipc特点:独立于进程、没有文件名和文件描述符、IPC对象具有Key和ID。
消息队列用法:
定义一个唯一的键值Key(ftok)
构造消息队列(msgget)
发送特定类型消息(msgsnd)
接收特定类型消息(msgrcv)
删除消息队列(msgctl)
ftok函数:可生成唯一的键值Key
//函数原型
key_t ftok(const char *path,int proj_id);
//返回值
成功:合法键值
失败:-1
msgget函数:获取消息队列ID
//函数原型
int msgget(key_t key,int msgflg); //键值,IPC_CREAT(消息队列不存在则创建)|权限
//返回值
成功:消息队列ID
失败:-1
msgsnd函数:发送消息到消息队列
//函数原型
int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);
msqid:消息队列ID
msgp:消息缓存区
struct msgbuf
{
long mtype; //消息标识
char mtext[1]; //消息内容
}
msgsz:消息正文的字节数
msgflag:IPC_NOWAIT-非阻塞发送、0-阻塞发送
//返回值
成功:0
失败:-1
msgrcv函数:在消息队列中读取
//函数原型
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
msqid:消息队列ID
msgp:消息缓存区
msgsz:消息正文的字节数
msgtyp:要接收消息的标识
msgflag:IPC_NOWAIT-非阻塞读取、MSG_NOERROR-截断消息、0-阻塞读取
//返回值
成功:0
失败:-1
msgctl函数:设置或获取消息队列的相关属性
//函数原型
ssize_t msgctl(int msqid,int cmd,struct msqid_ds *buf);
msqid:消息队列ID
cmd:IPC_STAT-获取消息队列的属性信息、IPC_SET-设置消息队列的属性、IPC_RMID-删除消息队列
buf:相关结构体缓冲区
//返回值
成功:0
失败:-1