言之者无罪,闻之者足以戒。 ——《诗序》
ctrl+alt+t 打开终端
一、进程间通信和线程间通信:
进程通信:在用户空间实现进程通信是不可能的,通过Linux内核通信
线程通信:可以在用户空间就可以实现,可以通过全局变量通信
二、通信方式:
管道通信:无名管道、有名管道(文件系统中有名)
信号通信:信号(通知)通信包括:信号的发送、信号的接收和信号的处理
IPC(Inyer-Process Communication) 通信:共享内存、消息队列和信号灯。
以上是单机模式下的进程通信(只有一个Linux内核)
Socket通信:存在于一个网络中两个进程之间的通信(两个Linux内核)。
三、进程通信学习思路:
每一种通信方式都是基于文件IO的思想
open:功能:创建或打开进程通信对象。函数的形式不一样,有的是多个函数完成
write:功能:向进程通信对象中写入内容。函数形式可能不一样
read:功能:从进程通信对象中读取内容。函数形式可能不一样
close:功能:关闭或删除进程通信对象。形式可能不一样
四、无名管道:
通信原理:
管道文件是一个特殊的文件,是由队列来实现的。(队列一端入队,一端出队)
在文件IO中创建一个文件或打开一个文件是由open函数来实现的,但open函数不能创建管道文件。
只能用pipe函数来创建管道。
函数形式:int pipe(int fd[2])
功能:创建管道,为系统调用:unistd.h
参数:就是得到的文件描述符。可见有两个文件描述符:fd[0]和fd[1],管道有一个读端fd[0]用来读和一个写端fd[1]用来写,这个规矩不能改变
返回值:成功是0,出错是-1
注意:
管道中的东西,读完了就删除了,就像队列中的出对一样
如果管道中没有东西可读,则会出现读阻塞
如果管道已经被写满,还要写东西就会出现写阻塞
下面写一个程序实现:
(1)创建一个管道
(2)向管道中写数据
(3)读取管道中的数据
(4)验证管道的读阻塞
程序如下:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd[2];
int ret;
char writebuf[]="hello linux";
char readbuf[128]={0};
ret=pipe(fd);
if(ret < 0)
{
printf("creat pipe failure\n");
return -1;
}
printf("creat pipe sucess fd[0]=%d,fd[1]=%d\n",fd[0],fd[1]);
write(fd[1],writebuf,sizeof(writebuf));
read(fd[0],readbuf,128);
printf("readbuf=%s\n",readbuf);
//validate read block
memset(readbuf,0,128);//clear readbuf
read(fd[0],readbuf,128);
printf("read again sucess\n");
close(fd[0]);
close(fd[1]);
return 0;
}
命令:ps -axj 可以查看进程的状态
下面写一个程序实现:
(1)创建一个管道
(2)向管道中写数据
(3)验证写阻塞
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd[2];
int ret;
int i=0;
char writebuf[]="hello linux";
char readbuf[128]={0};
ret=pipe(fd);
if(ret < 0)
{
printf("creat pipe failure\n");
return -1;
}
printf("creat pipe sucess fd[0]=%d,fd[1]=%d\n",fd[0],fd[1]);
while(i < 5457)
{
write(fd[1],writebuf,sizeof(writebuf));
i++;
}
printf("write is sucess\n");
close(fd[0]);
close(fd[1]);
return 0;
}
通过程序的验证:我们发现了阻塞空间的大小,当i<=5456时不阻塞,当i>=5457时阻塞
无名管道实现进程通信:
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int fd[2];
int ret;
char process_inter=0;
ret=pipe(fd);
if(ret < 0)
{
printf("creat pipe failure\n");
return -1;
}
printf("creat pipe sucess\n");
pid = fork();
if(pid==0)
{
int i=0;
read(fd[0],&process_inter,1);
while(process_inter==0);
for(i=0;i<5;i++)
{
printf("this is child process i=%d\n",i);
usleep(100);
}
}
if(pid>0)
{
int i=0;
for(i=0;i<5;i++)
{
printf("this is parent process i=%d\n",i);
usleep(100);
}
process_inter=1;
sleep(5);
write(fd[1],&process_inter,1);
}
while(1);
return 0;
}
无名管道的缺点:只能实现父子进程(有亲缘关系进程)之间的通信。
fork()函数:创建父子进程,父进程为1,子进程为0
五、有名管道:
正是由于无名管道的这一缺点,我们对无名管道进行改进:有名管道
所谓的有名,即文件系统中存在这样一个文件节点,每一个文件都有一个inode号,而且这是一个特殊的文件类型:p管道类型
1、创建这个文件节点,不可以通过open函数,open函数只能创建普通文件,不能创建特殊文件(管道-mkfifo、套接字-socket、字符设备文件-mknod、块设备文件-mknod、符号链接文件-ln -s、目录文件mkdir)
2、管道文件只有inode号,和套接字、字符设备文件、块设备文件一样都不占用磁盘空间。普通文件和符号文件以及目录文件,不仅有inode 号,而且还占用磁盘空间。
3、mkfifo 用来创建管道文件的节点,没有在内核中创建管道,只是通过open函数打开这个文件时才会在内核空间创建管道
4、mkfifo
函数形式:int mkfifo(const *filename , mode_t mode)
功能:创建管道文件
参数:管道文件名,权限,创建的文件权限仍然和umask有关系
返回值:创建成功返回0,创建失败返回-1
下面看一下mkfifo的用法:
include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int ret;
ret=mkfifo("./myfifo",0777);
if(ret < 0)
{
printf("creat myfifo failure\n");
return -1;
}
printf("creat myfifo sucess\n");
return 0;
}
下面我们就通过有名管道实现无亲缘关系进程间的通信:
(1)第一步:创建有名管道:
include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int ret;
ret=mkfifo("./myfifo",0777);
if(ret < 0)
{
printf("creat myfifo failure\n");
return -1;
}
printf("creat myfifo sucess\n");
return 0;
}
(2)第二步:创建第一个进程:
include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
int main()
{
int fd;
int i;
char process_inter1=0;
fd=open("./myfifo",O_WRONLY);
if(fd < 0)
{
printf("open myfifo failure\n");
return -1;
}
printf("open myfifo sucess\n");
for(i=0;i<5;i++)
{
printf("this is first process i=%d\n",i);
usleep(100);
}
process_inter1=1;
sleep(5);
write(fd,&process_inter1,1);
while(1);
return 0;
}
(3)第三步:创建第二个进程:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
int main()
{
int fd;
int i;
char process_inter2=0;
fd=open("./myfifo",O_RDONLY);
if(fd < 0)
{
printf("open myfifo failure\n");
return -1;
}
printf("open myfifo sucess\n");
read(fd,&process_inter2,1);
while(process_inter2==0);
for(i=0;i<5;i++)
{
printf("this is second process i=%d\n",i);
usleep(100);
}
while(1);
return 0;
}
(4)第四步:首先编译运行第一个进程,紧接着编译运行第二个进程;当我们运行第一个进程之后不会看到打印信息,那是因为只有读写两端都被启动,进程才会被启动,当我们运行第二个进程之后就可以看到第一个进程的打印信息,随后会看到第二个进程的打印信息(用两个终端运行两个进程)。