【Linux】进程间通信(IPC)-- 无名管道、命名管道

IPC机制 实现进程间通信

在多个进程间传输数据或共享信息的机制。

数据交换,共享资源,进程同步,消息传递。

IPC实现原理:通信进程能够访问相同的内存区域。

方法:

管道:无名管道pipe、命名管道FIFO

System V IPC:消息队列、信号量、共享内存

POSIX IPC :消息队列、信号量、共享内存

套接字:socket

一、无名管道pipe

在有父子关系或者有亲缘关系的进程之间通信

字节流通信,数据格式由用户定义/解析

单向通信(半双工模式),数据单向流动(在管道中同时读写会发生错误)

1.创建无名管道 pipe

两种形式

cpp 复制代码
int pipe( int fd[2] ) ;

int pipe( int fd[2] , int flags);

int fd[2]:整型数组,存储管道的文件描述符;fd[0] 读文件描述符,fd[1] 写文件描述符

int flags: 设置管道标志的参数,0或以下参数的按位和:

O_NONBLOCK 非阻塞 读管道或写管道时不会阻塞管道

O_CLOEXEC 设置当在execve系统调用执行时关闭管道

返回值:

成功 返回 0

失败 返回 -1,并设置errno

2.写管道write

cpp 复制代码
#define TEST_STRING "123456789"

void write( fd[1] , TEST_STRING , sizeof(TEST_STRING));

写端fd[1],将TEST_STRING代表的字符写入管道,长度为 sizeof(TEST_STRING)

3.读管道read

cpp 复制代码
char rbuf[100]={0};

void read( fd[0] , rbuf , 100);

读端fd[0] , 将管道中的数据读到rbuf中,每次读100个字符长度

4.关闭管道 close

关闭读端

cpp 复制代码
close(fd[0]);

关闭写端

cpp 复制代码
close(fd[1]);

5.实现父子进程间的半双工通信

以下是父进程和子进程通信,父进程写,子进程读:

无名管道一般跟fork()函数搭配使用。

第一步,调用pipe函数,生成pipeinnode文件节点,生成管道。

第二步,调用fork函数,父进程创建子进程,同时父进程中的文件表也会拷贝到子进程,此时子进程也可以通过fd4,fd5文件描述符访问管道。

第三步,关闭父进程的读端(fd[0]),关闭子进程的写端(fd[1])。

此时父子进程就可以通过管道进行通信。

PS:pipe一定要在fork之前

创建pipe_test文件

bash 复制代码
vi pipe_test.c

pipe_test文件代码实现

cpp 复制代码
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int fd[2]={};//存储pipe的文件描述符
void pipe_test()
{
	pid_t pid=fork();//创建子进程
	if(pid==0)//子进程
	{       
		printf("children\n");
		close(fd[1]);//子进程关闭写端
		while(1)
		{//读数据
			char rbuf[100]={0};
			read(fd[0],rbuf,100);
			printf("rbuf:%s\n",rbuf);
		}
		close(fd[0]);//读完数据关闭读端
	}
	else if(pid>0)//父进程
	{
		printf("parent\n");
		close(fd[0]);//父进程关闭读端
		#define TEST_STRING "1234567890"
		while(1)
		{
			write(fd[1],TEST_STRING,sizeof(TEST_STRING));
			sleep(1);
		}
		close(fd[1]);
		}
	}
	else
	{}
}

int main(int argc,char * argv[])
{
	int ret=pipe(fd);//创建管道
	if(ret==-1)//创建管道失败
	{
		perror("pipe error");//设置errno
		return -1;
	}
	pipe_test();//调用函数
	return 0;
}

退出 分别按下按键 Esc : w q

编译 pipe_test文件

bash 复制代码
gcc -o pipe_test pipe_test.c

运行

bash 复制代码
./pipe_test

结果

6.实现父子进程间的全双工通信

如图,父进程和子进程之间能够交换数据,双方都可以向对方写数据和读数据。

第一步,父进程调用两次pipe,创建两个管道。

第二步,父进程调用fork,创建子进程,此时子进程有和父进程相同的文件表,也有两个管道的文件描述符。

