Linux malloc内存分配实现原理

news2024/9/25 3:27:26

目录

 

一、用户进程虚拟内存空间布局

二、malloc工作原理

2.1 malloc实现流程

2.1.1 brk方式申请内存

2.1.2 mmap方式分配内存

2.2 核心代码

2.3 malloc分配物理内存的时机

2.4 malloc分配的实际内存大小

三、虚拟内存与物理内存

3.1 如何建立映射

3.2 分配物理内存

3.3 物理内存访问

四、new和malloc区别与联系

一、用户进程虚拟内存空间布局

以Linux 64位系统为例。理论上,64bit内存地址可寻址空间为0x0000000000000000 ~ 0xFFFFFFFFFFFFFFFF,这是个相当庞大的地址空间,Linux实际上只用了其中一小部分(256T)。Linux64位操作系统仅使用低47位,高17位做扩展(全0或全1)。因此,实际用到的地址为空间为0x0000000000000000 ~ 0x00007FFFFFFFFFFF和0xFFFF800000000000 ~ 0xFFFFFFFFFFFFFFFF,其中前者为用户空间(User Space),后者为内核空间(Kernel Space)。

同理,32位的系统,0x00000000 ~ 0xBFFFFFFF为用户空间地址(3G),0xC0000000 ~ 0xFFFFFFFF为内核空间地址(1G)。

64位和32位的内存布局,如下:

用户空间的内存空间通常包括以下几个部分,每个部分用于存放不同类型的数据:

  • Text(代码段)

存放程序的机器指令(即编译后的代码)。这部分内存是只读的,包含程序的执行代码。

  • Data(数据段)

存放已初始化的全局变量和静态变量。这些变量在程序启动时就已经有了初始值。

  • BSS(Block Started by Symbol)段

存放未初始化的全局变量和静态变量。这些变量在程序启动时默认初始化为零或空。

  • Heap(堆)

用于动态内存分配,如通过 malloc、calloc、realloc等函数分配的内存。堆的大小可以通过 brk或 sbrk系统调用进行调整。

  • Stack(栈)

用于存放局部变量、函数调用的参数和返回地址。栈是后进先出(LIFO)的数据结构,用于管理函数调用和返回。

mmap(内存映射区域):

用于内存映射文件、共享库、匿名映射等。通过 mmap系统调用,可以将文件或其他对象映射到进程的地址空间。

  • 保留区

保留区是一些未明确分配用途的虚拟地址空间,这些区域可能用于未来的扩展或其他特殊用途。

由上图可以看出,进程的虚拟地址空间,由多个虚拟内存区域构成。虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。上图中所示的text数据段、Data段、Bss数据段、堆、栈、mmap区域,都是一个独立的虚拟内存区域。linux 内核使用的vm_area_struct 结构来表示一个独立的虚拟内存区域,由于每个不同质的虚拟内存区域功能和内部机制不同;因此同一个进程使用多个vm_area_struct 结构来分别表示不同类型的虚拟内存区域。各个vm_area_struct 结构使用链表或者树形结构链接,方便进程快速访问。如下图所示:

二、malloc工作原理

2.1 malloc实现流程

malloc 实现通常会使用 brk 或 mmap 系统调用来向内核请求内存。

对于小块内存(<= 128kB)分配,malloc 通常使用 brk 系统调用扩展数据段。

对于大块内存(> 128kB)分配,malloc 通常使用 mmap 系统调用创建新的匿名映射。

最终通过伙伴系统分配物理内存。

2.1.1 brk方式申请内存

如果用户分配的内存小于或等于 128 KB,通过 brk() 申请内存。brk()的实现方式很简单,就是通过 brk() 函数将堆顶指针向高地址移动,获得新的内存空间。使用brk()分配了一段新的虚拟内存区域,但这并不会立即分配物理内存。实际的物理内存分配通常是在访问新分配的虚拟内存区域时,如果发生了缺页异常(Page Fault),操作系统才会开始分配并映射相应的物理内存页面。如下所示:

