内存管理源码分析-mmap函数在内核的运行机制以及源码分析

2024-06-14 10:08

本文主要是介绍内存管理源码分析-mmap函数在内核的运行机制以及源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

mmap函数的介绍

mmap函数的主要作用是可以将一个文件或者设备的内容映射到内存当中,用户就可以通过一些内存操作方式(如memcpymemset)对文件或者设备进行直接的操作。这种操作可以减少一些IO的开销,如通过传统的读写文件的方式,可能会频繁的触发系统调用导致IO效率的降低。需要注意的是mmap函数的内存分配方式是页对齐的,即使用户只需要2字节的数据,mmap函数也会分配一个页的内存空间给用户。用户如果使用mmap函数对文件进行映射,用户在这段内存修改的数据不会马上回写到文件中,只用调用msync或者munmap函数后,修改后的数据才会同步到文件当中。

mmap函数原型是

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

函数参数含义如下:
start:用户指定的映射区起始地址,如果设置为0时表示由系统决定映射区的起始地址。
length:指定映射的长度,单位是字节,最终分配的空间是页对齐的
prot:prot代表protection,内存保护标志,指定这段内存空间的保护特性,有下列的保护方式

  • PROT_EXEC: 映射空间的数据可以执行
  • PROT_READ: 映射空间的数据可以读取
  • PROT_WRITE: 映射空间的数据可以写入
  • PROT_NONE: 映射空间的数据不可以访问

flags: 指定映射对象的类型,常用有如下类型

  • MAP_SHARED: 以共享内存的方式打开这段内存,即其他进程可以同时访问这段mmap出来的空间
  • MAP_PRIVATE: 于MAP_SHARED相反,这个进程自己独有的内存空间
  • MAP_LOCKED: 可以让系统不对mmap映射出来的内存进行换出
  • MAP_ANONYMOUS: 匿名映射,即不通过文件进行映射,直接返回一段无指定文件关联的内存
  • MAP_POPULATE: 提前为映射出来的内存建立好页表,可以减少用户访问过程中出发啊page-fault的次数,只能用于匿名映射

fd: 文件描述符,如果是基于文件的映射,这个fd应该是open函数的返回值,如果是匿名映射,则这个值需要设置为-1
offset: 开始映射的位置,例如文件映射,表示从文件第offset字节的位置开始,映射length字节长的数据

mmap函数的内核源码分析(基于Linux 4.x版本)

mmap函数有多种用法,包括私有文件映射,共享文件映射,私有匿名映射,共享匿名映射(只能用于具有父子关系的进程之间)。

sys_mmap函数

mmap函数与其他函数一样,都是通过系统调用进入内核,mmap函数对应的内核系统调用是sys_mmap函数:

unsigned long sys_mmap(unsigned long addr, unsigned long len,unsigned long prot, unsigned long flags, unsigned long fd, unsigned long off);
{long error;error = -EINVAL;if (off & ~PAGE_MASK) // 判断offset是否是页对齐,如果不是页对齐就返回错误goto out;// 注意off >> PAGE_SHIFT表达式,用于计算offset位于第几个页error = sys_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT);
out:return error;
}

从函数可以知道,sys_mmap函数主要处理了offset的页对齐问题。

sys_mmap_pgoff函数

sys_mmap_pgoff函数源码如下(省略部分不核心的代码),需要注意最后一个参数pgoff表示的是从第几个页开始映射:

unsigned long sys_mmap_pgoff(unsigned long addr, unsigned long len,unsigned long prot, unsigned long flags, unsigned long fd, unsigned long pgoff)
{struct file *file = NULL;unsigned long retval = -EBADF;if (!(flags & MAP_ANONYMOUS)) { // 如果不是匿名映射,则表示是基于文件的映射file = fget(fd); // 文件映射先根据fd获取都文件对应的file结构retval = -EINVAL;}flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);
out_fput:if (file) fput(file); //释放对file的引用
out:return retval;
}

sys_mmap_pgoff函数的代码可以知道,这个函数主要处理了文件映射和匿名映射的预处理,在进入vm_mmap_pgoff函数之前,如果file不等于NULL,则表示为文件映射,如果是NULL,则表示是匿名映射。

mm_struct和vm_area_struct结构的介绍

在分析vm_mmap_pgoff函数以及接下来的其他函数之前,需要简单介绍两个重要的数据结构,mm_struct以及vm_area_struct。简单来说,linux每一个进程都对应了一段虚拟地址空间,进程的所有程序的执行,数据的更新都是在这段虚拟地址空间上进行操作,然后通过页表机制,将虚拟地址空间转换为物理内存的地址空间,然后进行具体的数据执行和保存。Linux的进程是通过task_struct结构进行表示,那么每个进程对应的虚拟地址空间通过mm_struct进行表示,因此我们可以看到task_struct结构内包含mm_struct结构的成员变量。mm_struct结构管理一个list,这个list保存很多的内存块(因为linux是按需分配,因此一个mm_struct结构包含多个内存块),这些内存块通过vm_area_struct结构进行表示,它记录了具体的虚拟地址的起始以及结束范围,一般被称为vma

vm_mmap_pgoff函数

接下来正式分析vm_mmap_pgoff函数

unsigned long vm_mmap_pgoff(struct file *file, unsigned long addr,unsigned long len, unsigned long prot,unsigned long flag, unsigned long pgoff)
{unsigned long ret;struct mm_struct *mm = current->mm; // 获取当前进程的虚拟地址空间的管理结构unsigned long populate; // 对应MAP_POPULATE参数,表示是否提前建立好页表ret = security_mmap_file(file, prot, flag); // 这个函数与安全相关,这里不作分析,默认返回trueif (!ret) {down_write(&mm->mmap_sem);ret = do_mmap_pgoff(file, addr, len, prot, flag, pgoff,&populate); // 这个函数处理完毕之后,返回populate变量的值up_write(&mm->mmap_sem);if (populate)mm_populate(ret, populate); // 根据populate参数的值,是否进行建立页表等操作}return ret;
}

vm_mmap_pgoff函数处理了一下安全性问题就进入了下一阶段的函数,然后根据返回结构处理页表建立的问题。

do_mmap_pgoff函数

do_mmap_pgoff函数是mmap函数的核心处理流程,因此篇幅比较长,裁剪了一部分变量合法性检查的代码:

unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,unsigned long len, unsigned long prot,unsigned long flags, unsigned long pgoff,unsigned long *populate)
{struct mm_struct *mm = current->mm;vm_flags_t vm_flags;*populate = 0; // 初始化为不进行预先页表建立len = PAGE_ALIGN(len); // len原来是字节的长度,这里转换为页长度,即将申请映射长度进行页对齐addr = get_unmapped_area(file, addr, len, pgoff, flags); // 获取一段当前进程未被使用的虚拟地址空间,并返回其起始地址if (addr & ~PAGE_MASK) // 如果地址不是是页对齐,addr值可能是错误值(大概类似-EINVAL这种?),然后将错误结果返回给用户了return addr;vm_flags = calc_vm_prot_bits(prot) | calc_vm_flag_bits(flags) |mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC; // 检查flag的参数设置,将mmap的flag转换为vm_area_struct的flagif (flags & MAP_LOCKED)  // 如果设置了MAP_LOCKED参数if (!can_do_mlock()) // 检查一下内存可用空间等信息,看看是否可以进行mlockreturn -EPERM;if (mlock_future_check(mm, vm_flags, len)) // 继续检查是否可以进行mlockreturn -EAGAIN;if (file) { // 如果file不为NULL,则表示是基于文件的映射,如果是NULL则是匿名映射struct inode *inode = file_inode(file); // 根据file获取inode结构switch (flags & MAP_TYPE) { // 根据是私有映射还是共享映射进行不同的处理case MAP_SHARED: // 共享映射if ((prot&PROT_WRITE) && !(file->f_mode&FMODE_WRITE))return -EACCES;if (IS_APPEND(inode) && (file->f_mode & FMODE_WRITE)) // 判断是否为APPEND-ONLY文件,mmap不允许写入这种类型文件return -EACCES;if (locks_verify_locked(file))return -EAGAIN;// 更新一系列的使用于vm_area_struct的flagvm_flags |= VM_SHARED | VM_MAYSHARE;if (!(file->f_mode & FMODE_WRITE))vm_flags &= ~(VM_MAYWRITE | VM_SHARED);case MAP_PRIVATE: // 私有映射,也是设置flagif (!(file->f_mode & FMODE_READ)) // 如果文件本身不允许读,那么就直接返回return -EACCES;if (file->f_path.mnt->mnt_flags & MNT_NOEXEC) {if (vm_flags & VM_EXEC)return -EPERM;vm_flags &= ~VM_MAYEXEC;}if (!file->f_op->mmap)return -ENODEV;if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))return -EINVAL;break;default:return -EINVAL;}} else { // 匿名映射,也是设置flagswitch (flags & MAP_TYPE) {case MAP_SHARED:if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))return -EINVAL;pgoff = 0; // 共享匿名映射忽略pgoffvm_flags |= VM_SHARED | VM_MAYSHARE;break;case MAP_PRIVATE:pgoff = addr >> PAGE_SHIFT; // 匿名私有映射使用分配出来的addr作为pgoffbreak;default:return -EINVAL;}}addr = mmap_region(file, addr, len, vm_flags, pgoff);if (!IS_ERR_VALUE(addr) &&((vm_flags & VM_LOCKED) ||(flags & (MAP_POPULATE | MAP_NONBLOCK)) == MAP_POPULATE))*populate = len; // 根据返回的结果,以及MAP_POPULATE的参数,决定了进行页表建立的长度return addr;
}

