进程的那些事--进程间的通信(重点说明管道和共享内存)

news2024/11/25 16:23:32

目录

前言

一、初始进程间通信

二、管道

1.匿名管道

2.命名管道

三、共享内存

四、消息队列(了解)

五、信号量(了解)


前言

提示:这里可以添加本文要记录的大概内容:

        进程是一个能够独立运行,独立分配资源,独立调度的基本单位,每个进程都有自己独立的虚拟地址空间。当两个进程要想通信,必须使用特定的通信机制来实现进程间的通信。


提示:以下是本篇文章正文内容,下面案例可供参考

一、初始进程间通信

进程间的通信方式:

        进程间的通信方式有多种,面对不同的场景延伸出的情况也有多种。下面就对这四种进程间的通信方式进行说明。管道、共享内存、消息队列、信号量

二、管道

管道:实现进程间通信的一种方式。

        特性:是一种半双工通信。        本质:内核中的一块缓冲区。

        生命周期:随进程存在而存在,只有当所有使用管道的进程全部结束,管道才结束。

        分类:匿名管道、命名管道。

         在Linux下常用 | 来标识匿名管道

1.匿名管道

        匿名管道只能用于具有亲缘关系的进程之间进行通信。如:父子进程。同时匿名管道是没有名字的,也就是无法通过文件来访问。那么匿名管道是如何让进程进行访问的呢?这时候就要引入一个概念,文件描述符(操作句柄)

文件描述符(操作句柄)

        文件描述符主要用于Linux系统,操作句柄主要用于Windows系统。

        我们首先要知道Linux下一切皆文件,所有的东西,包括显示都是文件。文件描述符是用来标识文件的一系列整数。是进程和内核直接进行交流的桥梁通过文件标识符可以访问系统调用接口,来达到进程调用和访问Linux中的文件标识了当前要操作的文件

        如:一个进程运行会默认打开三个文件,标准输入、标准输出、标准错误,分别是0、1、2。在Linux下程序运行会加载到内存中,变成一个结构体PCB,这个结构体中有一个FILE类型的指针其中包含FILE* fd_arr[],指向文件结构体中,这个结构体像一个数组指针,包含指向标准输入、标准输出、标准错误的文件。我们通过下标来进行访问操作。如标准输入的下标为0,通过0作数组下标来访问指向标准输入文件的指针,达到调用标准输入的目的。        

 

匿名管道的实现原理、

        1、进程A创建一个匿名管道(获取到管道的操作句柄--文件描述符)

        2、进程A创建一个子进程

                 被创建出来的子进程复制父进程的时候也复制了文件描述符,也就获得父进程能够进入管道的文件描述符,那么子进程和父进程拥有的就是指向同一个内核中的管道的文件描述符,这时候父子进程就可以通过这个管道实现数据通信

接口:

        int pipe(int pipefd[2]);                //        功能:创建一个管道

    参数:

       pipefd[2]:一个整形数组,用于存储管道的文件描述符。pipefd[0]用于从管道读数据。

                        pipefd[1]描述符用以向管道写入数据。

     返回值:成功返回0,失败返回-1。

      ssize_t read(int fd, void *buf, size_t len);        // 功能读取数据

      参数:

       ssize_t 是返回类型,表示成功读取的字节数,或者在出错时返回 -1;

  int fd是文件描述符,指向要读取的文件或管道;

       void *buf 是空间首地址,指向缓冲区,会读取这个缓冲区的数据

       siez_t len 要读取数据的长度

       返回值:失败返回-1

 

        ssize_t write(int fd, const void *buf, size_t len);        // 功能写入数据

        参数:

           ssize_t 是返回类型,表示成功读取的字节数,或者在出错时返回 -1;

    int fd是文件描述符,指向要读取的文件或管道;

   const void *buf        指向要写入数据的缓冲区

           size_t len:写入文件的数据长度

           返回值:成功返回写入的字节数,失败返回-1 

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

