Linux:共享内存

文章目录

  • System V
  • 共享内存的原理
  • 管理共享内存
    • shmget
    • shmat
    • shmdt
    • shmctl
  • 共享内存和管道实现进程间同步通信

前面介绍完了匿名管道和命名管道,那么本篇要引入的主题是共享内存

System V

作为进程通信部分的内容,共享内存必然有其存在的意义和价值,例如对于网络来说,有了对应的服务端和客户端,那么在服务端中有了一个用户发送的消息,这些消息都被放到了管道中,之后经过加工后,要不然会选择把信息放到数据库中,要不然会选择把内容返回到上层,这是管道的作用,而对于System V版本的共享内存来说,也有它自己的作用

对于操作系统来说,通信的场景是有很多很多种的,有的是要传输数据为目的,有的是要以传输特定数据块为目的,也有的是以进程之间进行协同控制为目的,但是不管出于什么场景,只要是通信,那么就必然意味着要有固定的通信方式,就会有对应的接口参数返回值等等,再基于一定的格式进行统一,打包整体就叫做进程之间的通信,那么现在问题是,统一的格式是什么?哪里有格式的问题呢?为了解决这样的问题,有专门的人去定标准,再去基于这些标准进行具体的视线,实现出的这通信的模式就叫做System V,也叫做系统V,所以说对于这个模式来说有很多的通信方式,例如有共享内存,消息队列,信号量等等诸多方式,但是不管是什么模式,它们的接口,参数返回值,都是基于一定的标准来实现的,具有一定的相似性,这样对于使用者来说是比较方便的

所以接下来本篇介绍的内容就是这个System V模式下的第一种通信方式–共享内存

共享内存的原理

共享内存是基于通信的目的的,那么通信的本质是让不同的进程看到同一份资源,那么现在就要将注意点转移到同一份资源这个角度,这个资源不能是和进程挂钩,必须是操作系统与来提供的,所以在前面的管道中,不管是匿名管道还是命名管道,其实都是操作系统提供的,包括文件缓冲区和文件结构体,所以对于共享内存来说也是一样的道理,首先要创建出一个资源,其次是要让不同的进程看到这份资源,这就是共享内存的基本原理

在这里插入图片描述
上图是前面已经讲述过很多次的一个逻辑,对于共享内存也会从这里入手进行讲解,每一个进程都有自己的进程控制块,也有自己的地址空间,这个进程地址空间最后会根据页表映射到物理内存上,而又由于缺页中断申请内存这样的机制存在,所以说在代码中申请内存的时候,其实真正的物理内存没有进行开辟,而是在地址空间中开辟好,当首次尝试访问这块空间的时候,再去触发缺页中断这个机制,来在对应的物理内存中进行开辟内存,从中也能侧面看出,操作系统具有直接在内存中申请空间的能力

所以想要实现通信的第一步已经实现了,操作系统已经在物理内存中给进程开辟好了一块资源,可以等待它们使用,第二步要通信,起码是需要两个进程,在有了进程之后,就会在堆栈之间开辟一块区域,这块区域用来实现进程之间的通信

堆栈之间的这块共享区也不是第一次接触了,在之前的动静态库中就有过提及,动态库的加载就是加载到内存中,然后映射到堆栈之间的共享区中,进行加载库的这个过程,本质上就是把物理内存中的库数据映射到堆栈之间,只不过这里操作系统做的是在物理内存中开辟一块空间,而在逻辑地址的堆栈之间开辟的是一块新的空的空间,里面没有任何数据,而加载库的过程中这块区域是有空间的

在这里插入图片描述
在操作系统中,在物理内存中已经申请好了一块空间,并且也映射到了共享区中,那么这个共享区的起始地址也就已经知晓,所以在上层用户就可以调用这块空间,直接对这块空间进行数据的写入等等操作

关于用户空间和内核空间

那为什么说,有了这块起始地址,就能直接访问申请的内存了呢?其实原因就在于,有起始地址,并且整个空间有多大也是清楚的,那么未来就可以通过指针的方式,向对应的缓冲区中写数据,数据就会通过页表映射到对应的物理内存中,那在操作系统内部,是如何用地址空间对于物理内存进行访问?虽然现在已经有了虚拟地址,并且也有页表进行自动转换,但是为什么有地址就可以直接访问呢?原因就在内存中的用户空间,对于管道文件,它其实是属于内核数据结构,那么所有的缓冲区文件的属性都是会在这个地址空间的这个内核区域内,想要访问就必须调用对应的系统调用,而现在创建的这个堆栈之间的共享区,是属于用户空间的,用户空间是可以直接访问的,不用对应的系统调用就可以访问,相当于是有了一块内存空间,支持随机访问