malloc 通过 brk() 方式申请的内存,free 释放内存的时候,并不一定会把内存归还给操作系统,而是缓存在 malloc 的内存池中,待下次使用,这样就可以重复使用。

brk()系统调用的优点:

可以减少缺页异常的发生,提高内存访问效率。

brk()系统调用的缺点:

由于申请的内存没有归还系统,在内存工作繁忙时,频繁的内存分配和释放会造成内存碎片。brk()方式之所以会产生内存碎片,是由于brk通过移动堆顶的位置来分配内存,并且使用完不会立即归还系统,重复使用,如果高地址的内存不释放,低地址的内存是得不到释放的。

正是由于使用brk()会出现内存碎片,所以在申请大块内存的时候才会使用mmap()方式,mmap()是以页为单位进行内存分配和管理的,释放后就直接归还系统了,所以不会出现这种小碎片的情况。

2.1.2 mmap方式分配内存

mmap是在进程的虚拟地址空间中的文件映射区域找一块空闲的虚拟内存,内存分配器通常使用这个系统调用来创建私有匿名映射,以分配内存,但不会立即分配物理内存。

实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必调用read,write等系统调用函数。相反,内核空间的这段区域的修改也直接反映用户空间,从而可以实现不同进程的文件共享。如下:

2.2 核心代码

  • brk

用户程序通过malloc进行内存分配会系统调用brk

SYSCALL_DEFINE1(brk, unsigned long, brk)
{
    unsigned long newbrk, oldbrk, origbrk;
    struct mm_struct *mm = current->mm;
    struct vm_area_struct *brkvma, *next = NULL;
    unsigned long min_brk;
    bool populate;
    bool downgraded = false;
    LIST_HEAD(uf);
    MA_STATE(mas, &mm->mm_mt, 0, 0);

    if (mmap_write_lock_killable(mm))
        return -EINTR;

    origbrk = mm->brk;

#ifdef CONFIG_COMPAT_BRK
    /*
     * CONFIG_COMPAT_BRK can still be overridden by setting
     * randomize_va_space to 2, which will still cause mm->start_brk
     * to be arbitrarily shifted
     */
    if (current->brk_randomized)
        min_brk = mm->start_brk;
    else
        min_brk = mm->end_data;
#else
    min_brk = mm->start_brk;
#endif
    if (brk < min_brk)
        goto out;

    /*
     * Check against rlimit here. If this check is done later after the test
     * of oldbrk with newbrk then it can escape the test and let the data
     * segment grow beyond its set limit the in case where the limit is
     * not page aligned -Ram Gupta
     */
    if (check_data_rlimit(rlimit(RLIMIT_DATA), brk, mm->start_brk,
                  mm->end_data, mm->start_data))
        goto out;

    newbrk = PAGE_ALIGN(brk);
    oldbrk = PAGE_ALIGN(mm->brk);
    if (oldbrk == newbrk) {
        mm->brk = brk;
        goto success;
    }

    /*
     * Always allow shrinking brk.
     * do_brk_munmap() may downgrade mmap_lock to read.
     */
    if (brk <= mm->brk) {
        int ret;

        /* Search one past newbrk */
        mas_set(&mas, newbrk);
        brkvma = mas_find(&mas, oldbrk);
        if (!brkvma || brkvma->vm_start >= oldbrk)
            goto out; /* mapping intersects with an existing non-brk vma. */
        /*
         * mm->brk must be protected by write mmap_lock.
         * do_brk_munmap() may downgrade the lock,  so update it
         * before calling do_brk_munmap().
         */
        mm->brk = brk;
        ret = do_brk_munmap(&mas, brkvma, newbrk, oldbrk, &uf);
        if (ret == 1)  {
            downgraded = true;
            goto success;
        } else if (!ret)
            goto success;

        mm->brk = origbrk;
        goto out;
    }

    if (check_brk_limits(oldbrk, newbrk - oldbrk))
        goto out;

    /*
     * Only check if the next VMA is within the stack_guard_gap of the
     * expansion area
     */
    mas_set(&mas, oldbrk);
    next = mas_find(&mas, newbrk - 1 + PAGE_SIZE + stack_guard_gap);
    if (next) {
        vma_start_write(next);
        if (newbrk + PAGE_SIZE > vm_start_gap(next))
            goto out;
    }

    brkvma = mas_prev(&mas, mm->start_brk);
    /* Ok, looks good - let it rip. */
    if (do_brk_flags(&mas, brkvma, oldbrk, newbrk - oldbrk, 0) < 0)
        goto out;

    mm->brk = brk;

success:
    // 如果标记有VM_LOCKED的flag,立刻进行物理内存分配
    populate = newbrk > oldbrk && (mm->def_flags & VM_LOCKED) != 0;
    if (downgraded)
        mmap_read_unlock(mm);
    else
        mmap_write_unlock(mm);
    userfaultfd_unmap_complete(mm, &uf);
    if (populate)
        mm_populate(oldbrk, newbrk - oldbrk);
    return brk;

out:
    mmap_write_unlock(mm);
    return origbrk;
}

