进程间通信概念
进程间的通信即不同的进程之间交换数据的过程,包括数据传输,资源共享,通知事件,控制其他进程等。
Linux下的进程通信机制基本是从UNIX平台继承而来,
例如传统的通信方式包括无名管道、有名管道以及信号;
System Ⅴ进程间通信(IPC)包括System Ⅴ消息队列、System Ⅴ信号量、System Ⅴ共享内存等;
POSIX进程间通信机制包括POSIX消息队列、POSIX信号量、POSIX共享内存区。
本篇博客介绍传统的进程间通信方式,System Ⅴ的进程间通信方式在下一篇博客再介绍。
管道
从一个进程连接到另一个进程的一个数据流称为一个“管道“。管道分为无名管道和有名管道,区别在于创建的管道能否在文件系统中可见。
无名管道
特点:
1. 创建之后在文件系统中不可见
2. 以半双工的方式进行通信
3. 拥有固定的读端和写端
4. 只能用于具有亲缘关系的进程间通信(如由fork得到的进程)
无名管道的创建:pipe函数
#include <unistd.h>
int pipe(int pipefd[2]);
参数:
pipefd :存放无名管道读端和写端的文件描述符数组
pipefd[0]——读端
pipefd[1]——写端
返回值:成功返回0,失败返回-1
注:无名管道的创建最好在fork创建子进程之前,不然子进程也会创建管道。
例子:在一个进程中创建一个子进程,子进程从键盘获取数据,父进程打印输出。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd[2];
if(pipe(fd)<0)
{
perror("pipe");
return -1;
}
pid_t pid=fork();
if(pid<0)
{
perror("fork");
return -1;
}
if(pid==0)//子进程
{
char buf1[64]={0};
while(1)
{
fgets(buf1,64,stdin);
write(fd[1],buf1,strlen(buf1));
}
}
else //父进程
{
char buf2[64]={0};
while(1)
{
memset(buf2,0,64);
read(fd[0],buf2,64);
fputs(buf2,stdout);
}
}
return 0;
}
无名管道的特性:
读特性:
写端存在:
管道有数据:返回读到的字节数
管道无数据:阻塞
写端不存在:
管道有数据:返回读到的字节数
管道无数据:返回0
写特性:
读端存在:
管道有空间:返回写入的字节数
管道无空间:阻塞,直到由空间为止
读端不存在:
无论管道有无空间,管道破裂,系统内核会发送SIGPIPE信号给写进程并将其结束
有名管道
有名管道创建之后会在文件系统中以管道文件的形式存在
有名管道可用于任意两个进程间通信
有名管道的创建:mkfifo函数
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:pathname :文件名(可含路径)
mode :文件权限(如0664)
返回值:成功返回0,失败返回-1
例子:创建一个有名管道,一个进程向管道中输入数据,另一个进程输出数据
//输入程序,里面有些头文件是我之前测试留的,不过对当前例子程序没啥用
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd=open("fifo",O_WRONLY);
if(fd<0)
{
perror("open");
return -1;
}else
{
printf("fifo succes\n");
}
char buf[64]={0};
printf("输入\n");
while(1)
{
fgets(buf,64,stdin);
buf[strlen(buf)-1]='\0';
write(fd,buf,strlen(buf));
}
return 0;
}
//输出程序,里面有些头文件是我之前测试留的,不过对当前例子程序没啥用
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
int ret=mkfifo("fifo",0664);
if(ret<0)
{
perror("mkfifo");
return -1;
}
int fd=open("fifo",O_RDONLY);
if(fd<0)
{
perror("open");
return -1;
}else
{
printf("fifo succes\n");
}
char buf1[64]={0};
while(1)
{
memset(buf1,0,64);
read(fd,buf1,64);
printf("输出%s\n",buf1);
}
return 0;
}
信号
信号:是中断机制在软件层次上的一种模拟。在原理上,一个进程收到一个信号与处理器收到一个中断请求是基本类似的。
信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达。
信号的处理方式有三种:
默认处理,Linux对每种信号都规定了默认操作
忽略,即对信号不做任何处理。不过有两个特殊信号不能忽略,SIGKILL、SIGSTOP
捕获信号,定义信号处理函数,当信号发生时,执行相应的处理函数
- 如果一个进程当前并未处于执行态,则发送给该进程的信号由内核保存起来,直到该进程恢复执行再传递给它
- 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
信号相关指令
kill -l //可查看信号类型
可得如下图的信号编号以及相应宏
kill -信号编号 进程号PID
可向指定进程发送对应编号的信号,信号编号也可以是宏,因为宏的值就是数字编号
例:
kill -9 11023 //向进程11023发送信号9
kill -9 -11023 //向进程组11023发送信号9
kill -9 -1 //向除了init进程以外的其他所有进程发送信号9
信号相关函数
1)kill()
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数:
pid :指定进程号
sig : 信号编号或者其对应宏
返回值:成功返回0,失败返回-1
2)raise()
#include <signal.h>
int raise(int sig);
参数:sig : 信号编号或者其对应宏
返回值:成功返回0,失败返回-1
功能:向当前进程或线程发送信号
3)定时器 alarm()
- 一个进程中最多只能存在一个定时器
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
参数:
seconds :定时秒数
返回值:
成功则返回上一次定时器定时剩余时间,第一次定时的话返回0
#include <unistd.h>
int pause(void);
功能:该进程(或线程)休眠(阻塞),直到发出终止进程或调用信号捕获函数的信号
返回值:仅在捕获到信号并返回信号捕获函数时返回。 在这种情况下,pause() 返回 -1,并且 errno 设置为 EINTR
4)捕获信号
#include <signal.h>
typedef void (*sighandler_t)(int);//信号处理函数
sighandler_t signal(int signum, sighandler_t handler);
参数:
signum :指定信号
handler :信号处理函数,这个处理函数有个固定的参数,为捕获的信号的编号
signal() 将信号符号的处置设置为处理程序,它可以是 SIG_IGN、SIG_DFL 或程序员定义的函数(“信号处理程序”)的地址
SIG_IGN :选择以忽略方式处理指定信号
SIG_DFL :选择以默认方式处理指定信号
返回值:返回信号处理程序的前一个值,如果出错则返回 SIG_ERR。