在这里插入图片描述

所以现在进程就和共享内存之间建立了对应的映射关系,只要创建好内存,让当前进程把内存块映射到自己的地址空间中,那此时如果有另外一个进程,想要实现进程的通信,就也要进行相同的操作来进行映射,此时这个新的进程也会获得一个虚拟地址,对于这两个进程来说,它们的虚拟地址可以相同也可以不同,到此,这两个进程就都可以使用各自地址空间内的虚拟地址,借助页表来对物理内存中进行访问,相当于是间接的借助地址空间访问同一块内存空间,这样就通过共享内存的原理达到了进程间通信的前提,叫做让不同的进程看到同一份资源

共享内存的本质,在系统层面上把内存申请好,再映射到两个进程的地址空间中,映射结束之后,此时只需要把映射在虚拟地址中的起始地址返回给用户,用户就可以通过起始地址进行访问了

谈谈释放的问题

对于共享内存的释放,只需要把虚拟地址和物理地址之间的这层关系取消掉就可以了,具体的实操来说,就是把页表清空就可以,清空了页表,虚拟空间的所有地址就都失去了对应的意义,这也就是为什么说,所有的地址的概念,都是建立在有页表的基础上,如果没有页表,所有的地址其实都没有多大的意义,而对应与malloc或者是new申请的内存,也都是在页表上申请的,而物理内存并不需要立刻映射,而是在访问的时候再借助缺页中断来填充

管理共享内存

综合上述的内容,可以得出的一个结论是,在操作系统中,一定会存在多个共享内存被创建,在操作系统中会有很多个共享内存,那操作系统当然需要对于共享内存进行管理,那管理的前提是要描述,所以在操作系统内部一定会存在管理共享内存的概念,于是就有了下面的话题:管理共享内存

在管理之前,要有的第二个概念是,对于这块空间的识别问题,如何保证两个想要通信的进程可以识别到同一块资源呢?说明共享内存被创建出来之后,一定是具有一定的识别能力,有它独特的标识,才能让另外一个进程能够找到这块内存,进而进行后面的通信工作,所以下面就要研究这个识别的东西到底是什么

  1. 标识由什么组成?
  2. 怎么传递给另外一个进程?

Linux在内部提供了很多的接口,那么就要对于这些接口进行一定的认识了

shmget

在这里插入图片描述
这个命令就是创建共享内存的命令,对于参数的解析来说,抛开最前面的key值,对于第二个参数size来说,这个参数的意思是要开辟的共享内存有多大,函数的返回值会返回一个标识符,也就是内存标识符,如果创建失败会返回-1,并且会设置错误码,第三个参数是选项,不再多说,主要是介绍有两个选项

在这里插入图片描述
第一个选项的意思是,如果这个shm不存在就创建,存在就获取,并且返回
第二个选项不会单独使用,它一般会和第一个选项组合起来使用,表示的意思是,如果shm不存在就创建,存在就会提示出错并且返回,这样的意义是可以保证每次申请的共享内存都是全新的内存

这个内存标识符实际上就是前面所说的识别问题,这个整数的作用就有些类似于文件描述符的作用,在文件的接口中,都是靠这个文件描述符来工作,同理,在共享内存的接口中也是根据这个值来运转的

key值的问题

对于key值是多少,其实没有一个明确的标准,想写多少就写多少,在创建共享内存的时候,只要让通信的这两个进程之间约定一个数字,把这个数字作为标记符,那么在创建共享内存的时候,就会把这个数字表示写到共享内存的属性中,未来另外一个进程只需要在创建好的这些共享内存中去寻找这个标识符,就能找到前面的这个共享内存,进而就可以进行通信了

这个数字的取值是有讲究的,如果出现两个共享内存的key值相同,那必然是会出现严重的问题,所以对于这个key值的取值需要做足准备,而在接口中当然是有对应的解决措施的,这个函数就叫做ftok函数:

在这里插入图片描述
这个函数的作用就是专门用来,把一个地址和id转换成一个key值,所以借助这个东西就能生成一个不错的key值,以来区分内存

所以此时,对于创建共享内存的这个接口的参数就都介绍结束了,下面就实践一下,创建一个共享内存:

int main()
{
    int key = ftok("linux-system-and-network/shm", 1234);
    int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL);
    // 创建失败就返回
    if (shmid < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        exit(2);
    }
    cout << "create success" << endl;
    return 0;
}

