Linux--进程通信

进程通信介绍

进程通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一给进程
  • 资源共享:多个进程之间共享同样的资源
  • 通知事件:一个进程需要向另一个或另一组进程发送消息,通知它(们)发生了某种事件(eg:进程终止时需要通知父进程)
  • 进程控制:有些进程希望完全控制另一个进程的执行(eg:Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变

如何做到进程通信

  • 进程具有独立性(数据层面)
  • 进程间通信,要借助第三方(OS)资源
  • 进程A-》数据“拷贝”给OS-》OS数据“拷贝”给进程B(OS一定要提供一段内存区域,能够被双方进程看到)

进程通信本质是让不同的进程,看到同一份资源(内存文件内核缓冲等)

进程间通信发展

  • 管道
  • System V进程通信
  • POSIX进程间通信

进程间通信发展分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息队列
  • System V共享内存
  • Systwm V信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

管道

  • 管道是Unix中最古老的进程间通信的形式
  • 一个进程连接到另一个进程的数据流称为一个管道
    在这里插入图片描述
    管道虽然用的是文件的方案,但是OS一定不会把数据刷新到磁盘(有IO参与,降低效率)
    在这里插入图片描述

在这里插入图片描述

匿名管道

匿名管道本质是没有文件名。
管道只能单向通信

#include <unistd.h>
//功能:创建无名管道
int pipe(int fd[2]);
//fd:文件描述符组,fd[0]代表读端,fd[1]代表写端
//返回值:成功返回0,失败返回错误代码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
  //child->write father->read 
  
  close(2);
  int fd[2] = {0};
  if(pipe(fd) < 0)
  {
    perror("pipe!\n");
    return 1;
  }

  printf("fd[0]:%d\n",fd[0]);
  printf("fd[1]:%d\n",fd[1]);
  //相当于一个文件被打开两次

  return 0;
}

在这里插入图片描述

用fork来共享管道原理

在这里插入图片描述
在这里插入图片描述

建立管道

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
  //child->write father->read 
  int fd[2] = {0};
  if(pipe(fd) < 0)
  {
    perror("pipe!\n");
    return 1;
  }
  pid_t id = fork();
  if(id == 0)
	{
	  //child
	   close(fd[0]);
	}
    close(fd[1]);
  }
  close(fd[1]);
  //father
  waitpid(id,NULL,0);
  return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
  //child->write father->read 
  int fd[2] = {0};
  if(pipe(fd) < 0)
  {
    perror("pipe!\n");
    return 1;
  }
  pid_t id = fork();
  if(id == 0){
    close(fd[0]);
    const char* msg = "hello father ,I am child!";
    int count = 5;
    while(count){
      write(fd[1],msg,strlen(msg));
      count--;
      sleep(1);
      }
    }
    close(fd[1]);
    exit(0);
  }
  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 send to father#%s\n",buff);
    }
    else if(s == 0)
    {

      printf("read file end!\n");
      break;
    }
    else 
    {
      printf("read error!\n");
      break;
    }
  }
  //father
  waitpid(id,NULL,0);
  return 0;
}

在这里插入图片描述
父子通信不可以创建全局缓冲区来完成通信,因为进程运行具有独立性!

内核角度–管道本质

在这里插入图片描述

管道特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可应用该管道
  • 管道提供流式服务
  • 一般而言,进程退出,管道释放,所以管道的生命期随进程
  • 一般而言,内核会对管道操作进程同步与互斥
  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
  //child->write father->read 
  int fd[2] = {0};
  if(pipe(fd) < 0)
  {
    perror("pipe!\n");
    return 1;
  }
  pid_t id = fork();
  if(id == 0){
    close(fd[0]);
    const char* msg = "hello father ,I am child!";
    int count = 5;
    while(count){
    	if(count == 2){
    		sleep(1000);
    	}
    	else{
      		write(fd[1],msg,strlen(msg));
		    count--;
		    sleep(1);
      	}
      }
    close(fd[1]);
    exit(0);
  }
  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 send to father#%s\n",buff);
    }
    else if(s == 0)
    {

      printf("read file end!\n");
      break;
    }
    else 
    {
      printf("read error!\n");
      break;
    }
    close(fd[0]);
    break;
  }
  sleep(10);
  //father
  int status = 0;
  waitpid(id,&status,0);
  printf("child quit:sig:%d\n",status & 0x7F);
  return 0;
}

在这里插入图片描述
在这里插入图片描述

查看管道大小

