Linux 进程通信

文章目录

  • 匿名管道
    • 匿名管道使用
    • 匿名管道原理
    • 匿名管道读写
  • 命名管道
    • 命名管道使用
    • 命名管道特性
  • 共享内存
    • 共享内存原理
    • 共享内存使用
  • 补充说明

补充说明部分为相关函数和不太重要的概念介绍

匿名管道

匿名管道使用

使用方法一:
使用函数介绍:

#include <unistd.h>
功能:创建一无名管道
原型:
int pipe(int fd[2]);
参数:
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

简单的父子进程通过管道通信范例:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

 int main() 
 {
 	int pipefd[2]; 
 	if (pipe(pipefd) == -1)
 	{
 	 perror("pipe error");
 	 exit(-1);
 	}
 	pid_t pid;
 	pid = fork();
 	
 	if (pid == -1)
 	{
 		perror("fork error");
 		exit(-2);
 	}
 	if (pid == 0) 
 	{
 		close(pipefd[0]);
 		write(pipefd[1], "hello", 5);
 		close(pipefd[1]);
 		exit(1); //子进程成功运行
 	}
 	close(pipefd[1]);
 	char buf[10] = {0};
 	//read(pipefd[0], buf, 10);
	while(read(pipefd[0], buf, 10)) 
	{
		// 这样写的原因,见匿名管道的读写部分。
	}

 	printf("buf=%s\n", buf);
 
 	return 0;
}

使用方法二:
使用管道操作符 |
Linux命令行中的管道操作符本质上就是创建了一个匿名管道,将前一个进程的结果发送给下一个进程。

匿名管道原理

上述代码中,利用管道进行进程间通信,虽然是利用文件描述符的形式进行读写,但实际上并没有创建实际的文件,并没有实际消耗磁盘空间。 实际上匿名管道是一个内核缓冲区,存储在内存之上。管道自动销毁的机制,是读写两端,也就是父子进程都关闭了文件描述符,操作系统为了避免资源浪费,自动的销毁了管道。

指令管道:

在这里插入图片描述
代码管道:
在这里插入图片描述
父子进程通关管道通信原理图

在这里插入图片描述
操作系统视角下的匿名管道父子进程通讯:
在这里插入图片描述

匿名管道读写

读写规则:

  1. 文件描述符设置为阻塞且管道为空时: read阻塞,进程阻塞,直到管道内有数据被输入。
  2. 文件描述符设置为非阻塞且管道为空时:read函数执行失败返回值为-1。
  3. 对端管道文件描述符关闭,read函数执行返回值为0。 如果管道内容还有内容,会将剩余的内容读完,并在下一次调用返回为0,因为本质上是read读到第一个文件结尾的标志位从而判断对端退出。
  4. 文件描述符设置为阻塞且管道为满时 :write阻塞,进程组设,直到管道内内容被取走。
  5. 文件描述符设置为非阻塞为满时:write函数执行失败返回值为-1.
  6. 对端管道文件描述符关闭,write产生SIGPIPE信号,可能导致write进程退出。
  7. 如果write写入管道的数据大于管道存储空间大小,则要写入的数据会分次写入管道,无法保证原子性。 如果write写入管道的数据小于管道存储空间大小,则要写入的数据会一次性写入管道,保证原子性。
  8. 进程退出,则管道释放。
  9. 匿名管道的通信被局限于有情缘关系的进程直接。
  10. 对于同一管道不能同时读写。

命名管道

命名管道使用

利用mkfifo函数(见补充说明部分)创建命名管道范例:

读取端:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    if(mkfifo("./testFileName", O_RDONLY) < 0 ) // 成功时返回0,失败时返回-1
    {
        perror("mkfifo error");
        exit(-1);
    }

    int input = open("./testFileName", O_RDONLY) ; 
    if(0 > input)
    {
        perror("open error");
        exit(-1) ;
    }

    char buf[1024];

    while(true)
    {
        buf[0] = 0;
        printf("Please wait...\n");
        ssize_t s = read(input, buf, sizeof(buf)-1);
        if(s > 0 )
        {
            buf[s-1] = 0;
            printf("client say# %s\n", buf);
        }
        else if(s == 0)
        {
            printf("client quit, exit now!\n");
            exit(1);
        }
        else
        {
            perror("read");
            exit(-1);
        }
    }

    close(input) ; 
    return 0  ;
}

写入端:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    int output = open("./testFileName", O_WRONLY);
    char buf[1024] ;

    while (true)
    {
        buf[0] = 0 ; 
        ssize_t s = read(0, buf, sizeof(buf)-1);
        if(s > 0)
        {
            if(0 > write(output, buf, sizeof buf - 1))
            {
                perror("write error\n");
                exit(-1);
            }
        }
        else
        {
            perror("read error");
            exit(-1);
        }
    }
    
    close(output);
    return 0 ; 
}

命名管道特性

O_NONBLOCK是文件描述符的属性
对于读操作:
当文件描述符属性设置为O_NONBLOCK时,读取操作立刻返回,无论是否有其他进程以写方式打开进程,如果管道文件中没有数据可读,那么读取操作将返回错误码,表示没有数据可读。 如果设置为非O_NONBLOCK时,阻塞到直到有相应的进程为写而打开该管道文件。
对于写操作:
当文件描述符被设置为O_NONBLOCK时,写操作立刻失败,并设置错误码。当文件描述符设置为非O_NONBLOCK时,阻塞到直达有进程为读操作而打开管道文件

共享内存

共享内存原理

共享内存本质是将要通讯的两个进程之间的进程地址空间映射到同一块物理内存中,共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。 共享内存的管理也是先描述再组织的,描述的结构体为shmid_ds结构体。

struct shmid_ds 结构体定义

struct shmid_ds 
{
    struct ipc_perm shm_perm;  // 共享内存的权限信息
    size_t shm_segsz;          // 共享内存的大小(字节)
    time_t shm_atime;          // 上次访问时间
    time_t shm_dtime;          // 上次分离时间
    time_t shm_ctime;          // 上次变更时间
    pid_t shm_cpid;            // 创建共享内存的进程ID
    pid_t shm_lpid;            // 最后一次操作共享内存的进程ID
    unsigned short shm_nattch; // 当前附加到共享内存的进程数
}

共享内存原理图
先上原理图

此外使用 ipcs -m指令可查看操作系统上所有的共享内端信息。
在这里插入图片描述

利用指令ipcrm -m + shmid 即可删除指定shmid的共享内存。

共享内存使用

利用共享内存实现server端和client端通信代码例子

代码逻辑:

./server —> ftok(依据路径和自定义ID生成key,key作为生成共享内存的参数) —>
shmet(生成共享内存) —> shmat(将共享内存与该进程绑定,得到用于使用共享内存的指针) —> 使用指针进行读取共享内存中的内容 ——> shmdt(解除共享内存和本进程的绑定) —>
shmctl(销毁共享内存) —> 结束

server先启动 —> client启动 —> shmget(只用IPC_CREAT选项,如果用了IPC_EXCL就会报错) —> 得到共享内存shmid —> shmat(将共享内存与该进程绑定,得到用于使用共享内存的指针) ——> 使用指针进行向共享内存中写入内容 shmdt(解除共享内存和本进程的绑定) —> 结束

#include "comm.hpp"

static int commShm(int size , int falgs)
{
    // ftok为生成进程通信键值的函数
    // 参数相同会生成一样的key  
  // 导致的效果就是两个进程调用该函数得到是同一块共享内存!
    // PROJ_ID/PATHNAME为宏定义
    key_t key = ftok(PATHNAME, PROJ_ID) ; 
    if(0 > key)
    {
        perror("ftok error");
        return -1 ;
    }

    int shmid ;
    //参数: key  共享内存大小  权限 


	//flage: IPC_CREAT(创建共享内存段)和 IPC_EXCL
	//(与 IPC_CREAT 一起使用,确保创建一个新的共享内存段)。
	//0666为共享内存权限
    if(0 > (shmid = shmget(key, size, falgs)) )
    {
        perror("shmget error");
        return -1 ; 
    }

    return shmid ;
}

int destroyShm(int shmid)
{
    if(shmctl(shmid, IPC_RMID, NULL) < 0)
    {
        perror("shmctl error\n"); 
        return -1 ;
    }

    printf("\n ipc destory success \n") ;
    return 0  ;
}


int createShm(int size)
{
    //IPC_CREAT表示如果共享内存不存在,则创建一个新的共享内存;IPC_EXCL表示如果共享内存已经存在,则创建失败;
    return commShm(size, IPC_CREAT | IPC_EXCL | 0666) ; 
}

int getShm(int size)
{
    return commShm(size, IPC_CREAT) ; 
}

client.cpp

#include "comm.hpp"

