作者:华清远见讲师
传统的进程间通信其中有无名管道(PIPE)、有名管道(FIFO)和信号(Signal)。咱们今天就说说linux中基于POSIX的有名管道(FIFO)和无名管道(PIPE)。
1. 描述:
管道提供一个单向的进程间通讯通道。一个管道,有一个读端和一个写端。写到写端的数据能从这个管道的读端读出。
无名管道,在linux中可以通过pipe2函数来创建一个无名管道,执行这个函数后,会返回两个文件描述符,一个指向管道的读端,一个指向管道的写端。无名管道用于有亲缘关系的进程之间通信。通信方式类似半双工通信方式。
有名管道(先进先出的缩写),在linux文件系统内有一个名字(区别无名管道),可以通过mkfifo函数来创建一个有名管道。任何进程都可以通过open函数来打开一个有名管道文件,前提是这个进程有打开权限。读端使用open函数打开的标识是O_RDONLY;写端在open函数中使用O_WRONLY来打开。用于任何进程之间的通信。
注意:
0)尽管一个有名管道在文件系统中有一个名字,但是对于隐藏设备,管道的I/O操作不起作用。
1)有名管道和无名管道的不同就在他们的创建和打开的方式上。一旦创建和打开任务已经完成,那么有名管道和无名管道的I/O操作意义完全相同。
2)管道提供的通讯通道是字节流:没有消息边界的概念。
3)不能使用lseek函数来操作管道。
4)在使用fork创建子进程的时候,要注意适当合理的使用close关闭子进程从父进程继承拷贝的多余的读端或者是写端的文件描述符。
5)管道中的数据从读端读走了,就从管道中消失了。
2. 规则:
1. 读(读端写端都存在):如果一个进程读一个空管道,read函数会阻塞,直到管道中有可用数据。
2. 写(读端写端都存在):如果一个进程想写一个已满管道,write函数会阻塞,直到管道中有足够的数据被读出来让write函数完成写操作。
如果想让读写不阻塞,可以参考fcntl函数。使用F_SETFL和O_NONBLOCK来完成。
3. 读(写端全部关闭):如果指向写端的所有文件描述符都被关闭,read函数对该管道读的尝试结果会返回0;
4. 写(读端全部关闭):如果指向读端的所有文件描述符都被关闭,write函数对该管道的写操作会产生SIGPIPE信号,并发送给调用write函数的进程;如果调用write的进程忽略了SIFPIPE信号,那write会带着EPIPE的错误而失败。
管道中的数据可读性只有一次。也就是说数据在管道中阅后即焚。管道中的数据读了就没有了。
(1) 无名管道:int pipe(int pipefd[2]);
参数:
//pipefd 获得操作管道的文件描述符 ,读:pipefd[0],写:pipefd[1]。
返回值: 成功返回0,失败返回-1
(2)创建有名管道文件
int mkfifo(const char *pathname, mode_t mode);
@pathname 管道文件名
@mode 指定文件的权限
返回值:
成功返回0,失败返回-1
下面以两个线程中使用有名管道的同步通信为例,展示相关代码的使用:
#include
#include
//strlen memset
#include
//mkfifo open
#include
#include
//open
#include
//read
#include
//errno
#include
//pthrea_mutex_unlock
#include
//sem_init sem_wait sem_post
#include
#define N 1024//缓存buf的大小
sem_t read_sem;//读信号资源
sem_t write_sem;//写信号资源
void *read_fifo(void* args){//读管道线程的线程主体
int ret = -1;
int fd_r = -1;
char buf[N] = {0};
ssize_t num = 0;
ret = mkfifo((char*)args,0666);
if(ret < 0 && errno != EEXIST){
perror("read_fifo mkfifo failed");
return (char*)args;
}
fd_r = open((char*)args,O_RDONLY|O_NONBLOCK,0666);/*非阻塞方式打开文件获取文件描述符*/
if(fd_r < 0){
perror("read_fifo open failed");
return (char*)args;
}
while(1)
{
sem_wait(&read_sem);/*读管道线程获取读管道的资源,实现和写线程的同步*/
memset(buf,0,sizeof(buf));/*清空缓冲区*/
num = read(fd_r,buf,sizeof(buf));
buf[N-1] = '\0';/*输出前的防止越界操作*/
if(num > 0){
printf("%d read:%s\n",num,buf);
}
if(strncmp(buf,"q",1) == 0){/*对退出字符串的捕捉*/
close(fd_r);
sem_post(&write_sem);
break;
}
sem_post(&write_sem);/*释放写操作资源*/
}
close(fd_r);
return (char*)args;
}
void *write_fifo(void* args){
int ret = -1;
int fd_w = -1;
char buf[N] = {0};
ssize_t num = 0;
ret = mkfifo((char*)args,0666);
if(ret < 0 && errno != EEXIST){//排除已经创建管道的错误退出情况
perror("read_fifo mkfifo failed");
return (char*)args;
}
fd_w = open((char*)args,O_WRONLY|O_NONBLOCK,0666);
if(fd_w < 0){
perror("read_fifo open failed");
return (char*)args;
}
while(1)
{
sem_wait(&write_sem);
printf("#:");/*输入提示符*/
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf) - 1] = '\0';/*对fgets获取到终端的’\n’字符的消除*/
num = write(fd_w,buf,strlen(buf));
if(strncmp(buf,"q",1) == 0){
close(fd_w);
sem_post(&read_sem);
break;
}
sem_post(&read_sem);
}
close(fd_w);
return (char*)args;
}
int main(int argc, const char *argv[])
{
int ret_p = -1;
pthread_t tid[2] = {0};
sem_init(&read_sem,0,0);/*程序启动时,管道没有资源,故读的资源要初始化为0*/
sem_init(&write_sem,0,1);/*程序启动时,管道首先需要一个写操作,故写资源为1*/
if(argc < 2){/*程序运行时,紧跟其后的输入参数判断*/
perror("Input wrong format");
puts("Excuting_code_name + new_fifoname");
exit(EXIT_SUCCESS);
}
ret_p = pthread_create(&tid[0],NULL,read_fifo,(char*)argv[1]);/*把在程序后的参数传递给相关进程作为有名管道文件的文件名*/
if(ret_p < 0){
perror("read_fifo pthread_create failed");
exit(EXIT_FAILURE);
}
#if 1
ret_p = pthread_create(&tid[1],NULL,write_fifo,(char*)argv[1]);
if(ret_p < 0){
perror("write_fifo pthread_create failed");
exit(EXIT_FAILURE);
}
#endif
pthread_join(tid[0],NULL);/*线程的阻塞回收,亦可以防止主进程结束*/
pthread_join(tid[1],NULL);
return 0;
}
文章选自华清远见嵌入式培训