查看系统资源:
在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
  //child->write father->read 
  int fd[2] = {0};
  if(pipe(fd) < 0)
  {
    perror("pipe!\n");
    return 1;
  }
  pid_t id = fork();
  if(id == 0)
  {
    close(fd[0]);
    const char* msg = "hello father ,I am child!";
    char c='a';
    int count = 0;
    while(1)
    {
      write(fd[1],&c,1);
      count++;
      printf("%d\n",count);
    }
    close(fd[1]);
    exit(0);
  }
  close(fd[1]);
  char buff[64];
  while(1)
  {
    sleep(1000);
    ssize_t s = read(fd[0],buff,sizeof(buff));
    if(s > 0)
    {
      buff[s] = '\0';
      printf("child send to father#%s\n",buff);
    }
    else if(s == 0)
    {

      printf("read file end!\n");
      break;
    }
    else 
    {
      printf("read error!\n");
      break;
    }
  }
  //father
  waitpid(id,NULL,0);
  return 0;
   

}

在这里插入图片描述
在这里插入图片描述

管道读写规则

没有数据可读

  • O_NORNBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止
  • O_NORNBLOCK enable: read返回-1,errno值为EAGAIN

当管道满的时候

  • O_NORNBLOCK disable:write调用阻塞,知道有进程走读数据
  • O_NORNBLOCK enable: 调用返回-1,errno值为EAGAIN

1、如果所有管道写端对应的文件描述符被关闭,则read返回0
2、如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
3、当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性
4、当要写入的数据量大于PIPE_BUF时,Linux不再保证写入的原子性

命名管道

  • 管道应用的一个限制就是只能在具有相同祖先(有亲缘关系)的进程间通信
  • 在不相关的进程之间交换数据,可以用FIFO文件,它被称为命名管道
  • 命名管道是一种特殊类型的文件
    想让无关系的进程通信-》通过名字打开同一个文件-》看到同一份资源

创建一个命名管道

在命令行中

mkfifo filename

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在程序中

int mkfifo(const char* filename,mode_t mode);

创建简单命名管道

//Makefile 
.PHONY:all
all:client server
client:client.c
		gcc -o $@ $^
server:server.c
		gcc -o $@ $^
.PHONY:clean
clean:
		rm client server myfifo

1\

//server.c

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#define FILE_NAME "myfifo"
int main()
{
  if(mkfifo(FILE_NAME,0644) < 0)
  {
    perror("mkfifo");
    return 1;
  }
  return 0;
}

//client.c
#include <stdio.h>
int main()
{
	return 0;
}

在这里插入图片描述

2\

//comm.h
#pragma once
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#define FILE_NAME "myfifo"

//client.c
#include "comm.h"
int main()
{
  int fd = open(FILE_NAME,O_WRONLY);
  if(fd < 0)
  {
    printf("open error!\n");
    return 1;
  }
  char msg[128];
  while(1)
  {
      msg[0] = 0;
      printf("please enter# ");
      fflush(stdout);
      ssize_t s = read(0,msg,sizeof(msg));
    if(s > 0)
    {
      msg[s - 1] = 0;
      write(fd,msg,strlen(msg));
    }
  }
  close (fd);
  return 0;
}
//server.c
#include "comm.h"
int main()
{
  if(mkfifo(FILE_NAME,0644) < 0)
  {
    perror("mkfifo");
    return 1;
  }
  int fd = open(FILE_NAME,O_RDONLY);
  if(fd < 0)
  {
    perror("open error!\n");
    return 2;
  }
  char msg[128];
  while(1)
  {
    msg[0] = 0;
  }
  return 0;
}

此时myfifo为0-》并未被刷新到磁盘中,还在内存里-》在内存之中通信(和匿名管道底层原理一样采用文件通信)

在这里插入图片描述
3\

//server.c
#include "comm.h"
int main()
{
  if(mkfifo(FILE_NAME,0644) < 0)
  {
    perror("mkfifo");
    return 1;
  }
  int fd = open(FILE_NAME,O_RDONLY);
  if(fd < 0)
  {
    perror("open error!\n");
    return 2;
  }
  char msg[128];
  while(1)
  {
    msg[0] = 0;
    ssize_t s = read(fd,msg,sizeof(msg) - 1);
    if(s > 0)
    {
      msg[s] = 0;
      printf("client# %s\n",msg);
    }
    else if(s == 0)
    {
      printf("client quit\n");
      break;
    }
    else{
      printf("read error!\n");
      break;
    }
  }

  return 0;

}

在这里插入图片描述
4\

