更多信息 进群了解
QQ群:712333606
进程间通信
以下内容通过pipe、fifo、mmap来进行进程间通信
管道pipe()
- 管道pipe也称为匿名管道,只有在有血缘关系的进程间进行通信。管道的本质就是一块内核缓冲区。
- 进程间通过管道的一端写,通过管道的另一端读。管道的读端和写端默认都是阻塞的。
- 管道中的内容读取了就没了,不能重复读取
- 如果想要数据双向流动,那么需要两个管道
- 管道的内部实现是一个环形队列,通过命令
ulimit -a
进行查看大小
pipe size (512 bytes, -p) 8
- 使用命令查看管道大小
printf("pipe size==[%ld]\n", fpathconf(fd[0], _PC_PIPE_BUF));
//输出
pipe size==[4096]
若pipe()函数调用成功,fd[0]存放管道的读端,fd[1]存放管道的写端
int pipe(int pipefd[2]);
返回值
成功返回0,然后使用pipefd[2]来操作管道的读写
失败返回-1
示例:
使用管道来进行进程间通信
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// pid_t fork(void);
int fd[2];
int ret = pipe(fd);
if (ret < 0)
{
perror("pipe error");
return -1;
}
ret = fork();
if (ret > 0)
{
//father
//close read
printf("here is father,child is [%d]\n",ret);
close(fd[0]);
write(fd[1],"hello",strlen("hello"));
pid_t waitp = wait(NULL);
if (waitp>0)
{
printf("child [%d] is over\n",waitp);
}
}
else if (ret == 0)
{
//child
//close write
close(fd[1]);
char buf[64];
memset(buf,0x00,sizeof(buf));
read(fd[0],buf,sizeof(buf));
printf("father say:[%s]\n",buf);
}
else
{
perror("fork error");
exit(-1);
}
return 0;
}
//输出
here is father,child is [24961]
father say:[hello]
child [24961] is over
管道的读写行为
管道读写默认是阻塞的。
读操作
如果有数据,read正常读,返回读出的字节数
如果没有数据
- 如果写端全部关闭,read返回0
- 如果还有写端,read阻塞
写操作
如果读端全部关闭,管道破裂,进程终止,内核发送SIGPIPE信号给当前进程
如果读端没有没有全部关闭
- 如果缓冲区写满了,write阻塞
- 如果缓冲区没有满,继续执行write写操作
如果将管道读端或者写端设置为非阻塞的,需要进行如下操作
//下面是将读端修改为非阻塞
//1.获取读端的文件属性
int flags = fcntl(fd[0], F_GETFL, 0);
//2.添加非阻塞属性
flags |= O_NONBLOCK;
//3.设置读端属性
fcntl(fd[0], F_SETFL, flags);
若是读端设置为非阻塞:
写端没有关闭,管道中没有数据可读,则read返回-1;
写端没有关闭,管道中有数据可读,则read返回实际读到的字节数
写端已经关闭,管道中有数据可读,则read返回实际读到的字节数
写端已经关闭,管道中没有数据可读,则read返回0
使用单个进程对上述进行验证
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// pid_t fork(void);
int fd[2];
int ret = pipe(fd);
if (ret < 0)
{
perror("pipe error");
return -1;
}
// 设置管道读端为非阻塞
int flags = fcntl(fd[0], F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(fd[0], F_SETFL, flags);
// 关闭写端
write(fd[1],"hello",strlen("hello"));
close(fd[1]);
char buf[64];
memset(buf, 0x00, sizeof(buf));
ssize_t len = read(fd[0], buf, sizeof(buf));
if (len == 0)
{
printf("len is [%ld]\n", len);
}
else if (len < 0)
{
printf("len is [%ld]\n", len);
}
else
{
printf("len is [%ld]\n", len);
printf("str is [%s]\n",buf);
}
return 0;
}
//输出
len is [5]
str is [hello]
命名管道fifo()
FIFO可以用于没有血缘关系的进程间通信。FIFO是Linux基本文件类型的一种,文件类型为p。
简单来说,FIFO可以理解为一个特殊的文件,创建它之后,可以使用 ls或ll来进行查看文件基本信息。
FIFO就是标识内核的一条管道,进程可以通过read/write进行读写操作。实际就是进程间在对内核缓冲区在进行读写操作从而进行通信。
注意事项 :这里mkfifo函数的路径需要是Linux本地文件夹下,不能是挂载的文件夹路径,此外需要当前用户有相应权限,如果没有权限可以使用sudo利用root角色来执行
还可以使用命令的方式来提前创建fifo文件
sudo mkfifo ./mfifo
或者使用函数mkfifo()进行创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
第一个参数是创建fifo的路径,第二个参数是fifo的权限
返回值
等于0 创建成功
其他 创建失败,由下面进行判断具体原因
EACCES 路径名中的一个目录不允许搜索(执行)权限
EDQUOT 用户在文件系统上的磁盘块或索引节点配额已用完。
EEXIST 路径名已存在。
ENAMETOOLONG 路径名的总长度大于PATH_MAX,或者单个文件名组件的长度大于NAME_MAX。
ENOENT 路径名中的目录组件不存在
ENOSPC 目录或文件系统没有空间容纳新文件。
ENOTDIR 在路径名中用作目录的组件实际上不是目录。
EROFS 路径名只读
fifo_write.c
//fifo_write.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int ret = mkfifo("/home/myfifo", 0777);
if (ret != 0)
{
perror("mkfifo error");
return -1;
}
int fd = open("/home/myfifo",O_RDWR);
if (fd<0)
{
perror("open error");
return -1;
}
int i = 0;
char buf[64];
while(1)
{
memset(buf, 0x00, sizeof(buf));
sprintf(buf, "%d:%s", i, "hello");
write(fd, buf, strlen(buf));
sleep(1);
i++;
}
//关闭文件
close(fd);
return 0;
}
fifo_read.c
//fifo_read.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int fd = open("/home/myfifo",O_RDWR);
if (fd<0)
{
perror("open error");
return -1;
}
char buf[64];
while(1)
{
memset(buf, 0x00, sizeof(buf));
ssize_t len = read(fd, buf, sizeof(buf));
printf("str len is [%ld], [%s]\n",len,buf);
}
//关闭文件
close(fd);
return 0;
}
存储映射区mmap()
存储映射区就是将一个磁盘文件的空间映射到虚拟内存。可用于无亲缘关系进程。
此外,Linux系统中,每个进程都有独立的虚拟地址空间,因此两个非亲缘关系的进程,它们各自进程里使用的虚拟地址是不同的,因此在不同进程中返回的mmap地址值也不同。
当两个进程调用mmap方法时,分别返回的是两个进程自己的对应的虚拟地址。它们不同的虚拟地址对应同一块存储映射区,映射的是同一块物理内存,从而实现进程间通信。
mmap()函数介绍:
映射文件或者设备到内存。munmap相反,是取消映射。
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *addr, size_t length);
mmap返回值
成功则返回系统分配的内存地址
失败则返回MAP_FAILED
munmap返回值
成功则返回0
失败则返回-1
参数
addr: 自己指定地址,或者设置为NULL,让系统分配。
length: 指定映射的长度
prot: 映射区读写权限
读:PROT_READ 写:PROT_WRITE 执行:PROT_EXEC
flags: 映射区的特性。
MAP_SHARED: 写入映射区的数据会写回文件, 且允许其他映射该文件的进程共享。
MAP_PRIVATE: 对此区域所做的修改不会写回原文件。
fd: 打开文件的文件描述符
offset: 打开文件的偏移量,通常设置为0,即不偏移
示例一:
非亲缘关系进程间通信
//mmap_write.c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
// void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t
// offset); int munmap(void *addr, size_t length);
int fd = open("./test.txt", O_RDWR);
if (fd < 0) {
perror("open error");
return -1;
}
int len = lseek(fd, 0, SEEK_END);
void *addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
memset(addr, 0x00, strlen(addr));
printf("write process map addr is %p\n", addr);
close(fd);
if (MAP_FAILED == addr) {
perror("mmap error");
return -1;
}
memcpy(addr, "hello world", strlen("hello world"));
return 0;
}
//mmap_read.c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
// void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t
// offset); int munmap(void *addr, size_t length);
int fd = open("./test.txt", O_RDWR);
if (fd < 0) {
perror("open error");
return -1;
}
int len = lseek(fd, 0, SEEK_END);
void *addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
printf("read process map addr is %p\n", addr);
close(fd);
if (MAP_FAILED == addr) {
perror("mmap error");
return -1;
}
char *p = (char *)addr;
printf("str is [%s]\n", p);
return 0;
}
//1.在当前目录下创建一个文件test.txt
//2.这个文件不能为空文件,随便有什么内容都行
//3.先执行write,后执行read
//执行
./mmap_write
//输出
write process map addr is 0x7fd47bfd5000
//执行
./mmap_read
//输出
read process map addr is 0x7fb90928b000
str is [hello world]
从mmap映射获取的地址可以看出,非亲缘关系的进程的确具有不同的虚拟地址,它们映射出来的内存地址通常也是不同的。
示例二:
父子进程使用mmap进行通信
//mmap.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
// int munmap(void *addr, size_t length);
int fd = open("./test", O_RDWR);
if (fd < 0)
{
perror("open error");
return -1;
}
int len = lseek(fd, 0, SEEK_END);
void *addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
memset(addr, 0x00, strlen(addr));
close(fd);
if (MAP_FAILED == addr)
{
perror("mmap error");
return -1;
}
int ret = fork();
if (ret > 0)
{
// father
memcpy(addr, "hello world", strlen("hello world"));
wait(NULL);
ret = munmap(addr, len);
if (ret < 0)
{
perror("munmap error");
return -1;
}
else
{
printf("mmap released\n");
}
}
else if (ret == 0)
{
// child
sleep(1);
char *p = (char *)addr;
printf("str is [%s]\n", p);
}
else
{
perror("fork error");
return -1;
}
return 0;
}
//输出
str is [hello world]
mmap released
匿名映射区
添加匿名标志MAP_ANONYMOUS,并手动设置映射区大小
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
// int munmap(void *addr, size_t length);
// int fd = open("./test", O_RDWR);
// if (fd < 0)
// {
// perror("open error");
// return -1;
// }
// int len = lseek(fd, 0, SEEK_END);
int len =4096;
void *addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
memset(addr, 0x00, strlen(addr));
//close(fd);
if (MAP_FAILED == addr)
{
perror("mmap error");
return -1;
}
int ret = fork();
if (ret > 0)
{
// father
memcpy(addr, "hello world", strlen("hello world"));
wait(NULL);
ret = munmap(addr, len);
if (ret < 0)
{
perror("munmap error");
return -1;
}
else
{
printf("mmap released\n");
}
}
else if (ret == 0)
{
// child
sleep(1);
char *p = (char *)addr;
printf("str is [%s]\n", p);
}
else
{
perror("fork error");
return -1;
}
return 0;
}
//输出
str is [hello world]
mmap released