Linux入门进程间的通信
Posted 世_生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux入门进程间的通信相关的知识,希望对你有一定的参考价值。
进程间通信
进程间通信介绍
进程间通信目的
进程通信的目的有四点:
- 数据传输:一个进程需要将它的数据发送给另一个进程。
- 资源共享:多个进程之间共享同样的资源
- 通知事件:一个进程需要向另一个进程或一个组发送消息,通知它(它们)发生了某种事件
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程间通信发展
- 管道
- System V进程通信
- POSIS进程间通信
进程间通信分类
管道
- 匿名管道
- 命名管道
System V IPV
- System V 消息队列
- System V 共享内存
- System V 信号量
POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
但是这篇博客是讲管道、System V共享内存。
管道
什么是管道
在学Linux指令的时候,知道管道是|
来表示的。
如一行指令head -n5 text.txt | tail -n3
表示获取text.txt文件3~5行内容。
head命令通过选项 -n5 把text.txt文件中的前五行内容通过管道传输给tail进程把传输过来的内容中的末尾三行内容拿出来。这里管道就提供了传输数据的作用。
- 管道是最古老的进程间通信的形式。
- 我们把从一个进程连接另一个进程是一个数据流称为一个"管道"
如图(简易图)
head进程是通过管道来对tail进程进行数据传输,这看似很正常。但这个管道是谁提供的呢?进程不是具有独立性吗?这两个进程是怎么玩到一块去的呢?
进程和进程具有独立性没错,但是不一定玩不到一块去。比如,人是独立的,但是人可以和人交流,只是要通过一个媒介。而OS就可以提供一个媒介——管道。
OS提供一段内存区域,让head进程和tail进程都看到这块区域,让这两个进程进行通信。所以,进程与进程之间进行通信的本质是看到同一份资源,这种资源称之为临界资源(内存、文件内核缓存等)。
匿名管道
匿名管道:创建一种无名的管道,提供具有血缘关系的进程进行通信。
函数
#include<unistd.h>
int pipe(int fd[2])
参数:fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>
int main()
int fd[2];
//创建匿名管道
if(pipe(fd)<0)
perror("pipe!");
return 1;
//创建子进程
int id=fork();
if(id==0)
//child
close(fd[0]);
const char* mag="hello linux\\n";
int count=5;
while(count)
write(fd[1],mag,strlen(mag));
count--;
sleep(1);
exit(0);
//father
close(fd[1]);
char buff[64];
while(1)
ssize_t s=read(fd[0],buff,sizeof(buff));
if(s>0)
buff[s]='\\0';
printf("child --->father#%s",buff);
sleep(1);
else if(s==0)
printf("read end\\n");
break;
else
printf("error\\n");
break;
//等待子进程结束,回收资源,防止僵尸进程。
waitpid(id,NULL,0);
close(fd[0]);
return 0;
运行结果:
child --->father#hello linux
child --->father#hello linux
child --->father#hello linux
child --->father#hello linux
child --->father#hello linux
read end
<1> 如果说通信可以通过文件来作为媒介,那么为什么不直接open一个文件来呢?要用pipe来创建管道?
答:pipe创建的文件是内存文件,数据一定不会刷新到磁盘。并且用普通文件会有很多问题(同步与互斥),有IO参与会降低效率,没有必要。
<2> 创建子进程进行写入难道不会发生写时拷贝吗?
答:不会,管道是OS提供的,子进程写入时,不会改变父进程的数据区,故不会发生写时拷贝。
<3> 子进程还没写完,父进程就不会读取吗?
答:管道是自带同步与互斥的。不会发生子进程还没写完,父进程就开始读了。
用fork来共享管道的原理
父进程创建了匿名管道,父进程PCB中的files*指针指向file_struct,通过文件描述符找到file结构体,对管道进行读写。
fork子进程,是为了让子进程也可以看到相同的管道,对管道进行读写,这样就可以通信了。
管道只能单向通信,只能一方读,另一方写。所以在fork之后,要关闭掉不需要的描述符。
站在内核角度-深度理解管道
站在内核角度-管道的本质
所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件的思想”。
管道读写规则
当没有数据可读时:
- 子进程不write,父进程会被挂起。
当管道满了时:
- 子进程会被挂起,等待父进程read
如果管道写端对应的文件描述符被关闭,read返回0
如果父进程write关闭时,子进程read没有意义,子进程会接收到13号信号退出。
用代码演示子进程接收信号退出:
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main()
int fd[2];
if(pipe(fd)<0)
perror("pipe!");
return 1;
pid_t id=fork();
if(id==0)
//child
close(fd[0]);
const char* mag="hello linux\\n";
write(fd[1],mag,strlen(mag));
exit(0);
//father
//关闭读端和写端
close(fd[1]);
close(fd[0]);
int status=0;
waitpid(id,&status,0);
//打印信号
printf("child get signal:%d\\n",status&0x7F);
return 0;
运行结果:
child get signal:13
接收的是13号信号,信号是由OS发送的。
当要写入的数据量不大于PIPE_BUF时,Linux将保持原子性。
当要写入的数据量大于PIPE_BUF时,Linux将不保持原子性。
那么PIPE_BUF是什么呢?
(翻译过来的) POSIX.1-2001规定,小于PIPE_BUF字节的写入(2)必须是原子的:输出数据作为连续序列写入管道。写入超过PIPE_BUF字节可能是非原子的:内核可能会将数据与其他进程写入的数据交错。POSIX.1-2001要求管道长度至少为512字节。(在Linux上,PIPE_BUF是4096精确的语义取决于文件描述符是否为非阻塞(O_NON?块),管道是否有多个写入程序,以及在n上,要写入的字节数
管道有多大?代码测试一下。
#include<stdio.h>
#include<unistd.h>
int main()
int fd[2];
pipe(fd);
pid_t id=fork();
if(id==0)
close(fd[0]);
char a='a';
int count=0;
while(1)
write(fd[1],&a,1);
count++;
printf("%d:a\\n",count);
sleep(1000);
return 0;
管道特点
-
只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
-
管道提供流式服务
-
一般而言,进程退出,管道释放,所以管道的生命周期随进程
-
一般而言,内核会对管道操作进行同步与互斥
-
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
命名管道
匿名管道对具有血缘关系的进程进行通信,那么两个毫不相关的进程是如何通信的呢?
答:进程的通信本质是看到同一份资源,毫不相干的进程可以通过命名管道进行通信。
- 匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
- 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
- 命名管道是一种特殊类型的文件
创建一个命名管道
命名管道可以直接在命令行上进行创建。
mkfifo filename
利用命名管道让两个毫不相干的进程进行通信
//server.c ->写
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
int fd=open("filename",O_WRONLY);
char *mag="ni hao a\\n";
write(fd,mag,strlen(mag));
close(fd);
return 0;
//client.c ->读
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
int main()
char buff[64];
int fd=open("filename",O_RDONLY);
ssize_t s=read(fd,buff,sizeof(buff));
buff[s]='\\0';
printf("srever----->client:%s",buff);
close(fd);
return 0;
运行结果
srever----->client:ni hao a
命名管道也可以从程序里创建,相关函数有:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename,mode_t mode);
第一个参数是文件名或者路径。第二个参数是文件的权限。
成功是返回0,失败是返回-1。
匿名管道与命名管道的区别
- 匿名管道由pipe函数创建并打开。
- 命名管道由mkfifo函数创建,打开用open
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。
- 命名管道作用于毫不相干的进程,匿名管道作用于具有血缘关系的进程
例子1-用命名管道实现文件拷贝
原理:
把一个普通文件的内容通过server1进程读取到管道中,再通过client1进程创建新的一个文件,并把管道中的内容写入新文件中,这样就完成有文件的copy.
//server1.c 读取普通文件内容——>把内容写入管道中。
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
umask(0);
int fd1=open("copyfile",O_RDONLY);//以读的方式打开
if(mkfifo("filename",0666)<0)
perror("mkfifo!");
return 1;
int fd=open("filename",O_WRONLY);//创建管道
char mag[128];
while(1)
mag[0]=0;
ssize_t s=read(fd1,mag,sizeof(mag));//读取普通文件的数据
if(s>0)
mag[s]=0;
write(fd,mag,strlen(mag));//把数据写入管道中
else
break;
return 0;
//client1 读取管道内容,并把读取到的内容写入到新创建的文件中。
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
umask(0);
int fd1=open("copy",O_WRONLY|O_CREAT,0666);//以写的方式创建一个新文件
int fd=open("filename",O_RDONLY);//以读的方式打开管道
char buff[128];
while(1)
buff[0]=0;
ssize_t s=read(fd,buff,sizeof(buff));//读取管道的数据
if(s>0)
write(fd1,buff,strlen(buff));//把读到的数据写入到新创建的文件中
else
break;
return 0;
例子2-用命名管道实现server&client通信
//server 写端
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
umask(0);
if(mkfifo("filename",0666)<0)//创建命名管道
perror("mkfifo!");
return 1;
int fd=open("filename",O_WRONLY);//只以写的方式打开命名管道
if(fd<0)
perror("open!");
return 1;
char mag[128];
while(1)
mag[0]=0;
printf("server say $:");//提示发信息
ffshul(stdout);//刷新缓冲区
ssize_t s=read(0,mag,sizeof(mag));//从键盘中获取内容填入mag
if(s>0)
mag[s-1]=0;
write(fd,mag,strlen(mag));//把mag的内容写入命名管道
else
printf("error\\n");
break;
return 0;
//client读端
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
int fd=open("filename",O_RDONLY);
if(fd<0)
perror("open!");
return 1;
char buff[128];
while(1)
buff[0]=0;
ssize_t s=read(fd,buff,sizeof(buff));
if(s>0)
buff[s]=0;
printf("server-->client#:%s\\n",buff);
return 0;
例子3-用命名管道实现对进程的控制
//server.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
umask(0);
if(mkfifo("filename",0666)<0)
perror("mkfifo!");
return 1;
int fd=open("filename",O_WRONLY);
char mag[128];
while(1)
mag[0]=0;
printf("server say $:");
fflush(stdout);
ssize_t s=read(0,mag,sizeofLinux入门进程间的通信