Linux系统-进程间通信(上)

简介: Linux系统-进程间通信(上)

零、前言


本章主要讲解学习Linux中本系统下的进程间通信


一、进程间通信介绍


  • 概念:

进程间通信简称IPC(Inter process communication),进程间通信就是在不同进程之间传播或交换信息


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


  • 进程间通信本质:让不同的进程看到同一份资源

由于进程之间具有独立性,代码数据独立拥有,若想实现通信,可以通过向第三方资源(实际上就是操作系统提供的一段内存区域)写入或是读取数据,进而实现进程之间的通信


  • 进程间通信发展:

管道->System V进程间通信->POSIX进程间通信


  • 进程间通信分类:
  1. 管道

匿名管道pipe;命名管道


  1. System V IPC

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


  1. POSIX IPC

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


二、管道


  • 概念:

管道是Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

  • 示图:统计当前使用云服务器上的登录用户个数

7056934a6fd5785adcfe49ec6a6502a2.png

202203122241082.png


注:who命令用于查看当前云服务器的登录用户(一行显示一个用户);wc -l用于统计当前的行数


1、匿名管道

  • 概念:

匿名管道用于本地具有亲戚关系的进程之间通信,常用与父子进程间通信


  • pipe函数原型:
#include <unistd.h>
int pipe(int fd[2]);


  • 功能:

创建一无名管道


  • 参数:
  1. fd:文件描述符数组,是一个输出型参数,拿到打开的管道文件的问文件描述符,其中fd[0]表示读端文件,fd[1]表示写端文件
  2. 返回值:成功返回0,失败返回错误代码


  • 示图:

202203122328999.png

  • 示例:父子进程匿名管道通信
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>
#include<stdlib.h>
int main()
{
    int pipe_id[2]={0};
    //创建管道文件资源
    int ret=pipe(pipe_id);
    if(ret<0)
    {
        perror("pipe");
        exit(1);
    }
    printf("pipe_id[0]:%d pipe_id[1]:%d\n",pipe_id[0],pipe_id[1]);
    //创建子进程,共享管道资源
    pid_t id=fork();
    if(id==0)
    {
        //child->write
        //关闭子进程的读端
        close(pipe_id[0]);
        const char* msg="Hello father, I am child!\n";
        int cnt=0;
        while(1)
        {
            cnt++;
            printf("child write:%d\n",cnt);
            write(pipe_id[1],msg,strlen(msg));//结束符不用写入,结束符是C语言的规则不是系统的规则
            sleep(1);
            if(cnt==10)
                break;
        }
        close(pipe_id[1]);
        exit(0);
    }
    else if(id>0)
    {
        //father->read 
        //关闭父进程的写端
        close(pipe_id[1]);
        //进行读取管道信息
        while(1)
        {
            char buffer[128]={0};
            ssize_t s=read(pipe_id[0],buffer,sizeof(buffer)-1);//给结束符留一个位置
            if(s>0)
            {
                buffer[s]=0;//设置结束符
                printf("msg from child:%s",buffer);
            }
            else if(s==0)
            {
                printf("子进程写端关闭...\n");
                break;
            }
            else 
                break;
        }
        close(pipe_id[0]);
    }
    else 
    {
        perror("fork");
        exit(2);
    }
    //父进程等待
    int status=0;
    if(waitpid(id,&status,0)>0&&WIFEXITED(status))//等待成功并退出正常
    {
        printf("wait success! exit code:%d\n",WEXITSTATUS(status));
    }
    else  
    {
        printf("exit sign:%d\n",status&0x7F);
    }
    return 0;
}


  • 效果:

6f9692dded0b0bdaa470413929657b66.png

  • 共享管道原理:
  1. 对于同个文件可以以读方式和以写方式打开,文件在文件系统虽然只有一份,但是在进程的PCB中的文件结构体中的文件地址数组中可以保存两份,一份指向文件的读端口,一份指向文件的写端口
  2. 管道通过系统接口创建管道文件资源,并构建文件与PCB的映射关系,当fork创建子进程时父子进程就见到同一份文件资源,依靠管道文件的缓冲区选择性进行单向的实时读写


注:如果是刷新到磁盘上再进行读写非常影响效率


  • 单向读写:

父进程进行读,子进程进行写;父进程进行写,子进程进行读


  • 示图:

202203182346231.png

  • 注意:
  1. 只有在先fork之前读写打开文件,父子进程才能共享相同的文件指针数组,进一步灵活控制读写
  2. 管道只能够进行单向通信,关闭对应的读写端也是为了避免误操作
  3. 从管道写端写入的数据会被内核缓冲,直到从管道的读端被读取


  • 以文件描述符视角理解:

202203190930269.png

  • 以内核角度理解:

202203190932664.png

  • 注意:
  1. 管道就是特殊的文件,管道的使用和文件一致
  2. 但是依靠管道通信的本质上依靠管道的缓冲区进行读写,其缓冲并不会真正的刷新到磁盘上


  • 管道读写规则:
  1. 写端不写,读端无数据可读

O_NONBLOCK disable:read调用阻塞,即进程暂停执行,进行等待写端写入数据

O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN


  1. 写端不写,并将写端文件关闭

如果所有管道写端对应的文件描述符被关闭,则read返回0


  1. 读端不读,写端一直写

O_NONBLOCK disable: write调用阻塞,直到有进程读走管道缓冲区的数据

O_NONBLOCK enable: write调用返回-1,errno值为EAGAIN


  1. 读端不读,并将读端文件关闭

如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程被终止退出

  • 示图:

202203191030473.png


  1. 数据写入的原子性

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性

当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性


