Linux之进程间通信(上)

news2024/11/15 9:50:44

目录

进程间通信的目的

进程通信的分类

进程通信之匿名管道

创建匿名管道

匿名管道的特点

匿名管道四种通信类型 


在现实生活中,人们要进行合作,就必须进行交流,那么在进程之间,会存在交流的情景吗?答案是肯定的,进程之间肯定也会交流,我们称之为进程间通信,本期将开始进程间通信的学习。

进程间通信的目的

1.数据传输:一个进程要将它的数据传输给另一个进程。

2.资源共享:多个进程共享同样的资源。

3.通知事件:子进程退出时,会通知父进程将自己的退出信息进行回收,避免子进程成为僵尸进程。

进程通信的分类

进程通信主要有三个类别,管道通信,System V进程间通信,POSIX进程间通信。

管道通信:分为匿名管道和命名管道。

System V进程间通信:System V消息队列,System V共享内存,System V信号量。这些主要针对进程间通信。

POSIX进程间通信:消息队列,共享内存,信号量,互斥量,条件变量,读写锁。这些主要是针对线程间通信的。

进程通信之匿名管道

在谈及匿名管道之前,我们先讨论一下管道,在生活中管道也很常见,管道有两个端口,一个端口用来进物质,一个端口用来出物质。进程中的管道也是同理的,也有两个端口,一个端口用来写数据,一个端口用来读数据,通过管道实现了两个进程的通信,基于此,我们下来学习匿名管道。

在日常生活中,我们可能会见到这样的场景,比如在学习疫情期间大家上网课,比如在某讯课堂这个平台,老师在这个平台上讲课,学生在这个平台上听课,从而达到了学生和老师的通信。又比如,微信,qq等等这些app可以让任意的两个人实现通信,但是大家稍微留意一下就会发现,两个个体要实现通信,首先必须得有一个公共的平台,这一点在进程之间也是适用的,两个进程要进行通信,也必须看到一份公共的资源,我们首先以父子进程为例,怎么样让父子进程看到同一份公共的资源呢。我们通过下图回顾一下以往知识点。

我们知道使用fork在创建子进程时,父进程相关的数据结构子进程也会拷贝一份,所以对应的struct files_struct也会拷贝一份,这就会导致struct files_struct中的指针数组arr也被拷贝了一份,这就相当于,子进程也和父进程一样打开了同一份文件,这就会导致父子进程看到了同一份文件,这也就具有了两个进程通信的前提,具有了同一份公共资源。

创建匿名管道

使用pipe函数创建匿名管道,要实现管道通信,必须保证一个进程向管道中写文件,一个进程从管道中读文件。所以pipe中的参数就是对应了两个文件描述符,将同一个文件分别以读的形式和写的形式打开,返回值为0时创建成功,返回值小于0时创建失败。 

分析以下代码。

#include<stdio.h>
#include<unistd.h>



int main()
{
  int pid[2]={0};
  if(pipe(pid)<0)
  {
    perror("pipe fail\n");
    return 1;
  }
  printf("%d,%d\n",pid[0],pid[1]);


  return 0;
}

运行结果如下。

我们发现,当我们在使用读和写打开同一份文件时,所分配的文件描述符为3和4,这是因为所有进程已经默认使用了0,1,2号文件描述符。直白点说,创建的这个管道其实就是之前我们讲的文件缓冲区中的操作系统中的文件内核缓冲区中的一块空间。读的内容和写的内容都是在文件的内核缓冲区进行的,不涉及底层物理文件的写入,不涉及驱动中的相关write接口和read接口的使用。 

匿名管道的特点

图示如下。

管道的第一个特点:管道是单向的,一个进程从一个端口写入数据,另一个进程从另一个端口读出数据。 

分析下述代码。

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>

int main()
{

  int pid[2]={0};
  if(pipe(pid)<0)
  {
    perror("pipe fail\n");
    return 1;
  }

 // printf("%d,%d\n",pid[0],pid[1]);
  
//子进程,子进程进行数据写入
//子进程关闭读端

  pid_t id=fork();
  if(id < 0)
  {
    perror("fork error");
    return 2;
  }
  else if(id == 0)
  {
    //子进程,子进程进行文件的写入
    close(pid[0]);
    const char* msg="hello yjd";
    while(1)
    {
      sleep(5);
    write(pid[1],msg,strlen(msg));
    }
  // close(pid[1]);
    exit(0);
  }
  else
  {
    //父进程,父进程进程数据读取                                                                                                                   
    //父进程关闭写端     
    close(pid[1]);
    
    char buff[64]={0};
    while(1)
    {
   // sleep(1);
    ssize_t size = read(pid[0],buff,sizeof(buff)-1);
    if(size > 0)
    {
      buff[size]=0;
      printf("parent get message from child: %s\n",buff);
    }
    else if(size == 0)
    {
      printf("child quit\n");
      break;
    }
    else{
      perror("read fail\n");
      break;
    }
   // close(pid[0]);
    }
  }
  return 0;
}

