linux下共享内存的3种使用方式

进程是资源封装的单位,内存就是进程所封装的资源的一种。一般情况下,进程间的内存是相互隔离的,也就是说一个进程不能访问另一个进程的内存。如果一个进程想要访问另一个进程的内存,那么必须要进过内核这个桥梁,这就是共享内存。

在linux中,共享内存有3种方式,分别是POSIX接口,mmap以及system V风格的接口。本文分别介绍这3种共享内存的使用方式。在3种方式中,POSIX接口简洁易用,是最常使用的;system V易用性不是很好,很少使用。

另外,在工作中,共享内存作为一种进程间通信的方式,我们很轻易就能想到它的优点:减少拷贝次数。但是一项技术有优点,同时也必然有局限性,共享内存的局限性就是,在使用的时候往往需要在进程间做同步,进程间同步也会带来性能上的损耗。

1POSIX

1.1/dev/shm

POSIX共享内存接口使用了/dev/shm临时文件系统。

在介绍POSIX共享内存接口之前,有必要先了解linux下的临时文件系统/dev/shm,从名字也能看出来,这个文件系统是专门用作共享内存的。从mount显示的信息中可以看出来,临时文件系统tmpfs被mount到了/dev/shm下。

这是一个临时文件系统,同时也是一个内存文件系统,也就是说在这个文件系统上创建的文件,都是保存在内存中的,而不是保存在磁盘上。可想而知,性能会比较高。可以像普通文件系统一样使用临时文件系统,打开、读写、关闭、删除、拷贝等操作和普通文件是一样的。但是要注意,临时文件系统是保存在内存中的,机器重启之后不再存在。

/dev/shm默认大小是机器物理内存的一半:

使用df -i /dev/shm可以查看默认的inode个数:

修改大小和inode个数,比如我想将大小修改为4G,inode个数修改为1000,使用如下命令进行修改,可以看到,修改是生效的。

1.2example

如下例子是linux文档中的例子,通过man shm_open可以看到这个例子。从例子的实现可以看出来,共享内存依赖项有两个:一个是/dev/shm临时文件系统,一个是mmap。mmap本身就是一种共享内存的方式。所以说POSIX共享内存和mmap并不是完全割裂的,前者依赖于后者。其实我们也可以完全不使用shm_open、shm_unlink接口,而是直接使用mmap,在/dev/shm下创建共享内存也是可以的。

pshm_ucase_bounce.c和pshm_ucase_send.c中分别创建共享内存,大小是struct shmbuf的大小。后者向内存中写hello,前者将hello改成大写的,然后后者打印数据。

pshm_ucase.h:

#include <sys/mman.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \} while (0)#define BUF_SIZE 1024   /* Maximum size for exchanged string *//* Define a structure that will be imposed on the sharedmemory object */struct shmbuf {sem_t  sem1;            /* POSIX unnamed semaphore */sem_t  sem2;            /* POSIX unnamed semaphore */size_t cnt;             /* Number of bytes used in 'buf' */char   buf[BUF_SIZE];   /* Data being transferred */
};

pshm_ucase_bounce.c:

#include <ctype.h>
#include "pshm_ucase.h"int
main(int argc, char *argv[])
{if (argc != 2) {fprintf(stderr, "Usage: %s /shm-path\n", argv[0]);exit(EXIT_FAILURE);}char *shmpath = argv[1];/* Create shared memory object and set its size to the sizeof our structure */printf("size:%d\n", sizeof(struct shmbuf));int fd = shm_open(shmpath, O_CREAT | O_EXCL | O_RDWR,S_IRUSR | S_IWUSR);if (fd == -1)errExit("shm_open");if (ftruncate(fd, sizeof(struct shmbuf)) == -1)errExit("ftruncate");/* Map the object into the caller's address space */struct shmbuf *shmp = mmap(NULL, sizeof(*shmp),PROT_READ | PROT_WRITE,MAP_SHARED, fd, 0);if (shmp == MAP_FAILED)errExit("mmap");/* Initialize semaphores as process-shared, with value 0 */if (sem_init(&shmp->sem1, 1, 0) == -1)errExit("sem_init-sem1");if (sem_init(&shmp->sem2, 1, 0) == -1)errExit("sem_init-sem2");/* Wait for 'sem1' to be posted by peer before touchingshared memory */if (sem_wait(&shmp->sem1) == -1)errExit("sem_wait");/* Convert data in shared memory into upper case */for (int j = 0; j < shmp->cnt; j++)shmp->buf[j] = toupper((unsigned char) shmp->buf[j]);/* Post 'sem2' to tell the to tell peer that it can nowaccess the modified data in shared memory */if (sem_post(&shmp->sem2) == -1)errExit("sem_post");/* Unlink the shared memory object. Even if the peer processis still using the object, this is okay. The object willbe removed only after all open references are closed. */shm_unlink(shmpath);exit(EXIT_SUCCESS);
}

pshm_ucase_send.c:

#include <string.h>
#include "pshm_ucase.h"int
main(int argc, char *argv[])
{if (argc != 3) {fprintf(stderr, "Usage: %s /shm-path string\n", argv[0]);exit(EXIT_FAILURE);}char *shmpath = argv[1];char *string = argv[2];size_t len = strlen(string);if (len > BUF_SIZE) {fprintf(stderr, "String is too long\n");exit(EXIT_FAILURE);}/* Open the existing shared memory object and map itinto the caller's address space */int fd = shm_open(shmpath, O_RDWR, 0);if (fd == -1)errExit("shm_open");struct shmbuf *shmp = mmap(NULL, sizeof(*shmp),PROT_READ | PROT_WRITE,MAP_SHARED, fd, 0);if (shmp == MAP_FAILED)errExit("mmap");/* Copy data into the shared memory object */shmp->cnt = len;memcpy(&shmp->buf, string, len);/* Tell peer that it can now access shared memory */if (sem_post(&shmp->sem1) == -1)errExit("sem_post");/* Wait until peer says that it has finished accessingthe shared memory */if (sem_wait(&shmp->sem2) == -1)errExit("sem_wait");/* Write modified data in shared memory to standard output */write(STDOUT_FILENO, &shmp->buf, len);write(STDOUT_FILENO, "\n", 1);exit(EXIT_SUCCESS);
}

2mmap

mmap在linux中是经常使用的,mmap不仅仅可以用来共享内存,当我们使用malloc申请内存时,默认情况下如果申请的内存大于128K,那么底层便会使用mmap来从系统申请内存;mmap同样也可以将系统的设备内存映射到用户态。

void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);

MAP_SHARED共享,一个进程修改之后,另一个进程能看到。如果我们要使用共享内存,那么需要设置这个标志
MAP_PRIVATE

私有,也就是不共享,即使两个进程使用mmap映射的是同一个文件,偏移量都是一样的,那么一个进程的修改,另一个进程也看不到。

从下边的注释可以看到,设置MAP_PRIVATE,使用copy on write机制,当一个进程要写的时候,进程内会拷贝一份。另外,私有的情况下,数据最终会不会保存到文件中,是不确定的。

MAP_ANONYMOUS匿名映射,mmap的倒数第二个参数是一个fd,如果要进行文件映射,那么需要首先打开这个文件,再使用mmap进行映射;如果是匿名映射,那么就不需要指定fd,将fd设置为-1即可。

匿名映射常常用于父子进程间的内存共享,文件映射常常用于没有父子关系的进程间的内存共享。 如果我们想要内存中的内容会保存到一个文件中,并且开机之后还能够使用,那么就必须使用共享和文件映射的方式。

2.1文件映射

文件映射就是要基于文件系统的一个文件来映射,第一节中的POSIX接口,就是使用的临时文件系统中的文件进行映射。如下两个文件aa.c和bb.c,编译之后先运行aa,再运行bb,可以看到aa写的数据,bb能够读到;bb写的数据,aa也能读到。

aa.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>int main() {const char *filename = "./shared_file.txt";const size_t length = 100;int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);ftruncate(fd, length);char *shared_mem = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);close(fd);if (shared_mem == MAP_FAILED) {perror("mmap");return 1;}strcpy(shared_mem, "Hello from aa!");printf("after aa write\n");sleep(5);printf("aa read:%s\n", shared_mem);munmap(shared_mem, length);return 0;
}

bb.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>int main() {const char *filename = "./shared_file.txt";const size_t length = 100;int fd = open(filename, O_RDWR, 0666);char *shared_mem = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);close(fd);if (shared_mem == MAP_FAILED) {perror("mmap");return 1;}printf("bb read:%s\n", shared_mem);strcpy(shared_mem, "Hello from bb!");munmap(shared_mem, length);return 0;
}

2.2匿名映射

匿名映射可以用于父子进程间的内存共享,如下是一个例子。

①父进程中首先向共享内存中写"Hello from parent!"

②父进程调用fork创建子进程

③子进程读取内存中的内容,然后向内存中写数据“Hello from child!”

④父进程等待子进程退出,然后读取共享内存中的数据

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>int main() {// 创建匿名共享内存size_t length = 100;char *shared_mem = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);if (shared_mem == MAP_FAILED) {perror("mmap");return 1;}strcpy(shared_mem, "Hello from parent!");pid_t pid = fork();  // 创建子进程if (pid == 0) {// 子进程printf("child read:%s\n", shared_mem);strcpy(shared_mem, "Hello from child!");printf("child write:%s\n", shared_mem);} else {// 父进程wait(NULL);  // 等待子进程结束printf("parent read:%s\n", shared_mem);  // 读取共享内存内容}munmap(shared_mem, length);  // 解除映射return 0;
}

运行结果如下,从结果可以看出来,子进程写的数据,父进程可以读出来,说明内存在父子进程之间是共享的。

如果把mmap中的标志MAP_SHARED改为MAP_PRIVATE,那么内存在父子进程间是不共享的。子进程写数据之后,父进程也看不到,父进程中读出来还是“Hello from parent!”。这个时候内存在父进程和子进程中各有一份。MAP_PRIVATE下,使用写时拷贝的机制,只有写的时候,才会分配一份内存,读的时候不会,所以在子进程中一开始读内存中数据的时候还能看到一开始父进程写的数据。

2.3查看文件映射和匿名映射

/pro/pid/maps中显式了进程的内存映射。

只有在共享映射的时候,才能在maps文件中看到对应的映射,私有映射的时候看不到。

如下是文件映射,可以看到shared_file.txt映射的内存范围。

如下是匿名映射,可以看到匿名映射映射的是/dev/zero。

2.4mmap使用注意问题

2.4.1offset应为PAGE_SIZE的整数倍

mmap的最后一个形参offset,需要是页大小的整数倍,页大小通过sysconf(_SC_PAGE_SIZE)来获取。如果offset不是页的整数倍,那么会返回错误Invalid argument。本人测试,无论是文件映射还是匿名映射,MAP_SHARED还是MAP_PRIVATE,这条限制都存在。

2.4.2文件映射时,映射的内存不能超过文件的大小

当使用文件映射时,如果映射的内存的大小大于文件本身的大小,那么在调用mmap时并不会返回错误,但是在写数据的时候会出现段错误。

如下代码,如果shared_file.txt是不存在的,那么调用open的时候会创建该文件,默认大小是0。不调用ftruncate,直接调用mmap,映射的内存大小是100,这个时候mmap不会返回错误,但是在写内存的时候会出现段错误。所以在使用mmap映射文件的时候,必须要保证offset+length的数据不超过文件的大小才可以。ftruncate可以设置文件的大小。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>int main() {const char *filename = "./shared_file.txt";const size_t length = 100;int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);// ftruncate(fd, length);char *shared_mem = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);close(fd);if (shared_mem == MAP_FAILED) {perror("mmap");return 1;}strcpy(shared_mem, "Hello from aa!");printf("after aa write\n");sleep(5);printf("aa read:%s\n", shared_mem);munmap(shared_mem, length);return 0;
}

