Linux内核源代码情景分析-系统调用brk()

2023-11-19

     首先看下进程地址空间示意图:



    我们简单的说,从低地址到高地址,代码区和数据区,空洞,堆栈区。
    在Linux内核源代码情景分析-内存管理之用户堆栈的扩展,我们申请了从堆栈区往下,数据区上面的页面。
    在Linux内核源代码情景分析-内存管理之用户页面的换入,我们申请了用于换入/换出的页面。
    在本文中,我们申请的是从数据区往上,堆栈区下面的页面。
    我们通过一个实例来分析,brk(),见下图:

    


    1、由于新边界比旧边界地址高,我们申请旧边界和新边界之间的页面。就是把对应的虚拟地址映射到物理页面。
    brk对应的系统调用是sys_brk,代码如下:

asmlinkage unsigned long sys_brk(unsigned long brk)
{
	unsigned long rlim, retval;
	unsigned long newbrk, oldbrk;
	struct mm_struct *mm = current->mm;

	down(&mm->mmap_sem);

	if (brk < mm->end_code)//brk不能大于代码段末端地址
		goto out;
	newbrk = PAGE_ALIGN(brk);
	oldbrk = PAGE_ALIGN(mm->brk);
	if (oldbrk == newbrk)
		goto set_brk;

	/* Always allow shrinking brk. */
	if (brk <= mm->brk) {//目前新边界大于旧边界
		if (!do_munmap(mm, newbrk, oldbrk-newbrk))
			goto set_brk;
		goto out;
	}

	/* Check against rlimit.. */
	rlim = current->rlim[RLIMIT_DATA].rlim_cur;
	if (rlim < RLIM_INFINITY && brk - mm->start_data > rlim)//不能超过限制
		goto out;

	/* Check against existing mmap mappings. */
	if (find_vma_intersection(mm, oldbrk, newbrk+PAGE_SIZE))//看看是否与已经存在的虚拟空间有冲突
		goto out;

	/* Check if we have enough memory.. */
	if (!vm_enough_memory((newbrk-oldbrk) >> PAGE_SHIFT))//是否有足够的空闲内存页面
		goto out;

	/* Ok, looks good - let it rip. */
	if (do_brk(oldbrk, newbrk-oldbrk) != oldbrk)//新边界大于旧边界,建立映射
		goto out;
set_brk:
	mm->brk = brk;//brk设置为新的边界
out:
	retval = mm->brk;
	up(&mm->mmap_sem);
	return retval;
}


    本例中,新边界大于旧边界,我们建立映射。do_brk代码如下:

unsigned long do_brk(unsigned long addr, unsigned long len)
{
	struct mm_struct * mm = current->mm;
	struct vm_area_struct * vma;
	unsigned long flags, retval;

	len = PAGE_ALIGN(len);
	if (!len)
		return addr;

	/*
	 * mlock MCL_FUTURE?
	 */
	if (mm->def_flags & VM_LOCKED) {
		unsigned long locked = mm->locked_vm << PAGE_SHIFT;
		locked += len;
		if (locked > current->rlim[RLIMIT_MEMLOCK].rlim_cur)
			return -EAGAIN;
	}

	/*
	 * Clear old maps.  this also does some error checking for us
	 */
	retval = do_munmap(mm, addr, len);//find_vm_intersection对冲突的检查,实际上检查的只是新区的高端,没有检查低端。对于低端的冲突是允许的,解决的办法是以新的映射为准,先通过do_munmap把原来的映射解除,再来建立映射
	if (retval != 0)
		return retval;

	/* Check against address space limits *after* clearing old maps... */
	if ((mm->total_vm << PAGE_SHIFT) + len   
	    > current->rlim[RLIMIT_AS].rlim_cur)
		return -ENOMEM;

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

	if (!vm_enough_memory(len >> PAGE_SHIFT))
		return -ENOMEM;

	flags = vm_flags(PROT_READ|PROT_WRITE|PROT_EXEC,
				MAP_FIXED|MAP_PRIVATE) | mm->def_flags;

	flags |= VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
	

	/* Can we just expand an old anonymous mapping? */
	if (addr) {//先看看是否可以跟原有的区间合并
		struct vm_area_struct * vma = find_vma(mm, addr-1);
		if (vma && vma->vm_end == addr && !vma->vm_file && 
		    vma->vm_flags == flags) {
			vma->vm_end = addr + len;
			goto out;
		}
	}	


	/*
	 * create a vma struct for an anonymous mapping
	 */
	vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);//如果不能跟原有的区间合并,则分配vm_area_struct结构
	if (!vma)
		return -ENOMEM;

	vma->vm_mm = mm;
	vma->vm_start = addr;//起始地址
	vma->vm_end = addr + len;//结束地址
	vma->vm_flags = flags;
	vma->vm_page_prot = protection_map[flags & 0x0f];
	vma->vm_ops = NULL;
	vma->vm_pgoff = 0;
	vma->vm_file = NULL;
	vma->vm_private_data = NULL;

	insert_vm_struct(mm, vma);

