进程间通信--IPC机制
今天主要讲述独立的进程之间的通信,我们知道,进程是一个完整代码的执行过程,那么我们想实现在进程运行的过程之中传递信息或者是发送数据,就是通过IPC机制来完成。
一、什么是IPC机制
IPC:inter process communication 进程间通信机制
- 进程与进程间的用户空间、资源相互独立,内核空间共享。若要实现进程间通信,需要使用进程间通信机制
- 进程间通信需要操作共享的内核空间
下图简略画出了两种进程通信的方式,本文主要讲解传统的通信 方式,即管道通信和信号通信
-
IPC机制:
1、传统的进程间通信机制
a、管道fifo
b、信号signal
2、system V 操作系统的IPC对象
a、消息队列 message queue
b、共享内存 shared memory
c、信号灯集 semaphore
3、用于跨主机进程间通信机制
a、套接字 socket
二、IPC通信方式之一 (管道通信)
1、管道的基础概念
在进程的内核空间中(3G-4G),创建一个管道(通道),要通信的进程可以往管道中添加内容,另一个进程从管道中获取。
管道中的数据直接存储内核空间的内存中,用于通信
2、管道的特点
- 管道可以看作一个特殊的文件,一般的文件存储在磁盘上,管道文件的内容存储在内存中(用于通信)
-
管道遵循先进先出的原则
-
管道的数据是一次性的,对管道进行读操作,被读取的数据会从管道中删除
-
当管道被关闭后(读和写),管道对应的内存空间中的数据就消失了
-
管道是一种半双工的通信方式
-
管道的大小:64KB = 64 * 1024B
-
管道的使用,使用文件IO函数完成读和写,使用read、write,禁止使用lseek
虽然特点很多,但是只有记住,管道其实就是一个文件,我们如何使用文件IO读取数据和写数据,那么在管道当中也是相同的操作即可
3、管道的分类和区别
有名管道:
- 定义:管道名字在文件系统中可见,管道文件在磁盘上有对应的标识(在磁盘上的文件大小为0
无名管道:
- 定义:由于无名管道不可见文件名,所以不能用在任意的两个进程之间的通信(两个进程不一定找到的是同一个管道文件)
区别: 有名管道可以用于任意进程之间的数据传输,而无名管道由于不可见文件名,只能用于具有亲缘关系的进程之中,比如父进程和子进程之间传递数据
4、管道的创建
1、有名管道的创建
mkfifo函数创建有名管道
函数头文件:
#include <sys/types.h>
#include <sys/stat.h>
函数原型:
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道文件
参数:
参数1: const char *pathname:指定要创建的有名管道的文件路径名
参数2: mode_t mode:创建权限, mode & ~umask
返回值:
成功,返回 0
失败,返回 -1,设置errno。errno == 17,代表文件已经存在,是一个合法的错误,文件存在可以继续执行
//完成进程间通信
//0、创建管道文件,如果管道文件存储,直接打开
if( mkfifo("./fifofile",0664) < 0)
{
if(errno != 17)
{
perror("create fifo failed\n");
return -1;
}
printf("fifo file is ok\n");
}
else
printf("create fifo success\n");
2、无名管道的创建
pipe函数创建无名管道
头文件
#include <unistd.h>
函数原型
int pipe(int pipefd[2]);
功能:创建一个无名管道,同时打开无名管道文件的读写端
参数: int pipefd[2]:数组,实参传递数组名,用于设置存储两个文件描述符(管道文件读打开、管道文件写打开 的文件描述符)
pipefd[0]:读端
pipefd[1]:写端
返回值:
成功,返回0
失败,返回-1,且设置errno
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main()
{
//先创建无名管道通信
int fd[2];
if( pipe(fd) < 0 )
{
perror("pipe faild:");
return -1;
}
printf("pipe success\n");
//创建子进程
pid_t pid = fork();
if(pid > 0)
{//父进程
close(fd[0]);
char buf[128];
//父进程通过管道发送到子进程
while(1)
{
bzero(buf,128);
scanf("%s",buf);
write(fd[1],buf,strlen(buf));
if(strcmp(buf,"quit") == 0)
break;
}
close(fd[1]);
}
else if(pid == 0)
{//子进程
close(fd[1]);
char buf[128];
//子进程通过管道读取父进程数据
while(1)
{
int ret = read(fd[0],buf,128);
buf[ret] = '\0';
printf("child print : %s\n",buf);
if(strcmp(buf,"quit")==0)
break;
}
close(fd[0]);
}
return 0;
}
对于管道文件用得比较多的是有名文件,重点掌握
三、IPC通信方式之一(信号通信)
1、信号的概念
信号就是一种通知,由一个进程发送到另一个进程的通知,通知另一个进程现在需要完成什么操作
进程对信号的处理方式
1、执行默认操作(缺省操作)
定义:每一种信号都规定了默认处理操作
当信号发送时,进程去执行默认处理
2、忽略信号
定义:当信号发生时,对信号不做处理
有两个信号无法忽略 9)SIGKILL 19)SIGSTOP
3、捕获信号
定义:为特定的信号注册新的处理函数,当信号产生时,去执行自定义的处理函数
有两个信号无法忽略 9)SIGKILL 19)SIGSTOP
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
常见的信号如上图,共有62个信号:(1-31) (33-64)
2) SIGINT: 退出进程 ctrl + c
3) SIGQUIT :退出进程 ctrl + \
20) SIGTSTP : 挂起进程 ctrl + z
无法被捕获、忽略 的信号 9)SIGKILL 19)SIGSTOP
11) SIGSEGV 段错误信号 退出进程
13) SIGPIPE 管道破裂信号 退出进程
17) SIGCHLD 子进程退出结束,子进程会发送 17 这个信号 给父进程
2、信号通信常用的函数
1、kill:发送信号给指定的进程
头文件:
#include <sys/types.h>
#include <signal.h>
函数原型:
int kill(pid_t pid, int sig);
功能:发送信号给指定的进程
参数:
参数1: pid_t pid:
pid > 0 :将信号发送给给指定的进程,pid值 就是 进程号
pid == 0 :将信号发送给当前进程组的所有进程
pid == -1:将信号发送给当前进程权限所能发送的所有进程,除了 init 1号进程
pid < -1 :将信号发送给指定进程组夏的所有进程,进程组id == pid的绝对值
参数2:
int sig:指定要发送的信号,写数字或者宏
返回值:
成功,返回0
失败,返回-1,设置errno
2、signal:接收指定的信号执行命令
头文件:
#include <signal.h>
函数原型:
typedef void (* sighandler_t)(int) ;//类型替换 sighandler_t 表示函数指针类型
sighandler_t signal(int signum, sighandler_t handler);
功能:
捕获信号,为 信号 注册信号的处理函数:将 signum信号 与 handler函数指针对应的函数做绑定
参数: 参数1: int signum:指定要捕获的信号
参数2:
sighandler_t handler:函数指针,函数地址,指向返回值为 void,参数为 int 类型函数的地址
SIG_IGN:忽略信号
SIG_DFL:缺省操作,执行默认操作
捕获信号:函数名,注册 是执行 自定义的函数(即参数2可以换成函数名,这个函数名名代替第一个参数执行,而第一个参数是kill传递过来的信号,signal接收以后,用这个函数代替执行这个信号)
返回值:
成功,返回注册的处理函数的地址
失败,返回SIG_ERR ,设置errno
对于管道文件和信号,都是进程间传递数据的传统通信方式,信号里面的kill和signal:kill负责发送信号,signal复制接收信号,并不难理解,两个函数的联合使用就是信号传递的完整过程
四、问题小测
实现用管道文件和信号实现进程间的数据传输
进程1
用于发送数据:
//发送信号,使用kill这个函数
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(int argc, const char *argv[])
{
//创建管道文件
int num_mk=mkfifo("./fifofile",0664);
if(num_mk<0)
{
if(errno==EEXIST)
{
printf("file is exist\n");
}
// printf("mkfifo error\n");
}
//发送指定的信号
int num_pid=atoi(argv[1]);
int num_sig=kill(num_pid,10);//发送10这个信号给制定进程
printf("%d\n",num_pid);
if(num_sig==0)
{
printf("成功发送\n");
}
//打开管道文件,写入数据
int fd_wr=open("./fifofile",O_WRONLY);
if(fd_wr<0)
{
printf("open error\n");
return -1;
}
while(1)
{
char buf[128];
fgets(buf,128,stdin);
int ret=write(fd_wr,buf,strlen(buf));
if(ret<0)
{
printf("读取失败\n");
}
if(strcmp(buf,"quit\n")==0)
break;
}
close(fd_wr);
return 0;
}
进程2
用于接收信号
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
void signal_tep(int num);
void signal_tep(int num) {
// 打开管道文件
int fd_rd = open("./fifofile", O_RDONLY);
if (fd_rd < 0) {
perror("open error");
return;
}
while (1) {
char buf[128];
ssize_t ret = read(fd_rd, buf, sizeof(buf) - 1);
if (ret <= 0) {
perror("read error");
break;
}
buf[ret] = '\0'; // 确保字符串正确结束
printf("%s\n", buf);
if (strcmp(buf, "quit\n") == 0)
{
exit(0);//如果接收退出字符,退出进程
}
}
close(fd_rd); // 关闭文件描述符
}
int main(int argc, const char *argv[]) {
// 获取进程号
int ret = getpid();
printf("pid=%d\n", ret);
// 设置信号处理函数
if (signal(SIGUSR1, signal_tep) == SIG_ERR) {
perror("signal error");
exit(EXIT_FAILURE);
}
while (1) {
pause(); // 等待信号:
}
return 0;
}
注意:编译文件时要转换成其他文件名,不要用./aout
如下图: