进程通信是指在进程间传输数据(交换信息)。 进程通信根据交换信息量的多少和效率的高低,分为低级通信(只能传递状态和整数值)和高级通信(提高信号通信的效率,传递大量数据,减轻程序编制的复杂度)。其中高级进程通信分为三种方式:共享内存模式、消息传递模式、共享文件模式。
使用**fork()**函数创建的进程具有亲缘关系。
fork函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像是两个用户同时启动了该应用程序的两个副本。
#include
#include
#include
int main(){
pid_t pid;
pid = fork();
if (pid == 0) //子进程
{
int i = 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);
}
}
return 0;
}
在用户空间定义一个变量,用来控制两个进程的运行。运行结果只有父进程成功打印了信息。
#include
#include
#include
int main(){
pid_t pid;
pid = fork();
int thread_entry = 1;
if (pid == 0) //子进程
{
int i = 0;
while(thread_entry==1);
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);
}
thread_entry=0;
}
return 0;
}
对于该案例:进程与进程之间是独立的空间。在用户空间实现进程与进程之间的通信是不可能的。
进程与进程之间的通信方式:可以使用内核空间对象进行通信。
管道通信:无名管道(文件系统中无文件名),有名管道(文件系统中有名)
信号通信:信号(通知)通信包括:信号的发生,信号的接收和信号的处理
IPC(Inter-Process Communication)通信:共享内存、消息队列和信号灯
以上是单机通信,只有一个Linux内核
Socket通信:存在于一个网络中两个进程之间的通信(两个Linux内核)
无名管道是Linux系统内核的特殊文件,用于进程之间的通信。无名管道相当于一个队列结构,fd[1]为写入端(入队),fd[0]为读出端(出队)。其中信息读出后即删除,再次读取时即为下一个信息。管道是半双工的,数据只能向一个方向流动。只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。
无名管道缺陷:
不能实现不是父子线程(亲缘关系)之间的通信
fork()函数创建的进程有一个好处,子进程会对父进程的所有内容进行拷贝,包括通过pipe函数创建的文件描述符,所以fork()函数创建的进程可以对内核空间多的同一个管道进行操作。
通信原理:
管道问价是一个特殊的文件,由队列实现的。
在文件IO中创建一个文件或打开一个文件是由open函数来实现的,他不能创建管道文件。只能用pipe函数来创建管道
//创建管道为系统调用 unistd.h
参数:就是得到文件描述符。可见有两个文件描述符:
fd[0]和fd[1]。管道有一个读端fd[0]用来读,和一个写端fd[1]用来写,
这个规则不能改变。
返回值:成功是0,错误:-1
int pipe(int fd[2])
#include
#include
#include
int main(){
int fd[2];
int ret;
char write_buf[] = "hello world";
char read_buf[128] = {0};
ret = pipe(fd);
if (ret<0)
{
printf("create pipe failure\n");
return -1;
}
printf("create pipe successs fd[0] = %d, fd[1] = %d\n",fd[0],fd[1]);
write(fd[1],write_buf,sizeof(write_buf));
read(fd[0],read_buf,128);
printf("%s\n",read_buf);
close(fd[1]);
close(fd[0]);
return 0;
}
运行结果:
从上述可以看出,一个进程是一个对无名管道进行写读的。
#include
#include
#include
int main(){
int fd[2];
int ret;
char write_buf[] = "hello world";
char read_buf[128] = {0};
ret = pipe(fd);
if (ret<0)
{
printf("create pipe failure\n");
return -1;
}
printf("create pipe successs fd[0] = %d, fd[1] = %d\n",fd[0],fd[1]);
write(fd[1],write_buf,sizeof(write_buf)); //往管道写数据
read(fd[0],read_buf,128); //第一次读管道数据
printf("%s\n",read_buf);
memset(read_buf,0,128);
read(fd[0],read_buf,128); //第二次读管道数据
printf("%s",read_buf);
close(fd[1]);
close(fd[0]);
return 0;
}
可以看出第一次读写成功了。但是读完第一次之后,管道内容就会删除,此时在读就会进入阻塞状态。
证明了当管道为空时,在进行读操作就会进入阻塞状态。
#include
#include
int main(){
int fd[2];
int ret;
int i;
char write_buf[] = "hello world";
char read_buf[128] = {0};
ret = pipe(fd);
if(ret < 0 ){
printf("create pipe failure\n");
return -1;
}
printf("printf pipe success fd[0] = %d, fd[1] = %d\n",fd[0],fd[1]);
while(i<5500){
write(fd[1],write_buf,sizeof(write_buf));
i++;
}
//read(fd[0],read_buf,128);
printf("write end\n");
close(fd[1]);
close(fd[0]);
}
运行结果:
此时并没有打印出 “write end” 语句。
查看进程状态:
S+表示睡眠状态。此时./a,out进入了睡眠状态。说明了当管道写满了时,在写就会进入阻塞状态。
通过这个程序我们也可以计算出无名管道的缓存有多大。
在程序中循环次数为5500次。我们 不断减小次数,查看运行结果。
i<5457时
while(i<5457){
write(fd[1],write_buf,sizeof(write_buf));
i++;
}
i<5456时
while(i<5456){
write(fd[1],write_buf,sizeof(write_buf));
i++;
}
由此可以看出。管道缓存大小是 hello world X 5456那么大。如果超过这个范围,再次写管道就会进入到阻塞状态。
#include
#include
#include
int main(){
pid_t pid;
int fd[2];
int ret;
char thread_entry = 0;
ret = pipe(fd);
if(ret < 0 ){
printf("cretae pipe failure\n");
return -1;
}
printf("create pipe success\n");
pid = fork();
if (pid == 0) //子进程
{
int i = 0;
read(fd[0],&thread_entry,1);
while(thread_entry == 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);
}
thread_entry = 1;
write(fd[1],&thread_entry,1);
}
while(1);
return 0;
}
分析://父进程先运行,运行完毕之后往管道里写一个值,thread_entry = 1;然后子进程允许之前先读管道。如果管道是空的,那么程序就会停下这里,因为管道有读阻塞。父进程执行完毕会写管道,那么子进程就会从睡眠状态转换的运行状态,就会运行后面的代码。
有名管道也叫命名管道,在文件系统目录中存在一个管道文件。
管道文件仅仅是文件系统中的标示,并不在磁盘上占据空间。在使用时,在内存上开辟空间,作为两个进程数据交互的通道。
有名管道具有以下特点:
①它可以使互不相关的两个进程间实现彼此通信;
②该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用非常方便;
③FIFO严格地遵循先进先出规则,对管道及FIFO的读操作总是从开始处返回数据,对它们的写操作则是把数据添加到末尾。
mkfifo:用来创建管道文件的节点,没有在内核中创建管道,只有通过open函数打开这个文件时才会在内核空间创建管道。
int mkfifo(const char* fileName,mode_t mode)
头文件:
sys/types.h
sys/stat.h
创建管道文件
参数:
管道文件名,权限,创建的文件权限任然和umask有关系。
返回值:创建成功返回0,创建失败返回-1;
管道文件,字符设备文件,块设备文件,套接字文件。这几种文件类型不占空间,只有文件节点。
进程A的代码
#include
#include
#include
int main(){
int i;
int fd;
char process_inter = 0;
fd = open("./myfifo",O_WRONLY);
if(fd < 0){
printf("open myfifo failure\n");
return -1;
}
printf("open myfifo success\n");
for(i=0;i<5;i++){
printf("this is first process i=%d\n",i);
usleep(100);
}
process_inter = 1;
usleep(500);
write(fd,&process_inter,1);
while(1);
return 0;
}
进程B的代码:
#include
#include
#include
#include
int main(){
int i;
int fd;
char process_inter = 0;
fd = open("./myfifo",O_RDONLY);
if(fd < 0){
printf("open myfifo failure\n");
return -1;
}
printf("opem myfifo success\n");
read(fd,&process_inter,1);
while(process_inter==0);
for(i=0;i<5;i++){
printf("this is first process i=%d\n",i);
usleep(100);
}
while(1);
return 0;
}
将两个.c文件编译之后。为了验证实验。我们先自行进程B
可以发现second进程B一直等待执行。那么我们在另外一个中断执行进程A
当进程A执行完毕,进程B在执行。