out:
	mm->total_vm += len >> PAGE_SHIFT;
	if (flags & VM_LOCKED) {//仅在对区间加锁时才调用make_pages_present
		mm->locked_vm += len >> PAGE_SHIFT;
		make_pages_present(addr, addr + len);//为新增的虚拟空间建立起对内存页面的映射
	}
	return addr;
}

    make_pages_present,代码如下:
int make_pages_present(unsigned long addr, unsigned long end)
{
	int write;
	struct mm_struct *mm = current->mm;
	struct vm_area_struct * vma;

	vma = find_vma(mm, addr);
	write = (vma->vm_flags & VM_WRITE) != 0;
	if (addr >= end)
		BUG();
	do {
		if (handle_mm_fault(mm, vma, addr, write) < 0)
			return -1;
		addr += PAGE_SIZE;
	} while (addr < end);
	return 0;
}

   这里所用的方法很有趣,那就是对新区间中的每一个页面模拟一次缺页异常。


    最后返回sys_brk,mm->brk设置为新的边界。

    2、由于新边界比旧边界地址低,我们释放新边界和旧边界之前的页面。如下图:

    


    brk对应的系统调用是sys_brk,代码如下:

asmlinkage unsigned long sys_brk(unsigned long brk)
{
	unsigned long rlim, retval;
	unsigned long newbrk, oldbrk;
	struct mm_struct *mm = current->mm;

	down(&mm->mmap_sem);

	if (brk < mm->end_code)
		goto out;
	newbrk = PAGE_ALIGN(brk);
	oldbrk = PAGE_ALIGN(mm->brk);
	if (oldbrk == newbrk)
		goto set_brk;

	/* Always allow shrinking brk. */
	if (brk <= mm->brk) {//目前旧边界大于新边界
		if (!do_munmap(mm, newbrk, oldbrk-newbrk))//解除映射
			goto set_brk;
		goto out;
	}

	/* Check against rlimit.. */
	rlim = current->rlim[RLIMIT_DATA].rlim_cur;
	if (rlim < RLIM_INFINITY && brk - mm->start_data > rlim)
		goto out;

	/* Check against existing mmap mappings. */
	if (find_vma_intersection(mm, oldbrk, newbrk+PAGE_SIZE))
		goto out;

	/* Check if we have enough memory.. */
	if (!vm_enough_memory((newbrk-oldbrk) >> PAGE_SHIFT))
		goto out;

	/* Ok, looks good - let it rip. */
	if (do_brk(oldbrk, newbrk-oldbrk) != oldbrk)
		goto out;
set_brk:
	mm->brk = brk;//设置为新的边界	
out:
	retval = mm->brk;
	up(&mm->mmap_sem);
	return retval;
}

    do_munmap解除映射,如下:

int do_munmap(struct mm_struct *mm, unsigned long addr, size_t len)
{
	struct vm_area_struct *mpnt, *prev, **npp, *free, *extra;

	if ((addr & ~PAGE_MASK) || addr > TASK_SIZE || len > TASK_SIZE-addr)
		return -EINVAL;

	if ((len = PAGE_ALIGN(len)) == 0)
		return -EINVAL;

	/* Check if this memory area is ok - put it on the temporary
	 * list if so..  The checks here are pretty simple --
	 * every area affected in some way (by any overlap) is put
	 * on the list.  If nothing is put on, nothing is affected.
	 */
	mpnt = find_vma_prev(mm, addr, &prev);//试图找到结束地址高于addr的第一个区间
	if (!mpnt)
		return 0;
	/* we have  addr < mpnt->vm_end  */

	if (mpnt->vm_start >= addr+len)//如果该区间的起始地址也高于addr_len,说明落到了空洞中
		return 0;

	/* If we'll make "hole", check the vm areas limit */
	if ((mpnt->vm_start < addr && mpnt->vm_end > addr+len)
	    && mm->map_count >= MAX_MAP_COUNT)
		return -ENOMEM;

	/*
	 * We may need one additional vma to fix up the mappings ... 
	 * and this is the last chance for an easy error exit.
	 */
	extra = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
	if (!extra)
		return -ENOMEM;

	npp = (prev ? &prev->vm_next : &mm->mmap);
	free = NULL;
	spin_lock(&mm->page_table_lock);
	for ( ; mpnt && mpnt->vm_start < addr+len; mpnt = *npp) {
		*npp = mpnt->vm_next;
		mpnt->vm_next = free;
		free = mpnt;
		if (mm->mmap_avl)
			avl_remove(mpnt, &mm->mmap_avl);
	}
	mm->mmap_cache = NULL;	/* Kill the cache. */
	spin_unlock(&mm->page_table_lock);

	/* Ok - we have the memory areas we should free on the 'free' list,
	 * so release them, and unmap the page range..
	 * If the one of the segments is only being partially unmapped,
	 * it will put new vm_area_struct(s) into the address space.
	 * In that case we have to be careful with VM_DENYWRITE.
	 */
	while ((mpnt = free) != NULL) {
		unsigned long st, end, size;
		struct file *file = NULL;

		free = free->vm_next;

		st = addr < mpnt->vm_start ? mpnt->vm_start : addr;
		end = addr+len;
		end = end > mpnt->vm_end ? mpnt->vm_end : end;
		size = end - st;

		if (mpnt->vm_flags & VM_DENYWRITE &&
		    (st != mpnt->vm_start || end != mpnt->vm_end) &&
		    (file = mpnt->vm_file) != NULL) {
			atomic_dec(&file->f_dentry->d_inode->i_writecount);
		}
		remove_shared_vm_struct(mpnt);
		mm->map_count--;

		flush_cache_range(mm, st, end);
		zap_page_range(mm, st, size);//解除若干连续页面的映射
		flush_tlb_range(mm, st, end);

		/*
		 * Fix the mapping, and free the old area if it wasn't reused.
		 */
		extra = unmap_fixup(mm, mpnt, st, size, extra);
		if (file)
			atomic_inc(&file->f_dentry->d_inode->i_writecount);
	}

	/* Release the extra vma struct if it wasn't used */
	if (extra)
		kmem_cache_free(vm_area_cachep, extra);

	free_pgtables(mm, prev, addr, addr+len);//释放已经为空的页表所占的页面

	return 0;
}


    这里我们主要分析,zap_page_range,解除若干连续页面的映射;其他代码我们就假设是使vma->vm_end指向了新的边界,而不是旧的边界。

    zap_page_range代码如下:

void zap_page_range(struct mm_struct *mm, unsigned long address, unsigned long size)
{
	pgd_t * dir;
	unsigned long end = address + size;
	int freed = 0;

	dir = pgd_offset(mm, address);

	/*
	 * This is a long-lived spinlock. That's fine.
	 * There's no contention, because the page table
	 * lock only protects against kswapd anyway, and
	 * even if kswapd happened to be looking at this
	 * process we _want_ it to get stuck.
	 */
	if (address >= end)
		BUG();
	spin_lock(&mm->page_table_lock);
	do {
		freed += zap_pmd_range(mm, dir, address, end - address);
		address = (address + PGDIR_SIZE) & PGDIR_MASK;
		dir++;
	} while (address && (address < end));
	spin_unlock(&mm->page_table_lock);
	/*
	 * Update rss for the mm_struct (not necessarily current->mm)
	 * Notice that rss is an unsigned long.
	 */
	if (mm->rss > freed)
		mm->rss -= freed;
	else
		mm->rss = 0;
}


    zap_pmd_range,如下:
static inline int zap_pmd_range(struct mm_struct *mm, pgd_t * dir, unsigned long address, unsigned long size)
{
	pmd_t * pmd;
	unsigned long end;
	int freed;

	if (pgd_none(*dir))
		return 0;
	if (pgd_bad(*dir)) {
		pgd_ERROR(*dir);
		pgd_clear(dir);
		return 0;
	}
	pmd = pmd_offset(dir, address);
	address &= ~PGDIR_MASK;
	end = address + size;
	if (end > PGDIR_SIZE)
		end = PGDIR_SIZE;
	freed = 0;
	do {
		freed += zap_pte_range(mm, pmd, address, end - address);
		address = (address + PMD_SIZE) & PMD_MASK; 
		pmd++;
	} while (address < end);
	return freed;
}


    zap_pte_range,代码如下:
static inline int zap_pte_range(struct mm_struct *mm, pmd_t * pmd, unsigned long address, unsigned long size)
{
	pte_t * pte;
	int freed;

	if (pmd_none(*pmd))
		return 0;
	if (pmd_bad(*pmd)) {
		pmd_ERROR(*pmd);
		pmd_clear(pmd);
		return 0;
	}
	pte = pte_offset(pmd, address);
	address &= ~PMD_MASK;
	if (address + size > PMD_SIZE)
		size = PMD_SIZE - address;
	size >>= PAGE_SHIFT;
	freed = 0;
	for (;;) {
		pte_t page;
		if (!size)
			break;
		page = ptep_get_and_clear(pte);//清空页表项,并返回原页表项
		pte++;
		size--;
		if (pte_none(page))//如果页表项什么都没有,继续循环
			continue;
		freed += free_pte(page);//解除对内存页面以及盘上页面的使用
	}
	return freed;
}

    free_pte,代码如下:

static inline int free_pte(pte_t pte)
{
	if (pte_present(pte)) {//页表项一定有内容;如果页面没有在内存中,一定是交换页面,只要调用swap_free;如果页面在内存中,则往下执行,有可能是交换页面,也有可能是普通页面
		struct page *page = pte_page(pte);//获得page结构指针
		if ((!VALID_PAGE(page)) || PageReserved(page))
			return 0;
		/* 
		 * free_page() used to be able to clear swap cache
		 * entries.  We may now have to do it manually.  
		 */
		if (pte_dirty(pte) && page->mapping)
			set_page_dirty(page);//设置该页面page结构中的PG_dirty,并在相应的address_space结构中将其移入dirty_pages队列
		free_page_and_swap_cache(page);//解除对交换页面或者普通页面的使用
		return 1;
	}
	swap_free(pte_to_swp_entry(pte));//如果页面没有在内存,那就只调用swap_free
	return 0;
}


    free_page_and_swap_cache,解除对交换页面或者普通页面的使用,代码如下:
void free_page_and_swap_cache(struct page *page)
{
	/* 
	 * If we are the only user, then try to free up the swap cache. 
	 */
	if (PageSwapCache(page) && !TryLockPage(page)) {//如果是交换页面
		if (!is_page_shared(page)) {//当前进程是这个页面的最后一个用户,假设使用计数为2
			delete_from_swap_cache_nolock(page);//从三个队列中脱离处理
		}
		UnlockPage(page);
	}
	page_cache_release(page);//普通页面现在使用计数为1,只调用这个函数,释放对应的页面;交换页面delete_from_swap_cache_nolock后使用计数也变为1,再调用这个函数,释放对应的页面
}


    一个page数据结构,同时在三个队列,一是通过其队列头list链入某个换入/换出队列,即相应address_space结构中的clean_pages、dirty_pages以及locked_pages三个队列之一;二是通过其队列头lru链入某个LRU队列,即active_list、inactive_dirty_list或者某个inactive_clean_list之一;最后就是通过指针next_hash链入一个杂凑队列。

    delete_from_swap_cache_nolock将页面从上述队列中脱离出来,代码如下:

void delete_from_swap_cache_nolock(struct page *page)
{
	if (!PageLocked(page))
		BUG();

	if (block_flushpage(page, 0))
		lru_cache_del(page);//lru脱链

	spin_lock(&pagecache_lock);
	ClearPageDirty(page);
	__delete_from_swap_cache(page);//list,next_hash脱链
	spin_unlock(&pagecache_lock);
	page_cache_release(page);//使用计数减1,变成1了
}

    返回free_page_and_swap_cache,调用page_cache_release。如果是普通页面现在使用计数为1,只调用这个函数,释放对应的页面;如果是交换页面,delete_from_swap_cache_nolock后使用计数也变为1,再调用这个函数,释放对应的页面。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux内核源代码情景分析-系统调用brk() 的相关文章

  • 【Transformer系列(3)】 《Attention Is All You Need》论文超详细解读(翻译+精读)

    前言 哒哒 时隔好久终于继续出论文带读了 这次回归当然要出一手王炸呀 没错 今天我们要一起学习的就是传说中的Transformer 在2021年Transformer一经论文 Attention is All You Need 提出 就如龙
  • java数组学习

    2021 2 2 数组 一维数组的使用 1 一维数组的声明和初始化 2 如何调用数组的指定位置的元素 3 如何获取数组的长度 4 如何遍历数组 5 数组元素的默认初始化值 6 数组的内存解析 package day01 import jav
  • PyTorch-12 GAN、WGAN

    PyTorch 12 生成对抗网络 GAN WGAN 参考 https zhuanlan zhihu com p 34287744 GAN模型的目标函数如下 GAN模型优化训练 在训练过程中 生成网络的目标就是尽量生成真实的图片去欺骗判别网
  • ubuntu18.04 桌面卡死解决方法

    转载 https blog csdn net ATOOHOO article details 88169508 两个月关机 放个周末两天回来 使用系统文件夹很卡 直到因为复制文件卡死 内存和交换机空间都没满 第二次桌面又卡 因为心急 或者因
  • CUDA的下载安装

    大家好 下面将进行CUDA的下载安装 下载安装的详细步骤描述如下 1 CUDA下载 https download csdn net download qq 41104871 87462747 2 CUDA安装 1 首先 需要解压缩下载好的C
  • docker入门实践,制作Dockerfile镜像

    目前我知道的自制镜像有2种方式 根据容器制作镜像和根据Dockerfile制作镜像 根据现成的容器制作镜像 适用于已经有一个现成的容器已经满足需求的情况 docker ps a CONTAINER ID IMAGE COMMAND CREA
  • PCAP流量数据集(网络安全)

    MAWI Working Group Traffic Archive URL http mawi wide ad jp mawi CIC dataset Canadian Institute for Cybersecurity datase
  • Kmalloc

    Kmalloc内存分配和malloc相似 除非被阻塞否则他执行的速度非常快 而且不对获得空间清零 Flags参数 include
  • RabbitMQ的使用

    安装 Docker 安装 RabbitMQ docker run d name rabbitmq p 5671 5671 p 5672 5672 p 4369 4369 p 25672 25672 p 15671 15671 p 15672
  • 一文详解jwt token以及sprig boot如何整合实现 jwt token操作

    文章目录 1 jwt是什么 2 jwt的来源 2 1 传统的session认证 2 2 基于token的鉴权机制 3 JWT的构成 3 1 header 3 2 playload 3 3 signature 4 如何应用 5 spring
  • webrtc源码学习 - Track Source Sink的关系

    文章目录 1 source sink 的关系 2 Track 2 1 videotrack 的创建和使用 2 2 VideoTrack 的实现 3 Track接口类介绍 1 source sink 的关系 source是生产媒体资源的 si
  • win+R命令汇总

    我们通过WIN R 可以快速调取windows一些程序及服务 那具体有哪些命令呢 笔者总结如下 cmd cmd命令提示符 MS DOS regedit 注册表编辑器 services msc 系统服务 msconfig 系统配置实用程序 n
  • 对 Spring 的核心(AOP 和 IOC)的理解(大白话)

    Spring 首先它是一个开源而轻量级的框架 其核心容器的主要组件是Bean工厂 BeanFactory Bean工厂使用控制反转 IOC 模式来降低程序代码之间的耦合度 并提供了面向切面编程 AOP 的实现 正如其字面意思 是程序员的春天
  • 掩码、ip段转为单个ip地址,解决ValueError: IP(‘x.x.x.x/x‘) has invalid prefix length ()

    最近碰到的问题 简单记录下 from IPy import IP import re os time 解析10 245 1 1 10 245 1 10这种类型的ip段 def all for one dates ipx dates spli
  • R语言应用序列模式挖掘揭示客户购买行为:深度学习与机器学习的视角

    目录 序列模式挖掘 一个简介 使用R进行序列模式挖掘 应用深度学习和机器学习改善购买行为预测
  • 无向图的深度优先遍历非递归_数据结构系列图

    图 01 图的基本定义与基本术语 基本概念 图 Graph 是由顶点的集合和顶点之间边的集合组成 通常表示为 G V E 其中 G表示一个图 V是图G中顶点的集合 E是图G中边的集合 在图中的数据元素 我们称之为顶点 Vertex 顶点集合
  • 6.OS运行机制(补充)

    中断
  • C#的new关键字的几种用法

    一共有三种用法 在 C 中 new 关键字可用作运算符 修饰符或约束 1 new 运算符 用于创建对象和调用构造函数 这种大家都比较熟悉 没什么好说的了 2 new 修饰符 在用作修饰符时 new 关键字可以显式隐藏从基类继承的成员 3 n
  • 水文数据产品的网站

    主要记录在平常用到的水文数据产品的网站 包括水库 湖泊 河流等 1 hydroweb 官网 https www theia land fr en hydroweb 界面 下载后的数据是txt格式 如需转成csv 可这样批量操作 import