进程的堆边界:指进程虚拟地址空间中堆区域的结束地址,可动态调整。

内存动态分配:应用程序调用 malloc等函数请求内存时,如果当前堆空间不足,操作系统会通过调整堆边界来分配更多的内存。相反,当调用 free函数释放内存时,堆边界通常不会立即调整,而是由内存管理器在后续的内存分配请求中决定是否收缩堆。

  • do_brk_flags

根据给定的地址和长度,扩展或创建一个新的虚拟内存区域(VMA),并将其添加到当前进程的内存管理结构中。

static int do_brk_flags(struct ma_state *mas, struct vm_area_struct *vma,
        unsigned long addr, unsigned long len, unsigned long flags)
{
    struct mm_struct *mm = current->mm;

    validate_mm_mt(mm);
    /*
     * Check against address space limits by the changed size
     * Note: This happens *after* clearing old mappings in some code paths.
     */
    flags |= VM_DATA_DEFAULT_FLAGS | VM_ACCOUNT | mm->def_flags;
    if (!may_expand_vm(mm, flags, len >> PAGE_SHIFT))
        return -ENOMEM;

    if (mm->map_count > sysctl_max_map_count)
        return -ENOMEM;

    if (security_vm_enough_memory_mm(mm, len >> PAGE_SHIFT))
        return -ENOMEM;

    if (vma)
        vma_start_write(vma);
    /*
     * Expand the existing vma if possible; Note that singular lists do not
     * occur after forking, so the expand will only happen on new VMAs.
     */
    if (vma && vma->vm_end == addr && !vma_policy(vma) &&
        can_vma_merge_after(vma, flags, NULL, NULL,
                addr >> PAGE_SHIFT, NULL_VM_UFFD_CTX, NULL)) {
        mas_set_range(mas, vma->vm_start, addr + len - 1);
        if (mas_preallocate(mas, vma, GFP_KERNEL))
            return -ENOMEM;

        /* Set flags first to implicitly lock the VMA before updates */
        vm_flags_set(vma, VM_SOFTDIRTY);
        vma_adjust_trans_huge(vma, vma->vm_start, addr + len, 0);
        if (vma->anon_vma) {
            anon_vma_lock_write(vma->anon_vma);
            anon_vma_interval_tree_pre_update_vma(vma);
        }
        vma->vm_end = addr + len;
        mas_store_prealloc(mas, vma);

        if (vma->anon_vma) {
            anon_vma_interval_tree_post_update_vma(vma);
            anon_vma_unlock_write(vma->anon_vma);
        }
        khugepaged_enter_vma(vma, flags);
        goto out;
    }

    /* create a vma struct for an anonymous mapping */
    // 创建一个新的VMA
    vma = vm_area_alloc(mm);
    if (!vma)
        goto vma_alloc_fail;

    vma_set_anonymous(vma);
    vma->vm_start = addr;
    vma->vm_end = addr + len;
    vma->vm_pgoff = addr >> PAGE_SHIFT;
    vm_flags_init(vma, flags);
    vma->vm_page_prot = vm_get_page_prot(flags);
    vma_start_write(vma);
    mas_set_range(mas, vma->vm_start, addr + len - 1);
    // 新的VMA(虚拟内存区域)添加到当前进程的内存管理结构
    if (mas_store_gfp(mas, vma, GFP_KERNEL))
        goto mas_store_fail;

    mm->map_count++;
out:
    perf_event_mmap(vma);
    mm->total_vm += len >> PAGE_SHIFT;
    mm->data_vm += len >> PAGE_SHIFT;
    if (flags & VM_LOCKED)
        mm->locked_vm += (len >> PAGE_SHIFT);
    vm_flags_set(vma, VM_SOFTDIRTY);
    validate_mm(mm);
    return 0;

mas_store_fail:
    vm_area_free(vma);
vma_alloc_fail:
    vm_unacct_memory(len >> PAGE_SHIFT);
    return -ENOMEM;
}

  • mm_populate