int main()
{
    int shmid  = getShm(1024);
    sleep(1); 
    char* addr = (char*)shmat(shmid, nullptr, 0);
    sleep(1);
    int i = 0 ; 
    while (i < 26)
    {
        addr[i] = 'A' + i ;  // 写入共享内存
        i++ ; 
        addr[i] = 0 ; 
        sleep(1);
    }

    shmdt(addr);
    sleep(2);

    return 0 ; 
}

server.cpp:

#include "comm.hpp"

int main()
{

    int shmid = createShm(1024) ; 
    sleep(1) ; 
    char* addr = (char*)shmat(shmid, nullptr, 0); 
    sleep(2) ;
    int i = 0 ; 
    while (i < 26)
    {
        sleep(1);
        printf("client %s\n", addr);
        i++ ;
    }

    shmdt(addr); 
    sleep(1);
    destroyShm(shmid) ; 
    return 0 ; 
    
}

特别提醒:

  1. 如果写关于使用共享内存进行进程通信代码,测试或使用时,如果没有用代码将创建的共享内存销毁,或是代码还没执行到共享内存销毁的代码就因为其他原因终止,下次启动该程序会失败,因为共享内存没有销毁,且用于生成的共享内存的key不变,执行到shmget()函数的时候就会出错。 解决方法为手动删除该共享内存。
  2. 因为是对内存进行访问,所以写端在写入时,对端关闭也不会造成影响。上述代码读端读取后,如果没有销毁共享内内存,内存中内容任然保留。

补充说明

mkfifo函数

mkfifo函数的原型如下:

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

功能:创建命名管道。

参数说明:
pathname:要创建的命名管道的路径名。
mode:创建的命名管道的权限,通常使用八进制表示。
mkfifo函数成功创建命名管道时返回0,失败时返回-1,并设置相应的错误码。创建的命名管道可以通过文件I/O函数进行读写操作。

shmdt
功能:将共享内存段与当前进程脱离
原型

 int shmdt(const void *shmaddr);

参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmat
功能:将共享内存段连接到进程地址空间
原型

 void *shmat(int shmid, const void *shmaddr, int shmflg);