第三步,父进程只保留 管道1的读端 和 管道2的写端,

子进程只保留管道1的写端 和 管道2的读端。

二、命名管道FIFO

无名管道只能在具有亲缘关系的进程之间传递数据,如果想要在不相关的进程之间传递数据,可以使用 FIFO文件 即 "命名管道" (通常也被称为 "有名管道")完成这项任务。

命名管道是一种特殊类型的文件(别忘了Linux中所有事物都是文件),它在文件系统中以文件名形式存在。文件标识为"p"。

【Linux其他文件类型可查看博客 【Linux】Linux中七种文件类型-CSDN博客

1.创建FIFO文件

创建FIFO文件 可以在命令行上创建,也可以在程序中创建

(1)在命令行中创建命名管道

在命令行中创建管道也有两种方式

① mkfifo filename
bash 复制代码
mkfifo filename

filename 是FIFO文件的文件名

② mknod filename p
bash 复制代码
mknod filename p

filename 是FIFO文件的文件名

p表示创建的是管道文件

(2)在程序中创建命名管道

在程序中也有两个函数可以创建命名管道

注意添加头文件

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
① int mkfifo(filename , mode)
bash 复制代码
int mkfifo (const char * filename , mode_t mode) ;

const char * filename :表示FIFO文件的文件名

mode_t mode :表示该FIFO文件的文件权限,如可读、可写、可执行

命名管道的权限由 mkfifomode 参数和 umask 共同决定。

如 mkfifo(fifo_path, 0666); // 实际权限为 0666 & ~umask

② int mknod(filename , mode | S_IFIFO , 0)

对于这个函数,作者也不太懂,只是看到书上提到了,所以也在这里提出来。对于一般要求可以不掌握。

bash 复制代码
int mknod (const char * filename , mode_t mode | S_IFIFO , (dev_t) 0) ; 

举例:

创建 fifo_test.c文件

bash 复制代码
 vi fifo_test.c

fifo_test.c文件

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
int fifo_test()
{
        const char* fifo_path="./myfifo";
        int ret=mkfifo(fifo_path,0777);
        if(ret<0)
        {
                printf("create myfifo failure\n");
        }
        printf("create myfifo sucessed\n");
        return 0;
}
int main(int argc,char * argv[])
{
        fifo_test();
        return 0;
}

退出 分别按下按键 Esc : w q

编译 fifo_test文件

bash 复制代码
gcc -o fifo_test fifo_test.c

运行

bash 复制代码
./fifo_test

结果

(在后面的文章中,作者将不再赘叙关于编译运行文件的命令)

2.打开FIFO文件 open

打开FIFO的一个限制是,程序不能以O_RDWR模式(即可读可写模式)打开FIFO文件进行读写操作,这样做的后果未明确定义。但是这个限制是有道理的,如果一个管道以读/写方式打开,那么进程就会从这个管道中读取到它自己写入的数据。

通常使用FIFO文件是为了单向传递数据,要实现全双工模式,跟上文提到的无名管道一样,可以使用一对FIFO文件(即两个FIFO文件)。

cpp 复制代码
int open( const char * filepath , int oflags) ; 

const char * filepath : FIFO文件的路径名

int oflags : 有O_RDONLY ,O_WRONLY ,O_NONBLOCK 三种参数可选可组合(O_RDWR已被排除),共有四种组合方式:

O_RDONLY : 只读模式。这种情况下open调用将阻塞,除非有一个进程以写的方式打开同一个FIFO,否则将不会返回。

O_RDONLY | O_NONBLOCK :非阻塞读。即便没有其他进程以写的方式调用FIFO,这个open调用也会成功并立刻返回

O_WRONLY : 只写模式。这种情况下open调用将阻塞,除非有一个进程以读的方式打开同一个FIFO,否则将不会返回。

**O_WRONLY | O_NONBLOCK :非阻塞写。**如果没有其他进程以读的方式调用FIFO,这个open调用将返回错误码-1,并且FIFO也不会打开。如果有一个进程以读的方式打开FIFO文件,就可以通过它的返回的文件描述符对这个FIFO文件进行写操作。

