Linux内核相关知识点记录【内存管理】【进程调度】【异常调试】【锁】【kvm虚拟化】【内核启动】

一、内存管理 怎么阐述本身的理解?
。硬件原理,分页机制原理
内核内存管理由MMU提供硬件支持,MMU实现虚实地址VA=>PA的转换以及权限检查,虚拟地址和物理地址之间的映射关系是页表机制,每个页表项都保持物理地址页及其访问权限,页表机制和图书馆借书是一个原理,为何要用多级页表呢?假设只有一级,类比成数组,用a【i】记录每一项,那么记录全部映射关系i=4GB/4KB=1000*1000项,显然不现实,线性数组查找效率低下,分级的好处就是把线性查找转换才二分法查找,所需时间是log 2^n,极大提升效率,这也就是内存管理使用树结构的缘由,类比图书馆借书的例子:首先在电脑上查到书在 4楼-13巷 -5架-6层-100号,那么拿到这个地址去对应的楼层(一级页表),巷号(二级页表),架次(三级页表),层(4级页表),最后找到100这个编号的书(页内偏移)。

开启了mmu,那么只有mmu自己能够看到物理地址,CPU发出的都是虚拟地址.mmu的页表寄存器记录了页表自己保存在物理内存的位置。
每一个进程都有4G的地址空间,0-3G是用户地址空间,3-4G是共享的内核地址空间,每一个进程都有本身独立的页表,进程切换的时候,内核会把进程的页表地址填入MMU实现进程切换.这样就实现了各个进程地址空间隔离.
MMU如何实现内存权限保护?好比定义一个const 常量,那么C编译器就会把这个const常量连接到rodata(只读数据段),载入内存创建页表时被标记为readonly,MMU提供运行时检查,若是有人试图去写它,那么就会被MMU拦截发出segmentfault.实现了内存保护的机制.
TLB保存最频繁使用的页表数据,属于MMU的高速缓存.

物理内存划分区域: DMA zone, normal zone,highmem zone,
虚拟地址空间是指每一个进程的0-4g虚拟地址空间,属于虚拟地址的概念,而低端、高端内存是属于物理内存的概念.
内核中物理内存的管理是由buudy算法实现,buddy以2的n次方对空闲内存进行管理,最分配粒度是1页,也就是4k,而slub算法是基于buddy算法的二次管理,用于分配更小的内存粒度。

。内存动态分配和释放,kmalloc,vmalloc,slub,buddy system的特色和关系.讲述用户空间malloc 1M内存后发生了什么?
从buddy拿到的内存都是以1页为单位粒度,而slub是从buddy拿一大块内存进行二次管理,以更小的分配粒度分配和回收,kmallc就是从slub拿内存,kmallc/kfree和buddy不是同样对应关系,执行kfree不必定还给buddy,具体何时还,是slub算法 决定,这样作的目的是为了应对频繁申请释放内存的场景对性能的影响.

kmalloc申请的内存是线性映射关系,适用于频繁申请释放且小块的内存,效率高,可是注意kmalloc能够休眠,特别是内存紧张得时候.
vmalloc申请的内存是非线性映射关系,是使用红黑树数据结构管理,用于申请大块的对物理内存没有连续要求的场景,优先从highmem区域拿,经过alloc_page从buddy拿内存.
malloc是libc的库函数,不是系统调用,libc库对申请的内存作二次管理,其最终是经过brk和mmap向内核要内存.malloc/free不是系统调用和内核不是一一对应关系.free后不必定还给内核,具体何时还,由libc算法决定,这是为了性能的考量,由于对物理内存频繁的申请释放对性能有影响.

malloc 1M内存的时候并无真正拿到物理内存,只是将分配的连续VMA映射到一片清零的物理地址并标注为readonly,只有当有人去写这块VMA的时候,MMU执行地址转换检查权限发现是只读,会被MMU拦截而触发page fault,可是内核检查到当前的地址权限是R+W,发现是malloc致使的page fault,而后分配物理内存页,改写权限,此刻真正拿到物理内存。并且是边写边拿.这就是所谓内核分配内存的lazzy性,这样作的目的是由于内核没法控制应用程序的行为,尽可能防止申请了不用的浪费状况.
内核不信任应用程序只相信内核自己。lazzy只针对应用程序,不针对内核,内核调用kmalloc或者vmalloc就是当即拿到内存.

。进程的虚拟内存VMA(virtual memory areas)
VMA区域存在用户空间,能够是独占的也能够是共享的,一个进程拥有多个VMA,零散分别在0-3G的地址空间,每个进程的代码段,数据段,堆都是一个VMA实例,
查看vma的方法:
/proc/pid/maps 、 /proc/pid/smaps