这个函数首先处理的映射程度的页对齐,然后通过get_unmapped_area函数获取了一段未使用的内存,然后根据映射类型([文件,匿名] x [私有,共享])进行了对应的flag参数设置,接下来通过mmap_region函数完成映射过程,最后再判断一下是否要进行提前页表建立(populate=prefetch)。

mmap_region函数

mmap_region函数完成最后的映射过程,即将用户需要映射的虚拟地址范围建立起来,然后再将其加入当前进程的mm_struct结构中。

unsigned long mmap_region(struct file *file, unsigned long addr,unsigned long len, vm_flags_t vm_flags, unsigned long pgoff)
{struct mm_struct *mm = current->mm; // 获取当前进程的mm_struct结构struct vm_area_struct *vma, *prev;int error;struct rb_node **rb_link, *rb_parent;unsigned long charged = 0;if (!may_expand_vm(mm, len >> PAGE_SHIFT)) { // 检查需要的申请的长度是否超过的限制unsigned long nr_pages;if (!(vm_flags & MAP_FIXED))return -ENOMEM;nr_pages = count_vma_pages_range(mm, addr, addr + len);if (!may_expand_vm(mm, (len >> PAGE_SHIFT) - nr_pages))return -ENOMEM;}/* Clear old maps */error = -ENOMEM;
munmap_back:// 用find_vma_links函数寻找当前进程的虚拟地址空间所管理的内存块(vma)是否与目前预备分配的内存块的地址有相交的关系,如果有先将其unmapif (find_vma_links(mm, addr, addr + len, &prev, &rb_link, &rb_parent)) {if (do_munmap(mm, addr, len))return -ENOMEM;goto munmap_back;}if (accountable_mapping(file, vm_flags)) {charged = len >> PAGE_SHIFT;if (security_vm_enough_memory_mm(mm, charged))return -ENOMEM;vm_flags |= VM_ACCOUNT;}// 当前申请的虚拟地址空间是否可以当前进程的虚拟地址空间进行合并,如果可以合并,直接修改当前进程的vma的vm_start和vm_end的值vma = vma_merge(mm, prev, addr, addr + len, vm_flags, NULL, file, pgoff, NULL);if (vma)goto out;// 如果无法合并,根据用户申请的地址空间范围,分配一个新的vma结构vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);if (!vma) {error = -ENOMEM;goto unacct_error;}// 初始化vma的信息vma->vm_mm = mm;vma->vm_start = addr;vma->vm_end = addr + len;vma->vm_flags = vm_flags;vma->vm_page_prot = vm_get_page_prot(vm_flags);vma->vm_pgoff = pgoff;INIT_LIST_HEAD(&vma->anon_vma_chain);if (file) { // 如果是文件映射if (vm_flags & VM_DENYWRITE) { // 文件不允许写入error = deny_write_access(file);if (error)goto free_vma;}if (vm_flags & VM_SHARED) { // 内存是共享的,能被其他进程访问error = mapping_map_writable(file->f_mapping); // 增加mapping共享vma的统计数目,如果非共享的情况下,mapping的共享vma统计数目是-1if (error)goto allow_write_and_free_vma;}vma->vm_file = get_file(file); // 增加file的引用计数,然后赋给vma->vm_fileerror = file->f_op->mmap(file, vma); // 调用文件系统的mmap函数作处理,文件系统会根据设计设定各自的mmap函数,同时还是设定fault函数去处理page fault的情况if (error)goto unmap_and_free_vma;// 执行文件系统自身的mmap函数之后,重新赋值addr = vma->vm_start;vm_flags = vma->vm_flags;} else if (vm_flags & VM_SHARED) { // 如果是匿名共享映射error = shmem_zero_setup(vma); // 将映射的文件指向/dev/zero设备文件,基于这个设备文件创建共享内存映射,并利用system V的一套共享内存机制if (error)goto free_vma;}vma_link(mm, vma, prev, rb_link, rb_parent); // 将新的vma结构加入到当前进程管理的虚拟地址空间的结构(mm_struct)的list当中if (file) {if (vm_flags & VM_SHARED)mapping_unmap_writable(file->f_mapping);if (vm_flags & VM_DENYWRITE)allow_write_access(file);}file = vma->vm_file;
out:perf_event_mmap(vma);vm_stat_account(mm, vm_flags, file, len >> PAGE_SHIFT); // 更新当前的虚拟地址空间的使用统计信息if (vm_flags & VM_LOCKED) {if (!((vm_flags & VM_SPECIAL) || is_vm_hugetlb_page(vma) ||vma == get_gate_vma(current->mm)))mm->locked_vm += (len >> PAGE_SHIFT);elsevma->vm_flags &= ~VM_LOCKED;}if (file)uprobe_mmap(vma);vma->vm_flags |= VM_SOFTDIRTY;vma_set_page_prot(vma);return addr;
}