3system V

systemV类型的共享内存,要使用4个api。

①首先要使用ftok来获取一个key

②使用shmget获取一个id

③使用shmat获取共享内存的地址

④共享内存使用完毕之后使用shmdt删除共享内存

writer.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>#define SHM_SIZE 100  // 共享内存大小int main() {key_t key = ftok("./shmfile", 65);  // 创建一个唯一的键int shmid = shmget(key, SHM_SIZE, 0666 | IPC_CREAT);  // 获取共享内存段if (shmid < 0) {perror("shmget");return 1;}char *str = (char *)shmat(shmid, NULL, 0);  // 将共享内存附加到当前进程地址空间if (str == (char *)(-1)) {perror("shmat");return 1;}// 向共享内存写入数据strcpy(str, "Hello from writer!");printf("Writer wrote: %s\n", str);sleep(20);shmdt(str);  // 解除共享内存return 0;
}

reader.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>#define SHM_SIZE 100  // 共享内存大小int main() {key_t key = ftok("./shmfile", 65);  // 创建一个唯一的键int shmid = shmget(key, SHM_SIZE, 0666);  // 获取共享内存段if (shmid < 0) {perror("shmget");return 1;}char *str = (char *)shmat(shmid, NULL, 0);  // 将共享内存附加到当前进程地址空间if (str == (char *)(-1)) {perror("shmat");return 1;}// 读取共享内存内容printf("Reader read: %s\n", str);shmdt(str);  // 解除共享内存return 0;
}

如果共享内存只是在父子进程间共享,那么不需要使用ftok通过文件来获取一个key,在使用shmget的时候,直接使用IPC_PRIVATE即可。如下是一个例子,在父子进程间共享内存。

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>#define SHM_SIZE 100  // 共享内存大小int main() {int shmid = shmget(IPC_PRIVATE, SHM_SIZE, 0666 | IPC_CREAT);  // 获取共享内存段if (shmid < 0) {perror("shmget");exit(1);}// 创建子进程pid_t pid = fork();if (pid < 0) {perror("fork");exit(1);}if (pid > 0) {// 父进程char *str = (char *)shmat(shmid, NULL, 0);  // 将共享内存附加到父进程if (str == (char *)(-1)) {perror("shmat");exit(1);}// 向共享内存写入数据strcpy(str, "Hello from parent!");printf("Parent wrote: %s\n", str);sleep(4);printf("Parent read: %s\n", str);shmdt(str);  // 解除共享内存wait(NULL);  // 等待子进程结束} else {// 子进程sleep(1);  // 等待父进程写入数据char *str = (char *)shmat(shmid, NULL, 0);  // 将共享内存附加到子进程if (str == (char *)(-1)) {perror("shmat");exit(1);}// 读取共享内存内容printf("Child read: %s\n", str);strcpy(str, "Hello from child!");printf("child wrote: %s\n", str);shmdt(str);  // 解除共享内存}// 删除共享内存段if (pid > 0) {shmctl(shmid, IPC_RMID, NULL);  // 只有父进程删除共享内存}return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux基础3-基础工具4(git),冯诺依曼计算机体系结构

上篇文章&#xff1a;Linux基础3-基础工具3&#xff08;make,makefile,gdb详解&#xff09;-CSDN博客 本章重点&#xff1a; 1. git简易使用 2. 冯诺依曼计算机体系结构介绍 目录 一. git使用 1.1 什么是git? 1.2 git发展史 1.3 git创建仓库 1.4 git命令操作 二. 冯诺依…

1.3 计算机网络的分类

欢迎大家订阅【计算机网络】学习专栏&#xff0c;开启你的计算机网络学习之旅&#xff01; 文章目录 前言一、按分布范围分类二、按传输技术分类三、按拓扑结构分类四、按使用者分类五、按传输介质分类 前言 计算机网络根据不同的标准可以被分为多种类型&#xff0c;本章从分布…

Flink系列知识之:Checkpoint原理

Flink系列知识之&#xff1a;Checkpoint原理 在介绍checkpoint的执行流程之前&#xff0c;需要先明白Flink中状态的存储机制&#xff0c;因为状态对于检查点的持续备份至关重要。 State Backends分类 下图显示了Flink中三个内置的状态存储种类。MemoryStateBackend和FsState…

二叉搜索树(BSTree)原理及应用场景

目录 引言 二叉搜索树的基本概念 常见算法 插入节点 查找节点 删除节点 二叉搜索树的应用场景 1. 数据库索引 2. 符号表 3. 字典和词汇表 4. 动态集合 结论 引言 二叉搜索树&#xff08;Binary Search Tree, BST&#xff09;是一种特殊的二叉树&#xff0c;其每个节…

JavaEE: 深入探索TCP网络编程的奇妙世界(五)

文章目录 TCP核心机制TCP核心机制六: 拥塞控制为什么要有拥塞控制?动态调整的拥塞控制拥塞控制中,窗口大小具体的变化过程 TCP核心机制七: 延时应答TCP核心机制八: 捎带应答 TCP核心机制 前一篇文章 JavaEE: 深入探索TCP网络编程的奇妙世界(四) 书接上文~ TCP核心机制六: 拥…

Ubuntu20.04 搜索不到任何蓝牙设备

电脑信息 联想扬天YangTianT4900k 问题描述 打开蓝牙之后&#xff0c;一直转圈&#xff0c;搜索不到任何蓝牙设备 排查 dmesg | grep -i blue 有如下错误&#xff1a; Bluetooth: hci0: RTL: unknown IC info, lmp subver 8852, hci rev 000b, hci ver 000b lsusb 芯片型号如…

spark读取数据性能提升

1. 背景 spark默认的jdbc只会用单task读取数据&#xff0c;读取大数据量时&#xff0c;效率低。 2. 解决方案 根据分区字段&#xff0c;如日期进行划分&#xff0c;增加task数量提升效率。 /*** 返回每个task按时间段划分的过滤语句* param startDate* param endDate* param …

每日学习一个数据结构-Trie树(字典树)

文章目录 定义节点结构根节点插入操作查找操作删除操作特点应用示例 “Trie”树&#xff0c;又称为前缀树或字典树&#xff0c;是一种专门用于存储字符串的数据结构。它在许多应用程序中都非常有用&#xff0c;特别是在那些需要高效查找、插入和删除字符串的应用场景中。下面是…

[项目:微服务即时通讯系统客户端(基于C++QT)]三,左侧界面搭建

三&#xff0c;左侧界面搭建 一&#xff0c;导入 先把MainWidget类做成“单例类” 采用的是单例模式&#xff0c;让某一个类&#xff0c;在指定进程中只有唯一的实例 先看一下MainWidget的框架 QWidget//这部分是头文件保护宏&#xff0c;确保该头文件只被包含一次&#x…

低级编程语言和高级编程语言

一.区分低级编程语言和高级编程语言的方法 1.低级编程语言 低级编程语言,并不是简单的编程语言,而是写起来很费事的编程语言,如所有编程语言的"祖宗":汇编语言,写起来极其麻烦,说不定一个 int a1; 它就得写好几行,甚至十几行 这样麻烦的编程语言为什么还没消失那,因…

基于微信小程序的家教信息管理系统的设计与实现(论文+源码)_kaic