参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr
(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

ftok
ftok函数是一个用于生成System V IPC(Inter-Process Communication,进程间通信)中的键值的函数。它的原型如下:

key_t ftok(const char *pathname, int proj_id);

该函数接受一个路径名和一个项目标识符作为参数,并返回一个唯一的键值。这个键值通常用于创建或访问共享内存、消息队列和信号量等System V IPC资源。

ftok函数的工作原理如下:

ftok函数通过将给定的路径名和项目标识符转换为一个32位的键值。
路径名必须指向一个现有的文件。ftok函数使用该文件的inode号和设备号来生成键值。
项目标识符是一个整数,用于区分不同的IPC资源。通常情况下,同一个路径名下的不同项目标识符会生成不同的键值。
需要注意的是,ftok函数在生成键值时可能会受到系统的限制。具体来说,它使用的是32位的键值,因此可能存在键值冲突的情况。如果生成的键值已经被使用,则可能导致创建或访问IPC资源失败。

另外,由于ftok函数使用了文件的inode号和设备号来生成键值,因此如果使用不同的文件系统或文件系统重新挂载,可能会导致生成的键值不同。

shmctl
功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

在这里插入图片描述

shmid_ds

shmid_ds是System V共享内存的数据结构,用于描述和管理共享内存的属性和状态。它定义在<sys/shm.h>头文件中。

shmid_ds结构体的定义如下:

struct shmid_ds 
{
    struct ipc_perm shm_perm;  // 共享内存的权限信息
    size_t shm_segsz;          // 共享内存的大小(字节)
    time_t shm_atime;          // 上次访问时间
    time_t shm_dtime;          // 上次分离时间
    time_t shm_ctime;          // 上次变更时间
    pid_t shm_cpid;            // 创建共享内存的进程ID
    pid_t shm_lpid;            // 最后一次操作共享内存的进程ID
    unsigned short shm_nattch; // 当前附加到共享内存的进程数
    ...
};

shmid_ds结构体用于在共享内存的创建、访问和管理过程中记录共享内存的相关信息。通过操作shmid_ds结构体的成员,可以获取和修改共享内存的属性,例如权限、大小、创建者等信息。

需要注意的是,shmid_ds结构体中的其他成员没有在上述定义中列出,具体实现可能会有所差异。在使用shmid_ds结构体时,可以参考相关的系统文档和头文件中的定义。

POSIX IPC
POSIX IPC(Portable Operating System Interface Interprocess Communication)是一组跨平台的进程间通信机制,定义在POSIX标准中。它提供了一种可移植的方式来实现进程间的通信和同步。

POSIX IPC包括以下几种机制:

信号量(Semaphore):用于进程间的同步和互斥。通过使用信号量,进程可以等待某个事件的发生或者通知其他进程某个事件已经发生。

消息队列(Message Queue):用于进程间的异步通信。进程可以将消息发送到消息队列中,其他进程可以从队列中接收消息。

共享内存(Shared
Memory):允许多个进程共享同一块内存区域。这种机制可以提高进程间的数据传输效率,但需要进行适当的同步和互斥操作来保证数据的一致性。

互斥锁(Mutex):用于进程间的互斥访问共享资源。通过互斥锁,只有一个进程可以访问共享资源,其他进程需要等待锁的释放才能访问。

POSIX IPC提供了更高级别、更易用的进程间通信机制,相对于System V
IPC而言,它更加灵活、可移植,并且在现代操作系统中得到广泛支持。

System V IPC

System V IPC(进程间通信)是指System V Unix操作系统提供的一组机制,用于进程间通信。它包括三种主要类型的IPC:共享内存、消息队列和信号量。

共享内存允许多个进程访问同一内存段,实现进程间高效的数据共享。消息队列提供了进程发送和接收消息的方式,实现异步通信。信号量用于进程同步和协调,确保多个进程以受控的方式访问共享资源。

System V IPC提供了一种低级别的进程间通信接口,在类Unix操作系统中广泛使用。然而,它已经被较新的IPC机制(如POSIX IPC)所取代,这些机制提供了更好的功能和易用性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DevEco Studio自定义代码颜色

这里以ArkTS代码颜色举例 进入设置&#xff08;快捷键CtrlAltS&#xff09; 选择Editor > Color Scheme > JavaScript 由于之前用习惯VsCode了&#xff0c;这里以注释颜色举例&#xff0c;变为绿色。 上面说的不是以ArkTS代码颜色举例吗&#xff1f;为什么选择JavaScr…

极坐标下的牛拉法潮流计算14节点MATLAB程序

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 潮流计算&#xff1a; 潮流计算是根据给定的电网结构、参数和发电机、负荷等元件的运行条件&#xff0c;确定电力系统各部分稳态运行状态参数的计算。通常给定的运行条件有系统中各电源和负荷点的功率、枢纽…

产品经理之如何编写需求PRD文档(医疗HIS项目详细案例模板)

目录 前言 一.需求文档的含义 二.需求文档的作用及目的 三.编写前的准备 四.需求大纲 五.案例模板 前言 继上两篇的可行性分析文档和竞品分析报告&#xff0c;本篇将继续介绍如何编写PRD文档&#xff0c;并且会附上以医疗项目为例的模板 一.需求文档的含义 需求文…

设计模式——享元模式(结构型)

引言 享元模式是一种结构型设计模式&#xff0c; 它摒弃了在每个对象中保存所有数据的方式&#xff0c; 通过共享多个对象所共有的相同状态&#xff0c; 让你能在有限的内存容量中载入更多对象。 问题 假如你希望在长时间工作后放松一下&#xff0c; 所以开发了一款简单的游戏…

论文润色机构哪个好 快码论文

大家好&#xff0c;今天来聊聊论文润色机构哪个好&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff0c;可以借助此类工具&#xff1a; 标题&#xff1a;论文润色机构哪个好――专业、高效、可靠的学术支持 一…

条件概率密度

设二维随机变量的概率密度为&#xff0c;关于的边缘概率密度为。若对于固定的&#xff0c;有&#xff0c;则称为在条件下的的条件概率密度&#xff0c;记为&#xff1a;

(七)STM32 NVIC 中断、优先级管理及 AFIO 时钟的开启

目录 1. 中断相关知识简介 1.1 什么是中断 1.2 什么是内中断、外中断 1.3 什么是可屏蔽中断、不可屏蔽中断 2. CM3 内核中断介绍 2.1 F103系统异常清单 2.2 F103 外部中断清单 3. NVIC 简介 3.1 NVIC 寄存器简介 3.2 NVIC 相关寄存器的介绍 4. 中断优先级 4.1 优先…

黑马点评01

基础篇-07.Redis命令-数据结构介绍_哔哩哔哩_bilibili 1.NoSQL 非结构化数据库&#xff0c;和sql的区别在于没有数据库表之间的关系&#xff08;主键外键&#xff09;&#xff0c;一般的存储形式是JSON。每个json里面都存储了该记录的所有数据&#xff0c;所以有一定重复性。 …

spring6 为集合类型属性赋值 --引用集合类型的bean

1.准备工作&#xff1a; Student.java package bean.dimap;import java.util.List; import java.util.Map;public class Student {private String sid;private String sname; //private Map<String,Teacher> map;//private List<Lesson> lessonList;public List&…

空洞文件读取空洞部分的返回值

在空洞文件中&#xff0c;未显式写入的部分被称为"空洞"。当读取空洞部分时&#xff0c;系统会返回字节值为0的数据。 这意味着&#xff0c;当你在空洞文件中读取一个偏移量处的数据&#xff0c;而该偏移量位于空洞部分时&#xff0c;读取操作将返回一个全是0的字节…

Spring Boot3.1.6配置对应的Swagger

1. pom.xml导入Swagger依赖 <!--swagger3--> <dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.0.2</version> </dependency> 2.创建SwaggerCo…

buuctf-Misc 题目解答分解85-87

85.[UTCTF2020]file header 下载完就是一个图片 &#xff0c;但是显示图片错误&#xff0c;提示文件头 没有 用010editor 打开 找一个png 文件&#xff0c;看一下它的头部 只需要修改前四个字节为 89 50 4E 47 即可 就能拿到flag utflag{3lit3_h4ck3r} 86.[WUSTCTF2020]gir…

v-md-editor高级使用之自定义目录

​ 官方给出的目录设置参见&#xff1a;https://code-farmer-i.github.io/vue-markdown-editor/zh/senior/toc.html#%E7%9B%B8%E5%85%B3%E9%85%8D%E7%BD%AE ​ 在做实际使用中往往可能需要将目录结构独立出来&#xff0c;经过近一天的研究终于明白其实现逻辑&#xff0c;并将目…

Web开发:ibatis的使用笔记

一、简介 ibatis是一个基于SQL映射支持Java和.NET的持久层框架&#xff1a; 1.如下所示id是对应程序的statement&#xff0c;resultClass需要填写SQL查询到的字段对应的类的命名空间类名&#xff08;DAO.QueryForList<实体类>&#xff09;&#xff0c;以此完成持久层和…

数据可视化作用探析

数据可视化是一种将数据转化为图表、图形或其他视觉形式的过程&#xff0c;旨在更直观、更易于理解地展示数据信息。它不仅仅是对数据的简单呈现&#xff0c;更是一种利用视觉化手段帮助人们理解数据、发现模式、分析趋势和做出决策的强大工具。今天&#xff0c;我就从可视化从…

电子元器件-电阻

电阻 采样限流定时保护上拉 链接: 另类方式讲电阻&#xff01; 采样 应用场景&#xff0c;如我们在调节汽车座椅的过程中&#xff0c;如果座椅的行程达到尽头&#xff0c;此时控制座椅运动的电机就会停止&#xff0c;因而导致电机的电流非常大。如果正常运转的电流为1A&#…

【AI】模型结构可视化工具Netron应用

随着AI模型的发展&#xff0c;模型的结构也变得越来越复杂&#xff0c;理解起来越来越困难&#xff0c;这时候能够画一张结构图就好了&#xff0c;就像我们在开发过程中用到的UML类图&#xff0c;能够直观看出不同层之间的关系&#xff0c;于是Netron就来了。 Netron支持神经网…

在qemu平台使用gdb调试程序

1、使用gdb在qemu上调试程序 1.1、第一步&#xff1a;在qemu上运行程序并开启gdb server qemu-system-riscv64 -nographic -machine virt -m 128M -smp 1 -kernel …/bin/test.elf -s -S 1.2、第二步&#xff1a;使用gdb客户端连接gdb server -x&#xff1a;指定gdb的配置文件…

EnlightenGAN论文阅读笔记

EnlightenGAN论文阅读笔记 论文是2019年IEEE的EnlightenGAN: Deep Light Enhancement without Paired Supervision.这篇论文是低光增强领域无监督学习的开山之作。 论文链接如下&#xff1a;arxiv.org/pdf/1906.06972.pdf 文章目录 EnlightenGAN论文阅读笔记出发点**出发点1**&…

竞赛保研 python图像检索系统设计与实现

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; python图像检索系统设计与实现 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&#xff0c…