为VMA分配物理内存。

int __mm_populate(unsigned long start, unsigned long len, int ignore_errors)
{
    ...
    // 查询VMA
    vma = find_vma_intersection(mm, nstart, end);
    ...
    ret = populate_vma_page_range(vma, nstart, nend, &locked);
   ...
        return ret; 
}

long populate_vma_page_range(struct vm_area_struct *vma,
                unsigned long start, unsigned long end, int *locked)
{
        struct mm_struct *mm = vma->vm_mm;
        unsigned long nr_pages = (end - start) / PAGE_SIZE;
        int gup_flags;
        long ret;
        ...
        
        ret = __get_user_pages(mm, start, nr_pages, gup_flags,
                                NULL, NULL, locked);
        lru_add_drain();
        return ret;
}

static long __get_user_pages(struct mm_struct *mm,
                unsigned long start, unsigned long nr_pages,
                unsigned int gup_flags, struct page **pages,
                struct vm_area_struct **vmas, int *locked)
{
        long ret = 0, i = 0;
        struct vm_area_struct *vma = NULL;
        struct follow_page_context ctx = { NULL };
        ...
     
        page = follow_page_mask(vma, start, foll_flags, &ctx);
        if (!page || PTR_ERR(page) == -EMLINK) {
                // 缺页异常处理
                ret = faultin_page(vma, start, &foll_flags,
                                   PTR_ERR(page) == -EMLINK, locked);
                ...
         }             
        return i ? i : ret;
}

这块代码量较大,有兴趣可以自己跟读后续的代码。

2.3 malloc分配物理内存的时机

触发缺页异常,会去分配物理内存。

2.4 malloc分配的实际内存大小

如果malloc内存分配函数最终通过linux伙伴系统(以页为单位)分配器进行物理内存分配,申请的内存4KB,一律分配4KB.

三、虚拟内存与物理内存

3.1 如何建立映射

虚拟内存和物理内存建立映射的关键步骤:

1) 页表的创建

当一个进程被创建时,操作系统会为该进程分配一个页表。页表的初始状态通常是空的,因为此时进程的虚拟地址空间还没有与物理内存建立任何映射。

2)页表条目的填充

随着进程的运行,操作系统会根据需要将虚拟页映射到物理页框。这通常发生在以下几种情况:

- 进程首次访问某个虚拟地址时,如果对应的虚拟页还没有映射到物理内存,会发生缺页异常,操作系统会处理这个异常,将所需的页从磁盘加载到物理内存,并更新页表。

- 操作系统主动为进程分配内存时,例如通过 `malloc` 或 `mmap` 等系统调用,操作系统会在物理内存中找到可用的页框,并更新页表,建立虚拟页到物理页框的映射。