page fault的可能性:
a: 动态内存分配,写时拷贝,属于次要的page fault,由于不须要去硬盘load数据,开销小。
b: 进程访问不可读写的非法地址,会被MMU拦截,内核发出segment fault信号,致使oops.
c: 进程访问VMA区域,可是权限不对,好比代码段权限是R+X,可是你尝试去写,也会和上面同样的结局
d: 进程访问VMA区域,权限检查经过,可是须要从硬盘load数据到内存,这种属于大开销,所谓主要的page fault,好比代码段首次被载入内存的时候,这也就是为何第一次启动进程比第二次慢的缘由.(内存大手机跑得更流程缘由之一)

VSS - Virtual Set Size 虚拟耗用内存
RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)
PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
USS - Unique Set Size 进程独自占用的物理内存(堆内存,独占)
VSS >= RSS >= PSS >= USS
可使用smem工具查看进程的VSS,RSS.PSS,USS值,USS值就是堆内存占用,内存泄漏通常只看这个。
安装: $ sudo apt-get install smem
内存泄漏的断定,查看USS,多时间点采样,震荡发散的内存消耗就是存在泄漏.

。内存和IO交换
分两类:
a.有文件背景的页(file-backed),好比读写文件,程序代码段属于这类,在硬盘中有实在的文件存在;
b.没有文件背景的页,俗称匿名页,好比栈内存,堆内存等,这类内存的交互经过硬盘上的swap分区来实现匿名页的交换.

文件首次载入内存的page cache中,page cache至关于内存中文件的副本,进程经过操做page cache来实现对文件的读写.
$ free
total used free shared buffers cached
Mem: 16340236 14154128 2186108 482520 1835924 3946088
-/+ buffers/cache: 8372116 7968120
Swap: 31875068 384264 31490804
总物理内存大小 = total +reserved内存(开机预留好,包括kernel的代码段,data,bss....)
total = used + free
buffers :直接从裸分区读取的数据到内存的page cache;
cached:从文件背景读取到内存的page cache;
两个是一个东西,背景不同。

页交换使用LRU算法,最近最少使用的被交换出去
内核中使用kswapd进程做为内存回收.能够回收两类内存页。
zram原理:从内存中划分一块区域用于匿名页交换替代硬盘中的swap分区,压缩须要交换的匿名页,zram能够提供内存可用空间,可是会消耗CPU性能,须要平衡.

二、进程管理
。数据结构
进程是资源分配的单位,线程是调度的单元,这是经典定义.
内核中每个进程都有一个数据结构是task_struct,每个task_struct都包含着mm 资源,file资源,signal资源指针,每一个task_struct之间有三种联系的视角:
a: 造成双链表 -- 方便快速遍历全部进程;
b: 造成树 -- 方便父子进程的查询;
c: 造成哈希 -- 方便从pid找进程,好比:kill -9 $pid;
这是典型的以空间换时间的算法,提供不一样的视角下的时间最短查询.
。生命周期
六种:ready,run,stop,sleep,disk-sleep(深度睡眠)
sleep(浅度睡眠)能够被中断或者信号唤醒,而深度睡眠只能被资源唤醒,这里资源就是你要等的东西,好比mutex lock,代码段的page fault等。
僵尸进程:子死父没有及时清场的一种临时状态,子进程挂了后,父进程须要调用wait4pid接口才会消失,僵尸进程基本不暂用系统资源了。
。进程和线程的区别
内核中进程和线程都是使用task_struct描述,区别在于内存资源,文件资源,信号资源等是否共享,因此线程也称为轻量级的进程.对于调度器来讲,只要是task_struct就能够调度.因此在内核中,进程和线程能够理解为同义词.
。进程0,进程1
开机后第一个进程就是进程0,最后会称为idle进程,idle进程优先级最低,idle进程拿到cpu后会执行WFI指令进入低功耗状态,任何来一个中断就会唤醒调度到新的进程,这样很是有利于电源管理的设计.
进程1就是init进程,用户空间因此的进程都是1号进程直接或者间接fork出来的.