上述代码我们让子进程等待,让父进程不等待,其实就是让子进程写的慢,让父进程读的快。

运行结果如下。

因为让子进程等了5秒,所以刚开始缓冲区中没有数据,所以父进程就会等待子进程5秒,等待子进程往缓冲区中写入数据,当子进程写入数据之后,父进程立马读取了缓冲区中的数据,将数据读完之后,缓冲区又没有了数据,然后又等待子进程。且我们发现,我们写入数据时是以"hello yjd" 为单位的,但是我们发现读取时,输出的确实一大串字符串,我们称之为字节流,这便是匿名管道的另一个特点。

匿名管道的第二个特点:管道是面向字节流的。

我们上述讲述的匿名管道通信是建立在父子进程之上的,因为子进程可以继承父进程的数据结构,最终看到了同一份资源。所以两个进程之间只要是具有血缘关系的,那么两个进程就可以使用匿名管道进行通讯,这便是匿名管道的第三个特点。

匿名管道的第三个特点:匿名管道只允许具有血缘关系的进程进行通信。

匿名管道的第四个特点:在匿名管道内,子进程的写和父进程的读是原子性的,读和写的操作必须建立在另一个操作完成了的基础上,即匿名管道内部一次只允许一个操作。 

匿名管道的第五个特点:匿名管道是创建了一个文件之后所对应的文件内核缓冲区中所对应的一部分空间,这个缓冲区是父子进程用来进行数据的写入和读取的,所以这块空间的生命周期是随着父子进程的生命周期的。所以管道也具有生命周期,声明周期随着父子进程的生命周期。

匿名管道四种通信类型 

1.子进程写的慢,父进程读的快。

代码和运行结果上一标题已经展示。直接得出结论。

当子进程写的慢,父进程读的快时,父进程必须等待子进程的写入。

2.子进程写的快,父进程读得慢,这分为两种情况,一种是缓冲区没满,一种是缓冲区没满。

当缓冲区没满时,代码如下。

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>

int main()
{

  int pid[2]={0};
  if(pipe(pid)<0)
  {
    perror("pipe fail\n");
    return 1;
  }

 // printf("%d,%d\n",pid[0],pid[1]);
  
//子进程,子进程进行数据写入
//子进程关闭读端

  pid_t id=fork();
  if(id < 0)
  {
    perror("fork error");
    return 2;
  }
  else if(id == 0)
  {
    //子进程,子进程进行文件的写入
    close(pid[0]);
    const char* msg="hello yjd";
    while(1)
    {
    write(pid[1],msg,strlen(msg));
    }
  // close(pid[1]);
    exit(0);
  }
  else
  {
    //父进程,父进程进程数据读取                                                                                                                   
    //父进程关闭写端     
    close(pid[1]);
    
    char buff[64]={0};
    while(1)
    {
    sleep(5);
    ssize_t size = read(pid[0],buff,sizeof(buff)-1);
    if(size > 0)
    {
      buff[size]=0;
      printf("parent get message from child: %s\n",buff);
    }
    else if(size == 0)
    {
      printf("child quit\n");
      break;
    }
    else{
      perror("read fail\n");
      break;
    }
   // close(pid[0]);
    }
  }
  return 0;
}

运行结果如下。 

结论:当缓冲区没满时,父进程读的慢,但是在父进程没有读的时候,子进程仍然会往缓冲区写入数据,直到写满。 

当缓冲区满了时,代码如下。

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>

