linux 基本概念概括
- VFS 树链接:虚拟文件系统就是一个树,树的根部就是
/
, 树上不同的节点,都会指向不同的物理地址(文件系统的目录树的不同节点其实是来自不同的分区),可以是具体的文件系统,或者网络节点,或者自己虚拟的节点。不同的dev就相当于是挂载到了树上的不同的节点,也就是一个文件夹 - FD:文件描述符,指向INODE,进程打开文件的时候使用FD找到文件,同时FD是有数量限制的,默认是一个进程1024,可以使用
<font style="color:#F5222D;">ulimit -SHn 65535</font>
临时修改,也可以修改文件<font style="color:#F5222D;">/etc/security/limits.conf</font>
永久生效,在最后一行加入<font style="color:#F5222D;">- nofile 65535</font>
参考- FD是进程而言的,INODE是文件而言的,多个进程指向同一个文件,就是多个FD指向同一个INODE,
- 指针seek:每个应用(进程)读取文件的时候有自己的fd, 有自己的seek 指针
**inode**
: 虚拟文件系统里面的每一个文件都有一个ID,每一个文件打开的时候在内存里面有一个**/proc**
: 内核映射目录,内核的一些属性。- 系统的变量属性,进程的在这里都会在这里被映射成文件
- 只有开机之后才存在。
- /proc/$$ 获取和你当前交互的进程的ID号,$BASHPID 也可以获得
- /proc/$$/fd 目录下是当前进程的所有文件描述符
- lsof -op $$ 更加细节,查看当前进程打开文件的文件描述符的细节
- page cache:(内核缓冲区就是这个)优化IO性能,优先走内存。****会开一个一个页缓存,默认大小是4K。内存里面对数据的缓存,物理内存是一份,两个程序是共享这个pc的,也就是第二个访问的时候是缓存命中了。
- 脏****page cache:会有一个flush的过程,
- 缓存行:CPU里面,CPU Cache是由最小的存储区块-缓存行(cacheline)组成,缓存行大小通常为64byte。也就是一次读取的数据的大小。
- 一个缓存行对应多个内存块,所以缓存行有一个标志就是组标记tag区分不同的内存块,除了组标记Tag,还有实际数据data以及有效为valid bit,有效位是0的话无论CPU line中是否有数据都会直接访问内存。(具体看在MESI里面起到的作用)
- 有伪共享的问题,限制一行只放一个数据,就不会因为MESI导致的频繁将数据换入内存的问题发生
- 进程的NICE:涉及优先级的设置,越小优先级越高(其实是对应的虚拟运行时间越小,完全公平调度算法CFS就会优先调度这个进程。查看关于线程调度)
- Linux内核对进程的完全公平调度CFS算法:是针对CPU普通进程的调度而言的,不涉及DeadLine和RealTIME任务,只是Fair任务,大多数的进程(线程(task_struct))都是普通的任务,都使用的是CFS算法,两碗水端平的思想。查看关于线程调度)
- 中断:硬中断,软中断
- 异常:****故障,操作系统会将控制转移给相应的异常处理程序。如果处理程序能够修正这个错误情况,就将返回到引起异常的指令重新执行。否则,终止该应用程序。
- 陷阱:软中断的一种,就是系统调用
- 中断向量表 IVT、中断描述符表 IDT:中断向量表是一个通用的概念,在不同的架构下有不同的实现,比如在 x86 处理器下的实现是中断描述符表(Interrupt Descriptor Table,IDT)。
- 中断向量表(interrupt vector table,IVT)是由一系列中断向量(interrupt vector)组成的列表。每个中断向量都是一个中断处理程序的入口地址。中断向量的类型包括:硬件中断、软件中断和处理器异常,这些事件在中断向量表中统一称作中断
- 当中断或异常产生时,由硬件负责产生一个中断标记,CPU 根据中断标记获得相应的中断向量号****,然后将其作为偏移,在中断向量表中获得相应的处理程序地址,并执行。链接
系统启动过程
- BIOS uefi 工作
- BIOS 自检:硬件是否有问题
- BIOS 加载bootLoader到内存,
- BootLoader在硬盘上的位置也是写死的。硬盘的第一个扇区上,也就是主引导记录。BootLoader决定启动哪一个操作系统
- 读取可配置信息:
- CMOS :存可以配置的信息,必须加电,不加电下次开机就没了; 过几年密码也没了,因为自己的电池没电了。
- OS的代码放到内存,从OS的指令位置开始执行,之后操作权利放给OS,
CPU和线程和内核
Register + cache :存储单元
ALU:运算单元
控制单元
MMU :内存管理单元,管理虚拟内存
超线程:
一个运算单元对应多组寄存器和PC,不切换线程了,而是直接切换寄存器,没有上下文切换了,
存储器的层次结构
速度不一样,寄存器,一级缓存(每个核一个),二级缓存(每个核一个),三级缓存(每一个CPU一个),主存(多个CPU共享)
CPU的乱序执行
指令1在读等待(等待内存磁盘返回),指令2不依赖指令1的话,CPU会提高效率让指令2 先执行
禁止指令重排:
CPU实现:使用内存屏障
单例为什么要加volatile
指令重排,提前指向了半初始化状态,
JVM 规范要求的4个内存屏障
具体实现的时候可以使用lock指令。
happens-before--java语言规范
就是一个保证规则,最后的效果就是看上去是没有重排序的
as if serial
像是顺序执行
不管如何重排序,单线程执行 结果不变,看上去是Serial
用户态和内核态
CPU分为不同的指令级别,内核态可使用ring0级别的指针,用户态可以使用ring3级别的指令
Linux内核跑在ring 0级,用户程序是ring3级别,对于系统的关键访问,需要经过kernel的同意,保证系统的健壮性
内核执行的操作--> 200多个系统调用 sendFile read write pthread fork
JVM 在 操作系统的角度就是一个普通的程序
线程和进程和纤程
进程是资源分配的基本单位,线程是调度的基本单位
线程就是一个进程的不同执行路径
在Linux里面线程其实就是一个普通的进程,因为他是通过fork来创建的,只不过和其他进程共享资源(内存空间, 全局数据等等)。其他的操作系统都有自己所谓的LWP实现(Light Weight Process)
纤程:(用户空间级别的线程)线程中的线程,用户态的线程,切换和调度不需要经过OS
优势:占用资源少,OS的线程是1M,纤程是4K;切换简单不需要经过OS;可以启动好多个10w+,不像CPU多了就全是CPU切换了。
实现:java中没有内置纤程的支持,需要使用依赖包
不是对应操作系统里面的重量级线程,而是JVM内部自己调度的线程,原来的线程需要和硬件打交道,切换起来速度比较慢,不然CPU全耗在切换上了。多个纤程对应一个线程,每一个纤程都有自己的栈。
进程
Linux中也叫做Task,系统分配资源的基本单位
资源:独立的地址空间,内核数据结构(进程描述符...)全局变量、数据段...
进程描述符:PCB(ProcessControlBlock)
创建和启动
系统函数fork() exec()
从A中forkB的话,A就是B的父进程,
对于主进程 fork()返回新建的子进程ID, 子进程fork()返回0
在语句pid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的代码部分完全相同,将要执行的下一条语句都是if(pid==0)……。
两个进程中,原先就存在的那个被称作“父进程”,新出现的那个被称作“子进程”。父子进程的区别除了进程标志符(process ID)不同外,变量pid的值也不相同,pid存放的是fork的返回值。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
- 在父进程中,fork返回新创建子进程的进程ID;
- 在子进程中,fork返回0;
- 如果出现错误,fork返回一个负值;
fork出错可能有两种原因:(1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。(2)系统内存不足,这时errno的值被设置为ENOMEM
僵尸进程和孤儿进程
- 僵尸进程:每一个父进程都会维护自己子进程的PCB结构,子进程退出之后,父进程释放PCB,但是父进程不释放那么子进程就是一个僵尸进程,此时子进程占用的空间就是PCB的大小
ps -ef | grep defuct
defuct 就是僵尸
- 孤儿进程:子进程在结束之前,父进程已经退出,孤儿进程就会被1号进程管理,也就是init进程。
内核线程(不重要)
内核独有,内核启动之后经常需要做一些后台操作,这些由Kernel Thread 来完成只在内核空间运行
进程调度
Linux内核什么时候开始运行,运行多长时间,每一个进程有自己对应的调度方案,可以指定,也可以自己实现内核调度方案给内核打补丁。
单任务独占到多任务分时,原则就是压榨CPU资源
抢占式:进程调度器强制开始或者暂停,抢占某一进程的执行
非抢占式:除非进程主动让出(yielding),否则一直运行
Linux的内核进程调度:Linux2.5 使用经典的Unix O(1) 调度策略,偏向服务器。时间片轮询,对交互不友好。Linux2.6.23 采用CFS完全公平调度策略算法 Completely Fair Scheduler,按照优先级分配时间片的比例,记录每一个进程 的执行时间,如果有一个进程执行的时间不到他应该分配的比例,优先执行。
进程类型
- IO 密集型:大部分时间都在用于等待IO
- CPU密集型:大部分时间计算
进程优先级
- 实时进程 > 普通进程(0-99)
- 普通进程的nice值(-20 - 19)
时间分配
- Linux采用按照优先级的CPU时间比
- 其他系统使用按优先级的时间片
Linux默认的调度策略(看导图)
- 对于实时的进程:使用SCHD_FIFO(优先级分高低) 和 SCHED_RR(轮询) 两种
- FIFO 是等级最高的,除非自己让出CPU否则一直会执行它,也就是除非更高级别的FIFO或者RR抢占它
- RR只是这种线程中是同级别FIFO的平均分配
- 对于普通的进程:使用CFS:按照优先级分配时间片的比例,记录每一个进程的执行时间,如果有一个进程执行时间不到他应该分配的比例,优先执行
只有实时的进程主动让出,或者执行完毕之后,普通的进程才有机会运行
中断
硬件的电信号控制了软件的输出
按键--> 中断控制器 --> CPU芯片 --> kernerl --> 中断处理程序 --> 上半场下半场
- CPU芯片 只是知道中断了,不知道中断谁,
- 会去操作系统找kernel,让kernel说出是中断谁,interrupt 80 中断,软中断就是80中断
中断是一个信号,内核暂停处理,不一定会处理。
- 硬中断:1号键盘,2号鼠标
- 软中断:read 。read、fork ,内核会停下自己,拿到参数去执行返回
- 系统调用:interrupt 0x80(C语言层面) 或者 sysenter (原语层面)原语,通过ax寄存器填入调用号(调用号就代表使用的是几号函数,read fork等等)。参数通过bx cx dx si di 传入内核,返回值通过ax 返回
- java读网络:jvm read() --> c read() --> 内核空间 也就是inter 80 这个中断 --> system_call() 系统调用处理程序,查看ax参数里面使用的内核函数 --> sys_read(),之后再去剩下的五个bx cx dx si di 读取参数 ,最后将结果写在ax,返回ax,程序去ax读取结果即可
int 0x80
系统调用会调用这个,这个会导致中断,中断映射表找到 callback的地址开始执行对应的系统调用
虚拟内存管理--MMU
发展历程
DOS:同一时间只有一个进程在运行
win 9x :多个进程都放入1. 内存撑爆,2. 互相打扰,会访问到别人的空间
- 解决内存爆的问题:程序分页,内存划分为一个个个page frame页框,固定大小,装入分页之后的程序,页框大小就是4K,内存标准页大小就是4K,
- 局部性原理,时间局部性空间局部性
- 内存满了,进入swap交换分区,LRU算法
- 其实就是LinkedHashMap的实现,也就是hashMap保证查找是O1,链表保证删除插入是O1
- 还必须是双向链表就是为了避免找左右的时候还需要从头开始遍历
- 一个进程新建分配资源的时候只是分配了一张表,内存都没分配,记录了程序的页表在硬盘的位置就行了
- 缺页中断:内存中没有要使用的页面,产生缺页异常,由内核处理并且
- 解决相互打扰的问题:虚拟内存。直接访问程序的物理内存地址是比较危险的,为了保证不会互相影响,所以程序使用的空间地址不是物理空间,而是虚拟的地址,A永远访问不了B的地址
- 虚拟空间的大小:寻址空间,64位的操作系统就是 2^ 64的大小,只要自己可以表示就可以,单位是bit
- 在程序的角度,进程是独享整个系统+CPU的,每一个进程虚拟的独占整个CPU
- 段页式:进程的虚拟内存是分段的(按照程序功能),分段里面才是分页,需要该页的时候加载到页框
- 逻辑地址转换为虚拟空间的线性地址再映射到物理地址:内存映射的线性地址 = 基地址+偏移量(逻辑地址),操作系统+MMU来完成线性地址最终找到物理地址的转换
- 缺页异常:产生一个软中断去读取数据
MMU 虚拟内存管理单元
内存管理单元:使得每一个进程都有自己独立的虚拟地址空间。
作用:
- 地址转换:将线性地址映射为物理地址
- 提供硬件机制的内存访问授权
大多数使用MMU的机器都采用分页机制。虚拟地址空间以页为单位进行划分,而相应的物理地址空间也被划分,其使用的单位称为页帧,页帧和页必须保持相同,因为内存与外部存储器之间的传输是以页为单位进行传输的。
如果处理器启用了MMU,CPU执行单元发出的内存地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address,以下简称VA),而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将VA映射成PA
快表
快表其实是对MMU的加速,和MMU交互太慢所以直接和CPU里面的快表交互
物理内存
page cache
物理内存上是一个。但是可以有多个进程,每一个进程有自己的fd,维护自己的seek,所以页缓存是一份,但是不同的进程之间读取整个文件不会相互影响。(明析fd和INODE的关系,多个fd可以指向一个INODE,fd是针对进程而言的)
DMA-直接存储器访问
在实现DMA传输时,是由DMA控制器直接掌管总线,因此,存在着一个总线控制权转移问题。
即DMA传输前,CPU要把总线控制权交给DMA控制器,而在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU。一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。
虚拟文件系统--VFS树
df -h 查看虚拟目录树挂载的真正物理地址
磁盘分区(也就是文件系统。都按照一定的文件系统规则进行了格式化)挂载到VFS 树的不同目录节点,
其中系统启动会将内核的镜像文件系统(图中的/dev/sda1)加载之后,挂载到虚拟节点 /boot 下,会将操作系统(图中的/dev/mapper/centos-root)的文件系统挂载到虚拟节点 / 下,所以其实是 /dev/sda1 的文件系统覆盖了 /dev/mapper/centos-root 对于虚拟节点 /boot 的挂载,可以自己手动去除虚拟节点 /boot 上文件系统/dev/sda1 的挂载,得到 /boot 就是空的目录了
在将文件系统挂载到VFS树上的 /boot 虚拟节点上,文件夹里面又有了内容:
所以虚拟目录树是Linux的一个规范,结构是稳定化的。至于是哪一个文件系统挂载到这个树上的哪个节点是可以灵活改变的!!!
Linux 文件类型
冯诺依曼计算机:控制器(CPU)存储器(主存内存)输入输出设备(IO设设备)。
虚拟文件系统的抽象,就是一切皆文件。
<font style="color:#F5222D;">-</font>
: 普通文件(可执行文件)TYPE=REG- d : 目录 TYPE=DIR
- b : 块设备 TYPE=CHR,可以随意漂移,硬盘等等 (/dev目录下)
- c : 字符设备,不能随意漂移,键盘网卡都是(/dev目录下)
- s : socket
- p : pipline
- l : 连接
- eventPoll : 内存提供的epoll区域
- 等等
ls -l 之后第一列的内容就是文件的类型,之后的就是文件的权限,以及所属 用户以及所属的用户组,文件大小
/dev 目录下会有c 开头的字符设备和 b 开头的块设备