赞
踩
前言:记录了总6w字的面经知识点,文章中的知识点若想深入了解,可以点击链接学习。由于文本太多,按类型分开。这一篇是 操作系统 常问问题总结,有帮助的可以收藏。
Unity 实际上可以看作是一个使用 C++开发的游戏引擎,它使用.net 的脚本虚拟机。Unity 从虚拟内存中给原生(C++)对象和虚拟机分配内存,同样,第三方插件的内存分配也都是在虚拟内存池中。
Native
原生内存(Native Memory )是虚拟内存的一部分,它用来给所有需要的对象分配内存页面,包括 Mono 堆(Mono Heap)
而Unity的资源,是通过Unity的C++层,分配在Native堆内存上的那部分内存。
比如通过UnityEngine命名空间中的接口分配的内存,将会通过Unity分配在Native堆;
Mono堆
代码分配的内存,是通过Mono虚拟机,分配在Mono堆内存上的,其内存占用量—般较小,主要目的是程序员在处理程序逻辑时使用;
比如:通过System命名空间中的接口分配的内存,将会通过Mono Runtime分配在Mono堆。
Mono 堆是出于虚拟机的需要而专门分配的本机内存的一部分。它包含了所有由 C#分配的托管的内存类型,而这些内存的托管对象就是垃圾收集器,简称 GC。
Unity 内部有几个专门的分配器,它们负责管理虚拟内存的短期和长期分配需求。所有的 Unity 资产(Assets)都是存储在原生内存中的。但这些资产会被轻量的包装成 Class,以供逻辑访问和调用。也就是说,如果我们用 C#创建了一张Texture,那么它的大部分原始数据(RawData)存在于 Native 内存中,并且会有很小的一个 Class 对象进入到虚拟机中,也就是 Mono 堆中
早期,C#是微软的,只会在windows平台运行。
为了解决这个问题,引入了mono,这玩意是开源跨平台,其实是在各个平台实现了一套mono的虚拟机,这样你的代码就能运行在各个平台了。
mono的核心原理是,将C#代码,转化成 IL 中间代码,然后通过各个平台的mono虚拟机解释执行,在运行时,解释的过程中,最终转化成了机器码。
而 il2cpp,是在各个平台,先将 C# 转化成 C++ 代码,然后通过各个平台的C++编译器直接生成了机器码,也就是在你发包的时候已经是机器码了,所以很快。
可以看下 mono和il2cpp 机器码生成的时机,一个慢一个快。
另外,il2cpp 生成出的项目 无法反编译,这相当于将C#代码加密了;而 mono生成出的项目,是可以 ILSpy 反编译出来的。
详细请看:
堆、全局变量、静态变量、文件等公用资源
栈 寄存器
互斥条件、不可剥削条件、请求和保持条件、循环等待条件
1. 预防死锁–针对死锁的必要条件进行预防
破坏占有且等待(请求和保持条件)条件
一次性申请进程所需要的所有资源。改进:允许进程只获得运行初期的资源,在运行过程中逐步释放使用完毕的资源,申请需要的资源。
破坏不可抢占条件
当一个进程申请需要的资源没有被满足时,释放掉所占有的所有资源。
破坏循环等待条件
给每个资源编号,当一个进程占有某个资源时,只能申请比这个编号大的资源
2. 避免死锁–在分配资源之前判断是否会出现死锁
若一个进程的请求会导致死锁,则不启动。
若一个进程的增加资源会导致死锁,则不启动。
实现:银行家算法
需要记录可利用的资源向量,每个进程最大需求资源,已分配的资源,仍需要的资源。
当一个进程申请资源时,假设从可利用资源中分配给它申请的资源,看剩余的资源是否能满足某个进程执行完毕,若不能,则是不安全的,拒绝分配,若能,则假设可执行完毕的进程所占用的资源返还到可利用资源中,将其标记为可完成进程,继续判断其它进程,资源分配顺序则为安全序列。
现代计算机中内存空间都是按照字节(byte)划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排列,这就是对齐。
1.有些 CPU 可以访问任意地址上的任意数据,而有些 CPU 只能在特定地址访问数据,因此不同硬件平台具有差异性,这样的代码就不具有移植性,如果在编译时,将分配的内存进行对齐,这就具有平台可以移植性了。
2.CPU 每次寻址都是要消费时间的,并且 CPU 访问内存时,并不是逐个字节访问,而是以字长(word size)为单位访问,所以数据结构应该尽可能地在自然边界上对齐,如果访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问仅需要一次访问,内
存对齐后可以提升性能。
比如有 些平台每次读都是从偶数地址开始,如果一个 int 型(假设是 32 位)如果存放在偶数地址开始的地方,那么一个时钟周期就可以读出。而如果是存放在一个奇数地址开始的地方,就可能会需要 2 个时钟周期,并对两次读出的结果的高低字节进行拼凑才能得到该 int 型数据。显然在读取效率上下降很多。这也是空间和时间的博弈。
1. 管道(Pipe)
管道是Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。其本质是内核中固定大小的缓冲区。
2. 命名管道
“命名管道”又名“命名管线”(Named Pipes),命名管道支持可靠的、单向或双向的数据通信。不同于匿名管道的是:命名管道可以在不相关的进程之间和不同计算机之间使用,服务器建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。
3. 消息队列
消息队列用于在进程间通信的过程中将消息按照队列存储起来,常见的MQ有ActiveMQ、RocketMQ、RabbitMQ、Kafka等。
4. 信号量
有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量。
5. 共享内存
共享内存是三个IPC机制中的一个。它允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在进行的进程之间传递数据的一种非常有效的方式。
6. 套接字
就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。
每个进程创建加载的时候,会被分配一个大小为 4G 的连续的虚拟地址空间,虚拟的意思就是,其实这个地址空间时不存在的,仅仅是每个进程“认为”自己拥有 4G 的内存,而实际上,它用了多少空间,操作系统就在磁盘上划出多少空间给它,等到进程真正运行的时候,需要某些数据并且数据不在物理内存中,才会触发缺页异常,进行数据拷贝。
当一个进程试图访问虚拟地址空间中的某个数据时,会经历下面两种情况的过程:
CPU 想访问某个虚拟内存地址,找到进程对应的页表中的条目,判断有效位
1.如果有效位为 1,说明在页表条目中的物理内存地址不为空,根据物理内存地址,访问物理内存中的内容,返回。
2. 如果有效位为 0,但页表条目中还有地址,这个地址是磁盘空间的地址,这时触发缺页异常,系统把物理内存中的一些数据拷贝到磁盘上,腾出所需的空间,并且更新页表。此时重新执行访问之前虚拟内存的指令,就会发现变成了情况 1。
希望此篇文章可以帮助到更多的同学,此外对现在面临校招的大三大四的同学,以及热爱游戏或者即将面临找工作的朋友,可以点击下方链接,来解决游戏职业道路的种种困惑,并且还可以学习理论知识的同时,拓宽游戏制作的实践技能~
游戏行业大揭秘https://scrm.vipskill.com/CMS/prod/5726/54/home.html?mantisSiteId=175&track_id=__TRACKID__
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。