运行后,就会创建出一个共享内存了!

在这里插入图片描述
但是,如果再次异常会出错,提示现在已经有共享内存了,这说明一个结论,共享内存和文件不一样,打开的文件的生命周期是随进程的,进程结束这个打开的文件生命周期也就结束了,而共享内存是在System V当中单独设计出的用来进阶通信的方案,这个方案的特点是,共享内存必须让用户主动释放,也就是说如果不释放,这个内存就会一直存在,所以这里的结论是,共享内存以及未来的和通信有关的这些资源,和普通文件是不一样的,除非手动关闭,否则会一直存在

查看共享内存信息

ipcs -m

在这里插入图片描述
删除共享内存信息

ipcrm -m [shmid]

权限问题

在创建的时候,也可以在选项中带上权限的选项:

int main()
{
    int key = ftok("linux-system-and-network/shm", 1234);
    int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);
    // 创建失败就返回
    if (shmid < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        exit(2);
    }
    cout << "create success" << endl;
    return 0;
}

在这里插入图片描述

开辟空间大小的问题

正常来说,开辟空间是可以随意设置的,但是建议开辟大小设置成4096字节,原因在于,在操作系统申请内存的时候,是以4096为单位进行申请的,也就是说,如果申请大小为4097,实际上申请的是两个4096字节,只不过是用户层面上只用了4097个字节,即便未来被占用了,也不会进行分配,所以未来在越界方面可能会有异常,因此建议是以4096的整数倍进行大小的分配

shmat

在这里插入图片描述
这个命令可以将指定的共享内存挂接到自己的地址空间中

对于第二个参数shmaddr来说,这个shmaddr默认设置成nullptr就可以了,这个参数的意义是如果想要手动把共享内存挂接地址中的一个指定起始地址处,但是如果对于地址空间的不了解的情况下,直接传参传nullptr就可以了,让操作系统来进行选择就可以了

对于第三个参数shmflag来说,这个选项代表的是挂接到共享内存中的对应方式,这个不需要进行管控,因为在创建的时候就已经有权限来进行控制了,直接设置成0就可以了

对于返回值来说,如果挂接成功后,进程就会凭空多出来一块空间,有点类似于c语言中的malloc函数,所以在用法上也和它一样,需要进行强转成需要的类型

下面做出下面的实验:

int main()
{
    int key = ftok("linux-system-and-network/shm", 1234);
    int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);
    // 创建失败就返回
    if (shmid < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        exit(2);
    }
    cout << "create success" << endl;
    // 创建成功后就挂接到当前进程
    cout << "开始挂接" << endl;
    sleep(2);
    char *s = (char*)shmat(shmid, nullptr, 0);
    cout << "进程退出" << endl;
    sleep(2);
    return 0;
}

在这里插入图片描述
从中看出这也确实完成了初步的预期,在进程退出的时候会删除页表,页表清空后就随之解除了映射关系,这样共享内存也就随之没有关联的必要了,所以这里关联数就减去1即可,这里可以类比是一种引用计数,但是又不完全是,因为不会伴随着减到0而自动释放这段空间,所以只能算是继承了这样的一种思想

shmdt

在这里插入图片描述
这个命令的意思是去关联,其实也就是和上述的函数意思相反,要不然是在特定的空间内取消映射关系,最终的参数就是获得共享内存的起始地址,其实也就是上面这个函数的返回值,就可以用做这个函数的参数

如何理解这个过程呢?其实可以从关联的角度来讲,关联的角度就是修改页表,所以,只需要找到虚拟地址所对应的起始地址就可以了,虚拟地址的起始地址知道,并且共享内存的大小也知道,所以就可以从共享内存的起始地址释放空间,解除对应空间大小的关联关系,那么在这样的基础下,也就将整个的挂接关系都去掉了,所以就实现了解除关联的效果

int main()
{
    int key = ftok("linux-system-and-network/shm", 1234);
    int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);
    // 创建失败就返回
    if (shmid < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        exit(2);
    }
    cout << "create success" << endl;
    // 创建成功后就挂接到当前进程
    cout << "开始挂接" << endl;
    char *s = (char*)shmat(shmid, nullptr, 0);
    sleep(2);

    cout << "解除关联" << endl;
    shmdt(s);
    sleep(1);
    cout << "进程退出" << endl;
    sleep(2);
    return 0;
}

现象也比较简单,这里就不再展示了

shmctl

在这里插入图片描述
这个接口的作用就是删除指定的共享内存

在对于这个函数了解前,先从源码看一下共享内存:

在这里插入图片描述
在这里插入图片描述
这是在内核中关于共享内存的描述,上面的这个struct shmid_ds结构体中包含了的内容就有,挂接的时间,最后使用时间等等信息,而在其中的这个struct ipc_perm,其实描述的就是关于共享内存的属性信息,例如有key值和多种id的属性,而操作系统也是用这些信息来对共享内存进行管理的,操作系统对于共享内存的管理,就转换成了对于这些数据结构的管理

其实在前面用到的例如有ipcs -m命令或者是其他的删除命令,从本质上来说就是从系统中获取已经被创建的共享内存的属性,例如有共享内存的大小,最近的使用时间,有多少个挂接数,权限等等,都是从这里来的

那转回到我们的这个函数,对于这个函数来说,第一个参数不多解释,第二个参数一般是IPC_RMID,大致意思就可以理解为是立即删除的意思,第三个参数是一个结构体,这里暂时先设置为nullptr,未来有使用场景再继续补充

共享内存和管道实现进程间同步通信

有了上面的基础,对于代码进行简单的封装,可以得到如下的成品

// comm.hpp
#pragma once
#include <iostream>
#include <cstdlib>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

const std::string pathname = "/home/test/linux-system-and-network/shm";
const int proj_id = 0x11223344;
const int size = 4096;
const std::string filename = "fifo";

// 利用路径名和特定id获取key值
key_t GetKey()
{
    key_t key = ftok(pathname.c_str(), proj_id);
    if (key < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        exit(1);
    }
    return key;
}

// 将十进制转换成十六进制
std::string ToHex(int id)
{
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "0x%x", id);
    return buffer;
}

// 按照特定选项创建对应共享内存
int CreateShmHelper(key_t key, int flag)
{
    int shmid = shmget(key, size, flag);
    if (shmid < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        exit(2);
    }

    return shmid;
}

// 以644权限创建共享内存
int CreateShm(key_t key)
{
    return CreateShmHelper(key, IPC_CREAT | IPC_EXCL | 0644);
}

// 获取共享权限的shmid
int GetShm(key_t key)
{
    return CreateShmHelper(key, IPC_CREAT);
}

// 创建一个命名管道
bool MakeFifo()
{
    int n = mkfifo(filename.c_str(), 0666);
    if (n < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        return false;
    }

    std::cout << "mkfifo success... read" << std::endl;
    return true;
}
// client.cc
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "comm.hpp"

int main()
{
    // 利用key值获取共享内存并挂接到程序共享区
    key_t key = GetKey();
    int shmid = GetShm(key);
    char *s = (char *)shmat(shmid, nullptr, 0);
    std::cout << "attach shm done" << std::endl;
    int fd = open(filename.c_str(), O_WRONLY);

    // 向共享内存中写入信息,并利用管道形成同步
    sleep(5);
    for (char c = 'a'; c <= 'z'; c++)
    {
        s[c - 'a'] = c;
        std::cout << "write : " << c << " done" << std::endl;
        sleep(1);
        int code = 1;
        write(fd, &code, sizeof(4));
    }

    // 对共享内存解除挂接,并释放管道
    shmdt(s);
    std::cout << "detach shm done" << std::endl;
    close(fd);
    return 0;
}
// server.cc
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include "comm.hpp"

// 对共享内存和管道的初始化以及释放工作
class Init
{
public:
    Init()
    {
        bool r = MakeFifo();
        if (!r)
            return;

        key_t key = GetKey();
        std::cout << "key : " << ToHex(key) << std::endl;
        sleep(3);
        shmid = CreateShm(key);
        std::cout << "shmid: " << shmid << std::endl;

        sleep(10);
        std::cout << "开始将shm映射到进程的地址空间中" << std::endl;

        s = (char *)shmat(shmid, nullptr, 0);
        fd = open(filename.c_str(), O_RDONLY);
    }
    ~Init()
    {
        sleep(5);
        shmdt(s);
        std::cout << "开始将shm从进程的地址空间中移除" << std::endl;

        sleep(5);
        shmctl(shmid, IPC_RMID, nullptr);
        std::cout << "开始将shm从OS中删除" << std::endl;

        close(fd);
    }

public:
    int shmid;
    int fd;
    char *s;
};

int main()
{
    // 创建共享内存和管道
    Init init;
    sleep(5);

    // 从共享内存中读取信息
    while (true)
    {
        // wait
        int code = 0;
        ssize_t n = read(init.fd, &code, sizeof(code));
        if (n > 0)
        {
            std::cout << "共享内存的内容: " << init.s << std::endl;
            sleep(1);
        }
        else if (n == 0)
        {
            break;
        }
    }

    sleep(10);
    return 0;
}

从上面的代码中可以看出,在进行进程间通信的时候,使用了一个命名管道,那这个命名管道的作用是什么呢?

这就要涉及到共享内存的同步机制了,共享内存本身是不会存在同步机制的,所以加装一个管道,可以造成的效果就是向共享内存写入数据后,必须要另外一个进程得到了共享内存的信息后,再得到管道的信息,这样就能借助管道的同步性,使得共享内存也有了一个模拟的同步机制

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

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

相关文章

༺༽༾ཊ—Unity之-04-工厂方法模式—ཏ༿༼༻

首先创建一个项目&#xff0c; 在这个初始界面我们需要做一些准备工作&#xff0c; 建基础通用文件夹&#xff0c; 创建一个Plane 重置后 缩放100倍 加一个颜色&#xff0c; 任务&#xff1a;使用工厂方法模式 创建 飞船模型&#xff0c; 首先资源商店下载飞船模型&#xff0c…

mkcert的安装和使用,5分学会在本地开启localhost的https访问方式

mkcert官方仓库地址&#xff1a;https://github.com/FiloSottile/mkcert#installation mkcert 是一个简单的工具&#xff0c;用于制作本地信任的开发证书。它不需要配置。 简化我们在本地搭建 https 环境的复杂性&#xff0c;无需操作繁杂的 openssl 实现自签证书了&#xff…

风速编辑一下

clear clc % 假设已知的南北和东西风速分量时程 time 0:1:999; % 时间步长为1秒 north_south_wind_speed 8 2 * sin(2 * pi * 0.1 * time); % 南北风向分量 east_west_wind_speed 6 1 * sin(2 * pi * 0.1 * time); % 东西风向分量 % 计算合风速和风向 total_wind_spe…

Node.js的学习1

Node.js简介 浏览器是JavaScript的前端运行环境Node.js是JavaScript的后端运行环境Node.js中无法调用DOM和BOM等浏览器内置API 终端中的快捷键 使用向上箭头&#xff0c;可以快速定位到上一次执行的命令使用tab键&#xff0c;可以快速补全路径使用esc键&#xff0c;可以快速清…

django 访问后台数据库管理程序报错:CSRF verihcation failed. Request aborted.

解决方案 在 settings.py 最后加上这个 CSRF_TRUSTED_ORIGINS ["https://<你自己 heroku 的项目部署地址>.herokuapp.com" ]注意下面的事项&#xff1a; https 前面和 .com 后面不要带空格&#xff0c;因为你在复制的时候可能引入空格.com 后面不要加斜杠&a…

Rust循环和函数

下面聊聊以下主题&#xff1a; 基于条件的分支循环函数属性测试 基于条件的分支 基于条件的分支&#xff0c;可以通过常见的 if、if else 或 if else if else 构造来完成&#xff0c;例如下面的示例&#xff1a; fn main() { let dead false; let health 48; if dead { p…

第十八章 Redis查看配置文件和数据类型

文章目录 前言1、查看配置2、修改配置项2.1、配置项说明2.2、配置支持远程访问 3、数据类型3.1、String3.1.1、string扩容规则3.1.2、字符串命令3.1.3、string 常用命令 3.2、Hash3.2.1、数据存储3.2.2、常用命令 3.3、list3.3.1、常用命令 3.4、set3.4.1、常用命令 3.5、zset有…

SpringCloud-高级篇(十六)

前面学习了Lua的语法&#xff0c;就可以在nginx去做编程&#xff0c;去实现nginx类里面的业务&#xff0c;查询Redis&#xff0c;查询tomcat等 &#xff0c;业务逻辑的编写依赖于其他组件&#xff0c;这些组件会用到OpenResty的工具去实现 &#xff08;1&#xff09;安装OpenRe…

第十三章认识Ajax(四)

认识FormData对象 FormData对象用于创建一个表示HTML表单数据的键值对集合。 它可以用于发送AJAX请求或通过XMLHttpRequest发送表单数据。 以下是FormData对象的一些作用&#xff1a; 收集表单数据&#xff1a;通过将FormData对象与表单元素关联&#xff0c;可以方便地收集表…

DjangoURL调度器(二)