3)页表的维护

操作系统需要持续地维护页表,确保虚拟地址空间和物理内存之间的映射是正确的。这包括在内存不足时进行页面置换(将不常用的页写回磁盘,释放物理页框),以及在进程结束时清理页表,释放相关的物理内存。

4)页表的更新

当物理内存中的页框发生变化时(例如,由于页面置换或内存回收),操作系统需要更新相关的页表条目,以反映最新的映射关系。

总结来说,虚拟内存和物理内存之间的映射是通过页表的创建、填充、维护和更新来建立和管理的。

3.2 分配物理内存

分配物理内存的基本步骤:

1)虚拟内存分配

当应用程序调用 malloc 或其他内存分配函数时,操作系统会在进程的虚拟地址空间中分配一块内存。

2)缺页异常

当进程首次尝试访问新分配的虚拟内存时,由于这块内存尚未映射到物理内存,会发生缺页异常。

3)物理内存分配

内核在处理缺页异常时,会检查是否有可用的物理内存页。如果有可用页,内核会通过伙伴系统或其他内存分配机制分配一个物理内存页。

4)建立映射

内核随后会将这个新分配的物理内存页映射到发生缺页异常的虚拟地址,并更新页表以反映这种映射关系。

一旦映射建立完成,进程就可以访问刚刚分配的内存。

3.3 物理内存访问

一旦虚拟内存与物理内存之间的映射建立完成,进程访问这块物理内存的过程是通过CPU的内存管理单元(MMU)和页表自动完成的。以下是进程访问物理内存的具体步骤:

1)虚拟地址生成

当进程中的指令需要访问内存时,它会生成一个虚拟地址。这个虚拟地址是进程在其虚拟地址空间中看到的地址。

2)虚拟地址到物理地址的转换

CPU中的MMU负责将虚拟地址转换为物理地址。MMU使用进程的页表来进行这种转换。页表中包含了虚拟页号到物理页帧号的映射信息。

3)物理地址计算

MMU首先查找虚拟地址的页表条目。如果页表条目中的present bit(存在位)被设置,表示该虚拟页已经映射到了物理内存中。如果在页表查找过程中发现present bit没有被设置,这意味着对应的虚拟页当前不在物理内存中,MMU会产生一个缺页异常。内核会处理这个异常,可能会从磁盘上的交换空间加载缺失的页,并更新页表和TLB。

MMU使用页表条目中的物理页帧号和虚拟地址中的页内偏移量来计算出最终的物理地址。

为了加速虚拟地址到物理地址的转换,CPU通常有一个TLB(Translation Lookaside Buffer),它是一个小型的硬件缓存,存储了最近使用的虚拟页到物理页的映射。如果TLB中有所需的映射,MMU可以直接从TLB中获取物理地址,而不需要访问页表,这样可以显著提高内存访问速度。

4)内存访问

计算出物理地址,CPU就可以直接访问物理内存中的数据。这个访问可以是读取操作,也可以是写入操作,具体取决于进程的指令。

整个过程对进程来说是透明的,进程只需要使用虚拟地址,而不需要知道物理内存的具体位置。内核和MMU共同确保了进程能够正确地访问其请求的内存。

四、new和malloc区别与联系

new分配内存:

int *p;
p = new int; //返回类型为int *类型,分配的大小为sizeof(int)
p = new int[100]; //返回类型为int *类型,分配的大小为sizeof(int) * 100

malloc分配内存:

int *p;
p = (int *)malloc(sizeof(int));

主要区别有3点:

1)new返回指定类型的指针,并且可以自动计算所需要的大小;而malloc则必须由我们计算字节数,并且在返回的是void*,需强转成实际指定类型的指针

2)malloc的实参是sizeof(int),用于指明一个整形数据需要的大小,如果我们写成p = (int *)malloc(1), 只是申请了一个字节的空间,如果向里面存放了一个整数的话,将会占用额外的3个字节,可能会改变原有内存空间中的数据