根据源码我们可以知道,该函数根据用户需要分配的地址空间的信息,或扩展当前的进程的虚拟地址空间范围,或创建一个新的vma结构加入到进程的mm_struct当中,这样当前进程就有了可以直接访问的mmap分配的内存区域。这样就完成了整个mmap的映射过程,但实质上只是分配了vma结构去进程的虚拟地址空间当中,访问的时候会触发page-fault缺页异常,才会给这些刚刚分配的虚拟地址空间的vma结构建立虚拟地址和物理地址的映射关系。

文件私有映射和文件共享映射

基于mmap_region函数的分析可以发现: 基于mmap函数的私有文件映射和共享文件映射都需要建立各自的vma结构加入到当前进程的地址虚拟地址空间。对于共享文件映射(共享内存)而言,不同进程之间都是建立各自的vma结构加入到mm_struct,这些各自的vma结构可能还没有建立虚拟地址到物理地址的页表映射,因此可能需要触发多次page-fault缺页异常,才能建立好各自进程的页表映射。

如进程A通过mmap函数创建了一个基于文件的共享内存区域,进程A将对应范围的vma结构加入进程A的mm_struct结构中,在后续访问中,通过page-fault缺页异常,逐渐构建了页表映射。此时进程B也通过mmap函数访问这段共享内存区域,此时仍然需要通过mmap_region函数创建vma结构,加入到进程Bmm_struct结构中。虽然进程A为这段共享内存创建好了页表,但是对于进程B的页表仍然是没有创建好,仍然需要page-fault缺页异常构建其页表映射,即使进程A和进程B的页表映射都会指向同一段内存区域。因此,基于文件映射而言,会触发比较多的page-fault缺页异常,影响到IO的性能。

