在谈到进程的概念时,大多数人的第一反应可能就是PCB(就是进程控制块), 当然这是对的,因为PCB是
进程存在的唯一标示,但与此同时我们还要想到虚拟地址空间、页表、物理地址以及三者之间的映射关系。
在学习了进程后,接下来就要进行深入的理解有关进程的内容。
我们都知道,进程具有独立性,进程之间一般不会相互影响。但也有特殊情况,比如说进程间通信。
首先我们来了解一下什么是进程间通信?
所谓进程间通信,表面意思就是让两个或多个进程之间能够进行交流,但进程间通信的本质实质上是:
通过操作系统,让两个(或者多个)不相干的进程看到同一份公共资源。该公共资源由操作系统提供。
进程间通信的方式有多种,接下来先学习一下管道。
管道分为匿名管道和命名管道:(1)匿名管道:进程间的一种通信方式,只能用在具有血缘关系的斧子进程之间。(2)命名管道:是一种特殊类型的文件,与有无血缘关系无关。
在Linux中,一切皆文件,由此可以说明管道也是一种文件,可以用来两个进程间进行通信。
接下来我们对管道进行简单的实现。
一、匿名管道
1、从键盘读取字符,写入管道,读取管道,写入屏幕。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main()
{
int fds[2];//fds[0]present read;fds[1]present write.
char buf[100];
int len;
if (pipe(fds) == -1)
{
perror("make pipe");
exit(1);
}
//read from stdin
while (fgets(buf, 100, stdin))
{
len = strlen(buf);
//write into pipe
if (write(fds[1], buf, len) != len)
{
perror("write to pipe");
break;
}
memset(buf, 0x00, sizeof(buf));
//read from pipe
if ((len = read(fds[0], buf, 100)) == -1)
{
perror("read from pipe");
break;
}
//wrrite to stdout
if (write(fds[1], buf, len) != len)
{
perror("write to stdout");
break;
}
}
}
在用fork创建子进程,父子进程通过管道来进行通信之前,我们得了解一个东西--文件描述符。
文件描述符相当于一个数组,数组中存放着指向内核为每一个进程所维护的该进程打开文件的记录表。
数组的第0,1,2,个元素分别为标准输入、标准输出、标准出错。
2、用fork来共享管道。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd[2] = { 0 };
if (pipe(fd) == -1)
{
perror("pipe");
return 1;
}
pid_t pid = fork();
if (pid == -1)
{
perror("fork");
return 2;
}
else if (pid == 0)
{//child write
close(fd[0]);
char *msg = "father,I am child";
int i = 5;
while (i--)
{
write(fd[1], msg, strlen(msg));
sleep(1);
}
}
else
{//father read
close(fd[1]);
char buf[1024];
while (1)
{
ssize_t s = read(fd[0], buf, sizeof(buf));
if (s>0)
{
buf[s] = 0;
printf("father: %s\n", buf);
}
else if (s == 0)
{
printf("child quit!!!\n");
break;
}
else
{
perror("read");
return 3;
}
}
}
return 0;
}
3、管道通信的四种情况:
· 写端的文件描述符关闭,读端一直读 —–读到文件结尾返回0
· 写端的文件描述符没关闭,但写端也不写数据,而读端一直读 —–读端一直等待
· 写端一直写数据,读端不读,也不关闭它的文件描述符 —–写端写满后一直等待
· 写端写数据,而读端的文件描述符关闭 —–系统直接结束掉写进程(用13信号)
4、匿名管道的特点:
· 只能用于具有亲缘关系的进程之间进行通信(常为父子)
· 管道的生命周期随进程
· 管道是半双工的,数据只能向一个方向流动
· 管道自带同步机制
· 管道通信基于字节流
二、命名管道
匿名管道的一个限制就是只能在句有亲缘关系的进程间进行通信,要使两个或多个不相干的进程
彼此间进行通信,那就可以用命名管道。
1、命名管道的创建命名管道不像匿名管道,他不需要由父进程fork,只需一条命令即可创建一个管道。
命名管道也可以通过调用函数来创建。
int mkfifo(const char *filename, mode_t mode);
返回值:创建成功返回0,创建失败返回 - 1
参数pathname代表的是你创建出的管道名称,mode是这个管道具有的权限
下面就举一个小栗子:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
int main()
{
if (mkfifo("fp", 0644) == -1)
{
perror("mkfifo");
return 1;
}
return 0;
}
2、命名管道与匿名管道的区别
匿名管道由pipe函数创建并打开命名管道由mkfifo函数创建,由open打开FIFO(命名管道)与pipe
(匿名管道)之间唯一的区别在于他们常见与打开的方式不同,一但这些工作完成之后,他们具有相同的语义。
3、用命名管道实现两个进程间的通信。(server&client)
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
if (mkfifo("mypipe", 0644) == -1)//创建命名管道
{
perror("mkfifo");
return 1;
}
int rfd = 0;
if ((rfd = open("mypipe", O_RDONLY)) == -1)//以只读方式打开管道
{
perror("open");
return 2;
}
char buf[1024];
while (1)
{
ssize_t s = read(rfd, buf, sizeof(buf)-1);//从管道中读数据
if (s>0)
{
buf[s - 1] = 0;
printf("client say:%s\n", buf);
}
else if (s == 0)
{
printf("client quit!!!\n");
break;
}
else
{
perror("read");
return 3;
}
}
return 0;
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int wfd = 0;
if((wfd = open("mypipe",O_WRONLY))==-1)//以只写方式打开管道
{
perror("open");
return 1;
}
char buf[1024];
while(1)
{
printf("Please Enter:");
//从标准输入读取数据
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
write(wfd,buf,strlen(buf));//将读取到的数据写进管道
}
else if(s==-1)
{
perror("write");
return 2;
}
}
return 0;
}
Makefile
进行测试:
首先通过编译后,运行server来创建管道(打开两个终端,在一个终端运行server,
另一个终端观察管道是否创建成功)
再运行client进行两个文件通信
这样我们就实现了两个文件通过命名管道来通信。