【北京迅为】《iTOP-3588开发板系统编程手册》-第10章 存储映射 I/O

RK3588是一款低功耗、高性能的处理器,适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用,RK3588支持8K视频编解码,内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP,内置NPU,支持INT4/INT8/INT16/FP16混合运算能力,支持安卓12和、Debian11、Build root、Ubuntu20和22版本登系统。了解更多信息可点击迅为官网   

【粉丝群】824412014

【实验平台】:迅为RK3588开发板

【内容来源】《iTOP-3588开发板系统编程手册》

【全套资料及网盘获取方式】联系淘宝客服加入售后技术支持群内下载

【视频介绍】:【强者之芯】 新一代AIOT高端应用芯片 iTOP -3588人工智能工业AI主板


第10章 存储映射 I/O

当我们需要对大量数据进行读写时,传统的I/O操作可能会因为频繁的系统调用而导致效率低下。此时,我们可以考虑使用存储映射I/O技术,将磁盘上的数据映射到内存中,通过对内存的操作来实现对数据的读写,从而提高程序的效率。在本章将对存储映射I/O进行讲解。

10.1建立映射区

本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\54”目录下,如下图所示:

存储映射 I/O(Memory-Mapped I/O,MMIO)是一种特殊的输入输出(I/O)操作方式,它将输入/输出寄存器(I/O register)映射到内存地址空间,使得I/O寄存器和普通内存单元具有相同的访问方式。这样,CPU通过访问内存地址来进行I/O操作,就像读写普通内存一样。

与传统的I/O方式相比,存储映射 I/O 的优势在于:

(1)提高了数据传输的效率。传统的I/O操作需要涉及到数据缓冲区的复制,而存储映射I/O方式直接操作内存,避免了数据复制的过程,从而提高了数据传输的效率。

(2)降低了对CPU的开销。传统的I/O操作需要调用系统调用进入内核态,而存储映射I/O方式是在用户态下完成的,避免了频繁的系统调用和内核态和用户态的切换,从而降低了对CPU的开销。

(3)存储映射I/O的实现需要硬件和操作系统的支持。硬件需要将I/O寄存器映射到内存地址空间,操作系统则需要提供相关的系统调用来进行I/O操作。

下面是使用存储映射I/O的基本步骤:

步骤

作用

函数

1

打开文件,获取文件描述符

open()

2

将设备寄存器映射到进程的虚拟地址空间中

mmap()

3

对映射的内存区域进行读写操作

访问内存

4

解除内存映射关系

munmap()

5

关闭文件

close()

对与open()函数大家已经很熟悉了,本小节不再对open()函数进行讲解。使用系统调用mmap()用于创建内存映射,可以将一个文件或其他对象映射到进程的地址空间,使得文件内容可以像访问内存一样直接被读写。其函数原型和所需头文件如下所示:

所需头文件

函数原型

1

2

#include <sys/mman.h>

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

mmap 函数将指定的文件映射到调用进程的虚拟地址空间,并返回该映射区域的起始地址。如果映射失败,则返回 MAP_FAILED。

下面对参数进行详细解释:

参数名称

参数含义

addr

映射区域的建议起始地址,通常传入 NULL,由内核选择。

2

length

映射区域的长度。

3

prot

映射区域的保护模式,可以是以下几种模式的组合:

(1)PROT_EXEC:映射区域可被执行。

(2)PROT_READ:映射区域可被读取。

(3)PROT_WRITE:映射区域可被写入。

(4)PROT_NONE:映射区域不能被访问。

4

flags

映射区域的类型,可以是以下几种类型的组合:

(1)MAP_FIXED:指定映射区域的起始地址,如果该地址已经被占用,则调用失败。

(2)MAP_PRIVATE:创建一个私有的映射区域,对该区域的写操作不会影响到原文件。

(3)MAP_SHARED:创建一个共享的映射区域,对该区域的写操作会影响到原文件。

(4)MAP_ANONYMOUS:创建一个匿名的映射区域,不需要使用文件描述符。