摘 要 随着互联网时代的来临&#xff0c;使得传统的家教模式已不复存在&#xff0c;亟需一种方便、快捷的在线教学平台。因此&#xff0c;利用Java语言作为支撑和MySQL数据库存储数据&#xff0c;结合微信小程序的便利性&#xff0c;为用户开发出了一个更加人性化、方便的家庭…

超越sora,最新文生视频CogVideoX-5b模型分享

CogVideoX-5B是由智谱 AI 开源的一款先进的文本到视频生成模型&#xff0c;它是 CogVideoX 系列中的更大尺寸版本&#xff0c;旨在提供更高质量的视频生成效果。 CogVideoX-5B 采用了 3D 因果变分自编码器&#xff08;3D causal VAE&#xff09;技术&#xff0c;通过在空间和时…

ps证件照蓝底换白底

ps证件照蓝底换白底 1、打开 Photoshop&#xff0c;导入需要处理的照片。 2、左侧工具栏中选择“魔棒工具”&#xff0c;点击证件照的背景区域进行选择。 3、使用快捷键 Shift F5 或者从顶部菜单选择“编辑” -> “填充”&#xff0c;在弹出的对话框中选择“填充内容”中…

【全网最全】2024年华为杯研究生数学建模A题成品论文

您的点赞收藏是我继续更新的最大动力! 一定要点击如下的卡片&#xff0c;那是获取资料的入口&#xff01; 点击链接获取群聊【2024华为杯研赛资料汇总】&#xff1a;https://qm.qq.com/q/yB6JDUTaWAhttps://qm.qq.com/q/yB6JDUTaWAA题第一问是关于如何建立一个低复杂度模型&a…

【M-LOAM学习】

M-LOAM(INITIALIZATION) Article Analysis Scan-Based Motion Estimation 通过在consecutive frame (each LiDAR)&#xff08;因为omp parallel&#xff09;中寻找correspondences然后通过最小化所有考虑feature之间residual error的transformation between frame to frame 针…

通过解预测和机器学习促进蚁群优化

文章目录 Abstract1. Introduction2. Background and related work2.1 定向越野问题2.2 ACO优化3. 基于预测的蚁群优化算法3.1 构建训练集3.2 训练与解预测3.3 将预测解融入蚁群优化Abstract ML - ACO 算法的第一阶段,使用一组已知最优解的小定向越野问题实例训练一个 ML 模型…

tornado

Tornado通过使用非阻塞网络1/0&#xff0c;可以扩展到数以万计的开放链接&#xff0c;非常适合 长时间轮询&#xff0c;WebSockets和其他需要与每个用户建立长期连接的应用程序。 特点 注重性能优越&#xff0c;速度快解决高并发异步非阻塞websockets 长连接内嵌了HTTP服务器…

Linux 一些快捷键使用操作技巧

ctrl c : 强制停止 如图仅输入tail命令时程序会卡住&#xff0c;这时就需要强制停止 ctrl d : 退出或者登出 history : 查看历史输入命令 &#xff01;命令 &#xff1a;自动执行上一次匹配前缀的命令 &#xff08;注意不要用这个命令执行太过久远的&#xff0c;容易执行错误…

AWS 管理控制台

目录 控制台主页 AWS 账户信息 AWS 区域 AWS 服务选择器 AWS 搜索 AWS CloudShell AWS 控制面板小部件 控制台主页 注册新的 AWS 账户并登录后&#xff0c;您将看到控制台控制面板。这是与各种 AWS 服务以及其他重要控制台组件进行交互的起点。控制面板由页面顶部的导航…

C语言 | Leetcode C语言题解之第423题从英文中重建数字

题目&#xff1a; 题解&#xff1a; char * originalDigits(char * s) {int lenstrlen(s);int arr[26]{0},num[10]{0},cot0;for(int i 0; i < len; i)arr[s[i] - a];num[0] arr[z-a];num[2] arr[w-a];num[4] arr[u-a];num[6] arr[x-a];num[8] arr[g-a];num[1] arr[o…