匿名私有映射和匿名共享映射

匿名私有映射

匿名私有映射的作用是返回一段匿名的内存空间给用户,但是也许会有疑问,这和一般通过malloc函数分配的内存空间有什么差异呢。在分析差异之前,需要了解基于glibcmalloc函数是如何实现的。

malloc函数基于三种方式给用户分配内存(需要注意的是,分配的都是虚拟地址空间,即vma,实际的物理内存还是需要page-fault缺页异常才能真实分配):

  • 字节级别的内存分配: malloc内部有一个许多小的内存块(chunk)组成的chunk list,这个chunk list包含了小的内存块,当用户内存块的时候会首先在chunk list找是否有满足需求的内存块,如果有就直接返回,这样的设计是为了避免频繁的系统调用。
  • 基于brk的内存分配: 当chunk list无法满足用户的需求时,就要通过brk系统调用,brk系统调用主要是将进程虚拟地址空间的data段的尾指针(end_data))根据内存请求尺寸扩展一下,同时更改或创建对应的vma,同时内核和进程都记录更新之后data段尾指针的地址,后续访问中再通过page-fault缺页异常获得这段空间的实际物理内存。
  • 基于mmap的内存分配: 当请求内存的空间大于128KB(默认值,可调)时,会调用mmap匿名映射进行内存分配,它的分配位置时位于进程虚拟地址空间的堆和栈之间的一块独立的空间,同时也会创建一个新的vma结构,被进程记录。

brk是通过扩展data尾指针(end_data)的方式进行虚拟地址空间的分配,那么带来一个现象,如果用户连续通过malloc函数分配50KB,以及80KB的内存,那么就会将进程的end_data指针扩位于130KB处,如果这个时候释放掉50KB的空间,那么这段空间不会实际释放,因为如果将指针地址减去30KB,那么第二次分配的80KB的数据就是寻址错误,因此基于brk的内存必须等指针末尾的内存free了之后,才能free前面的内存。

mmap是在堆和栈之间的找一块独立的空间进行分配,那么这段独立的空间就可以自由地真正地释放掉内存。

因此brkmmap的主要差别在于其分配的方式,以及是否真正释放内存。

那么为什么内存不全用mmap的方式进行分配呢? 这是因为mmap的开销相对比较大,而且mmap释放的空间都是真释放的,因此重新mmap一段内存的时候会触发大量的page-fault缺页异常。而brk分配的空间,由于不是真正释放,因此可以将那段假释放的空间重新投入使用,这样不会触发大量page-fault缺页异常。

因此,通过上面的分析以及讨论,我们可以知道通过mmap函数进行匿名私有分配的内存的特性以及用途。

匿名共享映射

匿名共享映射主要作用是建立一块没有backing-file的共享内存,即没有对应一个实际的文件系统上的普通文件的共享内存(实际上匿名共享映射对应了一个特殊的文件)。它相对基于文件的共享映射有更高的性能,因为文件所在的硬盘相对于内存是非常慢的设备,因此触发page-fault时,他需要花很多时间访问硬盘才能将该文件对应的页的数据读入到内存,然后返回给用户。需要注意的是基于mmap的共享内存只能适用于具有父子关系的进程之间。

匿名共享映射的实现基于POSIX共享内存机制,我们针对这部分进行源码分析,我们从mmap_region函数的调用的shmem_zero_setup函数开始:

shmem_zero_setup函数

shmem_zero_setup函数获得了一个特殊的文件,然后基于这个特殊的文件进行共享内存映射,并获取了一套共享内存专用的操作函数集合。