(5)MAP_NORESERVE:不预留交换空间。

5

fd

映射区域所关联的文件描述符。

存储映射 I/O 示意图如下所示:

首先创建一个名为demo53_pthread_mutex.c文件,向该文件写入以下内容,如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

int main(int argc, char *argv[]) 
{
    int fd;                         // 文件描述符
    char *addr;                     // 映射区的起始地址
    off_t size;                     // 文件大小

    if (argc != 2) 
	{                // 判断是否输入了文件名
        fprintf(stderr, "用法: %s <文件名>\n", argv[0]);
        exit(1);                   // 异常退出
    }

    fd = open(argv[1], O_RDONLY);  // 打开文件,只读方式
    if (fd == -1) 
	{                // 判断是否打开成功
        perror("打开文件");        // 打印错误信息
        exit(1);                   // 异常退出
    }

    // 获取文件大小
    size = lseek(fd, 0, SEEK_END); // 将文件指针定位到文件末尾
    if (size == (off_t) -1) 
	{      // 判断是否定位成功
        perror("获取文件大小");    // 打印错误信息
        exit(1);                   // 异常退出
    }

    // 映射文件到内存中
    addr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (addr == MAP_FAILED) 
	{      // 判断映射是否成功
        perror("映射文件");        // 打印错误信息
        exit(1);                   // 异常退出
    }

    printf("%.*s", (int) size, addr);  // 输出映射到内存中的文件内容

    if (munmap(addr, size) == -1) 
	{   // 解除映射
        perror("解除映射");       // 打印错误信息
        exit(1);                   // 异常退出
    }

    close(fd);                     // 关闭文件
    exit(0);                       // 正常退出
}

保存退出之后,使用以下命令对 demo54_mmap.c进行编译,编译完成如下图所示:

gcc -o demo54_mmap demo54_mmap.c

然后创建test文件并向该文件中写入“hello mmap”,最后使用命令“./demo54_mmap test”来建立test文件的存储映射区并进行读取,如下图所示: 

可以看到test文件中的内容就显示了出来,test的文件映射也就成功了。

10.2解除映射关系

在使用内存映射时,系统会将文件的部分或全部内容映射到进程的虚拟地址空间中。这样一来,进程就可以像访问内存一样来访问文件,而不必使用read或write等系统调用来进行I/O操作,这样可以大大提高文件I/O操作的性能。

但是,内存映射也有其不足之处,因为内存映射使用的是虚拟地址空间,而不是真正的物理内存,所以映射的空间是有限制的。另外,映射的内容也是与文件相关联的,如果映射的文件被删除或修改,则映射区的内容也会被影响,因此需要谨慎处理。另外,映射区域的大小也是有限制的,当映射的文件过大或者进程需要使用的映射区域过多时,也有可能导致内存不足的问题。

所以,当不再需要访问映射区域时,应该及时将其解除映射,以释放系统资源,避免资源的浪费和内存泄漏等问题。同时,解除映射也能够保证对映射区域的修改能够被同步到文件中,从而保证文件内容的完整性和一致性。

munmap()函数用于解除内存映射,也就是将一个已经映射到进程地址空间的文件或设备释放。munmap()函数所系头文件和函数原型如下所示:

所需头文件

函数原型

1

#include <sys/mman.h>

int munmap(void *addr, size_t length);

其中,addr参数是内存映射区的起始地址,length参数是映射区的长度。函数成功时返回0,失败时返回-1,并设置errno变量指示错误原因。调用munmap()函数后,不能再访问该内存区域。因此,解除映射应该在程序不再需要使用内存映射区时执行,以确保不会发生任何未定义的行为。

再上一小节所举的案例中的44-48行已经添加了接触映射关系的代码,所以本小节不再进行举例。

10.3父子进程间通信

本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\55”目录下,如下图所示:

本小节将通过 mmap建立的映射区完成父子进程数据通信。

 首先创建一个名为demo55_mmap.c文件,向该文件写入以下内容,如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

#define SHARED_MEMORY_SIZE 1024   // 定义共享内存区大小为 1024 字节

int main() 
{
    int *shared_memory;   // 定义指向共享内存区的指针
    pid_t pid;   // 定义进程标识符

    // 创建共享内存区
    shared_memory = mmap(NULL, SHARED_MEMORY_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    /*
     * mmap 函数创建一段新的共享内存区,返回该内存区的指针
     * 参数 NULL 表示让系统自动选择一个未使用的地址作为共享内存区的起始地址
     * 参数 SHARED_MEMORY_SIZE 表示共享内存区的大小,单位为字节
     * 参数 PROT_READ | PROT_WRITE 表示共享内存区可读可写
     * 参数 MAP_SHARED | MAP_ANONYMOUS 表示该共享内存区对所有进程可见,且不存在对应的文件
     * 参数 -1 和 0 表示该共享内存区不使用文件
     */
    if (shared_memory == MAP_FAILED) 
	{   // 如果创建共享内存区失败
        perror("mmap");   // 输出错误信息
        exit(1);   // 退出程序
    }

    // 创建子进程
    pid = fork();   // 调用 fork 函数创建子进程

    if (pid == -1) 
	{   // 如果创建子进程失败
        perror("fork");   // 输出错误信息
        exit(1);   // 退出程序
    } 
	else if (pid == 0) 
	{  // 子进程
        // 在共享内存中写入数据
        *shared_memory = 123;   // 向共享内存中写入数据

        // 子进程等待 1 秒钟
        sleep(1);

        // 输出共享内存中的数据
        printf("子进程: %d\n", *shared_memory);

        // 解除映射关系
        if (munmap(shared_memory, SHARED_MEMORY_SIZE) == -1) 
		{
            perror("munmap");
            exit(1);
        }

        exit(0);   // 子进程退出
    } 
	else 
	{  // 父进程
        // 父进程等待子进程写入数据
        sleep(2);

        // 输出共享内存中的数据
        printf("父进程: %d\n", *shared_memory);

        // 在共享内存中写入数据
        *shared_memory = 456;   // 向共享内存中写入数据

        // 父进程等待子进程读取数据
        sleep(1);

        // 输出共享内存中的数据
        printf("父进程: %d\n", *shared_memory);

        // 解除映射关系
        if (munmap(shared_memory, SHARED_MEMORY_SIZE) == -1) 
		{
            perror("munmap");
            exit(1);
        }

        exit(0);   // 父进程退出
    }
}

该程序创建了一个大小为1024的共享内存区域,创建了一个子进程并使用共享内存进行通信。在子进程中,向共享内存中写入了整数123,并等待1秒钟后输出共享内存中的数据,最后解除了映射关系。在父进程中,等待子进程写入数据,然后输出共享内存中的数据,接着在共享内存中写入整数456,等待子进程读取数据,最后输出共享内存中的数据并解除了映射关系。

保存退出之后,使用以下命令对 demo55_mmap.c进行编译,编译完成如下图所示:

gcc -o demo55_mmap demo55_mmap.c

然后使用命令“./demo55_mmap”进行程序的运行,如下图所示: 

其中最重要的是第二条打印信息,子进程向共享内存写入123之后,父进程也获取到了共享内存的123,证明使用存储映射IO成功进行了父子进程的通信。

10.4无血缘关系进程间通信

本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\56”目录下,如下图所示:

所需头文件

函数原型

1

2

3

#include <sys/mman.h>

#include <sys/stat.h>

#include <fcntl.h>

int shm_open(const char *name, int oflag, mode_t mode);

int shm_unlink(const char *name);

shm_open() 函数的返回值是一个文件描述符,可以通过这个文件描述符操作共享内存对象。对于需要在不同进程间共享的共享内存,需要将文件描述符传递给其他进程,其他进程通过。shm_open()的参数说明如下:

参数名称

参数含义

name

共享内存段的名字,必须以斜杠/开头,不同进程可以通过这个名字来连接到同一个共享内存段。

2

oflag

打开标志,可以是以下值之一或它们的组合:

O_RDONLY:只读方式打开共享内存段;

O_RDWR:读写方式打开共享内存段;

O_CREAT:如果共享内存段不存在,则创建它;

O_EXCL:与O_CREAT一起使用,表示如果共享内存段已存在则返回错误。

3

mode

创建共享内存段时设置的权限。

shm_unlink()的参数只有一个name,表示要共享内存段的名字。

共享内存段创建之后和前几个小节的步骤相同,使用mmap()函数进行存储映射即可。下面对没有血缘关系进程通过存储映射完成通信进行举例:

 首先创建一个名为demo56_mmap_a.c文件,向该文件写入以下内容,如下所示:

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

#define SHARED_MEMORY_SIZE 1024 // 定义共享内存的大小

int main() 
{
    // 创建或打开共享内存对象,获取一个文件描述符
    int fd = shm_open("/myshm", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd == -1) 
	{
        perror("shm_open"); // 打印错误信息
        exit(1); // 退出程序
    }

    // 调整共享内存区的大小为 SHARED_MEMORY_SIZE 字节
    if (ftruncate(fd, SHARED_MEMORY_SIZE) == -1) 
	{
        perror("ftruncate"); // 打印错误信息
        exit(1); // 退出程序
    }

    // 将共享内存区映射到当前进程的地址空间
    int *shared_memory = mmap(NULL, SHARED_MEMORY_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shared_memory == MAP_FAILED) 
	{
        perror("mmap"); // 打印错误信息
        exit(1); // 退出程序
    }

    // 关闭文件描述符
    close(fd);

    // 在共享内存中写入数据
    sprintf((char *)shared_memory, "Hello, World!");

    // 等待1秒
    sleep(1);

    // 输出共享内存中的数据
    printf("进程 A: %s\n", (char *)shared_memory);

    // 解除映射关系
    if (munmap(shared_memory, SHARED_MEMORY_SIZE) == -1) 
	{
        perror("munmap"); // 打印错误信息
        exit(1); // 退出程序
    }
return 0;
}

 然后创建一个名为demo56_mmap_b.c文件,向该文件写入以下内容,如下所示:

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

#define SHARED_MEMORY_SIZE 1024 // 定义共享内存的大小

int main() 
{
    // 打开共享内存对象,并获取文件描述符
    int fd = shm_open("/myshm", O_RDWR, S_IRUSR | S_IWUSR);
    if (fd == -1) 
    {
        perror("shm_open"); // 打印错误信息
        exit(1); // 退出程序
    }

    // 将共享内存区映射到当前进程的地址空间
    int *shared_memory = mmap(NULL, SHARED_MEMORY_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shared_memory == MAP_FAILED) 
    {
        perror("mmap"); // 打印错误信息
        exit(1); // 退出程序
    }

    // 关闭文件描述符
    close(fd);

    // 等待 1 秒钟
    sleep(1);

    // 输出共享内存中的数据
    printf("进程 B: %s\n", (char *)shared_memory);

    // 在共享内存中写入数据
    sprintf((char *)shared_memory, "Goodbye, World!");

    // 等待 1 秒钟
    sleep(1);

    // 输出共享内存中的数据
    printf("进程 B: %s\n", (char *)shared_memory);

    // 解除映射关系
    if (munmap(shared_memory, SHARED_MEMORY_SIZE) == -1) 
    {
        perror("munmap"); // 打印错误信息
        exit(1); // 退出程序
    }

    return 0;
}

上面两个代码都创建了一个共享内存对象并将其映射到自己的地址空间中,然后在共享内存中写入数据并读取数据,由于打开的共享内存段相同,所以两个进程可以实现通信,如果进程B中打印的数据是进程A写入的数据证明通信成功。

保存退出之后,使用以下命令对 demo56_mmap_a.c和 demo56_mmap_b.c进行编译,编译完成如下图所示:

gcc -o demo56_mmap_a demo56_mmap_a.c -lrt

gcc -o demo56_mmap_b demo56_mmap_b.c -lrt

首先使用命令“ ./demo56_mmap_a”运行进程A的可执行程序,运行成功如下图所示: 

然后使用命令“ ./demo56_mmap_b”运行进程B的可执行程序,运行成功如下图所示:

可以看到进程B首先会打印Hello, World!,证明进程A和进程B之间的通信完成了。

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

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

相关文章

Spark-Scala语言实战(17)

我带着大家一起来到Linux集群环境下&#xff0c;学习我们的spark。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢。 Spark-Scala语言实战&#xff08;16&#x…

基于Springboot的社区帮扶对象管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的社区帮扶对象管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系…

微信小程序日期增加时间完成订单失效倒计时(有效果图)

效果图 .wxml <view class"TimeSeond">{{second}}</view>.js Page({data: {tiem_one:,second:,//倒计时deadline:,},onLoad(){this.countdown();},countdown(){let timestamp Date.parse(new Date()) / 1000;//当前时间戳let time this.addtime(2024…

数据结构- 顺序表-单链表-双链表 --【求个关注!】

文章目录 一 顺序表代码&#xff1a; 二 链表单链表双向链表 一 顺序表 顺序表是线性表的一种 所谓线性表指一串数据的组织存储在逻辑上是线性的&#xff0c;而在物理上不一定是线性的 顺序表的底层实现是数组&#xff0c;其由一群数据类型相同的元素组成&#xff0c;其在逻辑…

JVM知识点总结二

参考文章&#xff1a;【Java面试题汇总】JVM篇&#xff08;2023版&#xff09;_jvm面试题2023-CSDN博客 1、说说你了解的JVM内存模型&#xff1a; JVM由三部分组成&#xff1a;类加载子系统、运行时数据区、执行引擎 JVM内存模型&#xff1a; 内存模型里的运行时数据区&#…

STM32实现硬件I2C通讯,读取MPU6050的ID号

今天学习了使用硬件I2C的方式成功读取MPU6050的ID号&#xff0c;特此记录一下过程&#xff1a; 首先需要学习的是MPU6050的初始化&#xff1a; 第一步&#xff1a;打开GPIOB的时钟&#xff08;因为I2C2的引脚10,11在GPIOB上&#xff09; 第二步&#xff1a;打开I2C2的时钟 …

LLAMA 3的测试之旅:在GPT-4的阴影下前行

Meta终于发布了他们长期期待的LLAMA 3模型&#xff0c;这是一个开源模型&#xff0c;实际上提供了一系列新的功能&#xff0c;使得模型在回答问题时表现得更好。这对AI社区来说是一个真正的里程碑事件。 Meta正在发布新版本的Meta AI&#xff0c;这是一种可以在他们的应用程序和…

用Python在PDF文档中插入单图像水印和平铺图像水印

PDF文档因其跨平台兼容性和内容保真度成为信息交换的标准载体&#xff0c;为应对版权侵犯、内容篡改以及未经授权的传播等风险&#xff0c;向PDF中插入图片水印成为一种强化文档安全性、彰显所有权及实施访问控制的有效手段。图片水印不仅能以直观的方式标示文档来源、强化版权…

Git学习笔记(三)Git分支

Git分支是Git中非常重要的一个概念&#xff0c;无论是个人开发还是多人协作中&#xff0c;分支都起着至关重要的作用。几乎所有的版本控制系统都以某种形式支持分支。 使用分支意味着你可以把你的工作从开发主线上分离 开来进行重大的Bug修改、开发新的功能&#xff0c;以免影响…

Discuz! X3.4 升级至 Discuz! X3.5 详细教程

第一步&#xff1a;从其他以前的 Discuz! X 版本升级Discuz! X3.4 请先升级到Discuz! X3.4&#xff0c;升级教程网上比较普遍&#xff0c;在此不再论述。 第二步&#xff1a;Discuz! X3.4 升级至 Discuz! X3.5 &#xff08;Discuz 从 X3.5 以后&#xff0c;不在发布GBK版本&…

K8S基础概念

一、MASTER Kubernetes里的Master指的是集群控制节点&#xff0c;在每个Kubernetes集群里都需要有一个Master来负责整个集 群的管理和控制&#xff0c;基本上 Kubernetes的所有控制命令都发给它&#xff0c;它负责具体的执行过程&#xff0c;我们后 面执行的所有命 令基本都…

测试数据整理--chatgpt 构造sql语句导出数据库数据

在测试过程中&#xff0c;我们有时候需要准备一些测试数据&#xff0c;若从系统中直接导出Excel数据&#xff0c;数据往往庞大且需要整合&#xff0c;不好整理&#xff0c;于是我们直接去数据库中查询一些表&#xff0c;数据整合后直接导出结果会更方便。 我们今天就 用 chatg…

云原生Kubernetes: K8S 1.29版本 部署Jenkins

目录 一、实验 1.环境 2.K8S 1.29版本 部署Jenkins 服务 3.jenkins安装Kubernetes插件 二、问题 1.创建pod失败 2.journalctl如何查看日志信息 2.容器内如何查询jenkins初始密码 3.jenkins离线安装中文包报错 4.jenkins插件报错 一、实验 1.环境 &#xff08;1&…

npm最新淘宝镜像站已经更新registry(2024-04-19)

1、npm替换地址 旧的 https://registry.npm.taobao.org 已替换为 https://registry.npmmirror.com 淘宝镜像的淘宝官方提供的方法&#xff08;最新的源配置&#xff09; npm config set registry https://registry.npmmirror.com 镜像站网址&#xff1a; npmm…

ELK日志采集系统

1.什么是ELK ELK 是一套流行的数据搜索、分析和可视化解决方案&#xff0c;由三个开源项目组成&#xff0c;每个项目的首字母合起来形成了“ELK”这一术语&#xff1a; Elasticsearch (ES): Elasticsearch 是一个基于 Apache Lucene 构建的分布式、实时搜索与分析引擎。它能够…

小程序AI智能名片S2B2C商城系统:做内容、造IP、玩社群打造私域流量的新营销秘籍

在数字化浪潮汹涌的新时代&#xff0c;小程序AI智能名片S2B2C商城系统正以其独特的魅力&#xff0c;引领着营销领域的新变革。这套系统不仅将人工智能与小程序技术完美结合&#xff0c;更通过创新的S2B2C模式&#xff0c;为企业打开了一扇通往成功的大门。 面对激烈的市场竞争&…

Jenkins 的构建时执行时间问题

我们希望我的项目能够在特定的时间自动执行&#xff0c;我们需要设定一个定时任务。 Jenkins 的定时任务是通过 Cron 任务来实现的&#xff0c;但是由有点不一样。 H/2 * * * * 比如说上面的设置就是每 2 分钟执行一次。 希望每分钟执行一次 Jenkins 的每分钟执行一次的设置…

c++头文件string函数的用法

目录 前言&#xff1a; 字符串截取 字符串插入与替换 字符串区间删除 字符串排序与相加和查找 如后续需文字描述&#xff0c;&#xff0c;请评论区告诉我&#xff0c;我看到后会进行添加一些文字描述。 前言&#xff1a; 因本人女朋友在学习c过程中在一些知识网页上学了st…

uni-app中页面生命周期与vue生命周期的执行顺序对比

应用生命周期 uni-app 支持如下应用生命周期函数&#xff1a; 函数名说明平台兼容onLaunch当uni-app 初始化完成时触发&#xff08;全局只触发一次&#xff09;&#xff0c;参数为应用启动参数&#xff0c;同 uni.getLaunchOptionsSync 的返回值onShow当 uni-app 启动&#x…

09 MySQL--操作真题

1. 用一条 SQL 语句&#xff0c;查询出每门课程都大于 80 分的人。 分析&#xff1a; 去重查询出存在课程小于 80 分的人&#xff0c;设为集合A查询不在集合 A 中的人 # 第一步&#xff1a;找小于等于80分的学员姓名 select distinct name from t_student where fenshu <…
最新文章