注:原子性是指 一个操作是不可中断的,要么全部执行成功要么全部执行失败,即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰


  • 管道特点:
  1. 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常父子进程之间就可应用该管道
  2. 管道提供流式服务,面向字节流,读写以字节为单位进行
  3. 进程退出,管道释放,所以管道的生命周期随进程内核会对管道操作进行同步与互斥,即保证数据的原子性
  4. 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道


  • 示图:

202203191015434.png


2、命名管道

  • 概念:
  1. 对于匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信
  2. 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道
  • 命名管道创建命令:
mkfifo filename


  • 示例:

202203191037337.png

  • 命名管道创建函数原型:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);


注:第一个参数即为管道的名称,第二个参数即为创建管道文件的权限,创建成功返回0,否则返回-1


  • 示例:
int main()
{
    mkfifo("fifo", 0644);
    return 0;
}


  • 匿名管道与命名管道的区别
  1. 匿名管道由pipe函数创建并打开,依靠父子进程的共享特性看到同一份文件资源
  2. 命名管道由mkfifo函数创建并主动调用函数打开,依靠文件路径的唯一性让不同进行找到并打开同一份文件资源
  3. FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义


  • 命名管道的打开规则
  1. 如果当前打开操作是为读而打开FIFO时

O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO

O_NONBLOCK enable:立刻返回成功


  1. 如果当前打开操作是为写而打开FIFO时

O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO

O_NONBLOCK enable:立刻返回失败,错误码为ENXIO


  • 示例:用命名管道实现server&client通信
server.c:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define FIFO "fifo"
int main()
{
    //创建命名管道
    if(mkfifo(FIFO,0644)<0)
    {
        perror("mkfifo");
        exit(1);
    }
    //打开管道文件
    int fd=open(FIFO,O_RDONLY);
    if(fd<0)
    {
        perror("open");
        exit(2);
    }
    //服务端进行客户端信息
    while(1)
    {
        char buffer[128]={0};
        //输出标识词
        printf("client#");
        fflush(stdout);
        //读取管道数据
        ssize_t s=read(fd,buffer,sizeof(buffer)-1);
        if(s>0)
        {
            buffer[s]=0;
            printf("%s",buffer);
        }
        else if(s==0)
        {
            printf("write close,child quit\n");
            break;
        }
        else 
            break;
    }
    return 0;
}
client.c:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO "fifo"
int main()
{
    //打开管道文件
    int fd=open(FIFO,O_WRONLY);
    if(fd<0)
    {
        perror("open");
        exit(2);
    }
    //向服务端发送消息
    while(1)
    {
        char buffer[128]={0};
        //输出标识词
        printf("please enter#");
        fflush(stdout);
        //读入数据
        ssize_t s=read(0,buffer,sizeof(buffer)-1);
        if(s>0)//写入到管道
        {
            buffer[s]=0;
            write(fd,buffer,strlen(buffer));
        }
        else 
            break;
    }
    return 0;
}


  • 效果:

202203191145783.png

相关文章
|
3天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
19 3
|
3天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
16 2
|
13天前
|
Linux 应用服务中间件 Shell
linux系统服务二!
本文详细介绍了Linux系统的启动流程,包括CentOS 7的具体启动步骤,从BIOS自检到加载内核、启动systemd程序等。同时,文章还对比了CentOS 6和CentOS 7的启动流程,分析了启动过程中的耗时情况。接着,文章讲解了Linux的运行级别及其管理命令,systemd的基本概念、优势及常用命令,并提供了自定义systemd启动文件的示例。最后,文章介绍了单用户模式和救援模式的使用方法,包括如何找回忘记的密码和修复启动故障。
35 5
linux系统服务二!
|
13天前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
44 4
linux进程管理万字详解!!!
|
4天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
41 8
|
3天前
|
安全 网络协议 Linux
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。通过掌握 ping 命令,读者可以轻松测试网络连通性、诊断网络问题并提升网络管理能力。
18 3
|
6天前
|
安全 Linux 数据安全/隐私保护
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。本文介绍了使用 `ls -l` 和 `stat` 命令查找文件所有者的基本方法,以及通过文件路径、通配符和结合其他命令的高级技巧。还提供了实际案例分析和注意事项,帮助读者更好地掌握这一操作。
23 6
|
6天前
|
Linux
在 Linux 系统中,`find` 命令是一个强大的文件查找工具
在 Linux 系统中,`find` 命令是一个强大的文件查找工具。本文详细介绍了 `find` 命令的基本语法、常用选项和具体应用示例,帮助用户快速掌握如何根据文件名、类型、大小、修改时间等条件查找文件,并展示了如何结合逻辑运算符、正则表达式和排除特定目录等高级用法。
31 6
|
7天前
|
机器学习/深度学习 自然语言处理 Linux
Linux 中的机器学习:Whisper——自动语音识别系统
本文介绍了先进的自动语音识别系统 Whisper 在 Linux 环境中的应用。Whisper 基于深度学习和神经网络技术,支持多语言识别,具有高准确性和实时处理能力。文章详细讲解了在 Linux 中安装、配置和使用 Whisper 的步骤,以及其在语音助手、语音识别软件等领域的应用场景。
34 5
|
7天前
|
缓存 运维 监控
【运维必备知识】Linux系统平均负载与top、uptime命令详解
系统平均负载是衡量Linux服务器性能的关键指标之一。通过使用 `top`和 `uptime`命令,可以实时监控系统的负载情况,帮助运维人员及时发现并解决潜在问题。理解这些工具的输出和意义是确保系统稳定运行的基础。希望本文对Linux系统平均负载及相关命令的详细解析能帮助您更好地进行系统运维和性能优化。
25 3