《X86 汇编语言:从实模式到保护模式》

series:

《X86 汇编语言:从实模式到保护模式》

1
2
3
4
5
6
mov ax,0xb800
mov ds,ax
mov [0x00],a
mov [0x02],s
mov [0x04],m
jmp $
1
nasm -f bin a.asm -o a.bin
1
2
3
error: symbol `’a’' not defined
error: symbol `’s’' not defined
error: symbol `’m’' not defined

mov 指令没有指定操作数的大小,nasm 不知道怎么理解这三个 ASCII 码,可以是 8bit 的也可以是 16 或者 32bit

把显存的基地址移动到段寄存器里,然后字符 A 加上黑底白字下划线的特效移动到偏移 0x0 处,最后死循环

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
mov ax,0xb800
mov ds,ax
mov word ds:[0x0],0x41
mov word ds:[0x1],0x07

spin:
    jmp spin 
    
    times 510 - ( $ - $$ ) db 0
    dw 0xaa55 ;=== 确保以 0x55 0xaa 为结尾    

hdc-0.11.img

series:

https://github.com/dibingfa/flash-linux0.11-talk

moutn hdc-0.11.img 会

1
2
mount: /home/dd/hdc: wrong fs type, bad option, bad superblock on /dev/loop0, missing codepage or helper program, or other error.
       dmesg(1) may have more information after failed mount system call.

编译 wsl 内核,加入 minix 文件系统

1
vi Microsoft/config-wsl
1
2
3
4
5
# CONFIG_MINIX_SUBPARTITION is not set
# CONFIG_MINIX_FS is not set
改成
CONFIG_MINIX_SUBPARTITION=y
CONFIG_MINIX_FS=y
1
make KCONFIG_CONFIG=Microsoft/config-wsl -j$(noproc)

arch/x86/boot/bzImage 拷出来

%userprofile%\.wslconfig

1
2
[wsl2]
kernel=D:\\tmp\\bzImage
1
2
3
4
5
6
7
8
# 查看第一个空闲 loop 设备 
losetup -f  
# 使用上一步得到的设备名,第一次创建 loop 设备
losetup /dev/loop0 hdc-0.11.img
# 查看信息 
fdisk -lu /dev/loop0 
kpartx -av /dev/loop0
mount /dev/mapper/loop0p1 /mnt

p1 代表img 文件的 /分区号,从 1 开始

bios中断

series:

BIOS中断

BIOS 和 DOS 存在于实模式下,通过建立在中断向量表(Interrupt Vector Table,IVT)中的软中断指令 int 中断号来调用

中断向量表

中断服务程序入口地址的偏移量与段基址,一个中断向量占据4字节空间。中断向量表是8086系统内存中最低端1K字节空间,作用就是按照中断类型号从小到大的顺序存储对应的中断向量,总共存储256个中断向量

在中断响应过程中,CPU通过从接口电路获取的中断类型号(中断向量号)计算对应中断向量在表中的位置,并从中断向量表中获取中断向量,将程序流程转向中断服务程序的入口地址

由于中断向量表可以在操作系统层面灵活修改,不同系统的中断向量表可能不同

intel在CPU的保护模式下,占用了0x00 ~ 0x1F共32个中断号,Linux下从0x20开始用于系统自身的中断,包括8259芯片的中断重置

汇编

series:

汇编语言主要是 Intel 格式和 AT&T 格式。最常用的是 Intel 格式

Windows 平台 Intel 格式最常用的是两种汇编格式:MASM 和 NASM

《x86汇编语言-从实模式到保护模式》教你如何直接控制硬件,不借助于 BIOS、DOS、Windows、Linux 或者任何其他软件支持的情况下来显示字符、读取硬盘数据、控制其他硬件等

王爽的《汇编语言》基于 MASM,不少内容也过于陈旧,只能作为参考了

nasm 官网 nasm.us

寄存器

寄存器 宽度 类型
rax 64bit long
eax 32bit int
ax 16bit short
ah 8bit ax 寄存器的高八位
al 8bit al 寄存器的低八位

“X” 表示 Extern,表示从 8 位扩展到 16 位

“E”,还是 Extern 的意思

64 位 , “E” 换成 “R”,如RAX

single number

在一个整型数组中,所有的数字都出现了两次,只有一个数是例外,找出这个数

还要求要在不使用额外空间的情况下找到解、要求的线性复杂度

使用二分查找倒是不会用到额外空间,可是时间复杂度为O(nlogn),又不符合线性复杂度

用异或运算的性质:

1
2
3
a xor a = 0
0 xor a = a
a xor b = b xor a

把数组中所有的数异或起来,得到的结果就是仅出现了一次的数

1
2
3
4
5
6
int[] n = new int[]{1, 1, 2, 2, 3, 4, 4, 5, 5};
int ans = 0;
for (int i = 0; i < n.length; ++i) {
	ans ^= n[i];
}
System.out.println(ans);

nginx编译

1
2
3
4
5
6
nginx -V
nginx version: nginx/1.25.3
built by gcc 11.3.1 20221121 (Red Hat 11.3.1-4) (GCC) 
built with OpenSSL 3.0.7 1 Nov 2022
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-http_v3_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -march=x86-64-v2 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie'

直接 ./configure 加参数后

1
2
3
4
5
6
7
8
checking for OS
 + Linux 5.14.0-362.13.1.el9_3.x86_64 x86_64
checking for C compiler ... found
 + using GNU C compiler
 + gcc version: 11.4.1 20230605 (Red Hat 11.4.1-2) (GCC) 
checking for gcc -pipe switch ... found
checking for --with-ld-opt="-Wl,-z,relro -Wl,-z,now -pie" ... not found
./configure: error: the invalid value in --with-ld-opt="-Wl,-z,relro -Wl,-z,now -pie"
1
2
3
yum install redhat-rpm-config.noarch
或者更全的
yum groupinstall "Development tools"

go nil channels

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

func main() {
	a := make(chan int)
	b := make(chan int)
	go func() {
		for i := 0; i < 5; i++ {
			a <- i
		}
		close(a)
	}()
	go func() {
		for i := 0; i < 15; i++ {
			b <- i
		}
		close(b)
	}()
	for {
		select {
		case v := <-a:
			fmt.Println("receive from a", v)
		case v := <-b:
			fmt.Println("receive from b", v)
		}

	}
}

创建两个 goroutines,在 channels a b 上发送消息。发送完值后,关闭 channel

main routine无限循环来消耗channel中的值。不关心处理的顺序,使用 for..select处理来自channel的值

打印从通道获得的值后,无限循环继续运行。循环继续打印来自两个通道 b 和 a 的 0 值

一旦channel关闭,并且所有值都从其缓冲区中耗尽,通道将始终立即返回零值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
receive from b 0
receive from b 1
receive from a 0
receive from a 1
receive from b 2
receive from b 3
receive from a 2
receive from a 3
receive from a 4
receive from a 0
receive from b 4
receive from b 5
receive from a 0
receive from b 6
receive from b 7
receive from a 0
receive from b 8
receive from a 0
receive from b 9
receive from a 0
receive from a 0
receive from a 0
receive from a 0
receive from b 10
receive from a 0
receive from b 11
receive from a 0
receive from b 12
receive from a 0
receive from a 0
receive from b 13
receive from a 0
receive from b 14
receive from a 0
receive from a 0
receive from b 0
receive from a 0
...

惊群

应用程序通过 socket 和协议栈交互,socket 隔离了应用程序和协议栈,socket 是两者之间的接口,对于应用程序,它代表协议栈;而对于协议栈,它又代表应用程序,当数据包到达协议栈的时候,发生下面两个过程:

  1. 协议栈将数据包放入socket的接收缓冲区队列,并通知持有该socket的应用程序
  2. 持有该socket的应用程序响应通知事件,将数据包从socket的接收缓冲区队列中取出

多个进程阻塞在 accept 调用,协议栈将 client 请求 socket 放入 listen socket 的 accept 队列的时,唤醒一个还是全部进程来处理?

古早linux 内核通过睡眠队列组织所有等待某个事件的 task,wakeup 机制可以异步唤醒整个睡眠队列上的 task,wakeup 逻辑在唤醒睡眠队列时,会遍历该队列链表上的每一个节点,调用每一个节点的 callback,从而唤醒睡眠队列上的每个 task

一个 connect 到达这个 lisent socket 时,内核会唤醒所有睡眠在 accept 队列上的 task

N 个 task 进程 (线程) 同时从 accept 返回,但只有一个 task 返回这个 connect 的 fd,其他 task 都返回 — 1(EAGAIN)。即典型的 accept"惊群" 现象

惊群消耗什么

  1. 系统对用户进程/线程频繁地做无效的调度,上下文切换系统性能大打折扣

    直接的消耗包括 cpu 寄存器要保存和加载(例如程序计数器)、系统调度器的代码需要执行。间接的消耗在于多核 cache 之间的共享数据

  2. 为了确保只有一个线程得到资源,用户必须对资源操作进行加锁保护,进一步加大了系统开销

    锁机制也会造成 cpu 等资源的消耗和性能损耗

常见的服务器有通过锁机制解决,比如 nginx(可关);还有些认为惊群对系统性能影响不大,没有去处理,比如 lighttpd