自学内容网 自学内容网

【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]    管道写端的文件描述符

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

#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号信号)杀掉

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

#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 字节

有名管道


原文地址:https://blog.csdn.net/fight_for1/article/details/136015885

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!