。进程调度
吞吐和响应本是一对矛盾行为,须要根据业务需求来选择侧重.
进程调度类型分为内核RT进程和普通用户进程,进程有分IO消耗型(鼠标)和CPU消耗型(编译).
a: RT进程调度策略又能够分为sched_fifo,sched rr类型,
sched_fifo:同优先级进程拿到cpu后,必须等当前进程运行完退出才交出CPU;
sched_rr: 同优先级进程直接轮转得到CPU时间片.
0-99是RT进程优先级,100-139是普通用户进程优先级,数字越大优先级越高,高优先级进程能够抢占低优先级进程.
b: 普通进程的调度是 CFS-彻底公平调度算法,使用红黑色数据结构,基本原理比较简单,老是调度vrruntime最小的进程,vrruntime = physical runtime/(nice权重系数),因此一个普通进程运行的越久,他的优先级越低,一个进程睡的越久,优先级越高,被调度的几率越高,优先级最高的进程就是睡眠越久并且nice值越高的IO消耗型进程.
。SMP负载均衡
a: RT进程:N个优先级最高的RT进程均匀分布到各个核;
b: 普通进程:核心思想是以“劳动为荣”,好比新fork出来一个进程,exec的时候会把它推到最闲的核上去运行
。cgroup - 资源控制.
核心就是分层调度,各个group内CFS
。RT-OS
为何linux不是一个硬实时的操做系统?
硬实时强调必定时间内必须完成调度,进程老是运行在4类区间:
a: 硬中断 - 外部中断
b: 软中断-系统调用
c: 不可调度的进程上下文,好比陷入内核的spinlock保护区
d: 可调度的进程上下文
只有第4类区间是能够实时抢占调度的,前面三类的时间都没法肯定,因此没法知足硬实时OS的要求。

RT-patch的原理:
a: 中断线程话 -- 是线程就能够抢
b: 优先级继承协议 -- 临时提升优先级,不受中等优先级进程的干扰
c: spinlock换成mutex,mutex能够睡眠
打上补丁后,基本上能够达到硬实时OS的需求.

3,内核启动流程,从head.S开始到init进程建立.
a: 切换CPU到svc模式,使能d-cache和i-cache,使能MMU;
b: 跳转到start_kernel函数执行,初始化特定体系结构的设置;
c: 解析uboot传入的命令行参数,是否使能早期串口打印;
d: 初始化各类核心数据结构,好比内存管理,异常处理,中断向量,dts,cgroup,等等;
e: 建立1号init进程和2号kthread进程,开始驱动初始化.
f: 最后是用户空间初始化,0号进程演变为idle进程.

四、内核死机等异常的分析手法,策略
分类型:
a: 调用BUG()出现的异常属于人为主动上报异常,通常能够直接从oops信息中找到问题点;
b: 异常现场和真实缘由重合的异常,同上能够根据oops的pc指针位置,相关寄存器信息判断异常类型是未定义指令异常,仍是data abort,或者其余类型;
c: 异常现场和真实缘由不重合的类型,抓ramdump使用工具离线分析寄存器信息,确认异常的类型,观察异常线程的变量参数是否异常,确认是否内存被踩致使,如果kmalloc致使的内存被踩,能够打开slub debug浮现问题后抓取。
d: 如果死锁类问题,须要抓到因此D状态的进程调用栈,逐一排查找出互锁关系,进一步分析缘由.若是有必要,须要打开mutex debug等开关辅助调试.

五、KVM,qemu虚拟化的架构认识,实现原理等认识
kvm - kernel-based virtual machine(基于内核的虚拟机)
基于intel,AMD提供的虚拟化平台,不提供硬件虚拟化操做,IO操做借助QEMU完成.
guest做为一个普通进程运行于宿主机.
guest的CPU(vCPU)做为进程的线程存在,并受到宿主机内核的调度.
KVM总体架构:

kvm提供CPU,内存模拟,qemu提供IO模拟.
kvm提供一个 /dev/kvm字符设备,qemu经过ioctl和内核kvm驱动交互,执行初始化,内存分配,指令集加载等操做.
guest的因此硬件操做都由qemu接管,qemu负责和宿主机真实硬件交互.

KVM简单来讲就是由两部分构成:
a: KVM驱动,已是内核一部分,负责虚拟机建立、内存分配、虚拟寄存器读写,和虚拟cpu运行;
b: qemu模拟虚拟机的用户空间组件,模拟IO操做访问外设的途径.

QEMU是完整的纯软件的虚拟化解决方案,性能比较低,KVM提供CPU和内存的模拟+QEMU提供IO模拟相结合实现互补.

KVM运行模式:
客户模式:guest os运行模式,其自己又包含用户模式和内核模式
用户模式:linux os的用户模式,qemu运行在这个模式
内核模式:linux kernel运行模式,kvm内核驱动模块运行在这个模式
ARM处理器有hypervisor层,虚拟化的硬件支持。

6. 内核锁
a. 类型:信号量锁(如今少用了)
b. mutex lock,能够睡眠,会引发上下文切换,效率不及spinlock
c. spinlock,不容许睡眠,会关抢断,中断中使用spin lock/unlock,进程上下文中使用spinlock irq save/restore. -- 若是当前保护的函数不会被中断上下文打断就不用irqsave,不然就须要用spinlock irqsave,由于中断会打断spinlock,而后又进入等待spinlock的状况,致使dead lock.
d. rcu, read-copy-update,一种支持并发的保护机制,很复杂.
e. 原子锁,atomic,如今用的少了。
使用注意:
被保护的代码若是不容许睡眠,追求效率,那么用spinlock,它不会切换上下文,好比中断上下文不可用mutex lock.