3)malloc只管分配内存,并不能对其进行初始化,所以得到的一片新内存中,其值将是随机的,可以用memset将其初始化为0

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

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

相关文章

传统CV算法——基于 SIFT 特征点检测与匹配实现全景图像拼接

全景图像拼接实现 定义 Stitcher 的类&#xff0c;用于实现两张图片的拼接。使用的技术是基于 SIFT 特征点检测与匹配&#xff0c;以及利用视角变换矩阵来对齐和拼接图像。 import numpy as np import cv2class Stitcher:#拼接函数def stitch(self, images, ratio0.75, repro…

Kubernetes 简介及部署方法

目录 1 Kubernetes 简介及原理 1.1 应用部署方式演变 1.2 容器编排应用 1.3 kubernetes 简介 1.4 K8S的设计架构 1.5 K8S 各组件之间的调用关系 1.6 K8S 的 常用名词感念 1.7 k8S的分层架构 2 K8S 集群环境搭建 2.1 k8s 中容器的管理方式 2.2 k8s中使用的几种管理容器的介绍 3 …

欧洲应用市场的特点

欧洲应用市场是一个充满活力和多样性的景观&#xff0c;其特点是复杂性和巨大的潜力。仅在27个欧盟&#xff08;EU&#xff09;国家就有5亿多人&#xff0c;该地区为希望扩大影响力的应用程序开发人员和企业提供了重要机会。然而&#xff0c;进入这个市场需要了解其独特的特征&…

幻觉问题综述

https://arxiv.org/pdf/2202.03629 分类 内在幻觉&#xff1a;生成的输出与源内容相矛盾 外部幻觉&#xff1a;生成的输出无法从源内容中验证 数据引发的幻觉&#xff08;来源不同引发分歧&#xff09; 训练和推理中的幻觉&#xff08;编码器不能很好的表征&#xff0c;解码…

【云原生-Docker】docker、docker-compose离线安装【包括dokcer、docker-compose资源下载】

资源资源下载在线下载百度网盘下载csdn下载 解压上传文件 配置系统配置配置 docker-compose安装验证 资源 资源下载 在线下载 下载地址&#xff1a;https://download.docker.com/linux/static/stable/x86_64/根据不同系统版本下载不同的docker版本在线下载&#xff1a;wget …

网络编程 0904作业

作业 1、多进程多线程并发服务器&#xff0c;再实现一遍&#xff08;重点模型&#xff09; 多进程并发服务器 多进程服务器 PIDserver.c 代码 #include <myhead.h> #define SERPORT 7777 #define SERIP "192.168.19.128" #define BACKLOG 10void hande(int…

【数据结构与算法 | 搜索二叉树篇 力扣篇】力扣530, 501

1. 力扣530&#xff1a;二叉搜索树的最小绝对差 1.1 题目&#xff1a; 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 示例 1&#xff1a; 输入&#xff1a;root [4,…

【人工智能】Transformers之Pipeline(十五):总结(summarization)

​​​​​​​ 目录 一、引言 二、总结&#xff08;summarization&#xff09; 2.1 概述 2.2 BERT与GPT的结合—BART 2.3 应用场景​​​​​​​ 2.4 pipeline参数 2.4.1 pipeline对象实例化参数 2.4.2 pipeline对象使用参数 ​​​​​​​ 2.4.3 pipeline返回参…

【MATLAB源码-第260期】基于simulink的OFDM+QPSK系统仿真,采用RS编码经过瑞利信道包含信道估计输出各节点波形图以及星座图。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 OFDM-QPSK系统是一种广泛应用于现代无线通信中的数字信号处理系统&#xff0c;结合了正交频分复用&#xff08;Orthogonal Frequency Division Multiplexing, OFDM&#xff09;和四相移相键控&#xff08;Quadrature Phase S…