int main()
{

  int pid[2]={0};
  if(pipe(pid)<0)
  {
    perror("pipe fail\n");
    return 1;
  }

 // printf("%d,%d\n",pid[0],pid[1]);
  
//子进程,子进程进行数据写入
//子进程关闭读端

  pid_t id=fork();
  if(id < 0)
  {
    perror("fork error");
    return 2;
  }
  else if(id == 0)
  {
    //子进程,子进程进行文件的写

    close(pid[0]);
    const char* msg="hello yjd";
    char a = 'a';
    int count = 0;
    while(1)
    {
    
    write(pid[1],&a,1);
    count++;
    printf("count:%d \n",count);
    }
  // close(pid[1]);
    exit(0);
  }
  else
  {
    //父进程,父进程进程数据读取                                                                                                                   
    //父进程关闭写端     
    close(pid[1]);
    
    char buff[64]={0};
    while(1)
    {
  //  sleep(5);
  //  ssize_t size = read(pid[0],buff,sizeof(buff)-1);
  //  if(size > 0)
  //  {
  //    buff[size]=0;
  //    printf("parent get message from child: %s\n",buff);
  //  }
  //  else if(size == 0)
  //  {
  //    printf("child quit\n");
  //    break;
  // }
  // else
  // {
  //  perror("read fail\n");
  //  break;
  // }
  // close(pid[0]);
   }
   } 
   return 0;
   }

 我们让子进程一直写入数据,一次写一个字节,在此期间,父进程不去读数据。

运行结果如下。

我们发现当子进程洗了65536个字节,也就是4KB时, 子进程不再写入,这也就意味着,管道所占缓冲区的大小为4KB。

接着让父进程进行读取,先读取63个字节的数据。

所产生的现象就是,刚开始在父进程读取时,因为读取的数据很少,我们也称读取的很慢,所以刚开始子进程并不会写入,但是当父进程读取了一定大小的数据之后,才激活了子进程的写入,此时子进程开始进行数据的写入。

 结论:当缓冲区没满,父进程读的很慢,子进程会往缓冲区中写入数据。当缓冲区满了之后,父进程读的很慢,当读取的数据大小到达了一定值时,子进程才会往缓冲区中写入数据。

3.子进程关闭写端口并且退出,父进程会将缓冲区中的数据读取完之后退出。

 代码如下。

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>

int main()
{

  int pid[2]={0};
  if(pipe(pid)<0)
  {
    perror("pipe fail\n");
    return 1;
  }

 // printf("%d,%d\n",pid[0],pid[1]);
  
//子进程,子进程进行数据写入
//子进程关闭读端

  pid_t id=fork();
  if(id < 0)
  {
    perror("fork error");
    return 2;
  }
  else if(id == 0)
  {
    //子进程,子进程进行文件的写

    close(pid[0]);
    const char* msg="hello yjd";
    char a = 'a';
    int count = 0;
    while(1)
    {
    
    write(pid[1],msg,strlen(msg));
    break;
   // count++;
   // printf("count:%d \n",count);
    }
    close(pid[1]);
    exit(0);
  }
  else
  {
    //父进程,父进程进程数据读取                                                                                                                   
    //父进程关闭写端     
    close(pid[1]);
    
    char buff[64]={0};
    while(1)
    {
   ssize_t size = read(pid[0],buff,sizeof(buff)-1);
   if(size > 0)
   {
     buff[size]=0;
     printf("parent get message from child: %s\n",buff);
   }
   else if(size == 0)
   {
     printf("child quit\n");
     break;
  }
  else
  {
   perror("read fail\n");
   break;
  }
 // close(pid[0]);
   }
   } 
   return 0;
   }

运行结果如下。

运行结果符合我们的预期,子进程写入字符串之后,关闭写端然后退出,父进程将缓冲区中的字符串读取之后,也会退出。

4.父进程关闭读端口并且退出,子进程也会直接退出。

代码如下。 

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>

int main()
{

  int pid[2]={0};
  if(pipe(pid)<0)
  {
    perror("pipe fail\n");
    return 1;
  }

 // printf("%d,%d\n",pid[0],pid[1]);
  
//子进程,子进程进行数据写入
//子进程关闭读端

  pid_t id=fork();
  if(id < 0)
  {
    perror("fork error");
    return 2;
  }
  else if(id == 0)
  {
    //子进程,子进程进行文件的写

    close(pid[0]);
    const char* msg="hello yjd";
    char a = 'a';
    int count = 0;
    while(1)
    {
    
    write(pid[1],&a,1);
    count++;
    printf("count:%d \n",count);
    }
    close(pid[1]);
    exit(0);
  }
  else
  {
    //父进程,父进程进程数据读取                                                                                                                   
    //父进程关闭写端     
    close(pid[1]);
    
    char buff[2]={0};
    while(1)
    {
    sleep(5);
   ssize_t size = read(pid[0],buff,sizeof(buff)-1);
   if(size > 0)
   {
     buff[size]=0;
     printf("parent get message from child: %s\n",buff);
   }
   else if(size == 0)
   {
     printf("child quit\n");
     break;
  }
  else
  {
   perror("read fail\n");
   break;
  }
  close(pid[0]);
   }
   } 
   return 0;
   }

运行结果如下。

我们发现运行结果符合预期,当父进程关闭读端口然后退出,子进程也会随之退出,此时的子进程并不是正常推出的,而是接受到了操作系统发送的信号导致的异常退出。

以上便是本期的所有内容,本期内容到此结束^_^

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1983329.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

音频转换器在线哪个好?提升音频质量的转换工具

你是否曾梦想过将手机里的铃声变成自己的原创作品&#xff1f;或者&#xff0c;有没有想过将一段演讲录音转化为易于分享的MP3格式&#xff1f; 如果答案是肯定的&#xff0c;那么这款音频转换器mp3就是你寻找的答案。现在&#xff0c;让我们一起探索它的魅力吧&#xff01; 一…

基于MATLAB机器学习、深度学习实践技术

近年来&#xff0c;MATLAB在机器学习和深度学习领域的发展取得了显著成就。其强大的计算能力和灵活的编程环境使其成为科研人员和工程师的首选工具。在无人驾驶汽车、医学影像智能诊疗、ImageNet竞赛等热门领域&#xff0c;MATLAB提供了丰富的算法库和工具箱&#xff0c;极大地…

浏览器用户文件夹详解 - Preferences(十)

1.Preferences简介 1.1 什么是Preferences文件&#xff1f; Preferences文件是Chromium浏览器中用于存储用户个性化设置和配置的一个重要文件。每当用户在浏览器中更改设置或安装扩展程序时&#xff0c;这些信息都会被记录在Preferences文件中。通过这些记录&#xff0c;浏览…

海思ISP记录七:低照度图像调整

Hi3519av100imx585 记录下低照度图像调整的流程和思路 一、环境与配置 环境&#xff1a;暗房不开灯&#xff0c;只有零散漏光和电脑光亮gain与帧率&#xff1a;根据手册我设置的是Again&#xff1a;31282&#xff1b;Dgain&#xff1a;8192&#xff1b;ISP Dgain&#xff1a;…

B1.1 关于应用程序员模型-概述

快速链接: . 👉👉👉 ARMv8/ARMv9架构入门到精通-[目录] 👈👈👈 付费专栏-付费课程 【购买须知】个人博客笔记导读目录(全部) B1.1 关于应用程序员模型–概述 本章节包含了应用程序开发所需的程序员模型信息。 本章节中的信息不同于支持和服务于操作系统下应用程…

1.MySQL面试题之innodb如何解决幻读

1. 写在前面 在数据库系统中&#xff0c;幻读&#xff08;Phantom Read&#xff09;是指在一个事务中&#xff0c;两次读取同一范围的数据集时&#xff0c;由于其他事务的插入操作&#xff0c;导致第二次读取结果集发生变化的问题。InnoDB 作为 MySQL 的一个存储引擎&#xff…

PyTorch深度学习实战(2)——PyTorch快速入门

PyTorch的简洁设计使得它易于入门&#xff0c;在深入介绍PyTorch之前&#xff0c;本文先介绍一些PyTorch的基础知识&#xff0c;以便读者能够对PyTorch有一个大致的了解&#xff0c;并能够用PyTorch搭建一个简单的神经网络。 1 Tensor Tensor是PyTorch中最重要的数据结构&#…

docker、k8s部署 mysql group replication 和 ProxySQL 读写分离

MySQL Group Replication&#xff08;简称MGR&#xff09;是MySQL官方推出的一个高可用与高扩展的解决方案。MySQL组复制它提供了高可用、高扩展、高可靠的MySQL集群服务&#xff0c;这里部署的 mysql 版本 5.7.33&#xff0c;架构是一读一写。特别要注意一个关键点: 必须保证各…

sqli-labs-php7-master第11-16关

猜注入点 先来猜数字型 单引号字符型&#xff1a; 发现注入点找到了 猜测数据库有多少个字段&#xff1a; 1’ order by 4 # 密码随便输的。 这里没有使用--注释&#xff0c;因为没作用&#xff0c;可能是过滤掉了 继续猜。刚才没猜对 1 order by 2 # 没报错&#xff0c;猜…

如何将neo4j,4.x版本部署到服务器上

