不求甚解,观其大略!
汇编语言的本质:机器语言的助记符号,就是机器语言
比如 move --> 10001000 在汇编语言中的move在执行的时候就会在对应的表中找到对应的记录并变成10001000 的value值
计算机通电–>CPU读取内存中的程序(电信号输入)–>时钟发生器不断震颤通电–>推动CPU内部一行一行的执行(执行多少步取决于指令需要的时钟周期)–>计算完成–>写回(电信号)–>写给显卡输出(sout,或者图形)
量子比特,同时表示1,0
其实我不太理解为什么同时表示1和0就能那么夸张的比只表示1或者0快
不是代表一个固定态,而是代表了所有的可能态。
传统的计算机都是使用二进制的,一个比特就是0或者1,而量子比特也是使用二进制,但它特别就在一个量子比特可以同时是0或者1,这就叫量子叠加。所以,两个量子比特就可以同时表示00、01、10、11这四个值。
具体就不再深入了,不是特别能理解。但是到此为止。
PC -> Program Counter 程序计数器(记录当前指令地址)
Registers -> 暂存器,暂时存储CPU计算要用到的数据
ALU -> 逻辑运算单元 Arithmetic & Logic Unit
CU -> 控制单元 Control Unit
MMU -> 内存管理单元 Memory Management Unit
https://www.cnblogs.com/z00377750/p/9180644.html
MESI协议
CPU中每个缓存行都使用4中状态进行标记(额外使用2位)
M:被修改(Modified)
该缓存行只被缓存在该CPU这个呢,并且是被修改过的(dirty),即与主存中的数据不一致,该缓存行需要在未来的某一个时间点(允许其他CPU读取主存中相应内存之前)写回到主存 。当被写会到主存之后。状态会改为E(独享的)
E:独享的(Exclusive)
该缓存行只被缓存在该CPU中,他是未被修改过的(clean),与主存中的数据一致。该状态可以在任何时刻当有其他CPU读取时变成共享状态S(shard)
同样的,当CPU修改了缓存行中的内容时,该状态可以变成M(被修改)状态
S:共享的(Shared)
该状态意味着该缓存行被多个CPU缓存,并且各个缓存的数据与主存数据一致(clean),当有一个CPU修改缓存行中的值。其他的CPU中的该缓存行可以呗作废(变成无效状态I(invalid)
I:无效的(invalid)
该缓存是无效的(可能是其他的CPU修改了该缓存行)
CPU读请求:除了invalid状态,其他的状态都能满足CPU的读请求,如果是invalid,那么会在主存中读取,并且变成shared或者exclusive
CPU写请求:只能在modified或者exclusive状态下能进行,如果是shared状态,需要变成invalid(不允许不同CPU修改同一个缓存行,即使数据处于不同的地方)。改操作通常使用广播的方式来完成
缓存能将任何一个非M状态直接作废,但是如果是M状态,那么必须先写会到主存
如果有CPU读取M对应的主存(M的缓存行负责监听),那么会先将M写会到主存在执行其他CPU读取M对应的主存的操作
处于S的缓存行会监听该缓存行变成invalid的操作或变成E的操作,并且会把自己的缓存行变成invalid
处于E的缓存行会监听主存中对应的位置,如果有其他的CPU读取会将状态变成shared
缓存行越大,局部性空间效率越高,但是读取时间越慢
缓存行越小,局部性空间效率越低,但是读取时间越快
Intel CPU选取了一个折中值 64字节
package com.example.demo;
/**
* 测试缓存行 64字节 位于同一个缓存行
*
* @Author: xiaobin
* @Date: 2020/5/26 12:58
*/
public class T03_CacheLinePadding {
public static volatile long[] arr = new long[2];
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10_0000_0000L; i++) {
arr[0] = i;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10_0000_0000L; i++) {
arr[1] = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
package com.example.demo;
/**
* 位于不同缓存行
*
* @Author: xiaobin
* @Date: 2020/5/26 13:02
*/
public class T04_CacheLinePadding {
public static volatile long[] arr = new long[16];
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10_0000_0000L; i++) {
arr[0] = i;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10_0000_0000L; i++) {
arr[8] = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
但是我在以上跑,前者时间仅仅是后者时间的1/3。比较奇怪
对于有些特别敏感的数字,会存在线程高竞争的访问,为了保证不发生伪共享,可以使用缓存行对齐的编程方式
在jdk1.7中,很多采用long padding提高效率。就是前面加8个long的声明,后面加8个long的声明
jdk1.8加入了@Contended注解。需要加上:JVM -XX:-RestrictContended
计算机为了执行效率,可能会发生指令重拍,没有依赖关系的命令可能重新排序
CPU层面:Intel -> 原语(mfence Ifence sfence)或者锁总线
JVM层次:8个hanppens-before原则 4个内存屏障(LL LS SL SS)
as-if-serial:不管硬件什么顺序,单线程执行的结果不变,看上去就像serial
Write Combining Buffer
一般是4个字节
由于ALU的速度太快,所以在写入L1的同时,写入一个WC buffer,满了之后,再直接更新到L2
Non Uniform Memory Access
ZGC -NUMA aware
分配内存会优先分配给靠近CPU的内存
通电 -> bios uefi工作 -> 自检 -> 到硬盘的固定位置加载bootloader(一般是磁盘的第一个扇区)-> 读取可配置信息 -> CMOS
微内核 -弹性部署 5G 物联网(The Internet of Things,简称lot)
宏内核 -pc phone
外核 -科研 实验中 为应用定制操作系统(不懂)
CPU分不同的指令级别
Linux内核跑在ring 0级,用户程序跑在ring 3级,对于系统的关键访问,需要经过kernel的同意,保证系统的健壮性
内核执行操作 --> 200多个系统调用 read write fork…
JVM --> 站在OS的角度看,就是一个普通的进程
面试高频:进程和线程有什么区别
答案:进程就是一个程序运行起来的状态,线程就是一个进程中的不同执行路径
专业回答:进程就是OS分配资源的基本单位,线程就是执行调度的基本单位。分配资源最重要的就是:独立的内存空间,线程调度执行(线程共享进程的内存空间,没有自己独立的内存空间)
纤程:用户态的线程,线程中的线程,切换和调度不需要经过OS
优势:
目前支持内置纤程的语言:go kotlin scala python(lib)
利用Quaser库(不成熟)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>mashibing.com</groupId>
<artifactId>HelloFiber</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/co.paralleluniverse/quasar-core -->
<dependency>
<groupId>co.paralleluniverse</groupId>
<artifactId>quasar-core</artifactId>
<version>0.8.0</version>
</dependency>
</dependencies>
</project>
package com.example.demo;
/**
* @Author: xiaobin
* @Date: 2020/5/26 19:18
*/
public class HelloFiber {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
Runnable r = new Runnable() {
@Override
public void run() {
calc();
}
};
int size = 20000;
Thread[] threads = new Thread[size];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(r);
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
for (int i = 0; i < threads.length; i++) {
threads[i].join();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
private static void calc() {
int result = 0;
for (int m = 0; m < 10000; m++) {
for (int i = 0; i < 200; i++) {
result += i;
}
}
}
}
package com.example.demo;
import com.sun.xml.internal.ws.api.pipe.Fiber;
/**
* @Author: xiaobin
* @Date: 2020/5/26 19:23
*/
public class HelloFiber2 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
int size = 10000;
Fiber<Void>[] fibers = new Fiber[size];
for (int i = 0; i < fibers.length; i++) {
fibers[i] = new Fiber<Void>(new SuspendableRunnable() {
public void run() throws SuspendExecution, InterruptedException {
calc();
}
});
}
for (int i = 0; i < fibers.length; i++) {
fibers[i].start();
}
for (int i = 0; i < fibers.length; i++) {
fibers[i].join();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
static void calc() {
int result = 0;
for (int m = 0; m < 10000; m++) {
for (int i = 0; i < 200; i++) result += i;
}
}
}
目前是10000个fiber对应一个线程,还可以通过将其分成10份对应10个线程来提高效率
纤程 vs 线程池 :很短的计算任务,不需要和内核打交道,并发量高!
#include
#include
#include
#include
#include
#include
int main() {
pid_t pid = fork();
if (0 == pid) {
printf("child id is %d\n", getpid());
printf("parent id is %d\n", getppid());
} else {
while(1) {}
}
}
子进程已经死了,并且已经回收了子进程的系统资源,但是父进程还只有子进程的引用
#include
#include
#include
#include
#include
#include
int main() {
pid_t pid = fork();
if (0 == pid) {
printf("child ppid is %d\n", getppid());
sleep(10);
printf("parent ppid is %d\n", getppid());
} else {
printf("parent id is %d\n", getpid());
sleep(5);
exit(0);
}
}
父进程死了,但是子进程还在,一般会挂到进程id为1的进程下,或者图形界面就会挂到图形界面的进程下
2.6采用CFS调度策略: Completely Fair Scheduler
按照优先级分配时间片的比例,记录每个进程的执行时间,如果有一个进程执行时间不到他应该分配的比例,优先执行
默认调度策略:
实时优先级分高低: FIFO (优先级高的先执行)优先级一样 RR(Round Robin)
普通进程:CFS
硬件和操作系统内核打交道的一种方式
软终端(80中断) == 系统调用
系统调用: int 0x80 或者 sysenter原语
通过ax寄存器填入调用号
参数通过 bx cx dx si di传入内核
返回值通过ax返回
Java 读网络 -> Jvm read() --> c read() --> 内核空间 --> System_call()(系统调用处理程序) --> sys_read()
yum install nasm
;hello.asm
;write(int fd, const void *buffer, size_t nbytes)
;fd 文件描述符 file descriptor - linux下一切皆文件
section data
msg db "Hello", 0xA
len equ $ - msg
section .text
global _start
_start:
mov edx, len
mov ecx, msg
mov ebx, 1 ;文件描述符1 std_out
mov eax, 4 ;write函数系统调用号 4
int 0x80
mov ebx, 0
mov eax, 1 ;exit函数系统调用号
int 0x80
编译:nasm -f elf hello.asm -o hello.o
链接:ld -m elf_i386 -o hello hello.o
一个程序的执行,要么处于用户态,要么处于内核态
DOS时代: 同一时间只能有一个进程在运行(也有一些特殊的算法可以支持多进程)
windows 9x 多个进程装入内存,但是存在 内存不够 相互打扰
为了解决这两个问题,诞生了现再的内存管理系统:虚拟地址 分页装入 软硬件结合寻址
1.分页(解决内存不够用问题)内存中分成固定大小的页框(4K),把程序(硬盘)分成4K大小的块,用到哪一块就将哪一块加载到内存,如果加载的过程中内存满了,就将最不常用的那一块放到swap分区,把新的一块加载进来,这个就是著名的LRU算法
2.虚拟内存(解决相互打扰的问题)
1. DOS Win31 ...相互干掉,甚至干掉操作系统
2. 为了保证不相互影响,让进程工作在虚拟内存,程序使用的是虚拟内存,而不是直接的物理地址,这样A进程就永远不知道B进程的空间
3. 虚拟内存有多大? 寻址空间 = 2^64 ,比物理内存大很多 单位是byte
4. 站在虚拟的角度,进程是独享整个内存空间的
5. 内存映射: 偏移量+段的基地址 = 线性地址(虚拟地址)
6. 线性地址通过OS +MMU 内存管理单元 (硬件 Memory Management Unit)找到物理地址
3.缺页中断(不是很重要)
算法叫做:Colored Pointer
GC信息记录在指正上,而不是记录在头部 immediate memory use
42位指针 寻址空间4T JDK13 -> 16T 目前为止最大16T 2^44
总线内部分为:数据总线 地址总线 控制总线
地址总线目前:48位
颜色指针本质上包含了地址映射的概念
互斥锁 排它锁 共享锁 分段锁