【Linux从青铜到王者】Linux进程间通信(一)——待完善

前言

本节重点:

  • 进程间通信介绍。
  • 管道。
  • 消息队列(不涉及)。
  • 共享内存。
  • 信号量(网络时涉及)。

进程间通信介绍

1.进程间通信目的

数据传输:一个进程需要将它的数据发送给另一个进程。

资源共享:多个进程之间共享同样的资源。

通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

2.进程间通信发展

管道。

System V进程间通信。

POSIX进程间通信

3.进程间通信分类

管道。

匿名管道pipe。

命名管道。

System V IPC

System V 消息队列。

System V 共享内存。

System V 信号量。

POSIX IPC

消息队列。

共享内存。

信号量。

互斥量。

条件变量。

读写锁。

4.为什么需要进程间通信?

上面那么多都是为了让进程之间通信,为什么不直接让一个进程发信息给另一个进程呢,或者一个进程直接去一个进程中获取对应的信息,这样不是还方便一点吗?

因为进程要保持独立性,如果一个进程可以直接被另一个进程之间获取,或者可以接受另一个进程的信息,那么就进程之间就没有独立性了,也可能导致进程之间彼此影响

上面那么多种操作的本质,其实都是为了一件事------必须让不同的进程看到同一份资源(由os提供)重点!!!

不同的进程看到同一份资源了,就可以对其进行写入和读取,进程也就可以通过该资源获得另一个进程所发的信息了,然后执行对应的任务

管道

什么是管道?

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

下面这个命令就是统计登录当前用户的个数

who和wc是两个不同的进程,who命令用于查看当前云服务器的登录用户(一行显示一个用户),wc -l用于统计当前的行数。

who进程通过标准输出将数据打到"管道"当中,wc进程再通过标准输入从"管道"当中读取数据,至此便完成了数据的传输,进而完成数据的进一步加工处理。

匿名管道

匿名管道只适用于父子进程之间的通信

原理:

  • 父进程先创建管道文件,通过pcb里的inode找到管道的对应位置,并且读端和写端都打开
  • 然后父进程通过fork创建子进程,由之前可得子进程的pcb中绝大部分信息和父进程保持一致,文件描述符表和inode也会被拷贝,所以此时子进程能找到管道文件,并且读端和写端也是打开着的
  • 通过用户自主选择关闭每个进程的一个端口,就能实现通信了

注意:注意这里的通信是单向的,如果想要实现双向通信,那么可以建立两个管道

图解过程:

从内核角度来看

重点

  • 因为管道只用于传输数据而执行不同的任务,就没有必要将其存到磁盘中而影响os效率,所以管道只存在于磁盘中
  • 由上图我们可以发现管道的本质其实就是一个文件缓冲区
  • 看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了"Linux一切皆文件思想"。

pipe

创建匿名管道需要使用pipe函数 ,pipe函数调用成功时返回0,调用失败时返回-1。

int pipe(int pipefd[2]);

pipe函数的参数是一个输出型参数,数组pipefd用于返回两个指向管道读端和写端的文件描述符

数组元素 含义

pipefd[0] 管道读端的文件描述符

pipefd[1] 管道写端的文件描述符

我们可以做个实验,子进程向管道里写入数据,父进程读取文件并打印,也顺便使用一下函数接口

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    // child->write, father->read

    // 使用pipe创建匿名管道
    int fd[2] = {0};
    if (pipe(fd) < 0)
    {
        perror("pipe fail");
        exit(-1);
    }
    // 使用fork创建子进程 ,关闭子进程读端,向管道写入数据
    pid_t id = fork();
    if (id == 0)
    {
        // child
        close(fd[0]);
        const char *msg = "hello father, I am child...";
        int count = 10;
        while (count--)
        {
            write(fd[1], msg, strlen(msg));
            sleep(1);
        }
        close(fd[1]); // 子进程写入完毕,关闭文件
        exit(0);
    }
    // 关闭父进程写端,创建buff数组,将管道文件读到buff中并打印
    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]);
    waitpid(id,NULL,0);
    return 0;
}

结果

pipe2

pipe2函数与pipe函数类似,也是用于创建匿名管道,其函数原型如下:

int pipe2(int pipefd[2], int flags);

pipe2函数的第二个参数用于设置选项

1、当没有数据可读时:

O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来为止。

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

2、当管道满的时候:

O_NONBLOCK disable:write调用阻塞,直到有进程读走数据。

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

管道的特点

  • 1、管道内部自带同步与互斥机制。
  • 2、管道的生命周期随进程。
  • 3、管道提供的是流式服务。
  • 4、管道是半双工通信的。

管道的四种特殊情况

  • 1.写端进程不写,读端进程一直读,那么此时会因为管道里面没有数据可读,对应的读端进程会被挂起,直到管道里面有数据后,读端进程才会被唤醒。
  • 2.读端进程不读,写端进程一直写,那么当管道被写满后,对应的写端进程会被挂起,直到管道当中的数据被读端进程读取后,写端进程才会被唤醒。
  • 3.写端进程将数据写完后将写端关闭,那么读端进程将管道当中的数据读完后,就会继续执行该进程之后的代码逻辑,而不会被挂起。
  • 4.读端进程将读端关闭,而写端进程还在一直向管道写入数据,那么操作系统会向写端进程发送信号(13号信号)杀掉

可以通过以下代码实现,也就是对上面代码略微修改一下即可

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	int fd[2] = { 0 };
	if (pipe(fd) < 0){ //使用pipe创建匿名管道
		perror("pipe");
		return 1;
	}
	pid_t id = fork(); //使用fork创建子进程
	if (id == 0){
		//child
		close(fd[0]); //子进程关闭读端
		//子进程向管道写入数据
		const char* msg = "hello father, I am child...";
		int count = 10;
		while (count--){
			write(fd[1], msg, strlen(msg));
			sleep(1);
		}
		close(fd[1]); //子进程写入完毕,关闭文件
		exit(0);
	}
	//father
	close(fd[1]); //父进程关闭写端
	close(fd[0]); //父进程直接关闭读端(导致子进程被操作系统杀掉)
	int status = 0;
	waitpid(id, &status, 0);
	printf("child get signal:%d\n", status & 0x7F); //打印子进程收到的信号
	return 0;
}

管道的大小

管道的容量是有限的,如果管道已满,那么写端将阻塞或失败

可以使用ulimit -a命令,查看当前资源限制的设定

通过计算可得总共有4096 字节

有名管道

相关推荐
maosheng11466 小时前
RHCSA的第一次作业
linux·运维·服务器
wifi chicken7 小时前
Linux 端口扫描及拓展
linux·端口扫描·网络攻击
旺仔.2917 小时前
Linux 信号详解
linux·运维·网络
放飞梦想C7 小时前
CPU Cache
linux·cache
Hoshino.418 小时前
基于Linux中的数据库操作——下载与安装(1)
linux·运维·数据库
恒创科技HK9 小时前
通用型云服务器与计算型云服务器:您真正需要哪些配置?
运维·服务器
吴佳浩 Alben9 小时前
GPU 生产环境实践:硬件拓扑、显存管理与完整运维体系
运维·人工智能·pytorch·语言模型·transformer·vllm
播播资源10 小时前
CentOS系统 + 宝塔面板 部署 OpenClaw源码开发版完整教程
linux·运维·centos
源远流长jerry10 小时前
在 Ubuntu 22.04 上配置 Soft-RoCE 并运行 RDMA 测试程序
linux·服务器·网络·tcp/ip·ubuntu·架构·ip
学不完的10 小时前
Docker数据卷管理及优化
运维·docker·容器·eureka