一、默认值与额外参数 1.1、默认值 1.1.1、urls.py from django.urls import pathfrom . import viewsurlpatterns [# http://127.0.0.1:8000/polls/blog/ 等同于 # http://127.0.0.1:8000/polls/blog/1/path(blog/, views.page),# http://127.0.0.1:8000/polls/blo…

解读BEVFormer,新一代自动驾驶视觉工作的基石

文章出处 BEVFormer这篇文章很有划时代的意义&#xff0c;改变了许多视觉领域工作的pipeline[2203.17270] BEVFormer: Learning Birds-Eye-View Representation from Multi-Camera Images via Spatiotemporal Transformers (arxiv.org)https://arxiv.org/abs/2203.17270 BEV …

架构篇19:单服务器高性能模式-Reactor与Proactor

文章目录 ReactorProactor小结上篇介绍了单服务器高性能的 PPC 和 TPC 模式,它们的优点是实现简单,缺点是都无法支撑高并发的场景,尤其是互联网发展到现在,各种海量用户业务的出现,PPC 和 TPC 完全无能为力。今天我将介绍可以应对高并发场景的单服务器高性能架构模式:Rea…

第18章_JDK8-17新特性(下)(新语法结构,API的变化,其它结构变化,小结与展望)

文章目录 第18章_JDK8-17新特性&#xff08;下&#xff09;6. 新语法结构6.1 Java的REPL工具&#xff1a; jShell命令6.2 异常处理之try-catch资源关闭6.3 局部变量类型推断6.4 instanceof的模式匹配6.5 switch表达式6.6 文本块6.7 Record6.8 密封类 7. API的变化7.1 Optional类…

MarkDown快速入门-以Obsidian编辑器为例

直接上图&#xff0c;左右对应。 首先是基础语法。 # 标题&#xff0c;几个就代表几级标题&#xff1b;* 单个是序号&#xff0c;两个在一起就是斜体&#xff1b;- [ ] 代表任务&#xff0c;注意其中的空格&#xff1b; 然后是表格按钮代码 | 使用中竖线代表表格&#xff0c…

SparkSql---用户自定义函数UDFUDAF

文章目录 1.UDF2.UDAF2.1 UDF函数实现原理2.2需求:计算用户平均年龄2.2.1 使用RDD实现2.2.2 使用UDAF弱类型实现2.2.3 使用UDAF强类型实现 1.UDF 用户可以通过 spark.udf 功能添加自定义函数&#xff0c;实现自定义功能。 如&#xff1a;实现需求在用户name前加上"Name:…

【QT+QGIS跨平台编译】之十四:【webp+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、webp介绍二、webp下载三、文件分析四、pro文件五、编译实践一、webp介绍 WebP 是一种现代的图像格式,由 Google 开发。它能够提供更好的压缩率和图像质量,相比于传统的 JPEG 和 PNG 格式。WebP 图像通常具有更小的文件大小,同时保持高质量的图像细节,这使得它…

总结6(循环(for))

循环 定义&#xff1a; 某些代码会被重复执行 分类&#xff1a; for 1.格式 for(1; 2; 3) 语句A; 2.执行的流程&#xff08;1,2,A,3 2,A,3 2,A,3..........&#xff09; 单个for循环的使用 多个for循环的嵌套使用 1). for&#xff08;1; 2; 3&#xff09; for&#xff0…

【数据分析】numpy基础第二天

文章目录 前言数组的形状变换reshape的基本介绍使用reshapereshape([10, 1])运行结果reshape自动判断形状reshape([-1, 1])运行结果 合并数组使用vstack和hstackvstack和hstack的运行结果使用concatenateconcatenate运行结果 分割数组array_split运行结果 数组的条件筛选条件筛…

qemu单步调试arm64 linux kernel

一、背景和目的 qemu搭建arm64 linux kernel环境-CSDN博客 之前介绍了qemu启动kernel的配置步骤和方法&#xff0c;现在开始我们的调试&#xff0c;这篇文章主要讲解如何单步调试内核&#xff0c;所有的实验还是基于ARM64&#xff1b; 二、环境准备 需要准备hostx86 target…

VirtualBox:安装提示缺少python core和win32 api

最近升级了Ubuntu22.04&#xff0c;查了一下&#xff0c;VirtualBox的增强功能&#xff0c;居然用时5分钟。 5min 18ms vboxadd.service 据说升级VirtualBox的增强功能能解决这个问题&#xff0c;于是先升级VirtualBox&#xff0c;但是安装VirtualBox却报缺少python core及win3…