Linux下的系统编程——进程间的通信(九)

简介: Linux下的系统编程——进程间的通信(九)

一、进程间通信常用方式

IPC方式:

       Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。

 

       在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有:

1.管道(使用最简单)

2.FIFO(无血缘关系,单次读取)

3.共享映射区(无血缘关系,反复读取)

4.信号(开销最小)

5.本地套接字(最稳定)

二、管道

1.概念:

     

        管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:

(1).其本质是一个伪文件(实为内核缓冲区)

(2).由两个文件描述符引用,一个表示读端,一个表示写端,只能一次读取

(3).规定数据从管道的写端流入管道,从读端流出,单向流动

管道的原理:  管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现

管道的:

       1)数据不能进程自己写,自己读。·

       2)管道中数据不可反复读取。一旦读走,管道中不再存在。

       3)采用半双工通信方式,数据只能在单方向上流动

       4)只能在有公共祖先的进程间使用管道

常用的通信方式: 单工通信、半双工通信、全双工通信

创建管道文件:

(不占用磁盘空间)

2.pipe函数:

pipe函数:用于有血缘关系的进程间通信

函数功能:创建,并打开管道。

   int pipe(int fd[2]);

   参数:    

      fd[0]: 读端。

       fd[1]: 写端。

   返回值:

        成功: 0

        失败: -1 errno

管道通信:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
 
void sys_err(const char *str)
{
  perror(str);
  exit(1);
}
 
 
int main(int argc, char *argv[])
{
  int ret,re;
  int fd[2];
  pid_t pid;
  char *str = "hello pipe\n";
  char buf[1024];
 
  ret = pipe(fd);     //父进程先创建一个管道,持有管道的读端和写端
  if(ret == -1)
    sys_err("pipe error");
  
  pid = fork();        //子进程同样持有管道的读和写端
 
  if(pid > 0){            //父进程
    close(fd[0]);       //关闭读段
    write(fd[1],str,strlen(str));//写入数据
        sleep(1);
    close(fd[1]);            //关闭写段
  }else if(pid == 0){        //子进程
    close(fd[1]);          //关闭写段
    re = read(fd[0],buf,sizeof(buf)); //读取数据
    write(STDOUT_FILENO,buf,re);      //写到屏幕上
    close(fd[0]);        //关闭读段
  }
 
  return 0;
 
}

3.管道的读写行为:

读管道:

       1. 管道有数据,read返回实际读到的字节数。

       2. 管道无数据:    

                       1)无写端,read返回0 (类似读到文件尾)

                       2)有写端,read阻塞等待。

写管道:

       1. 无读端, 异常终止。 (SIGPIPE导致的)

       2. 有读端:    

                       1) 管道已满, 阻塞等待(少见)

                       2) 管道未满, 返回写出的字节个数。

1)读管道,管道无数据(无写端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
 
void sys_err(const char *str)
{
  perror(str);
  exit(1);
}
 
 
int main(int argc, char *argv[])
{
  int ret,re;
  int fd[2];
  pid_t pid;
  char *str = "hello pipe\n";
  char buf[1024];
 
  ret = pipe(fd);    //父进程先创建一个管道,持有管道的读端和写端
  if(ret == -1)
    sys_err("pipe error");
  
  pid = fork();        //子进程同样持有管道的读和写端
 
  if(pid > 0){            //父进程
    close(fd[0]);       //关闭读段
  //  write(fd[1],str,strlen(str));//写入数据
    close(fd[1]);            //关闭写段
  }else if(pid == 0){        //子进程
    close(fd[1]);          //关闭写段
    re = read(fd[0],buf,sizeof(buf)); //读取数据
    
    printf("child read ret =%d\n",ret);
    
    write(STDOUT_FILENO,buf,re);      //写到屏幕上
    close(fd[0]);        //关闭读段
  }
 
  return 0;
 
}

read返回0

4.父子间进程通信 :

       使用管道实现父子进程间通信,完成:ls | wc -l。假定父进程实现ls,子进程实现wc

ls | wc -l命令:

实现流程:

(1)父进程创建管道 pipe()

(2)父进程创建子进程 fork()

(3)设置父进程执行ls命令,子进程执行wc命令 execlp()

(4)设置父子进程通过管道的单项流动(设置指向标准输出的指向管道)  dup2()

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
 
void sys_err(const char *str)
{
  perror(str);
  exit(1);
}
 
int main(int argc,char *argv[])
{
  /***************
    dup2();
    fork();
    pipe();
    execlp();
  ****************/
  int fd[2]; 
  int ret;
  pid_t pid;
 
    //父进程创建管道
  ret = pipe(fd);     //父进程先创建一个管道,持有管道的读端和写端
  if(ret == -1){
    sys_err("pipe error");
  }
 
    //父进程创建子进程 
  pid = fork();        //子进程同样持有管道的读和写端
 
  if(pid == -1){
    sys_err("fork error");
  }else if(pid > 0){                     //父进程 读,关闭写端
    close(fd[1]);                      //关闭写,设置单项流动
    dup2(fd[0],STDIN_FILENO);          //设置读管道信息,重定向stdin 到管道读端
    execlp("wc","wc","-l",NULL);       //设置父进程wc命令
    sys_err("execlp wc error");
  }else if(pid == 0){
    
        close(fd[0]);                      //关闭读,设置单项流动
    dup2(fd[1],STDOUT_FILENO);         //设置写操作指向管道,重定向stdout 到管道写端
    execlp("ls","ls",NULL);            //设置子进程执行ls命令 
    sys_err("execlp ls error");
  }
 
  return 0;
}
 

5.兄弟间进程通信:

使用管道实现兄弟进程间通信,完成:ls | wc -l。假定父进程实现ls,子进程实现wc

实现流程:

(1)父进程创建管道 pipe()

(2)父进程创建俩个(兄弟)子进程 fork()

(3)设置兄进程执行ls命令,第进程执行wc命令 execlp()

(4)设置兄弟进程通过管道的单项流动(设置指向标准输出的指向管道)  dup2()

(5)回收父进程残余文件  wait()

刚创建出的兄弟进程:

设置兄弟进程通过管道的单项流动后

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
 
void sys_err(const char *str)
{
  perror(str);
  exit(1);
}
 
int main(int argc,char *argv[])
{
  /***************
    dup2();
    fork();
    pipe();
    execlp();
        wait();
  ****************/
  int fd[2]; 
  int ret;
  int i;
  pid_t pid;
 
    //父进程创建管道
  ret = pipe(fd);
  if(ret == -1){
    sys_err("pipe error");
  }
 
  for(i = 0;i < 2;i++){              //表达式2 出口,仅限父进程使用
    pid = fork();
    if(pid == -1){
      sys_err("fork error");
    }
    if(pid == 0)                   //子进程出口
      break;
  }
 
 
 
  if(i == 2){                        //父进程 不参与管道使用
        //不需要父进程所以需要关闭他管道的读写端
    close(fd[0]);          
    close(fd[1]);
        //回收子进程
    wait(NULL);
    wait(NULL);
  }else if(i == 0){                  //兄进程
    close(fd[0]);
    dup2(fd[1],STDOUT_FILENO);     //重定向stdout
    execlp("ls","ls",NULL);        //兄进程执行ls命令
    sys_err("ececlp ls error");
  }else if(i == 1){              //弟进程
    close(fd[1]);
    dup2(fd[0],STDIN_FILENO);      //重定向stdin
    execlp("wc","wc","-l",NULL);   //弟进程执行wc命令
    sys_err("ececlp wc error");
  }
 
  return 0;
}

6.多个读写端操作管道

实现一个pipe有一个写端,多个读端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
 
void sys_err(const char *str)
{
  perror(str);
  exit(1);
}
 
int main(int argc,char *argv[])
{
  /***************
    dup2();
    fork();
    pipe();
    execlp();
  ****************/
  int fd[2],i,n; 
  int ret;
  char buf[1024];
  pid_t pid;
 
    //父进程创建管道
  ret = pipe(fd);
  if(ret == -1){
    sys_err("pipe error");
    exit(1);
  }
 
  for(i = 0;i < 2;i++){
    pid = fork();
    if(pid == -1){
      sys_err("fork error");
      exit(1);
    }
    if(pid == 0)
      break;
  }
 
 
  if(i == 2){            //父进程
    close(fd[1]);       //父进程关闭写端,留读端读取数据
    sleep(1);
    n = read(fd[0],buf,1024);    //从管道中读取数据
    write(STDOUT_FILENO,buf,n); 
 
    for(i == 0;i < 2;i++)        //两个儿子wait两次
      wait(NULL);
 
  }else if(i == 0){        //兄进程
    close(fd[0]);
    write(fd[1],"1.hello\n",strlen("1.hello\n"));
  }else if(i == 1){    //弟进程
    close(fd[0]);
    write(fd[1],"2.world\n",strlen("2.world\n"));
  }
 
  return 0;
}

7.管道缓冲区大小:

可以使用 ulimIt -a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。通常为:

       pipe size               ......(512 bytes,-p)  8

也可以使用fpathconf函数,借助参数―选项来查看。使用该宏应引入头文件

       long fpathconf(int fd, int name);成功:返回管道的大小―失败:-1,设置errno

.8.管道的优劣

优点:简单,相比信号,套接字实现进程间通信,简单很多。

缺点:

               1.只能单向通信,双向通信需建立两个管道。

               2.只能用父子、兄弟进程(有共同祖先)间通信。该问题后来使用fifo有名管道解决)

三、FIFO:  

fifo管道:可以用于无血缘关系的进程间通信。

   命名管道:  mkfifo

   无血缘关系进程间通信:

                       读端,open fifo O_RDONLY

                       写端,open fifo O_WRONLY

   

1.命名管道fifo的创建和原理:

使用命令:myfifo myfifo

使用myfifo创建

#include<stdio.h>
#include<sys/stat.h>
#include<errno.h>
#include<pthread.h>
#include<stdlib.h>
 
 
void sys_err(const char *str){
  perror(str);
  exit(1);
}
 
int main(int argc,char *str)
{
  int ret = mkfifo("mytestfifo",0664);
  if(ret == -1)
    sys_err("mkfifo error");
 
  return 0;
  
}

2.非血缘关系进程间通信avi:

向管道写数据fifo_w.c

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
 
void sys_err(char *str)
{
    perror(str);
    exit(-1);
}
 
int main(int argc, char *argv[])
{
    int fd, i;
    char buf[4096];
 
    if (argc < 2) {
        printf("Enter like this: ./a.out fifoname\n");
        return -1;
    }
    fd = open(argv[1], O_WRONLY);       //打开管道文件
    if (fd < 0) 
        sys_err("open");
 
    i = 0;
    while (1) {
        sprintf(buf, "hello itcast %d\n", i++);
 
        write(fd, buf, strlen(buf));    // 向管道写数据
        sleep(1);
    }
    close(fd);
 
    return 0;
}

向管道读数据fifo_r.c

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
 
void sys_err(char *str)
{
    perror(str);
    exit(1);
}
 
int main(int argc, char *argv[])
{
    int fd, len;
    char buf[4096];
 
    if (argc < 2) {
        printf("./a.out fifoname\n");
        return -1;
    }
 
 
    fd = open(argv[1], O_RDONLY);   // 打开管道文件
    if (fd < 0) 
        sys_err("open");
    while (1) {
        len = read(fd, buf, sizeof(buf));   // 从管道的读端获取数据
        write(STDOUT_FILENO, buf, len);
        sleep(3);           //多個读端时应增加睡眠秒数,放大效果.
    }
    close(fd);
 
    return 0;
}

一方写,一方读:

一个写,多个读:管道特性,不能反复读取


目录
相关文章
|
3天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
18 3
|
3天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
16 2
|
4天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
40 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 的步骤,以及其在语音助手、语音识别软件等领域的应用场景。
33 5
|
7天前
|
缓存 运维 监控
【运维必备知识】Linux系统平均负载与top、uptime命令详解
系统平均负载是衡量Linux服务器性能的关键指标之一。通过使用 `top`和 `uptime`命令,可以实时监控系统的负载情况,帮助运维人员及时发现并解决潜在问题。理解这些工具的输出和意义是确保系统稳定运行的基础。希望本文对Linux系统平均负载及相关命令的详细解析能帮助您更好地进行系统运维和性能优化。
25 3
|
7天前
|
监控 网络协议 算法
Linux内核优化:提升系统性能与稳定性的策略####
本文深入探讨了Linux操作系统内核的优化策略,旨在通过一系列技术手段和最佳实践,显著提升系统的性能、响应速度及稳定性。文章首先概述了Linux内核的核心组件及其在系统中的作用,随后详细阐述了内存管理、进程调度、文件系统优化、网络栈调整及并发控制等关键领域的优化方法。通过实际案例分析,展示了这些优化措施如何有效减少延迟、提高吞吐量,并增强系统的整体健壮性。最终,文章强调了持续监控、定期更新及合理配置对于维持Linux系统长期高效运行的重要性。 ####
|
12天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
46 4