【Linux】进程间通信(IPC)
进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
进程间通信的目的
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止
时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态。
进程间通信分类
管道:匿名管道pipe,命名管道
System V IPC:System V 消息队列、System V 共享内存、System V 信号量
POSIX IPC:消息队列、共享内存、信号量、互斥量、条件变量、读写锁
管道
概念:管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
匿名管道
#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码
关闭管道只需将两个文件描述符关闭
用fork来共享管道
单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。如下图所示:
fork之后的半双工管道 从父进程到子进程的管道
若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0])与子进程的写端(fd[1]);反之,则可以使数据流从子进程流向父进程。
#include<stdio.h>
#include<unistd.h>
int main()
{
int fd[2]; // 两个文件描述符
pid_t pid;
char buff[20];
if(pipe(fd) < 0) // 创建管道
printf("Create Pipe Error!\n");
if((pid = fork()) < 0) // 创建子进程
printf("Fork Error!\n");
else if(pid > 0) // 父进程
{
close(fd[0]); // 关闭读端
write(fd[1], "hello world\n", 12);
}
else
{
close(fd[1]); // 关闭写端
read(fd[0], buff, 20);
printf("%s", buff);
}
return 0;
}
管道读写规则
当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
当管道满的时候
O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
如果所有管道写端对应的文件描述符被关闭,则read返回0
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程
退出
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性
命名管道的打开规则
命名管道的打开规则
如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
例子-用命名管道实现server&client通信
客户端client:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#define FIFO "fifo"
int main()
{
int fd = open(FIFO, O_WRONLY);
if(fd < 0)
{
perror("open error");
return 1;
}
char buf[1024];
while(1)
{
printf("eoch# ");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf)-1);
if(s>0)
{
buf[s] = 0;
write(fd, buf, strlen(buf));
}
}
close(fd);
return 0;
}
服务器端sreve:
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#define FIFO "fifo"
int main()
{
if(mkfifo(FIFO, 0644)<0)
{
perror("mkfifo error");
return 1;
}
int fd = open(FIFO, O_RDONLY);
if(fd < 0)
{
perror("open error");
return 2;
}
char buf[1024];
while(1)
{
ssize_t s = read(fd, buf, sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0;
printf("proc_two: %s\n", buf);
}
else if(s==0)
{
printf("proc_two quit, me too...");
break;
}
else
{
break;
}
}
close(fd);
return 0;
}
makefile
all为所有目标
“.PHONY”为所有依赖关系
$@:表示目标文件;一般是在规则中这么用:gcc -o $@ $(object);
$^:表示所有依赖文件;一般是在规则中这么用:gcc -o $@ $^ ;用所有依赖文件链接成目的文件;
$<:表示第一个依赖文件;在规则中使用:gcc -o $@ -c $< ;其实这个时候就是每个依赖文件生成一个目的文件;
运行截图:
client:
server:
system V共享(共享内存)
我们了解其概念即可
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递