一. 简介 当我们使用neo4j构建知识图谱时&#xff0c;我们希望让别人能和我们共用neo4j进行知识图谱的构建&#xff0c;我们的方法之一就是将neo4j部署到我们的服务器上&#xff0c;然后将7474,7687端口暴露出来&#xff0c;这样就可以通过访问服务器公网IP的7474端口来操作我…

电脑硬盘坏了数据可以恢复吗?如何恢复硬盘数据?

电脑硬盘坏了数据可以恢复吗&#xff1f;对于这种问题&#xff0c;还需要具体问题具体分析的&#xff0c;一般是可以恢复。 硬盘损坏可以分为物理损坏和逻辑损坏两种情况&#xff1a; 1.逻辑损坏 这通常是由于软件问题&#xff0c;如文件系统错误、病毒攻击、误删除、格式化等…

CentOS Linux release 7.9.2009 中sudo命令未找到

先在 Windows 环境中下载 sudo 的安装包 选择适合自己 Centos 版本的安装包下载到本地&#xff1a;https://www.sudo.ws/releases/stable/ 然后把安装包拷贝的 Centos &#xff08;Linux系统&#xff09;中&#xff0c;cd 进入安装包所在的目录执行下面的命令&#xff1a; 格…

【Unity】线性代数基础:矩阵、矩阵乘法、转置矩阵、逆矩阵、正交矩阵等

文章目录 矩阵&#xff08;Matrix&#xff09;矩阵能干啥&#xff1f;矩阵基本运算矩阵加减法矩阵和标量的乘法矩阵和矩阵的乘法矩阵的转置矩阵相等 特殊的矩阵方块矩阵对称矩阵对角元素&#xff08;Diagonal Elements&#xff09;对角矩阵&#xff08;Diagonal Matrix&#xf…

sqli-labs-master初学者题目练习

Less-1 从源码可以看出id为注入点&#xff0c;且为单引号过滤 使用 闭合 --为注释 原本应该用--‘space’&#xff0c;但-与‘连在一起无法起到注释作用 order by为联合查询——同时查询两张表&#xff0c;但两张表列数必须相同 所有从以上两张图可以看出此表格有三列数据 爆…

计算机网络知识汇总(超详细整理)从零基础入门到精通,看完这一篇就够了

文章目录 前言一、计算机网络概述 1 互联网的构成2.网络分类3.接入网4.网络核心的两大功能 ①路由②转发 5.网络分层 ①OSI 7层模型②TCP/IP 4层模型③两种模型比较 二、物理层 1.物理介质 ①引导型介质②非引导型介质 2.数据交换方式 ①分组交换②电路交换 3.信道复用 …

在亚马逊云科技AWS上利用PEFT和RLHF高效微调AI大模型减少有害回复

简介&#xff1a; 小李哥将继续每天介绍一个基于亚马逊云科技AWS云计算平台的全球前沿AI技术解决方案&#xff0c;帮助大家快速了解国际上最热门的云计算平台亚马逊云科技AWS AI最佳实践&#xff0c;并应用到自己的日常工作里。 本次我将介绍如何用亚马逊云科技的AI模型训练服…

基于K8S部署安装Jenkins

基于K8S部署安装Jenkins 1.Jenkins Kubernetes 清单文件2.Kubernetes Jenkins 部署1&#xff1a;为 Jenkins 创建 Namespace。 最好将所有DevOps工具分类为与其他应用程序分开的命名空间。2&#xff1a;创建“serviceAccount.yaml”文件并复制以下管理员服务帐户清单。1. kubec…

174.地下城游戏——LeetCode

题目 恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里&#xff0c;他必须穿过地下城并通过对抗恶魔来拯救公主。 骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻…

文章生成器免费版,自动写作文章让你无创作之忧

对于创作者而言&#xff0c;长期的内容输出都会遇到写作瓶颈发生&#xff0c;这常常让许多创作者陷入写作困难。而当前解决写作困难最好的方法就是文章生成器了&#xff0c;它自动写作文章的优势让广大的创作者有目共睹&#xff0c;并且是很多创作者们在内容创作中人手必备的神…

Gateway实现Redis拉取信息+用户模块开发

文章目录 &#x1f31e; Sun Frame&#xff1a;SpringBoot 的轻量级开发框架&#xff08;个人开源项目推荐&#xff09;&#x1f31f; 亮点功能&#x1f4e6; spring cloud模块概览常用工具 &#x1f517; 更多信息1.Gateway实现Redis拉取信息1.目录结构2.RedisConfig.java3.Re…