#include "comm.h"
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
  if(mkfifo(FILE_NAME,0644) < 0)
  {
    perror("mkfifo");
    return 1;
  }
  int fd = open(FILE_NAME,O_RDONLY);
  if(fd < 0)
  {
    perror("open error!\n");
    return 2;
  }
  char msg[128];
  while(1)
  {
    msg[0] = 0;
    ssize_t s = read(fd,msg,sizeof(msg) - 1);
    if(s > 0)
    {
      msg[s] = 0;
      printf("client# %s\n",msg);
      if(fork() == 0){
        //child
        execlp(msg,msg,NULL);
        exit(1);
      }
      waitpid(-1,NULL,0);//-1 ->等任何一个子进程
     // printf("client# %s\n",msg);
    }
    else if(s == 0)
    {
      printf("client quit\n");
      break;
    }
    else{
      printf("read error!\n");
      break;
    }
  }

  return 0;

}

在这里插入图片描述

5\

#include "comm.h"
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
  if(mkfifo(FILE_NAME,0644) < 0)
  {
    perror("mkfifo");
    return 1;
  }
  int fd = open(FILE_NAME,O_RDONLY);
  if(fd < 0)
  {
    perror("open error!\n");
    return 2;
  }
  char msg[128];
  while(1)
  {
    msg[0] = 0;
    ssize_t s = read(fd,msg,sizeof(msg) - 1);
    if(s > 0)
    {
      msg[s] = 0;
      printf("client# %s\n",msg);
      char* p = msg;
      const char* label = "+-*/%";
      int flag = 0;
      while(*p)
      {
       switch(*p)
       {
         case '+':
           flag = 0;
           break;
         case '-':
           flag = 1;
           break;
         case '*':
           flag = 2;
           break;
         case '/':
           flag = 3;
           break;
         case '%':
           flag = 4;
           break;
       }
        p++;
      }
      char* data1 = strtok(msg,"+-*/%");
      char* data2 = strtok(NULL,"+-*/%");
      int x = atoi(data1);
      int y = atoi(data2);
      int z = 0;
      switch(flag)
      {
        case 0:
          z = x + y;
          break;
        case 1:
          z = x - y;
          break;
        case 2:
          z = x * y;
          break;
        case 3:
          z = x / y;
          break;
        case 4:
          z = x % y;
          break;
      }
      printf("%d %c %d = %d\n",x,label[flag],y,z);
    }
    else if(s == 0)
    {
      printf("client quit\n");
      break;
    }
    else{
      printf("read error!\n");
      break;
    }
  }
  close(fd);
  return 0;
}

在这里插入图片描述
通信进程的意义:让多个进程进行协同完成某种事情:执行命令,发送字符串,完成计算机任务等等

匿名管道与命名管道的区别

  • 匿名管道由pipe函数创建并打开
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间的区别在于它们创建与打开的方式不同,一旦这些工作完成只后,它们具有相同的语义

命名管道的打开规则

1、如果当前开采操作是为读而打开FIFO时

  • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
  • O_NONBLOCK enable:立刻返回成功

2、如果当前开采操作是为写而打开FIFO时

  • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
  • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

用命名管道实现文件拷贝

在这里插入图片描述

//comm.h
#pragma once
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#define FILE_NAME "myfifo"

//client.c
#include "comm.h"

int main()
{
  int fd = open(FILE_NAME,O_WRONLY);
  if(fd < 0)
  {
    printf("open error!\n");
    return 1;
  }
  int in = open("file.txt",O_RDONLY);
  char msg[128];
  while(1)
  {
      msg[0] = 0;
      //dup2(in,0);//把in重定向成0
      //ssize_t s = read(in,msg,sizeof(msg));
      ssize_t s = read(0,msg,sizeof(msg));
    if(s == sizeof(msg))
    {
      msg[s - 1] = 0;
      write(fd,msg,s);
    }
    else if(s<sizeof(msg))
    {
      write(fd,msg,s);
      printf("read end of file!\n");
      break;
    }
    else{
      break;
    }
  }
  close(in);
  close (fd);
  return 0;
}

//server.c
#include "comm.h"
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
  if(mkfifo(FILE_NAME,0644) < 0)
  {
    perror("mkfifo");
    return 1;
  }
  int fd = open(FILE_NAME,O_RDONLY);
  if(fd < 0)
  {
    perror("open error!\n");
    return 2;
  }
  int out = open("file-bak.txt",O_CREAT|O_WRONLY,0644);

  char msg[128];
  while(1)
  {
    msg[0] = 0;
    ssize_t s = read(fd,msg,sizeof(msg) - 1);
    if(s > 0)
    {
      write(out,msg,s);
    }
    else if(s == 0)
    {
      printf("client quit\n");
      break;
    }
    else{
      printf("read error!\n");
      break;
    }
  }
  close(out);
  close(fd);
  return 0;
}

在这里插入图片描述

在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值