//父进程向管道写数据,子进程读取父进程的数据
int main()
{
        //创建两个描述符
        int pipefd[2];
        int pip = pipe(pipefd);//创建管道
        if (pip < 0) {            //判断管道是否创建成功
                perror("pip error");
                return -1;
                }

        pid_t ppid = fork();      //创建子进程
        if (ppid < 0){            //判断进程是否创建成功
                perror("ppid error");
                return -1;
        }else if (ppid == 0) {    //子进程
                char verser[1024] = {0};
                close(pipefd[1]); //关闭写端
                int pip = read(pipefd[0], verser, 1023);//子进程从管道中读取数据
                if(pip < 0) {     //是否通信成功
                        perror("read error");
                        return -1;
                        }
                printf("verser[%s]\n", verser);
                close(pipefd[0]); //读取完后关闭读端
        }else {           //父进程
                char *verser = "大鹏一日同风起,扶摇直上九万里";
                write(pipefd[1], verser, strlen(verser));       //父进程向管道写入数据
                close(pipefd[1]); //写入完毕后关闭写端
                wait(NULL);       //等待子进程退出,避免僵尸进程
        }
        return 0;
}

 

2.命名管道

        命名管道是具有标识符的管道,可用于同一主机上任意进程间通信。命名管道以文件的形式存在。文件只是一个名字,其本质的内核中的缓冲区。多个进程通过打开同一个管道文件来访问同一块内核中的缓冲区

命名管道创建

        mkfifo text.fifo 这里是用mkfifo创建了一个管道文件 text.fifo。它只是用来命名管道的管道文件,只是一个满足并不存储数据。多个进程通过打开同一个管道文件来进行通信。

命名管道的特性

        1、一个命名管道如果以只读的方式打开会阻塞,直到这个管道文件被写的方式打开

        2、一个命名管道如果以只写的方式打开会阻塞,直到这个管道文件被读的方式打开

        3、一个命名管道要以读写的方式打开才行。

接口:

        int mkfifo(const char* filename, mode_t mode);        //功能:创建管道文件

参数:

        const char* filename    //指向null结尾的字符串的指针,管道文件的名称,文件的路径名

        mode_t mode               //是一个整数,指定了管道文件的访问权限,限制了一个进程

                                             //能够对管道进行的操作

        返回值:成功返回0;失败返回-1;

//       

        int open(const char *pathname, int flags);          //功能:打开一个文件
        int open(const char *pathname, int flags, mode_t mode);     //功能:创建一个文件

参数:

        const char *pathname       //要打开或创建的文件的路径

        int flags                             //打开指定文件后能做的权限

        mode_t mode                    //创建新文件时使用

        返回值:成功返回一个非负的文件描述符,失败返回-1;

        

        ssize_t write(int fd, const void *buf, size_t count)        //功能向文件写数据

参数:

        int fd                                        //要写入的文件描述符

        const void *buf                        //指向要写入数据的缓冲区的指针

        size_t count                            //要写入数据的字节数

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
//      读去管道数据的进程
int main()
{
        int ret = mkfifo("./text.fifo", 0664);  //访问text.fifo的管道文件,并且设置权限为-rw-rw-r--
        if(ret < 0){
                        perror("mkfifo, error");
                        return -1;
                        }
        while(1){                               //读取数据
        char buf[1024] = {0};
        int ret = read(fd, buf, 1023);
        if(ret < 0){
                perror("read error");
                return -1;
                }
        else if(ret == 0){
                printf("所以写端被关闭");
                return -1;
                }
        printf("%s\n", buf);
        }
        close(fd);                              //关闭打开的文件                        
        return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
//       向管道中写入数据的进程
int main()
{
        int ret = mkfifo("./text.fifo", 0664);  //访问text.fifo的管道文件,并且设置权限为-rw-rw-r--
        if(ret < 0){
                if(errno != EEXIST){            //EEXIST表示文件已经存在,errno是记录系统错误的int型,这里管道文件已经存
在所以当文件不存在的时候在退出
                        perror("mkfifo, error");
                        return -1;
                        }
                }
        printf("fifo create success!!\n");      //成功访问到管道文件
        int fd = open("./text.fifo", O_WRONLY);
        if( fd < 0){
                perror("open error");
                return -1;
                }
        printf("open fifo success!!\n");
        int left_time = 100;
        while(1){
                char buf[1024] = {0};
    sprintf(buf, "还有%d秒后关机", left_time--);
                write(fd, buf, strlen(buf));    //向文件写入数据
                sleep(1);                       //休眠眠一秒
                }
        close(fd);
        return 0;
}

 

        在命名管道中,只有同时具备读写的时候才能进行通信,若两个进程一个读,一个写,任意一个进程运行都不能通信,任何一个进程关闭也会退出管道。 

三、共享内存

共享内存是最快的进程间通信方式

        作用:实现进程间的数据共享

        原理:内核开辟一块物理内存成为共享空间,其他想要实现进程间通信的进程,将这块

                   内存映射到自己的虚拟地址空间中,通过虚拟地址进行访问。

为什么共享内存是最快的:

        共享内存通过虚拟地址空间访问数据,不需要像管道那样,还有将数据拷贝到自己的进

        程空间中,同其他通信方式减少了拷贝这一步,故是最快的。

特征:

        1、生命周期随内核

        2、最快的进程间通信方式

注意事项

        多个进程对共享内存的访问操作是存在安全隐患的,需要借助其他用于实现,同步互斥的技术来搭配使用

Linux下实现共享的流程和接口

        1、创建一块共享内存,并获取操作句柄

        int shmget(key_t key, size_t size, int flag)

         key_t key:共享内存的标识符,通过者访问相同的共享内存段,通常通过ftok函数生成

        size_t size:共享内存的大小,以字节为单位

        int flag:控制 shmget 函数的行为,如0666权限和一些特殊的标志 

                       IPC_CREAT:指定的共享内存不存在,则创建一个新的共享内存

                       IPC_EXCL:与 IPC_CREAT一起使用,如果共享内存段已经存则 shmget 将

                                            失败。这通常用于确保创建的共享内存段是唯一的。

        返回值:成功返回一个非负整数-共享内存的操作句柄,失败返回-1;

        key_t ftok(const char *pathname, int proj_id);
        const char *pathname:这是一个指向字符数组(C字符串)的指针,表示一个已存在

                                                的文件路径

        int proj_id:这是一个整型值,通常是一个字符

        返回值:成功返回一个唯一的键值(key_t类型),失败返回key_t-1

        

        2、将共享内存映射到自己的虚拟地址空间

        void *shmat(int shmid, void* addr, int flag);

         int shmid:shmget返回的操作句柄

         void* addr:映射首地址,通常置NULL

         int flag:标识符 其中0-默认读写方式映射,SHM_RDONLY表示以只读方式映射。

        返回值:成功返回映射的首地址,失败返回(void*)-1,并设置errno指示错误类型

        

        3、内存操作

        如 printf、strcpy 等。凡是涉及到对内存进行操作的。

        

        4、解除映射关系

        int shmdt(void* start)        //接触映射关系不代表删除共享内存

        void* start:指向之前通过 shmdt 返回的共享内存起始地址

        返回值:成功返回0,失败返回-1

        

        5、删除共享内存

        int shmctl(int shmid, int cmd, struct shmid_ds *buf);

                int shmid:shmget返回的操作句柄

                 int cmd:对共享内存进行的操作

                        IPC_STAT:获取共享内存的状态存储在buf指向的shmid_ds结构中

                        IPC_SET:设置共享内存的属性,从buf指向的shmid_ds结构中获取

                        IPC_RMID:删除共享内存,不会立即释放,而是标记为不再使用。当所有进

                                             程都不再该共享内存中的时候,系统才会回收释放

                struct shmid_ds *buf:这是一个指向shmid_ds结构的指针,用于存储或接收共享

                                                    内存的状态信息。通常也包含共享内存段的标识符、创建者

                                                    和所有者的用户ID和组ID、访问权限、最后附加和分离的时

                                                    间等信息。

                返回值:成功返回0,失败返回-1;

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>

#define IPC_KEY 0x010101        //key值的标识
int main()
{
        int shmid = shmget(IPC_KEY, 4096, IPC_CREAT|0664);      //创建一个共享内存
        if(shmid < 0){
                perror("shmget error");
                return -1;
                }
        void* start = shmat(shmid, NULL, 0);                    //建立映射关系
        if(start == (void*)-1){
                perror("shmat error");
                return -1;
                }
        while(1){                       //访问数据 
                printf("%s\n", (char*)start);
                sleep(1);
                }
        shmdt(start);                   //解除映射关系
        shmctl(shmid, IPC_RMID, NULL);  //删除内存
        return 0;
}   
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>

#define IPC_KEY 0x010101        //key值的标识
int main()
{
        int shmid = shmget(IPC_KEY, 4096, IPC_CREAT|0664);      //创建一个共享内存
        if(shmid < 0){
                perror("shmget error");
                return -1;
                }
        void* start = shmat(shmid, NULL, 0);                    //建立映射关系
        if(start == (void*)-1){
                perror("shmat error");
                return -1;
                }
        while(1){                       //访问数据
                int num = 1;
                sprintf(start, "%s+%d", "正在写入数据", num++);
                sleep(1);
                }
        shmdt(start);                   //解除映射关系
        shmctl(shmid, IPC_RMID, NULL);  //删除内存
        return 0;
}

 

 

        通过两个代码,一个写数据,一个读数据来进行演示。 

四、消息队列(了解)

作用:实现进程间的数据传输

原理:在内核中创建一个指定标识符的优先级队列,多个进程通过相同的标识符访问内核中

           的同一个队列,通过向队列添加节点来实现进程间的数据传输。 

接口:几个常用接口

         int msgget(key_t key, int msgflg);        //创建或打开消息队列

        int msgsnd(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);                                                    //将消息发送到消息队列中

        ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

                                                                        //从消息队列中接收消息

        int msgctl(int msqid, int cmd, struct msqid_ds *buf);        //控制消息队列

特性:生命周期随内核,自带同步与互斥

五、信号量(了解)

作用:用于实现进程间的同步与互斥。是一个非负整数

本质:内核中的一个计数器+pcd等待队列。信号量大于0资源可用,等于0资源被占用。

操作:

        P操作(等待):对计数器进行判断,如果计数器数组<=0则阻塞进程,否则返回计数-1;

        V操作(释放):对计数器进行+1,且唤醒一个阻塞队列中的进程;

同步:

        通过某种条件控制,让资源分配的更加合理—通过计数器对资源进行计数。进程获取资

        源前进行P操作,其他进程产生一个资源后进行V操作。

互斥:

        临界资源:多个进程都可以访问的资源

                临界区:代码中专门用来访问临界资源的这段代码叫临界区。

                原子性:不可分割特性

                原子操作:访问临界资源的过程不可被打断。

        通过同一时间的唯一访问,让进程对临界资源的获取更加合理安全。

        实现:

                将信号量初始值设置为1,表示只有一个资源

                访问临界资源前进行P操作

                访问临界资源结束后进行V操作

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

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

相关文章

什么情况下数据库和缓存不一致?

首先&#xff0c;在非并发的场景中&#xff0c;出现不一致的问题大家都能比较容易的理解&#xff0c;因为缓存的操作和数据库的操作是存在一定的时间差的。而生两个操作是没办法保证原子些的&#xff0c;也就是说&#xff0c;是有可能一个操作功&#xff0c;一个操作失败的。所…

C语言-数据结构 折半查找

在折半查找中&#xff0c;刚开始学可能会在下标处产生困惑&#xff0c;例如奇数个长度的数组怎么处理&#xff0c;偶数个长度的数组怎么处理&#xff0c;不需要修改代码吗&#xff1f;并且下标我从1开始算和0开始算影响代码吗&#xff1f;其实都可以用一样的代码&#xff0c;产…

【含文档】基于Springboot+Vue的失物招领系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

如何替换OCP节点(一):使用oat | OceanBase应用实践

前言&#xff1a; OceanBase Cloud Platform&#xff08;简称OCP&#xff09;&#xff0c;是 OceanBase数据库的专属企业级数据库管理平台。 在实际生产环境中&#xff0c;OCP的安装通常是第一步&#xff0c;先搭建OCP平台&#xff0c;进而依赖OCP来创建、管理和监控我们的生…

docker升级mysql

一、首选备份原数据库所有数据 二、在Docker中查看正在运行的MySQL容器名称&#xff0c;可以使用以下命令&#xff1a; docker ps --filter "namemysql" 三、查看当前docker中正在运行mysql的版本 docker exec -it qgz-mysql mysql -V 可以看到当前运行的版本是8.…

数据传输——差错控制

一、检错纠错 1、通信链路不是完全理想的&#xff0c;在传输的过程中可能会产生比特差错。 2、误码率&#xff1a;传输错误的比特占所传输比特总数的比率。 3、检错&#xff1a;能自动发现差错。 4、纠错&#xff1a;不仅能发现差错而且能自动纠正差错。 5、码字(codeword…

Selenium打开外部应用程序的弹窗处理

问题 selenium自动化操作页面跳转到外部应用程序进行下载等操作&#xff0c;各种窗口处理方式无法解决 原因 该窗口属于浏览器窗口&#xff0c;与访问页面无关&#xff08;已经脱离页面操作层面&#xff09; 解决 selenium启动浏览器时&#xff0c;对浏览器进行相关窗口设…

Elasticsearch的安装与配置

注意&#xff1a;elasticsearch 禁止安装在/root路径下&#xff01; 1、创建用户组 groupadd elastic 2、创建用户 useradd es -d /home/es -g elastic echo es | passwd es --stdin 3、给新创建的用户进行授权 chown -R es:elastic /home/es chmod -R 775 /home/es 4…

sklearn机器学习实战——支持向量机四种核函数分类任务全过程(附完整代码和结果图)

sklearn机器学习实战——支持向量机四种核函数分类任务全过程&#xff08;附完整代码和结果图&#xff09; 关于作者 作者&#xff1a;小白熊 作者简介&#xff1a;精通python、matlab、c#语言&#xff0c;擅长机器学习&#xff0c;深度学习&#xff0c;机器视觉&#xff0c;目…

Nginx反向代理配置与负载均衡配置

简介&#xff1a;整理自黑马程序员苍穹外卖的第11节 nginx是什么&#xff1f; nginx的好处 nginx反向代理配置方式 nginx负载均衡的配置方式 nginx负责均衡策略

等保2.0测评 — WebSphere 中间件

查看版本信息&#xff1a; 登录websphere管理平台首页就能看到版本信息 可以进入\usr\IBM\WebSphere\AppServer\bin 下执行./versionInfo.sh查看版本 一、身份鉴别 a&#xff09;应对登录的用户进行身份标识和鉴别&#xff0c;身份标识具有唯一性&#xff0c;身份鉴别信息具有…

如何使用printf实现整齐美观的输出?

在编程中&#xff0c;尤其是在涉及控制台输出的应用场景中&#xff0c;我们需要让输出的信息更加整齐美观。printf 是 C 语言中用于格式化输出的强大工具之一。通过合理的格式化控制符&#xff0c;我们可以轻松地控制输出的宽度、对齐方式、填充字符等&#xff0c;从而达到整齐…

RiproV9.0主题wordpress主题免扩展可二开PJ版/WordPress博客主题Ripro全解密无后门版本

&#x1f525;&#x1f389; 全新RiPro9.0开源版发布 —— 探索无限可能&#x1f680;&#x1f310; 今天&#xff0c;我很高兴能与大家分享一个重磅资源——RiPro9.0开源版&#xff01;这不是一个普通的版本&#xff0c;而是一个经过精心打磨、全面解密的力作。&#x1f50d;…

使用KEIL5,不复位MCU,调试到程序运行到卡住之处

文章目录 前言步骤1步骤2步骤3步骤4步骤5 前言 经常有朋友在开发中遇到这样的窘境&#xff0c;当单片机程序运行异常以后&#xff0c;由于调试信息做得并不是很全面&#xff0c;导致相应的问题场景非常难分析。当时的你肯定会叹息道:“要是我一直插着仿真器就好了&#xff0c;…

【Concept Sliders】通过拖到滑块来精确控制特定图像特征

Concept Sliders 是一种用于扩散模型&#xff08;如 Stable Diffusion&#xff09;的LoRA 适配器&#xff0c;允许用户在图像生成过程中对特定概念进行精细控制。与依赖提示词生成图像的传统方法不同&#xff0c;Concept Sliders 通过引入可调整的“滑块”&#xff0c;用户可以…

前端读取本地表格数据

vue3tsvite 无后端提供数据的情况下&#xff0c;前端读取本地表格数据&#xff0c;并将数据放入页面结构中 展示在网页中 记得先安装npm install xlsx 目录 read_xlsx.ts import * as XLSX from xlsx; //将行,列转换 function transformSheets(sheets: { [key: string]: any })…

技术架构的演进之路

技术架构的演进之路 我们以电商系统的技术架构发展为例 文章目录 1. 单体架构2. 应用数据分离架构3. 应用服务集群架构4. 读写分离、主从分离架构5. 冷热分离架构6. 垂直分库架构7. 微服务架构8. 容器编排架构 1. 单体架构 在前期用户访问量很少的时候,没有对性能、安全等提出…

自然语言处理(NLP)论文数量的十年趋势:2014-2024

引言 近年来&#xff0c;自然语言处理&#xff08;NLP&#xff09;已成为人工智能&#xff08;AI&#xff09;和数据科学领域中的关键技术之一。随着数据规模的不断扩大和计算能力的提升&#xff0c;NLP技术从学术研究走向了广泛的实际应用。通过观察过去十年&#xff08;2014…

uniapp中添加colorUI的过程

1、先将colorUI文件粘到项目中去 2、common中添加两个文件 3、App文件中引入这两个文件

毕业设计选题:基于ssm+vue+uniapp的健身管理系统小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…