注意:O_NONBLOCK搭配 O_RDONLY和O_WRONLY的效果是不同的。

非阻塞读的open调用总是成功的。

如果没有进程以读的方式打开FIFO文件,那么非阻塞写的open调用将失败。

3.写FIFO文件 write

cpp 复制代码
int pipe_fd = open(FIFO_NAME , open_mode) ; //pipe_fd 为FIFO文件的文件描述符

#define BUFFER_SIZE PIPE_BUF

char buffer[BUFFER_SZIE + 1] ;

write( pipe_fd , buffer , BUFFER_SIZE) ; 

将 buffer 传入 文件描述符为pipe_fd的命名管道中 , 长度为BUFFER_SIZE。

成功返回0,失败返回-1。

系统对任意时刻在一个FIFO中可以存在的数据长度所有限制的。它由#define PIPE_BUF 语句定义,通常可以在头文件limits.h 中找到它。在Linux系统中,它的值一般为4096字节。

4.读FIFO文件 read

read函数的用法同write,就是在需要在另一个进程中使读函数。

bash 复制代码
ret = read(pipe_fd , buffer , BUFFER_SIZE);

5.实现两进程之间的命名管道通信

(1)在命令行中之间使用FIFO通信

① 新建一个myfifo 文件

bash 复制代码
mkfifo myfifo

② 打开一个终端,将FIFO文件中的内容输出

bash 复制代码
cat < ./myfifo

③ 打开另一个终端,往FIFO文件中输入

bash 复制代码
echo "hello world" > ./myfifo 

④ 查看两进程

(2)在程序中使用FIFO通信

fifocom.c文件

cpp 复制代码
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/wait.h>
void *writer(void *arg)
{
        printf("ww : writer begin\n");
        const char * fifo_path="./myfifo";
        int fd = open(fifo_path , O_WRONLY);
        if(fd==-1)
        {
                printf("ww: open fifo failure\n");
                exit(-1);
        }
        char * s="hello from writer\n";
        write(fd,s ,strlen(s));
        close(fd);
        printf("ww : close writer\n");
        return NULL;
}
void *reader(void * arg)
{
        printf("rr : reader begin\n");
        const char * fifo_path="./myfifo";
        int fd = open(fifo_path, O_RDONLY);
        if(fd==-1)
        {
                printf("ww: open fifo failure\n");
                exit(-1);
        }
        char  buff[100];
        read(fd, buff, sizeof(buff));
        printf("rr recived: %s\n",buff);
        close(fd);
        printf("rr : close reader\n");
        return NULL;
}
int main(int argc,char * argv[])
{
        printf("everything is ok------\n");
        pthread_t read_thread,write_thread;
        pthread_create(&read_thread , NULL , reader ,NULL);
        pthread_create(&write_thread , NULL , writer ,NULL);

        pthread_join(read_thread ,NULL);
        pthread_join(write_thread ,NULL);
        wait(NULL);
        return 0;
}

结果:

相关推荐
孙克旭_2 小时前
PXE_Kickstart_无人值守自动化安装系统
linux·运维·自动化
皓月盈江3 小时前
Linux电脑本机使用小皮面板集成环境开发调试WEB项目
linux·php·web开发·phpstudy·小皮面板·集成环境·www.xp.cn
深井冰水3 小时前
mac M2能安装的虚拟机和linux系统系统
linux·macos
leoufung3 小时前
内核内存锁定机制与用户空间内存锁定的交互分析
linux·kernel
菜菜why5 小时前
AutoDL租用服务器教程
服务器
IT专业服务商5 小时前
联想 SR550 服务器,配置 RAID 5教程!
运维·服务器·windows·microsoft·硬件架构
忧虑的乌龟蛋5 小时前
嵌入式Linux I2C驱动开发详解
linux·驱动开发·嵌入式·iic·i2c·读数据·写数据
I_Scholar6 小时前
OPENSSL-1.1.1的使用及注意事项
linux·ssl
firshman_start6 小时前
第十五章,SSL VPN
网络
Johnstons6 小时前
AnaTraf:深度解析网络性能分析(NPM)
前端·网络·安全·web安全·npm·网络流量监控·网络流量分析