Java字节码文件、组成、详解、分析;jclasslib插件、阿里arthas工具;Java注解

文章目录 一、字节码文件1.1 以正确的方式打开文件1.2 字节码文件的组成1.2.1 基础信息1.2.2 常量池1.2.3 方法 1.3 字节码常用工具1.4 总结 二、Java注解2.1 什么是Java注解2.2 注释和注解Annotation的区别&#xff08;掌握&#xff09;2.3 如何使用注解&#xff08;掌握&…

C语言典型例题61

《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 题目&#xff1a; 习题4.2 一个单位下设三个班组&#xff0c;每个班组人员不固定&#xff0c;需要统计每个班组的平均工资。分别输入3个班组所有职工的工资&#xff0c;当输入-1时&#xff0c;表示输入结束。输出…

常见排序方法详解(图示+方法)

一、插入排序 1.1基本思想 把待排序的记录 按其关键码值的大小逐个插入到一个已经排好序的有序序列中 &#xff0c;直到所有的记录插入完为止&#xff0c;得到 一个新的有序序列。 1.2直接插入排序 当插入第 i(i>1) 个元素时&#xff0c;前面的 array[0],array[1],…,array…

大文件上传vue插件vue-simple-uploader

https://www.cnblogs.com/xiahj/p/vue-simple-uploader.html

springboot-es(elasticsearch)搜索项目

目标界面 html页面 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>途牛旅游</title><link rel"stylesheet" href"https://a.amap.com/jsapi_demos/static/demo-center/css/d…

windows安装php7.4

windows安装php7.4 1.通过官网下载所需的php版本 首先从PHP官网&#xff08;https://www.php.net/downloads.php&#xff09;或者Windows下的PHP官网&#xff08;http://windows.php.net/download/&#xff09;下载Windows版本的PHP安装包。下载后解压到一个路径下。 2.配…

2024/9/4 Canlink配置介绍与常见故障排查

双击一个站进去配置&#xff0c;如果双击PLC则是PLC往外面发数据&#xff0c;双击伺服&#xff0c;则是伺服往外发数据。 例如我想读伺服的功能吗&#xff1f; 点击伺服的配置 将0b00的地址数据发给PLC&#xff08;D100&#xff09; ,寄存器长度是一个 然后下载程序即可

使用docker安装jenkins,然后使用jenkins本地发版和远程发版

使用docker安装jenkins&#xff0c;然后使用jenkins本地发版和远程发版 1、安装docker 1.安装必要的一些系统工具 sudo yum install docker-ce 2.添加软件源信息 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 3.更新…

电子行业最全【芯片标签二维码】知识剖析

电子行业最全【芯片标签二维码】知识剖析 本文为辰逸绅士小编原创&#xff0c;未经许可请勿私下复制转载 长 文 预 警 目录 ★01--------前言 ★02--------关于电子元器件协会ECIA ★03--------关于矩阵二维码 3.1--------矩阵二维码 构成 3.2--------矩阵二维码 种类 3.…

【数学分析笔记】第3章第1节 函数极限(3)

3. 函数极限与连续函数 3.1 函数极限 3.1.1 函数极限的性质 【局部有界性】若 lim ⁡ x → x 0 f ( x ) A \lim\limits_{x\to x_{0}}f(x)A x→x0​lim​f(x)A&#xff0c;则 ∃ δ > 0 , ∀ x ( 0 < ∣ x − x 0 ∣ < δ ) : m ≤ f ( x ) ≤ M \exists \delta>…

BUUCTF Crypto wp--RSA1

第一步 查看下载文件 我们发现出现了dp dq&#xff0c;属于dp、dq泄露攻击 上述方程本来是用于在加密中进行快速解密的&#xff0c;但是如果二者发生泄露&#xff0c;就有可能进行对密文的解密。 当我们知道了 dp、 dq、p、q、c,在不知道e的情况下&#xff0c;也可以求解明文。…