int shmem_zero_setup(struct vm_area_struct *vma)
{struct file *file;loff_t size = vma->vm_end - vma->vm_start; //计算需要分配的空间file = shmem_file_setup("dev/zero", size, vma->vm_flags); // 打开特殊设备文件/dev/zero,作为backed-fileif (IS_ERR(file))return PTR_ERR(file);if (vma->vm_file)fput(vma->vm_file);vma->vm_file = file; // 将vma->file更新为匿名共享内存对应的特殊的设备文件vma->vm_ops = &shmem_vm_ops; // 使用匿名共享内存的一套操作函数集合return 0;
}static const struct vm_operations_struct shmem_vm_ops = {.fault		= shmem_fault,.map_pages	= filemap_map_pages,
};

shmem_fault函数的主要作用是当触发page-fault缺页异常时,会调用这个共享内存专用的处理函数,进行一些处理。

shmem_file_setup函数

POSXI共享内存的内部,维护了一个tmpfs文件系统,所有的用于共享内存映射的特殊文件都是保存在这个文件系统中`。

struct file *shmem_file_setup(const char *name, loff_t size, unsigned long flags)
{return __shmem_file_setup(name, size, flags, 0); // 简单地调用__shmem_file_setup函数
}static struct file *__shmem_file_setup(const char *name, loff_t size,unsigned long flags, unsigned int i_flags)
{struct file *res;struct inode *inode;struct path path;struct super_block *sb;struct qstr this;if (IS_ERR(shm_mnt)) // 全局变量,表示TMPFS的挂载信息return ERR_CAST(shm_mnt);if (size < 0 || size > MAX_LFS_FILESIZE) // 大于最大可分配的内存空间return ERR_PTR(-EINVAL);if (shmem_acct_size(flags, size)) // 是否够空间进行分配return ERR_PTR(-ENOMEM);// 下面的所有操作都是为了在tmpfs建立一个特殊映射文件res = ERR_PTR(-ENOMEM);this.name = name; // 初始化名字this.len = strlen(name);this.hash = 0;sb = shm_mnt->mnt_sb;path.mnt = mntget(shm_mnt);path.dentry = d_alloc_pseudo(sb, &this); // 创建dentryif (!path.dentry)goto put_memory;d_set_d_op(path.dentry, &anon_ops); // 设置flagres = ERR_PTR(-ENOSPC);inode = shmem_get_inode(sb, NULL, S_IFREG | S_IRWXUGO, 0, flags); // 从tmpfs获取一个特殊映射文件对应的inode结构if (!inode)goto put_memory;inode->i_flags |= i_flags;d_instantiate(path.dentry, inode); // 将dentry和inode链接在一起,建立联系inode->i_size = size; // 文件尺寸为需要映射的空间的尺寸clear_nlink(inode);res = ERR_PTR(ramfs_nommu_expand_for_mapping(inode, size));if (IS_ERR(res))goto put_path;res = alloc_file(&path, FMODE_WRITE | FMODE_READ,&shmem_file_operations); // 有了inode和dentry之后,就可以创建file结构,并给与特殊映射文件专用的处理函数集合if (IS_ERR(res))goto put_path;return res; // 返回这个特殊映射文件的file结构put_memory:shmem_unacct_size(flags, size);
put_path:path_put(&path);return res;
}

综上所述,匿名共享映射其实本质上只是在tmpfs上建立一个特殊的映射文件(这个特殊的文件在用户态不可见),然后给这个映射文件赋予映射所需要的函数集合,如shmem_vm_ops函数集合,那么在触发缺页异常的时候,就会根据这个函数集合处理缺页异常,返回给用户实际的物理页信息。

同时也可以发现,为什么匿名共享内存只能用于具有父子关系的进程之间,这是因为不同的进程之间,由于是匿名映射,无法找到一个共同的特殊文件进行映射(由于该映射特殊文件在用户态不可见)。

这篇关于内存管理源码分析-mmap函数在内核的运行机制以及源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1060100

相关文章

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

安全管理体系化的智慧油站开源了。

AI视频监控平台简介 AI视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。用户只需在界面上进行简单的操作,就可以实现全视频的接入及布控。摄像头管理模块用于多种终端设备、智能设备的接入及管理。平台支持包括摄像头等终端感知设备接入,为整个平台提

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL