Linux下的进程间通信方式及实现
进程间通信的目的
进程间通信的目的主要是为了:
1、数据传输:实现不同进程之间的数据传输
2、资源共享:实现多个进程共享资源
3、通知事件:实现进程之间的消息传递
4、进程控制:实现进程对其余进程的控制
进程间通信方式的分类:
标准 | 分类 |
---|---|
管道 | 命名管道和匿名管道 |
System V标准的IPC | 消息队列,共享内存,信号量 |
POSIX 标准的IPC | 消息队列,共享内存,信号量,互斥量,条件变量,读写锁 |
管道
管道指的是把一个进程连接到另一个进程的一个 数据流称为“管道”
管道时Unix中最古老的进程间通信方式
匿名管道:
#include <unistd.h>
int pipe(int fd[2]
功能:创建一个匿名管道,成功返回0失败返回错误码
参数:文件描述符数组,fd[0]表示读端,fd[1]表示写端
用fork创建共享管道:
Linux下一切皆文件,所以我们可以采用文件的角度来理解管道,首先父进程创建出一个管道,然后就可以在内核中开辟一块缓冲区实现对数据的读写操作,当父进程fork出一个进程后,子进程就会拿到对这个管道操作的读写文件描述符,此时,若是需要父进程读取数据,子进程写入数据,我们只需要关掉父进程的写端fd和子进程的读端fd就可以实现父子进程之间的通信。这就是匿名管道的原理。因此,匿名管道常被用在具有亲缘关系的进程间进行通信
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
int pipefd[2];
int pid;
int ret;
char* buf = "abcdefgh";
ret = pipe(pipefd);
if(ret < 0)
{
perror("pipe error\n");
return -1;
}
pid = fork();
if(pid < 0)
{
perror("fork error\n");
return -1;
}
else if(pid == 0)
{
close(pipefd[0]);
write(pipefd[1], buf, sizeof(buf));
close(pipefd[1]);
}
else
{
close(pipefd[1]);
char buffer[100] = {0};
read(pipefd[0], buffer, sizeof(buf));
close(pipefd[0]);
printf("buffer = %s\n", buffer);
}
return 0;
}
管道的读写规则:
到没有数据的时候:
O_NONBLOCK disable:read调用阻塞,等待数据到来
O_NONBLOCK enable:read调用返回-1, errno值为EAGAAIN
当管道满的时候:
O_NONBLOCK disable:write调用阻塞,等待数据被读取
O_NONBLOCK enable:write调用返回-1, errno值为EAGAAIN
1、若是所有写端被关闭,读端返回0;
2、若是所有读端被关闭,写端产生SIGPIPE,导致write异常退出
3、当要写入的数据量不大于PIPE_BUF时,Linux保证操作的原子性,否则不保证
匿名管道的特点:
1、只能用于具有亲缘关系的进程之间进行通信
2、管道提供字节流服务
3、管道的生命周期随进程
4、管道支持同步与互斥
5、管道时半双工通信,需要双方通信需要搭建两个管道
命名管道
命名管道是一个FIFO文件,命名管道时一种特殊的文件
创建一个命名管道:
用指令创建一个管道文件:
命令:
mkfifo [文件]
功能:
创建一个管道文件
在程序中创建一个管道文件:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char* filename, mode_t mode);
功能:
创建一个管道文件
匿名管道和命名管道的区别:
1、匿名管道由PIPE函数创建并打开
2、命名管道由mkfifo函数创建,打开用open
3、匿名管道和命名管道的区别就是创建和打开的方式不同
命名管道的读写规则:
当前打开操作是为读而打开FIFO时:
O_NONBLOCK disable:阻塞等待,直到FIFO为写而打开
O_NONBLOCK enable:立即返回成功
当前打开操作是为写而打开FIFO时:
O_NONBLOCK disable:阻塞等待,直到FIFO为读而打开
O_NONBLOCK enable:立即报错返回,错误码 ENXIO
写入命名管道:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
mkfifo("tp", 0644);
int infd;
infd = open("abc", O_RDONLY);
if(infd < 0)
{
perror("open error\n");
return -1;
}
int outfd;
outfd = open("tp", O_WRONLY);
if(outfd < 0)
{
perror("open error\n");
return -1;
}
int ret;
char buf[1024];
while((ret = read(infd, buf, 1024)) > 0)
{
write(outfd, buf, ret);
}
close(infd);
close(outfd);
return 0;
}
读取命名管道:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
umask(0);
int outfd;
outfd = open("abc.back", O_CREAT|O_WRONLY|O_TRUNC, 0644);
if(outfd < 0)
{
perror("open error\n");
return -1;
}
int infd;
infd = open("tp", O_RDONLY);
if(infd < 0)
{
perror("open error\n");
return -1;
}
int ret;
char buf[1024];
while((ret = read(infd, buf, 1024)) > 0)
{
write(outfd, buf, ret);
}
close(infd);
close(outfd);
unlink("tp");
return 0;
}
使用命名管道实现server和client通信:
pipeServer.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
umask(0);
mkfifo("mypipe", 0644);
int fd = open("mypipe", O_RDONLY);
if(fd < 0)
{
perror("open, error\n");
return -1;
}
char buf[1024];
while(1)
{
buf[0] = 0;
printf("please wait...\n");
ssize_t read_size = read(fd, buf, sizeof(buf) - 1);
if(read_size > 0)
{
buf[read_size - 1] = 0;
printf("client say # %s\n", buf);
}
else if(read_size == 0)
{
printf("client quit,exit now!\n");
return -1;
}
else
{
perror("read error\n");
return -1;
}
}
close(fd);
return 0;
}
pipeClient.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("mypipe", O_WRONLY);
if(fd < 0)
{
perror("open error\n");
return 0;
}
char buf[1024];
while(1)
{
buf[0] = 0;
printf("press Enter # ");
fflush(stdout);
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if(read_size > 0)
{
buf[read_size - 1] = 0;
write(fd, buf, sizeof(buf) - 1);
}
else if(read_size <= 0)
{
perror("write error\n");
return -1;
}
}
close(fd);
return 0;
}
共享内存
共享内存是最快的IPC形式,共享内存进行进程间通信的时候不涉及内核。
共享内存就是允许两个不相关的进程访问同一个逻辑内存。
共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。
不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样。
如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
特点:
1、效率最高
2、不支持同步与互斥
共享内存函数:
1、shmget(创建共享内存)
int shmget(key_t key,, size_t size, int shmflg);
参数:
key:共享内存的字段名
size:共享内存大小
shmflg:由九个权限标志位构成,和创建文件的mode标志是一样的
返回值:
成功返回一个非负整数,失败返回-1
2、shmat(将共享内存段连接到进程地址空间)
void* shmat(int shmid, const void* shmaddr, int shmflg);
参数:
shmid:共享内存标识
shmaddr:指定连接的地址
shmflg:可以取值为SHM_RMD和SHM_RDONLY
返回值:
成功返回一个指针,失败返回-1
3、shmdt(将共享内存段与当前进程脱离)
int shmdt(const void* shmaddr);
参数:
shmaddr:由shmat所返回的指针
返回值:
成功返回0,失败返回-1
4、shmctl(控制共享内存)
int shmctl(int shmid, int cmd, struct shmid_ds* buf);
参数:
shmid:由shmget返回的共享内存标识符
cmd:将要采取的动作
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:
成功返回0,失败返回-1
命令 | 描述 |
---|---|
IPC_STAT | 把shmid_ds结构体中的数据设置为共享内存的当前关联值 |
IPC_SET | 在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据揭盅给出的值 |
IPC_RMID | 删除共享内存段 |