随机推荐

  • React hooks中ref、forwardRef、useImperativeHandle的结合使用

    ref 用来绑定到HTML元素或者组件上 获取其DOM forwardRef 帮助子组件拿到父组件中子组件上面绑定的ref 绑定到自己的某一个元素中 这样就将子组件的DOM直接暴露给了父组件 这种方式存在的弊端 1 直接暴露给父组件带来的问
  • Linux 查看目录和文件

    目录 1 显示当前目录 pwd 2 改变目录 cd 3 列出目录内容 ls 4 列出目录内容 dir和vdir 5 查看文本文件 cat和more 6 阅读文件的开头和结尾 head和tail 7 查找文件内容 grep 1 显示当前目录
  • 存储解决方案之——FC存储解决方案

    FC存储解决方案 一 需求分析 当前 在FC Fibre Channel 领域里鲜有新技术问世 很多技术都已经成为过去时 近来在技术上的演进就是从2Gbit s 到4Gbit s的过渡 而且基本上已经完成 基于光纤通道 FC 的存储局域网络
  • Win10中docker的安装与使用

    WIN10中DOCKER的安装与使用 WIN10中DOCKER的安装与使用 1 docker的安装 环境准备 下载安装 2 docker的入门 开始使用 3 docker的常用配置 在PowerShell中设置 tab键自动补全 其实用的都
  • 蓝牙设备中的Device UUID 与 Service UUID

    Device UUID也可以被称作为DeviceID Android 设备上扫描获取到的 deviceId 为外围设备的 MAC 地址 相对固定 iOS 设备上扫描获取到的 deviceId 是系统根据外围设备 MAC 地址及发现设备的时间
  • mysql的left join和inner join的效率对比,以及如何优化

    一 前言 最近在写代码的时候 遇到了需要多表连接的一个问题 初始sql类似于 select from a left join b on a x b x left join c on c y b y left join d on d z c
  • Idea项目如何打包

    项目代码打包 一 idea软件为例 二 打包前的准备 1 application yml修改 代码 第三行dev改为pro spring profiles active SPRING PROFILES ACTIVE dev activiti
  • thinkphp5.0 常量

    预定义常量 EXT 类库文件后缀 php THINK VERSION 框架版本号 路径常量 DS 当前系统的目录分隔符 THINK PATH 框架系统目录 D phpStudy WWW my tadmin thinkphp ROOT PAT
  • Hash映射理解

    先说数组 数组优点之一 能通过索引很快定位到值 hashmap 就是利用了数组这个优点 对比 线性映射 定义一个数组 数组的元素是结构体 结构体包括 一对键 值 伪代码表示 a 0 struct Bill 5 a 1 struct KK 6
  • 【Unity3d】Animator和Animation组件使用注意事项

    一 Animator一般用于人物动画控制 特点是动画是持续的 可能有动作切换 Animation一般用于间断性的动画的控制 比如一个场景特效的播放 只播放一次就完了 二 实测Animation速度比Animator快10 左右 内存占用没测
  • vue+高德地图 点击地图获取经纬度和详细地址

    html源码
  • docker什么命令启动服务?

    docker启动服务的命令是 1 启动 1 systemctl start docker 2 守护进程重启 1 sudo systemctl daemon reload 3 重启docker服务 1 systemctl restart do
  • HDLC原理及配置

    一 HDLC原理 HDLC是由国际标准化组织 ISO 制定的面向比特的同步数据链路层协议 主要用于封装同步串行链路上的数据 HDLC是在数据链路层中被广泛使用的协议之一 二 HDLC配置 AR1
  • 使用Qemu+gdb来调试内核

    原文地址 http blog csdn net iamljj article details 5655169 昨天听别人讲使用Qemu和gdb来实现源码级内核调试 今天试了一下 果然非常方便 现简单的记录一下 Qemu是一个开源的虚拟机软件
  • Unity --- Vector3类的API讲解

    1 Vector3中的静态变量是相对于世界坐标系的还是相对于自身坐标系呢 我们创建的Vector3类对象同理 答 这取决我们将创建的Vector3类对象 通过Vector3调用的静态变量传给了哪一个引用 如果是传给了positon的话 则该
  • 工程管理系统简介 工程管理系统源码 java工程管理系统 工程管理系统功能设计

    鸿鹄工程项目管理系统 Spring Cloud Spring Boot Mybatis Vue ElementUI 前后端分离构建工程项目管理系统 1 项目背景 一 随着公司的快速发展 企业人员和经营规模不断壮大 为了提高工程管理效率 减轻
  • 14_Deep Computer Vision Using Convolutional Neural Networks_max pool_GridSpec_tf.nn, layers, contrib

    cp15 Classifying Images with Deep Convolutional NN Loss Cross Entropy ax text mnist CelebA Colab ckhttps blog csdn net L
  • socket连接超时问题

    一部分 把CSDN与中文yahoo翻了底朝天 也没找到如何设置socket的连接超时的满意方法 问此问题的兄弟已有一大堆 这里偶就讲一下win下如何设置socket的connect超时 设置connect的超时很简单 CSDN上也有人提到过
  • hive jdbc/metastore客户端方式连接开启kerberos的hive集群api

    pom依赖
  • Linux内核源代码情景分析-系统调用brk()

    首先看下进程地址空间示意图 我们简单的说 从低地址到高地址 代码区和数据区 空洞 堆栈区 在Linux内核源代码情景分析 内存管理之用户堆栈的扩展 我们申请了从堆栈区往下 数据区上面的页面 在Linux内核源代码情景分析 内存管理之用户页面