oz1010's blog

记录生活或技能

告警

变量初始化顺序

1
2
3
4
5
6
7
8
9
10
11
[build] /root/project/tcl3/aadl2c/include/AADL2C/AIT/CtxUtility.h: In constructor ‘AADL2C::AIT::CtxComponent::CtxComponent(AADL2C::AIT::NodeArchitecture&, AADL2C::AIT::ASTBasePtr, AADL2C::AIT::NodeComponent&)’:
[build] /root/project/tcl3/aadl2c/include/AADL2C/AIT/CtxUtility.h:137:33: warning: ‘AADL2C::AIT::CtxComponent::mpnewInstance’ will be initialized after [-Wreorder]
[build] AADL2C::AIT::NodeComponent* mpnewInstance;
[build] ^~~~~~~~~~~~~
[build] /root/project/tcl3/aadl2c/include/AADL2C/AIT/CtxUtility.h:136:33: warning: ‘AADL2C::AIT::NodeComponent* AADL2C::AIT::CtxComponent::mpexistingInstance’ [-Wreorder]
[build] AADL2C::AIT::NodeComponent* mpexistingInstance;
[build] ^~~~~~~~~~~~~~~~~~
[build] /root/project/tcl3/aadl2c/src/AIT/CtxUtility.cpp:49:1: warning: when initialized here [-Wreorder]
[build] CtxComponent::CtxComponent(AADL2C::AIT::NodeArchitecture& instanceRoot, ASTBasePtr component,
[build] ^~~~~~~~~~~~
[build] In file included from /root/project/tcl3/aadl2c/src/AIT/CtxUtility.cpp:3:0:

这几句告警说的是变量顺序问题,英文大意为:

成员变量mpnewInstance将会在mpexistingInstance之后初始化,当调用类构造函数CtxComponent::CtxComponent进行初始化时。

解决方案:调整相关变量初始化顺序

成员变量

可见性:

  • private
  • protect
  • public

静态成员变量 static

成员常量 const

volitle?

register?

auto?

成员函数

构造函数 析构函数 拷贝构造函数

移动构造函数 移动赋值运算符

运算符(= & + - * / ++ – += -= [] () << >> …)

静态成员函数 static

只读成员函数 const

虚函数 纯虚函数 virtual =0

不可调用函数 =delete

默认类函数 =default

覆写函数 防止覆写函数 override final

单参数构造函数显示转换 explict

友元函数 friend

枚举

字符串化

C语言宏定义可以有一些特殊用法:

  • #: 预处理阶段,将宏参数转化为字符串
  • ##: 预处理阶段,将两个标识符拼接成一个标识符

具体步骤:

  1. 将需要的枚举名放到固定的地方统一管理,使用特别的宏函数ENUM_OR_STRING封装,例如枚举文件signal_list.gen
1
2
3
4
5
// signal_list.gen
ENUM_OR_STRING(LED_OPEN), \
ENUM_OR_STRING(LED_CLOSE), \
ENUM_OR_STRING(MSG_TEST), \
ENUM_OR_STRING(MSG_BUTT) \
  1. 定义宏函数ENUM_OR_STRING,使用枚举文件声明枚举
1
2
3
4
5
6
7
8
9
10
11
// signal_id.h
/* 消息ID转枚举 */
#ifdef ENUM_OR_STRING
#undef ENUM_OR_STRING
#endif
#define ENUM_OR_STRING(x) x

typedef enum
{
#include "signal_list.gen"
} E_MSG_ID;
  1. 定义宏函数ENUM_OR_STRING,实现获取枚举字符串方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifdef ENUM_OR_STRING
#undef ENUM_OR_STRING
#endif
#define ENUM_OR_STRING(x) #x

const int MAX_LENGTH_MSG = 50;
const char msgIdString[][MAX_LENGTH_MSG] = {
#include "signal_list.gen"
};

const char *GetMsgName(int msgID)
{
return msgIdString[msgID];
}

基本语法

缩进

<TAB>缩进符用于构建目标时需要执行的指令的前导符

<SPACE>空格符可以用于函数或判断排版,但不能用于构建目标的前导符

内置函数

wildcard

遍历符合条件的文件,支持通配符。查找NPC_HOME目录下所有cpp后缀的文件,并返回其列表

1
wildcard $(NPC_HOME)/**/*.cpp

error

错误打印,并退出构建

1
$(error NPC_HOME=$(NPC_HOME) is not a NPC repo)

patsubst

字符串处理函数,字符串替换。将目标字符串的替换,替换为目标格式的字符串。

基本格式:$(patsubst <模式>, <替换>, <文本>)

  • <模式>:指定需要匹配的字符串模式,可以包含通配符 %,表示零个或多个字符。
  • <替换>:指定用来替换匹配到的内容的模式,通配符 % 在替换中保留原始匹配内容。
  • <文本>:要进行匹配和替换的目标文本列表。

riscv32替换为'riscv64'

1
2
# CONFIG_ISA="riscv32"
$(patsubst "%32",'%64',$(CONFIG_ISA))

option

直接使用for option将会循环遍历所有参数,配合case完成解析。示例参考nginx项目nginx-1.22.1/auto/options如下:

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
for option; do
opt="$opt `echo $option | sed -e \"s/\(--[^=]*=\)\(.* .*\)/\1'\2'/\"`"

case "$option" in
-*=*) value=`echo "$option" | sed -e 's/[-_a-zA-Z0-9]*=//'` ;;
*) value="" ;;
esac

case "$option" in
--help) help=yes ;;

--prefix=) NGX_PREFIX="!" ;;
--prefix=*) NGX_PREFIX="$value" ;;
--sbin-path=*) NGX_SBIN_PATH="$value" ;;
# ...
--test-build-devpoll) NGX_TEST_BUILD_DEVPOLL=YES ;;
--test-build-eventport) NGX_TEST_BUILD_EVENTPORT=YES ;;
--test-build-epoll) NGX_TEST_BUILD_EPOLL=YES ;;

*)
echo "$0: error: invalid option \"$option\""
exit 1
;;
esac
done

case

直接使用循环,配合$1shift逐一进行解析。

函数

声明自定义函数

  • 使用变量进行声明
  • 使用define关键字定义

调用自定义函数:使用 $(call 函数名, 参数1, 参数2, ...)

  • 自定义函数的参数用 $1, $2, $3, … 表示。
  • $(call ...) 是用于调用自定义函数的 Makefile 内置函数。

示例1:使用变量将内置函数定义为自定义函数

1
2
3
4
5
CONFIG_ISA="riscv32"
# 变量自定义函数
remove_quote = $(patsubst "%32",%64,$(1))
# 调用自定义函数
GUEST_ISA ?= $(call remove_quote,$(CONFIG_ISA))

示例2:使用define定义多参数处理

注意:函数定义中需要使用空格符缩进

1
2
3
4
5
6
7
8
9
define add_prefix_suffix
$(addprefix $1, $(addsuffix $2, $3))
endef

FILES = main utils lib
RESULT = $(call add_prefix_suffix, src/, .c, $(FILES))

all:
echo $(RESULT)

项目实例

嵌入式处理器r5

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
ifneq ($(V),1)
Q := @
else
Q :=
endif

################################以下项目需用户根据需要更改##########################
TARGET := rtos

# 定义路径
TOP_DIR = .
BUILD_DIR := out
OBJ_DIR := $(TOP_DIR)/$(BUILD_DIR)/OBJ
OUT_DIR := $(TOP_DIR)/$(BUILD_DIR)
BUILD := ./out

#链接文件名称和所在路径
LDSCRIPT := ./cortex_r5.ld
LDSCRIPT += -Wl,-Map,$(BUILD)/$(TARGET).map

# libraries
LIBS +=
LIBDIR += $(TOP_DIR)/lib

#启动文件名称和所在路径
START_FILE_SOURCES += $(TOP_DIR)/boot/FreeRTOS_asm.S
START_FILE_SOURCES += $(TOP_DIR)/boot/boot.S
START_FILE_SOURCES += $(TOP_DIR)/os/freertos/portable/portASM.S

# 头文件路径
C_INCLUDES += -D SAFETY_GMAC
C_INCLUDES += -I $(TOP_DIR)/include
C_INCLUDES += -I $(TOP_DIR)/os/freertos/include
C_INCLUDES += -I $(TOP_DIR)/os/freertos/portable

# APP源文件路径
C_SOURCES :=
# C_SOURCES += $(subst ./,,$(shell find ./ -type f -iname "*.c"))
C_SOURCES += $(TOP_DIR)/app/entry/main.c
C_SOURCES += $(TOP_DIR)/app/base/gic_basic_api.c
C_SOURCES += $(TOP_DIR)/app/base/Uart_PBcfg.c
C_SOURCES += $(TOP_DIR)/app/base/isr_cfg.c
C_SOURCES += $(TOP_DIR)/app/base/Uart.c
C_SOURCES += $(TOP_DIR)/app/base/FreeRTOS_tick_config.c
C_SOURCES += $(TOP_DIR)/app/base/Os_Cfg.c
C_SOURCES += $(TOP_DIR)/app/base/libc_syscall.c
C_SOURCES += $(TOP_DIR)/app/base/Port_Port_Ci.c
C_SOURCES += $(TOP_DIR)/app/base/Port_Cfg.c
C_SOURCES += $(TOP_DIR)/app/base/Port.c
C_SOURCES += $(TOP_DIR)/app/base/Port_PBcfg.c
C_SOURCES += $(TOP_DIR)/app/base/Mcu.c
C_SOURCES += $(TOP_DIR)/app/base/utility_lite.c
C_SOURCES += $(TOP_DIR)/app/base/BswInit.c

C_SOURCES += $(TOP_DIR)/os/freertos/portable/portable_port.c
C_SOURCES += $(TOP_DIR)/os/freertos/portable/MemMang/heap_4.c
C_SOURCES += $(TOP_DIR)/os/freertos/src/queue.c
C_SOURCES += $(TOP_DIR)/os/freertos/src/stream_buffer.c
C_SOURCES += $(TOP_DIR)/os/freertos/src/timers.c
C_SOURCES += $(TOP_DIR)/os/freertos/src/croutine.c
C_SOURCES += $(TOP_DIR)/os/freertos/src/tasks.c
C_SOURCES += $(TOP_DIR)/os/freertos/src/event_groups.c
C_SOURCES += $(TOP_DIR)/os/freertos/src/list.c

# 定义编译标志
GCCFLAGS += -D__SINGLE_DISPLAY_DC__=2
GCCFLAGS += -D__DPTX_CONNECTOR__
GCCFLAGS += -mcpu=cortex-r5 -marm -mfloat-abi=hard -mfpu=vfpv3-d16 -O1 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -g3
ASFLAGS = $(GCCFLAGS) -x assembler-with-cpp
CCFLAGS = $(GCCFLAGS) $(C_INCLUDES)
LDFLAGS = $(GCCFLAGS) -T$(LDSCRIPT) -L$(LIBDIR) $(LIBS) -nostartfiles -Xlinker --gc-sections --specs=nano.specs -u _printf_float --specs=nosys.specs

# 支持双系统编译,故需选当前系统,0为linux,1为windows
SYS := 1

# 若指定了windows系统,则需确认编译器的路径,若安装时以默认路径安装,则正确
ifeq ($(SYS), 1)
GCC_PATH = "C:/eclipse/Arm_Embedded_Toolchain/arm-none-eabi/bin"
JLINK_PATH = "C:/Program Files (x86)/SEGGER/JLink"
endif

###################################用户修改结束###################################
# 编译器定义
PREFIX = arm-none-eabi-
ifdef GCC_PATH
SUFFIX = .exe
GDB := arm-none-eabi-gdb
SZ := arm-none-eabi-size
CC := arm-none-eabi-gcc
AS := arm-none-eabi-as
LD := arm-none-eabi-ld
AR := arm-none-eabi-ar
OBJCOPY := arm-none-eabi-objcopy
else
CC := $(PREFIX)gcc
SZ := $(PREFIX)size
OBJCOPY := $(PREFIX)objcopy
GDB := $(PREFIX)gdb
endif
BIN := $(OBJCOPY) -O binary -S
HEX := $(OBJCOPY) -O ihex

# Jlink定义,用于支持一键下载和gdb仿真
ifdef JLINK_PATH
SUFFIX = .exe
JLINKEXE := $(JLINK_PATH)/JLink$(SUFFIX)
JLINKGDBSERVER := $(JLINK_PATH)/JLinkGDBServer$(SUFFIX)
else
JLINKEXE := JLinkExe
JLINKGDBSERVER := JLinkGDBServer
endif

#################### CFLAGS config Start ##########################
#搜索所有的h文件,并输出携带-I的.h文件路径
#C_INCLUDES := $(addprefix -I,$(subst ./,,$(sort $(dir $(shell find ./ -type f -iname "*.h")))))

#################### CFLAGS config End ##########################
#链接指令集-specs=nosys.specs
#是否开启优化掉未使用的函数和符号

#制作启动文件依赖Obj,输出去掉路径的.o文件,可兼容.s和.S
START_FILE_OBJ = $(addsuffix .o, $(basename $(notdir $(START_FILE_SOURCES))))
OBJECTS = $(addprefix $(BUILD)/obj/, $(START_FILE_OBJ))
#搜索所有的c文件,制作所有的.c文件依赖Obj
#C_SOURCES = $(subst ./,,$(shell find ./ -type f -iname "*.c"))

OBJECTS += $(addprefix $(BUILD)/obj/, $(notdir $(C_SOURCES:%.c=%.o)))
#PS:去掉终极目标的原始路径前缀并添加输出文件夹路径前缀(改变了依赖文件的路径前缀,需要重新指定搜索路径)
#指定makefile搜索文件的路径(假如终极目标的依赖文件不携带.c文件所在的路径,
#且不指定搜索路径,makefile会报错没有规则制定目标)
vpath %.c $(sort $(dir $(C_SOURCES))) #取出路径并去重和排序(以首字母为单位)
vpath %.s $(dir $(START_FILE_SOURCES))
vpath %.S $(dir $(START_FILE_SOURCES))

#指定为伪目标跳过隐含规则搜索,提升makefile的性能,并防止make时携带的参数与实际文件重名的问题
.PHONY:all clean printf JLinkGDBServer debug download reset commit
all : $(BUILD)/$(TARGET).elf $(BUILD)/$(TARGET).bin $(BUILD)/$(TARGET).hex

#编译启动文件 备用参数:#-x assembler-with-cpp
$(BUILD)/obj/%.o : %.S Makefile | $(BUILD)/obj
$(Q)echo "$(CC) $(ASFLAGS) -c $< -o $@"
$(Q)$(CC) $(ASFLAGS) -c $< -o $@

#编译工程
$(BUILD)/obj/%.o : %.c Makefile | $(BUILD)/obj
$(Q)echo "$(CC) -c $(CCFLAGS) $(BUILD)/obj/$(notdir $(<:.c=.lst)) -c $< -o $@"
$(Q)$(CC) -c $(CCFLAGS) -Wa,-a,-ad,-alms=$(BUILD)/obj/$(notdir $(<:.c=.lst)) -c $< -o $@

#链接所有的.o生成.elf文件
$(BUILD)/$(TARGET).elf : $(OBJECTS) Makefile | $(BUILD)
$(Q)echo "$(CC) $(OBJECTS) $(LDFLAGS) -o $@"
$(Q)$(CC) $(OBJECTS) $(LDFLAGS) -o $@
$(Q)echo "make $@: "
$(Q)$(SZ) $@

$(BUILD)/%.hex: $(BUILD)/%.elf | $(BUILD)
$(HEX) $< $@

$(BUILD)/%.bin: $(BUILD)/%.elf | $(BUILD)
$(BIN) $< $@
# arm-none-eabi-objdump -D -S $(BUILD)/${TARGET}.elf > $(BUILD)/${TARGET}.dump

$(BUILD)/obj:
$(Q)mkdir -p $@
$(Q)echo "mkdir $@"

#用于检查链接脚本和启动文件是否存在,不存在则报错误
$(START_FILE_SOURCES):
$(Q)echo ERROR: The startup file does not exist or has the wrong path !;\
exit 1
$(LDSCRIPT):
$(Q)echo ERROR: The link file does not exist or has the wrong path !;\
exit 2

clean:
$(RM) -rf $(BUILD)

-include $(wildcard $(BUILD)/obj/*.d)

支持NFS启动的qemu

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# QEMU		:= qemu-system-aarch64
QEMU := QEMU_AUDIO_DRV=none qemu-system-arm
Machine := -M vexpress-a9
# CPU := -cpu cortex-a9
CPU := -smp 1
Memory := -m 512M
Kernel := -kernel linux-6.1.73/arch/arm/boot/zImage
Dtb := -dtb linux-6.1.73/arch/arm/boot/dts/vexpress-v2p-ca9.dtb
SDCard := -sd images/sd_rootfs.ext4
# Network := -net nic -net user
Network := -nic user

# try to generate a unique GDB port
GDBPORT = $(shell expr `id -u` % 5000 + 25000)
# QEMU's gdb stub command line changed in 0.11
QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \
then echo "-gdb tcp::$(GDBPORT)"; \
else echo "-s -p $(GDBPORT)"; fi)

all:
@echo "all done"

qemu-gdb:
@echo "*** Now run 'gdb' in another window." 1>&2
${QEMU} \
${Machine} \
${CPU} \
${Memory} \
${Kernel} \
${Dtb} \
${SDCard} \
${Network} \
-nographic \
-append "root=/dev/mmcblk0 rw console=ttyAMA0" -S -s

run:
@echo "host 10.0.2.2"
@echo "guest 10.0.2.15"
${QEMU} \
${Machine} \
${CPU} \
${Memory} \
${Kernel} \
${Dtb} \
${SDCard} \
${Network} \
-nographic \
-append "root=/dev/mmcblk0 rw console=ttyAMA0"

run-nfs:
@echo "host 10.0.2.2"
@echo "guest 10.0.2.15"
${QEMU} \
${Machine} \
${CPU} \
${Memory} \
${Kernel} \
${Dtb} \
${Network} \
-nographic \
-append "console=ttyAMA0,115200 noinitrd init=/linuxrc loglevel=8 nfsroot=10.0.2.2:/home/johnny/big-proj/os-dev/arm_rootfs/rootfs_v6.x,v3,nolock rw ip=10.0.2.15:10.0.2.2:10.0.2.1:255.255.255.0::eth0:off rootwait"

gui:
@echo "host 10.0.2.2"
@echo "guest 10.0.2.15"
${QEMU} \
${Machine} \
${CPU} \
${Memory} \
${Kernel} \
${Dtb} \
${SDCard} \
${Network} \
-display sdl -serial mon:stdio -append "root=/dev/mmcblk0 rw console=tty0"

# -net nic -net user
# -netdev user,id=network0 -device e1000,netdev=network0,mac=52:54:00:12:34:56
# qemu-system-arm -M vexpress-a9 \
-cpu cortex-a9 -m 256 -kernel ~/share/linux-4.4.157/object/arch/arm/boot/zImage -dtb ~/share/linux-4.4.157/object/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -append "root=/dev/mmcblk0 rw console=tty0" -sd rootfs.ext3

附录

参考文章

跟我一起写Makefile

常用技巧

常用关键词

cmake_minimum_required

1
cmake_minimum_required(VERSION 3.15)

指定最小CMake版本不能低于3.15

project

1
project(emodem C ASM)

指定工程名称,并指定所有源文件可能使用的语言为C或ASM(汇编)。

message

1
message(STATUS "cross compile cortex-r5")

输出消息。

  • STATUS为普通状态消息
  • FATAL_ERROR为致命异常消息,输出消息后配置会立即终止

if

1
2
3
4
5
6
7
if(NOT FREERTOS_CONFIG_FILE_DIRECTORY)
message(FATAL_ERROR "xxx")
elseif(NOT EXISTS xxx.h)
message(FATAL_ERROR "xxx.h")
else
message(status "x")
endif()

判断条件是否满足,并进行相关逻辑处理。

set

1
set(CMAKE_ASM_FLAGS "${COMMON_FLAGS} -x assembler-with-cpp")

指定CMake宏对应的值,子工程会继承父工程的CMake宏。

add_subdirectory

1
add_subdirectory(FreeRTOS/FreeRTOS-Kernel)

增加子工程。

file

1
file(GLOB_RECURSE APP_SRC   "app/**/*.c")

文件处理,GLOB_RECURSE用于递归查找文件,并将结果保存到CMake宏APP_SRC中。

add_executable

1
2
3
add_executable(${MAIN_TARGET}
${APP_SRC}
)

指定可执行文件目标,并指定编译源码${APP_SRC}

add_library

1
2
3
4
add_library(freertos_kernel STATIC
croutine.c
$<IF:$<BOOL:$<FILTER:${FREERTOS_HEAP},EXCLUDE,^[1-5]$>>,${FREERTOS_HEAP},portable/MemMang/heap_${FREERTOS_HEAP}.c>
)

指定静态库目标,并指定编译源码croutine.c portable/MemMang/heap_xxx.c

include

1
include(${CMAKE_TOOLCHAIN_FILE})

包含头文件${CMAKE_TOOLCHAIN_FILE}

include_directories

1
2
3
4
5
include_directories(${MAIN_TARGET}
include
FreeRTOS/FreeRTOS-Kernel/include
FreeRTOS/FreeRTOS-Kernel/portable/GCC/ARM_COTEXT_R5
)

目标增加include文件搜索路径include FreeRTOS/FreeRTOS-Kernel/include FreeRTOS/FreeRTOS-Kernel/portable/GCC/ARM_COTEXT_R5

target_include_directories

1
2
3
4
5
6
target_include_directories(freertos_kernel
PUBLIC
include
portable/GCC/ARM_COTEXT_R5
${FREERTOS_CONFIG_FILE_DIRECTORY}
)

目标增加include文件搜索路径include FreeRTOS/FreeRTOS-Kernel/portable/GCC/ARM_COTEXT_R5 ${FREERTOS_CONFIG_FILE_DIRECTORY}

add_definitions

1
add_definitions(-DDEBUG -DUSE_LOG)

增加编译宏DEBUGUSE_LOG

1
2
3
target_link_directories(${MAIN_TARGET} PRIVATE
${PROJECT_BINARY_DIR}/lib
)

目标增加链接库搜索路径${PROJECT_BINARY_DIR}/lib

1
2
3
4
target_link_libraries(${MAIN_TARGET} PRIVATE
freertos_kernel_port
freertos_kernel
)

目标增加链接库freertos_kernel_port freertos_kernel

常用CMake宏

指定CMake宏使用set关键词进行设置。

名称说明
PROJECT_NAME工程名称
PROJECT_BINARY_DIR工程二进制主路径,即build构建主路径
PROJECT_SOURCE_DIR工程源文件主路径,即主CMakeList.txt文件所在路径
CMAKE_C_COMPILERC语言编译器路径
CMAKE_CXX_COMPILERC++编译器路径
CMAKE_ASM_COMPILER汇编编译器路径
CMAKE_C_FLAGSC语言编译参数
CMAKE_ASM_FLAGS汇编编译参数
CMAKE_EXE_LINKER_FLAGS可执行文件链接参数
LIBRARY_OUTPUT_PATH库输出路径
EXECUTABLE_OUTPUT_PATH可执行文件输出路径

项目实例

嵌入式处理器r5

工程中有三个文件构成,主CMakeLists负责产生elf文件和生成bin文件。FreeRTOS代码在os/freertos中,CMakeLists用于生成内核静态库libfreertos_kernel.aos/freertos/portable下,CMakeLists用于生成处理器相关静态库libfreertos_kernel_port.a

原版CMakeLists内容比较多,这里固定选择GCC_ARM_COTEXT_R5,已将冗余项裁剪,具体可以参考FreeRTOSv202210.01-LTS对应文件。

主CMakeLists

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
cmake_minimum_required(VERSION 3.15)

project(emodem C ASM)

message(STATUS "cross compile cortex-r5")

set(TOOLCHAIN_PATH /usr/local/gcc-arm-none-eabi-8-2018-q4-major)
set(CMAKE_C_COMPILER ${TOOLCHAIN_PATH}/bin/arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PATH}/bin/arm-none-eabi-gcc)
set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PATH}/bin/arm-none-eabi-gcc)
set(COMMON_FLAGS "-mcpu=cortex-r5 -marm -mfloat-abi=hard -mfpu=vfpv3-d16 -O1 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -g3")
set(CMAKE_C_FLAGS "-D__SINGLE_DISPLAY_DC__=2 -D__DPTX_CONNECTOR__ ${COMMON_FLAGS} -D SAFETY_GMAC")
set(CMAKE_ASM_FLAGS "-D__SINGLE_DISPLAY_DC__=2 -D__DPTX_CONNECTOR__ ${COMMON_FLAGS} -x assembler-with-cpp")
set(CMAKE_EXE_LINKER_FLAGS "-T ${PROJECT_SOURCE_DIR}/cortex_r5.ld -Wl,-Map,${PROJECT_NAME}.map -nostartfiles -Xlinker --gc-sections --specs=nano.specs -u _printf_float --specs=nosys.specs")

set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
# option(MYDEBUG "enable debug compilation" OFF)
# if (MYDEBUG)
# add_definitions(-DMYDEBUG)
# message(STATUS "Currently is in debug mode")
# else()
# message(STATUS "Currently is not in debug mode")
# endif()

set(FREERTOS_CONFIG_FILE_DIRECTORY ${PROJECT_SOURCE_DIR}/include CACHE STRING "Absolute path to the directory with FreeRTOSConfig.h")
add_subdirectory(os/freertos)

file(GLOB_RECURSE APP_SRC "app/**/*.c")
file(GLOB_RECURSE BOOT_SRC "boot/*.S")

# find_library(FREERTOS_LIB testFunc HINTS ${PROJECT_SOURCE_DIR}/testFunc/lib)

set(MAIN_TARGET ${PROJECT_NAME}_freertos.elf)
add_executable(${MAIN_TARGET}
${BOOT_SRC}
${APP_SRC}
)
include_directories(${MAIN_TARGET}
include
os/freertos/include
os/freertos/portable
)
target_link_directories(${MAIN_TARGET} PRIVATE
${PROJECT_BINARY_DIR}/lib
${PROJECT_SOURCE_DIR}/lib
)
target_link_libraries(${MAIN_TARGET} PRIVATE
freertos_kernel_port
freertos_kernel
# mcal
)
set(MAIN_BIN_TARGET ${PROJECT_NAME}_freertos.bin)
add_custom_command(TARGET ${MAIN_TARGET} POST_BUILD
COMMAND ${TOOLCHAIN_PATH}/bin/arm-none-eabi-objcopy -O binary -S ${EXECUTABLE_OUTPUT_PATH}/${MAIN_TARGET} ${EXECUTABLE_OUTPUT_PATH}/${MAIN_BIN_TARGET}
COMMENT "Generate ${EXECUTABLE_OUTPUT_PATH}/${MAIN_BIN_TARGET}"
)


# target_link_options(${MAIN_TARGET} PRIVATE
# -T${PROJECT_SOURCE_DIR}/cortex_r5.ld
# )

# # indicate lib path
# target_link_directories(${MAIN_TARGET}
# PRIVATE
# ${OUT_PATH}/lib
# )



# # on the basic of debug to set CFLAGS
# if (debug STREQUAL "yes")
# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -O0 -ggdb3")
# else()
# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -O2")
# endif()

# # on the basic of r5_LITTLE_FLASH to set CFLAGS
# if (r5_LITTLE_FLASH STREQUAL "TRUE")
# # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSAMPLE_620U_NAND")
# endif()

os/freertos的MakeLists

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
cmake_minimum_required(VERSION 3.15)

# User is responsible to set two mandatory options:
# FREERTOS_CONFIG_FILE_DIRECTORY
# FREERTOS_PORT
#
# User can choose which heap implementation to use (either the implementations
# included with FreeRTOS [1..5] or a custom implementation ) by providing the
# option FREERTOS_HEAP. If the option is not set, the cmake will default to
# using heap_4.c.

# Absolute path to FreeRTOS config file directory
# set(FREERTOS_CONFIG_FILE_DIRECTORY ${FREERTOS_CONFIG_FILE_PATH} CACHE STRING "Absolute path to the directory with FreeRTOSConfig.h")

if(NOT FREERTOS_CONFIG_FILE_DIRECTORY)
message(FATAL_ERROR " FreeRTOSConfig.h file directory not specified. Please specify absolute path to it from top-level CMake file:\n"
" set(FREERTOS_CONFIG_FILE_DIRECTORY <absolute path to FreeRTOSConfig.h directory> CACHE STRING \"\")\n"
" or from CMake command line option:\n"
" -DFREERTOS_CONFIG_FILE_DIRECTORY='/absolute_path/to/FreeRTOSConfig.h/directory'")
elseif(NOT EXISTS ${FREERTOS_CONFIG_FILE_DIRECTORY}/FreeRTOSConfig.h)
message(FATAL_ERROR " FreeRTOSConfig.h file not found in the directory specified (${FREERTOS_CONFIG_FILE_DIRECTORY})\n"
" Please specify absolute path to it from top-level CMake file:\n"
" set(FREERTOS_CONFIG_FILE_DIRECTORY <absolute path to FreeRTOSConfig.h directory> CACHE STRING \"\")\n"
" or from CMake command line option:\n"
" -DFREERTOS_CONFIG_FILE_DIRECTORY='/absolute_path/to/FreeRTOSConfig.h/directory'")
endif()

# Heap number or absolute path to custom heap implementation provided by user
set(FREERTOS_HEAP "4" CACHE STRING "FreeRTOS heap model number. 1 .. 5. Or absolute path to custom heap source file")

# FreeRTOS port option
set(FREERTOS_PORT "GCC_ARM_COTEXT_R5" CACHE STRING "FreeRTOS port name")

if(NOT FREERTOS_PORT)
message(FATAL_ERROR " FREERTOS_PORT is not set. Please specify it from top-level CMake file (example):\n"
" set(FREERTOS_PORT GCC_ARM_CM4F CACHE STRING \"\")\n"
" or from CMake command line option:\n"
" -DFREERTOS_PORT=GCC_ARM_CM4F\n"
" \n"
" Available port options:\n"
" GCC_ARM_COTEXT_R5 - Compiller: GCC Target: ARM Cortex-R5")
endif()

add_subdirectory(portable)

add_library(freertos_kernel STATIC
src/croutine.c
src/event_groups.c
src/list.c
src/queue.c
src/stream_buffer.c
src/tasks.c
src/timers.c

# If FREERTOS_HEAP is digit between 1 .. 5 - it is heap number, otherwise - it is path to custom heap source file
$<IF:$<BOOL:$<FILTER:${FREERTOS_HEAP},EXCLUDE,^[1-5]$>>,${FREERTOS_HEAP},portable/MemMang/heap_${FREERTOS_HEAP}.c>
)

target_include_directories(freertos_kernel
PUBLIC
include
portable/GCC/ARM_COTEXT_R5
${FREERTOS_CONFIG_FILE_DIRECTORY}
)

target_link_libraries(freertos_kernel freertos_kernel_port)

os/freertos/portable的CMakeLists

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
39
40
41
42
43
44
45
46
47
48
49
50
# FreeRTOS internal cmake file. Do not use it in user top-level project

add_library(freertos_kernel_port STATIC
# ARM-Cortext-R5 port for GCC
$<$<STREQUAL:${FREERTOS_PORT},GCC_ARM_COTEXT_R5>:
portable_port.c
portASM.S>

# 16-Bit DOS ports for BCC
$<$<STREQUAL:${FREERTOS_PORT},BCC_16BIT_DOS_FLSH186>:
BCC/16BitDOS/common/portcomn.c
BCC/16BitDOS/Flsh186/port.c>
)

if( FREERTOS_PORT MATCHES "GCC_ARM_CM(3|4)_MPU" OR
FREERTOS_PORT STREQUAL "IAR_ARM_CM4F_MPU" OR
FREERTOS_PORT STREQUAL "RVDS_ARM_CM4_MPU" OR
FREERTOS_PORT MATCHES "GCC_ARM_CM(23|33|55|85)_NTZ_NONSECURE" OR
FREERTOS_PORT MATCHES "GCC_ARM_CM(23|33|55|85)_NONSECURE" OR
FREERTOS_PORT MATCHES "GCC_ARM_CM(33|55|85)_TFM" OR
FREERTOS_PORT MATCHES "IAR_ARM_CM(23|33|55|85)_NTZ_NONSECURE" OR
FREERTOS_PORT MATCHES "IAR_ARM_CM(23|33|55|85)_NONSECURE"
)
target_sources(freertos_kernel_port PRIVATE Common/mpu_wrappers.c)
endif()

target_include_directories(freertos_kernel_port PUBLIC
# ARM-Cortext-R5 port for GCC
$<$<STREQUAL:${FREERTOS_PORT},GCC_ARM_COTEXT_R5>:${CMAKE_CURRENT_LIST_DIR}>

# 16-Bit DOS ports for BCC
$<$<STREQUAL:${FREERTOS_PORT},BCC_16BIT_DOS_FLSH186>:
${CMAKE_CURRENT_LIST_DIR}/BCC/16BitDOS/common
${CMAKE_CURRENT_LIST_DIR}/BCC/16BitDOS/Flsh186>

$<$<STREQUAL:${FREERTOS_PORT},BCC_16BIT_DOS_PC>:
${CMAKE_CURRENT_LIST_DIR}/BCC/16BitDOS/common
${CMAKE_CURRENT_LIST_DIR}/BCC/16BitDOS/PC>
)

target_link_libraries(freertos_kernel_port
PUBLIC
$<$<STREQUAL:${FREERTOS_PORT},GCC_RP2040>:pico_base_headers>
$<$<STREQUAL:${FREERTOS_PORT},GCC_XTENSA_ESP32>:idf::esp32>
PRIVATE
freertos_kernel
"$<$<STREQUAL:${FREERTOS_PORT},GCC_RP2040>:hardware_clocks;hardware_exception>"
$<$<STREQUAL:${FREERTOS_PORT},MSVC_MINGW>:winmm> # Windows library which implements timers
)

其他说明

基本结构

工程文件CMakeLists.txt

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# 指定CMake工具的最小版本号
cmake_minimum_required(VERSION 3.10)

# 指定C++标准版本
set(CMAKE_CXX_STANDARD 17)

# 指定工程名称
# CXX可以不指定
project(TEST CXX)

# 判断主机平台,增加编译宏
if (CMAKE_HOST_SYSTEM_NAME MATCHES "Linux")
message("-- Detecting system: Linux")
add_definitions(-DBUILD_IN_LINUX)
else()
message("-- Detecting system: Windows")
endif()
# 判断平台
if(WIN32)
message(STATUS "This is windows")
elseif(UNIX)
message(STATUS "This is linux")
else()
message(STATUS "This is mac")
endif()

# 判断编译模式
if(CMAKE_BUILD_TYPE AND (CMAKE_BUILD_TYPE STREQUAL "Debug"))
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -O0")
message(STATUS "Build mode: ${CMAKE_BUILD_TYPE} ${CMAKE_CXX_FLAGS_DEBUG}")
else()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wall")
message(STATUS "Build mode: ${CMAKE_BUILD_TYPE} ${CMAKE_CXX_FLAGS_RELEASE}")
endif()

# 查找curl包,CMake自带包管理
# 根据依赖添加
find_package(CURL)
if(CURL_FOUND)
include_directories(${CURL_INCLUDE_DIR})
else()
message(FATAL_ERROR "CURL library not found")
endif()
# 自定义LARCH包,REQUIRED必须具备此包
# 根据依赖添加
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_BINARY_DIR}/../cmod)
find_package(LARCH REQUIRED)
if(LARCH_FOUND)
message(STATUS "LARCH library found")
include_directories(${LARCH_INCLUDE_DIR})
else()
message(FATAL_ERROR "LARCH library not found")
endif()

# 配置各子组件,目录中需要包含CMakeLists.txt文件
add_subdirectory(show)
add_subdirectory(lib)

# 配置工程各类组件输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
set(LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
set(ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/liba)

# 设置环境变量方式
set(ENV{ENV_PRO_PATH} ${PROJECT_BINARY_DIR})

# 递归(GLOB_RECURSE)查找src/AST目录中cpp文件
file(GLOB_RECURSE AST_SOURCE "src/AST/*.cpp")

# 增加可执行文件编译目标
add_executable(testc "src/tools/Generator.cpp"
${AST_SOURCE}
)

# 增加可执行文件编译的头文件目录
target_include_directories(testc PUBLIC include # include
)

# 处理过程执行命令
# 增加命令执行,并将结果输出到变量中,由message打印出来
exec_program(ls ${PROJECT_BINARY_DIR}
OUTPUT_VARIABLE o1
ARGS -l -h
RETURN_VALUE r1
)
message("ls output:\n${o1} \n ls return: ${r1}")

message

基本格式

message( [STATUS|WARNING|AUTHOR_WARNING|SEND_ERROR|FATAL_ERROR] "Message to display" ...)

STATUS

状态消息,自动在消息前增加--以示区别

1
2
3
message(STATUS "Detecting system: Linux")

[cmake] -- Detecting system: Linux

WARFATAL_ERRORNING

告警消息,消息单独显示一行,CMake会继续执行

1
2
3
4
5
6
7
message(WARNING "Detecting system: Linux")

[cmake] CMake Warning at CMakeLists.txt:8 (message):
[cmake] Detecting system: Linux
[cmake]
[cmake]
[cmake] Not searching for unused variables given on the command line.

AUTHOR_WARNING

开发者告警消息,消息单独显示一行,CMake会继续执行

1
2
3
4
5
6
7
message(AUTHOR_WARNING "Detecting system: Linux")

[cmake] CMake Warning (dev) at CMakeLists.txt:8 (message):
[cmake] Detecting system: Linux
[cmake] This warning is for project developers. Use -Wno-dev to suppress it.
[cmake]
[cmake] Not searching for unused variables given on the command line.

SEND_ERROR

错误消息,消息单独显示一行,CMake会继续执行但会跳过生成步骤

1
2
3
4
5
6
7
8
9
message(SEND_ERROR "Detecting system: Linux")

[cmake] CMake Error at CMakeLists.txt:8 (message):
[cmake] Detecting system: Linux
[cmake]
[cmake]
[cmake] Not searching for unused variables given on the command line.
[cmake] -- Configuring incomplete, errors occurred!
[cmake] See also "/root/workspace/tcl3/build/CMakeFiles/CMakeOutput.log".

FATAL_ERROR

错误消息,消息单独显示一行,CMake会终止所有处理

1
2
3
4
5
6
7
8
9
message(FATAL_ERROR "Detecting system: Linux")

[cmake] CMake Error at CMakeLists.txt:8 (message):
[cmake] Detecting system: Linux
[cmake]
[cmake]
[cmake] Not searching for unused variables given on the command line.
[cmake] -- Configuring incomplete, errors occurred!
[cmake] See also "/root/workspace/tcl3/build/CMakeFiles/CMakeOutput.log".

自定义包

自定义LARCH

创建文件cmod/FindLARCH.cmake,文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# set(CMAKE_FIND_LIBRARY_PREFIXES lib)
# set(CMAKE_FIND_LIBRARY_SUFFIXES .so)

find_path(LARCH_INCLUDE_DIR larch.h /tmp/test/p1/include)
find_path(LARCH_LIBRARY liblarch.so /tmp/test/p1/lib)
# find_library(LARCH_LIBRARY NAMES larch PATH /tmp/test/p1/lib)

if(LARCH_INCLUDE_DIR AND LARCH_LIBRARY)
set(LARCH_FOUND TRUE)
endif()

if(LARCH_FOUND)
if(NOT LARCH_FIND_QUIETLY)
message(STATUS "Found LARCH: ${LARCH_LIBRARY}")
endif()
else()
if(LARCH_FIND_REQUIRED)
message(FATAL_ERROR "LARCH library not found")
endif()
endif()

子组件

库文件

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
cmake_minimum_required(VERSION 3.10)

# 配置库源文件
set(LIB_SRC larch.c)

# 配置版本号
set(API_VERSION 1.2)
set(CUR_VERSION 1.2.1)

# 编译动态库;VERSION为当前版本;SOVERSION为API版本,它会链接到当前版本,不带版本库也会链接到API版本;
add_library(larch SHARED ${LIB_SRC})
set_target_properties(larch PROPERTIES VERSION ${CUR_VERSION} SOVERSION ${API_VERSION})

# 编译静态库,目标不能同名,静态库需要用属性来修改
add_library(larch_static STATIC ${LIB_SRC})
set_target_properties(larch_static PROPERTIES OUTPUT_NAME "larch")

# 获取目标属性
get_target_property(OV larch_static OUTPUT_NAME)
# message(STATUS "This is larch_static name:"${OV})

# 配置共享库和头文件安装路径
install(TARGETS larch larch_static
RUNTIME DESTINATION /${PROJECT_NAME}/bin # 执行程序
LIBRARY DESTINATION /${PROJECT_NAME}/lib # 动态库
ARCHIVE DESTINATION /${PROJECT_NAME}/libstatic # 静态库
# PERMISSIONS OWNER_EXECUTE OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ # 配置权限
)
install(FILES larch.h DESTINATION /${PROJECT_NAME}/include)

可执行文件

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
39
40
41
42
43
cmake_minimum_required(VERSION 3.10)

# 配置源码和输出路径
# set(DESTDIR /usr/local)
# CMAKE_INSTALL_PREFIX:=/usr/local
# set(SRC_LIST main.c)
aux_source_directory(. SRC_LIST)

# 配置cmake构建打印
message(STATUS "Start build tr")

# 配置编译执行程序
add_executable(tr ${SRC_LIST})

# 配置头文件搜索路径
# 这里应该改为工程包含路径${LARCH_INCLUDE_DIR},见自定义LARCH包
find_path(header_path NAMES larch.h PATHS ../lib)
if(header_path)
include_directories(${header_path})
message(STATUS "find header path: " ${header_path})
endif(header_path)

# 配置动态库或静态库(liblarch.a)
target_link_libraries(tr larch)
add_dependencies(tr larch)

# 配置程序和库安装路径
install(TARGETS tr
RUNTIME DESTINATION /${PROJECT_NAME}/bin # 执行程序
LIBRARY DESTINATION /${PROJECT_NAME}/lib # 动态库
ARCHIVE DESTINATION /${PROJECT_NAME}/libstatic # 静态库
# PERMISSIONS OWNER_EXECUTE OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ # 配置权限
)

# 配置文档或目录安装路径
install(PROGRAMS ReadMe.md DESTINATION /${PROJECT_NAME}/docs
PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ # 配置权限
)
install(DIRECTORY scripts/ DESTINATION /${PROJECT_NAME}/scripts
PATTERN "scripts/*.sh"
PATTERN "scripts/*.conf" EXCLUDE
)

Xmodem

参考Xmodem 协议介绍及应用(基于 ESP-IDF)

数据长度有两种:128字节和1K字节。以下主要以windterm版本进行说明,可能在不同版本中有些许差异。

协议控制符

缩写数值说明
SOH0x01modem 128字节头标识
STX0x02modem 1024字节头标识
EOT0x04结束文件传输
ACK0x06正常响应
NAK0x15非正常响应
ETB0x17传输块结束
CAN0x18取消文件传输
CRC160x43使用CRC16校验标志

Xmodem-1K协议帧格式

windterm版本发送Frame基本格式:

Byte 0Byte 1Byte 2Byte 3~1026Byte 1027
头标识序号序号反码数据区数据总长度

各字段说明:

  • 头标识可以为:SOH和STX
  • 序号范围0~255;
  • 数据区不足部分需要使用0x1a补齐;

Xmodem-1K交互流程

SenderFlowReceiver
<==NAK
Timeout after 0.5 seconds
<==NAK
Frame-01==>Frame OK
<==ACK
Frame-02==>Frame Error
<==NAK
Frame-02==>Frame OK
<==ACK
EOT==>OK
<==ACK

接收方 - 开启xmodem模式,每0.5s发送一次0x15; - 当接收到帧数据时,正确回复ACK响应,异常回复NAK响应; - 当接收到EOT时,传输结束;

发送方 - 开启xmodem模式,等待接收NAK; - 发送帧数据,等待接收响应; - 当所有数据发送完毕时,主动发送EOT,传输结束;

交互示例

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
send COM21 (1)
str:
d

recv COM21 (51)
str:

download safety code
start xModem Receive->
d a d d a 64 6f 77 6e 6c 6f 61 64 20 73 61 66 65 74 79 20 63 6f 64 65 d d a 73 74 61 72 74 20 78 4d 6f 64 65 6d 20 52 65 63 65 69 76 65 2d 3e 15

recv COM21 (1)
str:
15

send COM21 (12)
str: rx data.log
72 78 20 64 61 74 61 2e 6c 6f 67 d

recv COM21 (12)
str:
15 15 15 15 15 15 15 15 15 15 15 15

recv COM21 (1)
str:
15

recv COM21 (1)
str:
15

recv COM21 (1)
str:
15

recv COM21 (1)
str:
15

recv COM21 (1)
str:
15

send COM21 (1024)
str: ?123456789␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦
2 1 fffffffe 30 31 32 33 34 35 36 37 38 39 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a

send COM21 (4)
str: ␦␦␦
1a 1a 1a 9

recv COM21 (1)
str:
15

send COM21 (1024)
str: ?123456789␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦␦
2 1 fffffffe 30 31 32 33 34 35 36 37 38 39 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a 1a

send COM21 (4)
str: ␦␦␦
1a 1a 1a 9

recv COM21 (1)
str:
6

send COM21 (1)
str:
4

计划

  1. 模拟6502、总线和内存RAM,CPU能够从内存读写数据和获取指令
  2. 参考CPU的数据手册,能够对指令进行解析
  3. 解析卡带Cartidge的文件格式
  4. 实现Mapper功能
  5. 构建PPU模块,能够调用SFML输出画面
  6. 增加按键控制功能

CPU-6502

2A03简介

2A03是Ricoh(理光集团)基于6502制造的NMOS处理器。

小端字节处理器。

CPU内存分布

下图展示处理器如何使用总线访问内存。内存分为三个区域:卡带中的ROM、处理器RAM、I/O寄存器。8位控制总线负责通知组件请求是读还是写。8位数据总线用于读写字节。16位地址总线用于选择目标组件。请注意,ROM是只读的,需要通过MMC访问,以便切换bank。I/O寄存器用于和系统其他组件通信,比如PPU和控制器。

image-20230917160740959

16位地址总线可以访问64KB内存地址$0000-$FFFF。具体的分布见下图。RAM区域$0000-$2000,I/O寄存器区域$2000-$4020,扩展ROM区域$4020-$6000,SRAM区域$6000-$8000,程序ROM区域$8000-$10000

零页是指地址范围$0000-$00FF,在特定模式下允许更快的执行。内存地址$0000-$00FF会被镜像三次到$0800-$2000。比如,任何写到$0000的数据也会写到$0800$1000$1800

位置$2000-$2007每8字节会被镜像到$2008-$3FFF,剩余的寄存器都遵循这个镜像。

SRAM(WRAM)是存储内存,这个地址用于访问记忆游戏Cartridges卡带中的RAM。

$8000开始的地址是分配给Cartridges卡带的PRG-ROM。游戏只有一个16KB数据区(bank)的,会被加载到$8000$C000。游戏有两个16KB数据区的,将一个加载到$8000,另一个加载到$C000。游戏拥有超过两个16KB数据区的,使用内存映射器决定哪个数据区加载到内存。内存映射器会监视特定地址(或地址范围)的内存写入,当地址被写入时,它会执行数据区切换。详见附录D。

image-20230917165157967

寄存器

Program Counter (PC)

16位寄存器;

Stack Pointer (SP)

8位寄存器;固定偏移$0100;位置$0100-$01FF;自顶向下增长;不会检测越界;

Accumulator (A)

8位寄存器;从内存取值;

Index Register X (X)

8位寄存器;从内存取值;地址偏移值;读写SP寄存器;

Index Register Y (Y)

8位寄存器;从内存取值;地址偏移值;

Processor Status (P)

image-20230917211324567
  • Carry Flag (C) 上个指令结果越界标记;指令SEC/CLC,设置为1/0;
  • Zero Flag (Z) 上个指令结果为零标记;
  • Interrupt Disable (I) 中断禁用标记;指令SEI/CLI,设置为1/0;
  • Decimal Mode (D) BCD十进制模式,会被忽略;指令SED/CLD,设置为1/0;
  • Break Command (B) 指明BRK指令,会引发IRQ;
  • Overflow Flag (V) 溢出标记;
  • Negative Flag (N) 符号位;0为正,1为负;

中断

NES有三种类型的中断:NMI、IRQ、复位,中断向量表存储在$FFFA-$FFFF

发生中断时,需要进行如下步骤:

  1. 识别中断;
  2. 完成当前指令执行;
  3. PC和P寄存器压栈;
  4. 设置中断禁用位;
  5. 读取中断向量表处理函数赋值给PC;
  6. 执行处理函数;
  7. 执行RTI指令,恢复P和PC寄存器;
  8. 继续执行程序;

中断或可屏蔽中断,是由某些内存映射器生成的。

IRQ可以由软件使用BRK指令触发。当发生IRQ时,系统跳到$FFFE$FFFF所指地址。

NMI(不可屏蔽中断)是当V-Blank出现在每个帧的末尾时,PPU产生的中断类型。它不受中断禁用寄存器影响,所以它发生时,程序总是被中断。当PPU控制寄存器1($2000)被清空时,会阻止NMI的触发。当发生NMI时,系统跳到$FFFA$FFFB所指地址。

复位中断在系统第一次启动和用户按下复位按钮时触发。当发生复位时,系统跳到$FFFC$FFFD所指地址。

中断优先级为复位、NMI和IRQ。NES有7个周期的中断延时,即它会花费7个CPU周期来开始执行中断处理。

寻址模式

在6502上总共有13种不同的寻址模式。详见附录E。

指令

6502有56条不同的指令,尽管有些指令有多种,使用不同的寻址模式,使得在可能得256个操作码中总计151个有效。指令可以是1、2或3字节长,根据它们的寻址模式。第一个字节为操作码,其他字节为操作数。指令可以分为几个功能组:

  • 加载/存储操作
  • 寄存器传输操作
  • 栈操作
  • 逻辑操作
  • 算术操作
  • 递增/递减
  • 移位
  • 跳转/调用
  • 分支
  • 状态寄存器操作
  • 系统函数

PPU-2C02

内存分布

image-20230923162104444

图案表

英文名PatternTable,卡带CHR-ROM映射到此处,也可能是RAM。游戏中使用的一个图案被称为Tile,背景或精灵都是由若干个Tile组成。

image-20230924191909278

每个Tile大小为8x8像素,游戏屏幕大小为256x240像素,由32x30个Tile组成。

一个图案表占用大小4KB,总共有两个占用8KB。对于许多游戏拥有超过两个图案表,需要使用Mapper来控制映射。

简单计算:

一个PatternTable大小4KB,由256个Tile组成。每个Tile大小为4KB/256=16B。一个Tile大小8x8=64像素,每个像素占用16B/64=2bit。

参考

《Nintendo Entertainment System Documentation Version 1.0.pdf》

《任天堂产品系统文件.chm》

“玩”明白计算机底层系列-计组-红白机模拟器 - 知乎 (zhihu.com)

NES专题_金小庭的博客-CSDN博客

Docker容器安装

镜像源切换

CentOS7主机

软件源切换阿里开源镜像站

1
2
3
4
5
6
7
8
9
# 源备份
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/bk.CentOS-Base.repo

# 下载镜像源
curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo

# 升级系统相关组件
yum update
yum upgrade

容器本体安装

CentOS7主机

参考:菜鸟教程-CentOS Docker安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装容器基本依赖
sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
sudo yum-config-manager \
--add-repo \
https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/docker-ce.repo

# 安装特定版本容器,19.03.15
yum list docker-ce --showduplicates | sort -r
VERSION_STRING=19.03.15; sudo yum install -y docker-ce-${VERSION_STRING} docker-ce-cli-${VERSION_STRING} containerd.io

# 启动服务
systemctl start docker && systemctl enable docker

容器配置

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
# /home/imsdata/docker是Docker基本路径,镜像和容器都会占用此空间,根据需要改动
# https://hub-mirror.c.163.com
# https://reg-mirror.qiniu.com
mkdir -p /etc/docker && mkdir -p /mnt/data_20/docker-root && cat > /etc/docker/daemon.json <<-EOF
{
"registry-mirrors": [
"http://docker.mirrors.ustc.edu.cn",
"http://registry.docker-cn.com",
"http://hub-mirror.c.163.com"
],
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
},
"data-root": "$_"
}
EOF
# 老版本为graph
# 重启容器生效
systemctl daemon-reload && systemctl restart docker
# 容器命令验证,Docker Root Dir: 得到配置的路径
# Cgroup Driver: systemd
docker info | grep -E "Docker Root Dir:|Cgroup Driver:"

安装前准备

设置主机名

1
2
3
4
5
6
7
# 设置主机名
hostnamectl set-hostname <HOSTNAME>

# 将所有主机名加入hosts中,修改/etc/hosts
# echo -ne "192.168.11.251 node1\n192.168.11.252 node2\n192.168.11.253 node3\n" >> /etc/hosts
# echo -ne "n1 192.168.58.12\nn2 192.168.58.14\n" >> /etc/hosts
echo <IP> <HOSTNAME> >> /etc/hosts

禁用防火墙和缓存

CentOS主机

以下步骤二选一,若跳过禁用防火墙,需要增加端口规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 禁用防火墙
systemctl stop firewalld && systemctl disable firewalld

# 开启防火墙后,需要打开NAT转发
# 否则k8s正常启动后,dns解析失败
firewall-cmd --permanent --add-masquerade
# 开放端口规则
# 必须开放k8s使用的6443 10250
firewall-cmd --permanent --zone=public --add-port=6443/tcp
firewall-cmd --permanent --zone=public --add-port=10250/tcp
# 删除端口规则
# firewall-cmd --permanent --zone=public --remove-port=6443/tcp
# 开放指定范围端口规则
firewall-cmd --permanent --zone=public --add-port=31080-31090/tcp
# 开发虚拟网卡
firewall-cmd --permanent --zone=public --add-interface=cni0
# 让规则生效
firewall-cmd --reload
# 查看所有规则
firewall-cmd --list-all

禁用缓存:

1
2
3
4
5
6
7
8
9
10
11
12
# 禁用swap
# 注释/etc/fstab中swap行
swapoff -a
# 注释swap相关行 /mnt/swap swap swap defaults 0 0 这一行或者注释掉这一行
# 虚拟机中运行
# sed -i 's%^/dev/mapper/centos-swap.%# swap %g' /etc/fstab
sed -i 's/\(^[^#|.]*swap.*swap.*\)/#\1/g' /etc/fstab

# 修改启动等待时间为1s
# vim /boot/grub2/grub.cfg,修改第一个timeout值为0,跳过开机等待
# sed -i 's/^ set timeout=.*/ set timeout=0/' /boot/grub2/grub.cfg
sed -i 's/\(^\s*set timeout=\).*/\13/' /boot/grub2/grub.cfg

关闭SELinux

CentOS主机

永久方法 – 需要重启服务器

修改 /etc/selinux/config 文件中设置 SELINUX=disabled ,然后重启服务器,命令

1
2
3
#sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
sed -i 's/\(^SELINUX=\).*/\1disabled/g' /etc/selinux/config
setenforce 0

打开iptables的支持

Debian10通过命令sysctl -a查看,默认已经打开iptabels支持。

CentOS主机

1
2
3
4
5
6
7
8
9
10
11
12
# 配置内核参数(虚拟机方式)
echo -ne "net.bridge.bridge-nf-call-iptables = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\n" >> /etc/sysctl.conf
# 内核配置生效
sysctl -p

# 显示内核参数,回显1为打开,0为关闭
sysctl -a | egrep "bridge-nf-call-iptables|bridge-nf-call-ip6tables|ip_forward"
# 另一种查看方法
cat /proc/sys/net/bridge/bridge-nf-call-iptables
cat /proc/sys/net/bridge/bridge-nf-call-ip6tables
cat /proc/sys/net/ipv4/ip_forward

配置国内源

若有vpn可以跳过,参考:阿里开源-Kubernetes镜像

CentOS主机

1
2
3
4
5
6
7
8
9
10
11
# 配置国内源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
sudo yum update

若出现签名验证不过signature could not be verified for kubernetes,可以强行跳过签名验证:

1
2
3
4
5
6
7
8
9
10
11
# 配置国内源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
sudo yum update

安装辅助工具

安装特定版本辅助工具,例如:1.21.14

CentOS主机

1
2
3
4
5
6
7
8
# 安装特定版本的工具
yum list kubelet --showduplicates | sort -r
# KUBE_VERSION=1.21.11; yum install -y kubelet-${KUBE_VERSION} kubeadm-${KUBE_VERSION} kubectl-${KUBE_VERSION}
KUBE_VERSION=1.21.14; yum install -y kubelet-${KUBE_VERSION} kubeadm-${KUBE_VERSION} kubectl-${KUBE_VERSION}
# 启动kubelet
systemctl enable kubelet && systemctl start kubelet
# 此时因k8s配置文件不存在,kubelet启动失败
systemctl status kubelet

安装K8s

拉取镜像

从国内源在线拉取

1
2
3
4
5
6
7
8
9
10
# 列出所有需要的版本镜像
kubeadm config images list --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers

# 拉取所有需要的版本镜像
kubeadm config images pull --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers

# 测试域名 docker pull busybox:1.28.4
# 网络控制器 docker pull quay.io/coreos/flannel:v0.14.0
# ingress类控制器 docker pull quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.29.0
# 集群管理 docker pull swr.cn-east-2.myhuaweicloud.com/kuboard/kuboard:v3

若已下载镜像执行

1
2
ls k8s-images-1.21.9/*.image | while read file; do docker image load -i $file; done
ls flannel-images/*.image | while read file; do docker image load -i $file; done

【离线安装】批量镜像处理——非必须

1
2
3
4
5
# 批量保存镜像
docker image ls | grep "registry.cn-hangzhou.aliyuncs.com" | awk '{print $1 ":" $2}' | while read image; do file=${image//\//_}.image; file=${file//:/_}; docker image save $image -o $file; done

# 批量加载镜像
ls *.image | while read file; do docker image load -i $file; done

初始化K8s

从节点无需初始化

1
2
3
4
5
6
7
8
9
10
11
12
# 指定镜像源初始化
# 需要特定网络,需要在此步骤中指定
# 使用flannel网络需要指定--pod-network-cidr参数
# --pod-network-cidr 指定pod网络地址范围
# --apiserver-advertise-address 指定集群API server绑定地址
# --service-dns-domain 指定各服务顶级dns域名
kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=172.20.1.25 --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers --service-dns-domain=ice | tee ./kubeadm-init.log
#kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=192.168.8.101 --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers --service-dns-domain=viid

# 出现Your Kubernetes control-plane has initialized successfully!表明初始化正常
# 服务状态正常,但因未配置网络/etc/cni/net.d,后台上报异常
systemctl status kubelet

若有cgroup改systemd告警,处理参考

安装命令补全工具

参考后面K8s配置章节

启动集群

1
2
3
4
5
6
7
8
9
10
11
12
# 启动集群
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 若是root用户,需要执行
export KUBECONFIG=/etc/kubernetes/admin.conf

# 允许Master参与调度,单节点必须将Master置为可调度状态
kubectl get nodes
kubectl taint node localhost.localdomain node-role.kubernetes.io/master- #将 Master 也当作 Node 使用
kubectl taint node localhost.localdomain node-role.kubernetes.io/master=:NoSchedule #将 Master 恢复成 Master Only 状态

路由选择

默认kube-proxy使用iptables实现,随着service、pod数量增加,iptables顺序遍历的方式就会凸显。可替代方案可以选择:

  • iptables
  • IPVS

kube-proxy支持的另一种模式,性能比iptables更高,推荐使用。参考文章

  • eBPF

整体成熟度低,不建议单独使用,参考文章

替换kube-proxy方法,参考文章

可以对数据包进行观测,参考文章

网络配置

上面安装成功后如果通过查询kube-system下Pod的运行情况,会放下和网络相关的Pod都处于Pending的状态,这是因为缺少相关的网络插件,而网络插件有很多个(以下任选一个),可以选择自己需要的。参考:Kubernetes指南

flannel

参考官网

官方网站,在Documentation文件夹中,参考kube-flannel.yaml, kube-flannel-aliyun.yaml, kube-flannel-old.yaml描述文件

kube-flannel.yaml文件内容:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: psp.flannel.unprivileged
annotations:
seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default
seccomp.security.alpha.kubernetes.io/defaultProfileName: docker/default
apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default
apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default
spec:
privileged: false
volumes:
- configMap
- secret
- emptyDir
- hostPath
allowedHostPaths:
- pathPrefix: "/etc/cni/net.d"
- pathPrefix: "/etc/kube-flannel"
- pathPrefix: "/run/flannel"
readOnlyRootFilesystem: false
# Users and groups
runAsUser:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
fsGroup:
rule: RunAsAny
# Privilege Escalation
allowPrivilegeEscalation: false
defaultAllowPrivilegeEscalation: false
# Capabilities
allowedCapabilities: ['NET_ADMIN', 'NET_RAW']
defaultAddCapabilities: []
requiredDropCapabilities: []
# Host namespaces
hostPID: false
hostIPC: false
hostNetwork: true
hostPorts:
- min: 0
max: 65535
# SELinux
seLinux:
# SELinux is unused in CaaSP
rule: 'RunAsAny'
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: flannel
rules:
- apiGroups: ['extensions']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames: ['psp.flannel.unprivileged']
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- apiGroups:
- ""
resources:
- nodes
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes/status
verbs:
- patch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: flannel
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flannel
subjects:
- kind: ServiceAccount
name: flannel
namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: flannel
namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
name: kube-flannel-cfg
namespace: kube-system
labels:
tier: node
app: flannel
data:
cni-conf.json: |
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "host-gw"
}
}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds
namespace: kube-system
labels:
tier: node
app: flannel
spec:
selector:
matchLabels:
app: flannel
template:
metadata:
labels:
tier: node
app: flannel
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
hostNetwork: true
priorityClassName: system-node-critical
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
- name: install-cni
image: quay.io/coreos/flannel:v0.14.0
command:
- cp
args:
- -f
- /etc/kube-flannel/cni-conf.json
- /etc/cni/net.d/10-flannel.conflist
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
mountPath: /etc/kube-flannel/
containers:
- name: kube-flannel
image: quay.io/coreos/flannel:v0.14.0
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: false
capabilities:
add: ["NET_ADMIN", "NET_RAW"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: run
mountPath: /run/flannel
- name: flannel-cfg
mountPath: /etc/kube-flannel/
volumes:
- name: run
hostPath:
path: /run/flannel
- name: cni
hostPath:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg

Network.Network: 10.244.0.0/16需要改成kubeadm初始化参数所带网络参数。

Network.Backend.Type: vxlan为默认网络参数,已经改为host-gw测试文档中发现host-gw模式性能更高。

创建网络:

1
kubectl apply -f kube-flannel.yaml

等待片刻可以看到虚拟网桥cni0已经创建好。若coredns一直处于pending状态,请将flannel文件改为最新版本(0.19.2)

查看系统空间kube-systemflannel网络pod资源已经正常运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
kubectl -n kube-system get all

# NAME READY STATUS RESTARTS AGE
# pod/coredns-6f6b8cc4f6-95bng 1/1 Running 0 24m
# pod/coredns-6f6b8cc4f6-hkdn8 1/1 Running 0 24m
# pod/etcd-n1 1/1 Running 1 24m
# pod/kube-apiserver-n1 1/1 Running 1 24m
# pod/kube-controller-manager-n1 1/1 Running 1 24m
# pod/kube-flannel-ds-vz49s 1/1 Running 0 74s
# pod/kube-proxy-bbjzq 1/1 Running 1 24m
# pod/kube-scheduler-n1 1/1 Running 1 24m
#
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 24m
#
# NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
# daemonset.apps/kube-flannel-ds 1 1 1 1 1 <none> 74s
# daemonset.apps/kube-proxy 1 1 1 1 1 kubernetes.io/os=linux 24m
#
# NAME READY UP-TO-DATE AVAILABLE AGE
# deployment.apps/coredns 2/2 2 2 24m
#
# NAME DESIRED CURRENT READY AGE
# replicaset.apps/coredns-6f6b8cc4f6 2 2 2 24m

cni

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mkdir -p /etc/cni/net.d
cat >/etc/cni/net.d/10-mynet.conf <<-EOF
{
"cniVersion": "0.3.0",
"name": "mynet",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.244.0.0/16",
"routes": [
{"dst": "0.0.0.0/0"}
]
}
}
EOF
cat >/etc/cni/net.d/99-loopback.conf <<-EOF
{
"cniVersion": "0.3.0",
"type": "loopback"
}
EOF

等待片刻可以看到虚拟网桥cni0已经创建好。

查看是否安装成功

1
2
3
4
5
6
7
8
9
10
11
12
# 查看系统pod是否正常启动
kubectl get pods -n kube-system
# 看到类似结果
#NAME READY STATUS RESTARTS AGE
#coredns-6f6b8cc4f6-285bg 1/1 Running 0 7m38s
#coredns-6f6b8cc4f6-znlf7 1/1 Running 0 7m38s
#etcd-n175 1/1 Running 0 7m46s
#kube-apiserver-n175 1/1 Running 0 7m46s
#kube-controller-manager-n175 1/1 Running 0 7m46s
#kube-flannel-ds-8p8xx 1/1 Running 0 95s
#kube-proxy-5zvr2 1/1 Running 0 7m38s
#kube-scheduler-n175 1/1 Running 0 7m46s

域名解析测试方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 启动容器,-n xxx可以指定创建的命名空间
kubectl run -it --rm --image=busybox:1.28.4 --restart=Never sh

# 解析域名地址,格式:<Service>.<Namespace>.svc.cluster.local
nslookup kube-dns.kube-system
nslookup kube-dns.kube-system.svc.cluster.ice
nslookup ver-svc
nslookup ver-svc.ver-dev
nslookup ver-svc.ver-dev.svc
nslookup ver-svc.ver-dev.svc.cluster
nslookup ver-svc.ver-dev.svc.cluster.ice
# 正常能查看到类似结果
#Server: 10.96.0.10
#Address 1: 10.96.0.10 kube-dns.kube-system.svc.ice
#
#Name: kube-dns.kube-system
#Address 1: 10.96.0.10 kube-dns.kube-system.svc.ice

域名解析时都会优先尝试直接域名解析,若无法连通则会逐步扩大解析范围。使用简短域名解析通过tcpdump -i cni0 -w result.cap抓包dns流程分析,简短域名解析耗时10+ms,而完整域名解析1+ms,因此推荐使用完整域名解析。

工作Node节点加入集群

操作步骤到安装辅助工具后,只需要加载pause、proxy、网络插件(若有)镜像。再运行主节点部署时的回显命令即可加入集群。

1
2
3
# 加载节点镜像
docker image load -i k8s-images-1.21.8/registry.cn-hangzhou.aliyuncs.com_google_containers_pause_3.4.1.image
docker image load -i k8s-images-1.21.8/registry.cn-hangzhou.aliyuncs.com_google_containers_kube-proxy_v1.21.8.image

Ingress类控制器安装

ingress-nginx

官方GitHub仓,注意官方使用的镜像k8s.gcr.io可能无法下载,需要替换一下

nginx分支

参考文章

nginx-0.29.0分支为例,进入到资源描述路径deploy/static

  • configmap.yaml 提供configmap可以在线更新nginx的配置
  • namespace.yaml 创建一个独立的命名空间 ingress-nginx
  • rbac.yaml 创建对应的role rolebinding 用于rbac
  • with-rbac.yaml 有应用rbac的nginx-ingress-controller组件
  • mandatory.yaml 以上所有文件的集合
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
name: tcp-services
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
name: udp-services
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: nginx-ingress-clusterrole
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses/status
verbs:
- update

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: nginx-ingress-role
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
# Defaults to "<election-id>-<ingress-class>"
# Here: "<ingress-controller-leader>-<nginx>"
# This has to be adapted if you change either parameter
# when launching the nginx-ingress-controller.
- "ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: nginx-ingress-role-nisa-binding
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: nginx-ingress-role
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: nginx-ingress-clusterrole-nisa-binding
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nginx-ingress-clusterrole
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx

---

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
annotations:
prometheus.io/port: "10254"
prometheus.io/scrape: "true"
spec:
hostNetwork: true # 设置物理网络
# wait up to five minutes for the drain of connections
terminationGracePeriodSeconds: 300
serviceAccountName: nginx-ingress-serviceaccount
nodeSelector:
kubernetes.io/os: linux
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.29.0
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
# 增加额外参数指定暴露端口
- --http-port=31080
- --https-port=31443
securityContext:
allowPrivilegeEscalation: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
# www-data -> 101
runAsUser: 101
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 31080
protocol: TCP
hostPort: 31080 # 指定物理映射端口,可添加也可不添加
- name: https
containerPort: 31443
protocol: TCP
hostPort: 31443 # 指定物理映射端口,可添加也可不添加
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
lifecycle:
preStop:
exec:
command:
- /wait-shutdown

---

apiVersion: v1
kind: LimitRange
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
limits:
- min:
memory: 90Mi
cpu: 100m
type: Container

svc服务文件deploy/baremetal/service-nodeport.yaml,将ingress内部80端口暴露到节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
type: ClusterIP
#type: NodePort
ports:
- name: http
port: 31080
#nodePort: 31080
targetPort: 31080
protocol: TCP
- name: https
port: 31443
targetPort: 31443
protocol: TCP
selector:
app: ingress-nginx
#externalTrafficPolicy: Cluster

默认nginx-ingress-controller会随意选择一个node节点运行pod,为此需要我们把nginx-ingress-controller运行到指定的node节点上。首先需要给需要运行nginx-ingress-controller的node节点打标签,在此我们把nginx-ingress-controller运行在指定的node节点上

此步骤非必须

为节点打标签

1
2
3
4
5
# 为指定节点打标签,标签可以换成其他的
kubectl label node localhost.localdomain nodeFeature=nginx

# 查看节点已有标签
kubectl get nodes --show-labels

mandatory.yaml文件中nodeSelector属性中增加nodeFeature: nginx

验证程序

ingress-nginx测试程序httpd-dep.yaml:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: httpd
labels:
name: httpd
spec:
rules:
- http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: httpd
port:
number: 8000

---
apiVersion: v1
kind: Service
metadata:
name: httpd
spec:
selector:
app: httpd
ports:
- port: 8000
protocol: TCP
targetPort: 80
type: ClusterIP

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd
spec:
replicas: 4
selector:
matchLabels:
app: httpd
template:
metadata:
labels:
app: httpd
spec:
containers:
- name: httpd
image: httpd:2.4.52
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 80

若未指定ingress会随机选择节点启动pod,查看命令

1
2
3
4
kubectl -n ingress-nginx get pod -o wide

#NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
#nginx-ingress-controller-fd46d8644-s772j 1/1 Running 0 103m 192.168.31.192 n2 <none> <none>

K8s上部署Redis集群

【重点】不适合部署带持久化的Redis集群,因Redis集群以IP建立的。

重启后无法恢复原有集群。

本方案采用StatefulSet进行redis的部署。参考文章

环境信息

序列节点IP
1master192.168.1.100
2node1192.168.1.101
3node2192.168.1.102
4node3192.168.1.103

创建存储卷

  1. 安装nfs软件包
1
2
#任选一台节点(这里选k8s-master)
yum –y install nfs-utils rpcbind
  1. 创建共享存储
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 创建共享目录
mkdir -p /home/data/redis/pv{1,2,3,4,5,6}

# 配置共享路径
vi /etc/exports
# 增加如下配置
/home/data/redis/pv1 192.168.11.0/24(rw,sync,no_root_squash)
/home/data/redis/pv2 192.168.11.0/24(rw,sync,no_root_squash)
/home/data/redis/pv3 192.168.11.0/24(rw,sync,no_root_squash)
/home/data/redis/pv4 192.168.11.0/24(rw,sync,no_root_squash)
/home/data/redis/pv5 192.168.11.0/24(rw,sync,no_root_squash)
/home/data/redis/pv6 192.168.11.0/24(rw,sync,no_root_squash)

# 重启
systemctl restart rpcbind
systemctl restart nfs
systemctl enable nfs

# 其他节点验证nfs
yum -y install nfs-utils
showmount -e 172.20.1.25

创建PV

pv.yaml:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv1
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "redis"
nfs:
server: 192.168.1.10
path: "/usr/local/k8s/redis/pv1"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-vp2
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "redis"
nfs:
server: 192.168.1.10
path: "/usr/local/k8s/redis/pv2"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv3
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "redis"
nfs:
server: 192.168.1.10
path: "/usr/local/k8s/redis/pv3"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-vp4
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "redis"
nfs:
server: 192.168.1.10
path: "/usr/local/k8s/redis/pv4"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv5
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "redis"
nfs:
server: 192.168.1.10
path: "/usr/local/k8s/redis/pv5"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-vp6
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "redis"
nfs:
server: 192.168.1.10
path: "/usr/local/k8s/redis/pv6"

创建configmap

vim redis.conf

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
# appendonly yes
# cluster-enabled yes
# cluster-config-file /var/lib/redis/nodes.conf
# cluster-node-timeout 5000
# dir /var/lib/redis
# port 6379
# redis端口
port 6379
# # 连接密码
# requirepass hzzhcs@2020
# masterauth hzzhcs@2020
# 关闭保护模式
protected-mode no
# 开启集群
cluster-enabled yes
# 集群节点配置
cluster-config-file nodes-${PORT}.conf
# 超时
cluster-node-timeout 5000
# # 集群节点IP host模式为宿主机IP
# # cluster-announce-ip 192.168.195.10
# # cluster-announce-ip 192.168.28.170
# cluster-announce-ip 192.168.11.215
# # 节点端口 6379 - 6381
# # 集群端口 16379 - 16381
# cluster-announce-port ${PORT}
# cluster-announce-bus-port ${CPORT}
# 开启 appendonly 备份模式
appendonly yes
# 每秒钟备份
appendfsync everysec
# 对aof文件进行压缩时,是否执行同步操作
no-appendfsync-on-rewrite no
# 当目前aof文件大小超过上一次重写时的aof文件大小的100%时会再次进行重写
auto-aof-rewrite-percentage 100
# 重写前AOF文件的大小最小值 默认 64mb
auto-aof-rewrite-min-size 64mb

创建

1
kubectl create configmap redis-conf --from-file=redis.conf

创建headless service

vim headless-service.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---
apiVersion: v1
kind: Service
metadata:
name: redis-service
labels:
app: redis
spec:
ports:
- name: redis-port
port: 6379
clusterIP: None
selector:
app: redis

创建redis集群节点

通过StatefulSet创建6个redis的pod ,实现3主3从的redis集群。vim redis.yaml

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-app
spec:
serviceName: "redis-service"
replicas: 6
selector:
matchLabels:
app: redis
appCluster: redis-cluster
template:
metadata:
labels:
app: redis
appCluster: redis-cluster
spec:
containers:
- name: redis
image: "redis:3.2.8"
command:
- "redis-server"
args:
- "/etc/redis/redis.conf"
- "-protected-mode"
- "no"
resources:
requests:
cpu: "100m"
memory: "100Mi"
ports:
- name: redis
containerPort: 6379
protocol: "TCP"
- name: cluster
containerPort: 16379
protocol: "TCP"
volumeMounts:
- name: "redis-conf"
mountPath: "/etc/redis"
- name: "redis-data"
mountPath: "/var/lib/redis"
volumes:
- name: "redis-conf"
configMap:
name: "redis-conf"
items:
- key: "redis.conf"
path: "redis.conf"
volumeClaimTemplates:
- metadata:
name: redis-data
spec:
accessModes: [ "ReadWriteMany" ]
storageClassName: "redispv"
resources:
requests:
storage: 2Gi

初始化redis集群

获取节点信息

1
2
3
4
5
6
7
8
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
redis-app-0 1/1 Running 0 2m22s 10.244.0.248 n175 <none> <none>
redis-app-1 1/1 Running 0 97s 10.244.0.252 n175 <none> <none>
redis-app-2 1/1 Running 0 101s 10.244.0.251 n175 <none> <none>
redis-app-3 1/1 Running 0 117s 10.244.0.250 n175 <none> <none>
redis-app-4 1/1 Running 0 2m1s 10.244.0.249 n175 <none> <none>
redis-app-5 1/1 Running 0 2m31s 10.244.0.247 n175 <none> <none>

进入任意节点执行命令初始化集群

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
redis-cli --cluster create \
10.244.0.248:6379 \
10.244.0.252:6379 \
10.244.0.251:6379 \
10.244.0.250:6379 \
10.244.0.249:6379 \
10.244.0.247:6379 \
--cluster-replicas 1

# 无法正常建立集群
redis-cli --cluster create \
redis-app-0.redis-service.default.svc.ice:6379 \
redis-app-1.redis-service.default.svc.ice:6379 \
redis-app-2.redis-service.default.svc.ice:6379 \
redis-app-3.redis-service.default.svc.ice:6379 \
redis-app-4.redis-service.default.svc.ice:6379 \
redis-app-5.redis-service.default.svc.ice:6379 \
--cluster-replicas 1

# 回显信息
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 10.244.0.249:6379 to 10.244.0.248:6379
Adding replica 10.244.0.247:6379 to 10.244.0.252:6379
Adding replica 10.244.0.250:6379 to 10.244.0.251:6379
M: 8921255530faaa44fb793a7248d93c179211b7d9 10.244.0.248:6379
slots:[0-5460] (5461 slots) master
M: f32bcfa4dcacef662096d5ccdef4d741588aa2cb 10.244.0.252:6379
slots:[5461-10922] (5462 slots) master
M: dc0a1908830468f2070883e1c026fd5b1b2ff526 10.244.0.251:6379
slots:[10923-16383] (5461 slots) master
S: b35dcfba64b30050d3f71dc347acd3ce222a99e5 10.244.0.250:6379
replicates dc0a1908830468f2070883e1c026fd5b1b2ff526
S: 4ac41eded080c70132ab4de211fcdfa653874469 10.244.0.249:6379
replicates 8921255530faaa44fb793a7248d93c179211b7d9
S: dc2ddbb2ef518a23583dab53bdecccc0e017146d 10.244.0.247:6379
replicates f32bcfa4dcacef662096d5ccdef4d741588aa2cb
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
..
>>> Performing Cluster Check (using node 10.244.0.248:6379)
M: 8921255530faaa44fb793a7248d93c179211b7d9 10.244.0.248:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 4ac41eded080c70132ab4de211fcdfa653874469 10.244.0.249:6379
slots: (0 slots) slave
replicates 8921255530faaa44fb793a7248d93c179211b7d9
M: f32bcfa4dcacef662096d5ccdef4d741588aa2cb 10.244.0.252:6379
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: b35dcfba64b30050d3f71dc347acd3ce222a99e5 10.244.0.250:6379
slots: (0 slots) slave
replicates dc0a1908830468f2070883e1c026fd5b1b2ff526
S: dc2ddbb2ef518a23583dab53bdecccc0e017146d 10.244.0.247:6379
slots: (0 slots) slave
replicates f32bcfa4dcacef662096d5ccdef4d741588aa2cb
M: dc0a1908830468f2070883e1c026fd5b1b2ff526 10.244.0.251:6379
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

验证状态

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
redis-cli -c

# 操作回显信息
127.0.0.1:6379> CLUSTER INFO
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:315
cluster_stats_messages_pong_sent:335
cluster_stats_messages_sent:650
cluster_stats_messages_ping_received:330
cluster_stats_messages_pong_received:315
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:650
127.0.0.1:6379> CLUSTER NODES
4ac41eded080c70132ab4de211fcdfa653874469 10.244.0.249:6379@16379 slave 8921255530faaa44fb793a7248d93c179211b7d9 0 1658228657078 1 connected
f32bcfa4dcacef662096d5ccdef4d741588aa2cb 10.244.0.252:6379@16379 master - 0 1658228657580 2 connected 5461-10922
b35dcfba64b30050d3f71dc347acd3ce222a99e5 10.244.0.250:6379@16379 slave dc0a1908830468f2070883e1c026fd5b1b2ff526 0 1658228658082 3 connected
dc2ddbb2ef518a23583dab53bdecccc0e017146d 10.244.0.247:6379@16379 slave f32bcfa4dcacef662096d5ccdef4d741588aa2cb 0 1658228657000 2 connected
8921255530faaa44fb793a7248d93c179211b7d9 10.244.0.248:6379@16379 myself,master - 0 1658228657000 1 connected 0-5460
dc0a1908830468f2070883e1c026fd5b1b2ff526 10.244.0.251:6379@16379 master - 0 1658228657078 3 connected 10923-16383

创建用于访问的service

之前创建了用于实现StatefulSet的Headless Service,但该Service没有Cluster IP,因此不能用于外界访问。所以,我们还需要创建一个Service,专用于为Redis集群提供访问和负载均衡;也可以部署为Ingress供集群外部访问。这里只创建用于内部访问的service

redis-access-service.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Service
metadata:
name: redis-access-service
labels:
app: redis
spec:
ports:
- name: redis-port
protocol: "TCP"
port: 6379
targetPort: 6379
selector:
app: redis
appCluster: redis-cluster

K8s上部署Kafka集群

K8s上部署MySQL集群

Helm离线安装Chart包

参考文章

CICD引擎(非必须)

Jenkins替代工具

BuildMaster Drone.io GoCD

Argo

可视化管理工具(非必须)

kuboard

在线安装

1
2
3
4
5
6
7
8
9
10
11
12
  # 也可以使用镜像 swr.cn-east-2.myhuaweicloud.com/kuboard/kuboard:v3 ,可以更快地完成镜像下载。
# 请不要使用 127.0.0.1 或者 localhost 作为内网 IP \
# Kuboard 不需要和 K8S 在同一个网段,Kuboard Agent 甚至可以通过代理访问 Kuboard Server \
sudo docker run -d \
--restart=unless-stopped \
--name=kuboard \
-p 30080:80/tcp \
-p 31089:10081/tcp \
-e KUBOARD_ENDPOINT="http://192.168.11.178:30080" \
-e KUBOARD_AGENT_SERVER_TCP_PORT="31089" \
-v /opt/kuboard-data:/data \
swr.cn-east-2.myhuaweicloud.com/kuboard/kuboard:v3.4.1.0

docker-compose.yml方式:

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
39
40
41
42
43
44
45
46
47
48
version: "3"

networks:
kuboard-net:
external: false
driver: bridge
# ipam:
# config:
# - subnet: 172.90.161.0/24

services:
kuboard:
image: swr.cn-east-2.myhuaweicloud.com/kuboard/kuboard:v3.4.1.0
container_name: kuboard
restart: unless-stopped # always
environment:
# user/passwd:admin/Kuboard123
# modify: Zdxf@2021
KUBOARD_ENDPOINT: "http://172.20.1.25:30080"
KUBOARD_AGENT_SERVER_TCP_PORT: "31089"
KUBERNETES_CLUSTER_DOMAIN: "ice"
KUBOARD_ICP_DESCRIPTION: "ICP备案号"
KUBOARD_DISABLE_AUDIT: true
ports:
- 30080:80/tcp
- 31089:10081/tcp
volumes:
- /etc/localtime:/etc/localtime:ro
- ./data:/data
networks:
- kuboard-net

logging:
#driver: none
driver: json-file
options:
max-size: "200k"
max-file: "1"

# 使用deploy限制资源,启动时需要增加--compatibility参数,防止报错
deploy:
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '1'
memory: 200M

在浏览器输入 http://your-host-ip:31088 即可访问 Kuboard v3.x 的界面,登录方式:

  • 用户名: admin
  • 密 码: Kuboard123

浏览器兼容性

请使用 Chrome / FireFox / Safari 等浏览器

不兼容 IE 以及以 IE 为内核的浏览器

资源监控

metrics-server.yaml内容:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
---
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
ports:
- name: https
port: 443
protocol: TCP
targetPort: 443
selector:
k8s-app: metrics-server

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
rbac.authorization.k8s.io/aggregate-to-admin: 'true'
rbac.authorization.k8s.io/aggregate-to-edit: 'true'
rbac.authorization.k8s.io/aggregate-to-view: 'true'
name: 'system:aggregated-metrics-reader'
namespace: kube-system
rules:
- apiGroups:
- metrics.k8s.io
resources:
- pods
- nodes
verbs:
- get
- list
- watch

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
name: 'system:metrics-server'
namespace: kube-system
rules:
- apiGroups:
- ''
resources:
- pods
- nodes
- nodes/stats
- namespaces
- configmaps
verbs:
- get
- list
- watch

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: 'metrics-server:system:auth-delegator'
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: 'system:auth-delegator'
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: 'system:metrics-server'
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: 'system:metrics-server'
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
k8s-app: metrics-server
name: metrics-server-auth-reader
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system

---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system

---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
labels:
k8s-app: metrics-server
name: v1beta1.metrics.k8s.io
namespace: kube-system
spec:
group: metrics.k8s.io
groupPriorityMinimum: 100
insecureSkipTLSVerify: true
service:
name: metrics-server
namespace: kube-system
version: v1beta1
versionPriority: 100

---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
k8s-app: metrics-server
strategy:
rollingUpdate:
maxUnavailable: 1
template:
metadata:
labels:
k8s-app: metrics-server
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: node-role.kubernetes.io/master
operator: Exists
weight: 100
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
k8s-app: metrics-server
namespaces:
- kube-system
topologyKey: kubernetes.io/hostname
containers:
- args:
- '--cert-dir=/tmp'
- '--secure-port=443'
- '--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname'
- '--kubelet-use-node-status-port'
- '--kubelet-insecure-tls=true'
- '--authorization-always-allow-paths=/livez,/readyz'
- '--metric-resolution=15s'
image: >-
swr.cn-east-2.myhuaweicloud.com/kuboard-dependency/metrics-server:v0.5.0
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /livez
port: https
scheme: HTTPS
periodSeconds: 10
name: metrics-server
ports:
- containerPort: 443
name: https
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /readyz
port: https
scheme: HTTPS
initialDelaySeconds: 20
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 200Mi
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
volumeMounts:
- mountPath: /tmp
name: tmp-dir
nodeSelector:
kubernetes.io/os: linux
priorityClassName: system-cluster-critical
serviceAccountName: metrics-server
tolerations:
- effect: ''
key: node-role.kubernetes.io/master
operator: Exists
volumes:
- emptyDir: {}
name: tmp-dir

---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: metrics-server
namespace: kube-system
spec:
minAvailable: 1
selector:
matchLabels:
k8s-app: metrics-server

k8sLens

官方网站

K8s配置

删除资源

若资源是DEPLOY.yaml创建的,则可以使用命令

1
2
# 删除yaml文件中描述的资源
kubectl delete -f DEPLOY.yaml

删除命名空间中所有资源–暂时未验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1、先查找该命名空间下的资源有哪些,
kubectl api-resources --verbs=list --namespaced -o name | xargs -n 1 kubectl get --show-kind --ignore-not-found -n ingress-nginx
确定资源类型如下,
ingress
deployment
service

2、清理ingress-nginx命名空间下的资源
kubectl get ingress -n ingress-nginx |grep clife |awk '{print $1}'|xargs kubectl delete ingress -n ingress-nginx
kubectl get service -n ingress-nginx |grep clife |awk '{print $1}'|xargs kubectl delete service -n ingress-nginx
kubectl get deployment -n ingress-nginx |grep clife |awk '{print $1}'|xargs kubectl delete deployment -n ingress-nginx
3、删除命名空间ingress-nginx
kubectl delete ns ingress-nginx
4、查看该命名空间是否已删除
kubectl get ns ingress-nginx

命令补全工具

bash

bash需要安装bash-completion:

1
2
3
yum install bash-completion
echo "source <(kubectl completion bash)" >> ~/.bashrc
source ~/.bashrc

zsh

命令行执行:

1
2
echo "source <(kubectl completion zsh)" >> ~/.zshrc
source ~/.zshrc

普通用户命令权限

命令行执行:

1
2
3
4
5
6
7
# 比如用户名为USER
mkdir -p ~/.kube
sudo cp -i /etc/kubernetes/admin.conf ~/.kube
sudo chown USER:USER /etc/kubernetes/admin.conf
# 配置环境变量
# zsh配置到.zshrc文件中添加
export KUBECONFIG=~/.kube/admin.conf

集群证书

集群证书存放位置/etc/kubernetes/pki/

1
2
3
4
5
# 检查哪些证书过期
kubeadm certs check-expiration

# 手动刷新证书
kubeadm certs renew all

性能测试

工具使用参考测试文档

基础网络工具有:curl,iperf,Locust,kubemark

业务性能测试有:JMeter, LoadRunner

Apache JMeter是压力测试工具。

LoadRunner是一种预测系统行为和性能的负载测试工具。

1
2
3
4
5
6
# 时间指标说明
# 单位:秒
# time_connect:建立到服务器的 TCP 连接所用的时间
# time_starttransfer:在发出请求之后,Web 服务器返回数据的第一个字节所用的时间
# time_total:完成请求所用的时间
curl -o /dev/null -s -w '%{time_connect} %{time_starttransfer} %{time_total}' "http://sample-webapp:8000/"

iperf在CentOS安装方法

1
2
3
yum install epel-release
yum update
yum install iperf

使用方法

1
2
3
4
5
6
7
8
9
10
11
# 启动tcp服务端
iperf -s
# 启动客户端测试
iperf -c <SERVER_IP>

# 启动udp服务端
iperf -s -u
# 启动客户端测试,udp可能受参数限制带宽,可以用-b更改最大带宽
iperf -c <SERVER_IP> -u

# 双向测试只需要客户端增加-d参数

容器化构建发布

构建发布

参考文章,Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
FROM golang:buster as build
WORKDIR /go/src/greeter-server
RUN curl -o main.go https://github.com/grpc/grpc-go/blob/91e0aeb192456225adf27966d04ada4cf8599915/examples/features/reflection/server/main.go && \
go mod init greeter-server && \
go mod tidy && \
go build -o /greeter-server main.go

FROM gcr.io/distroless/base-debian10
COPY --from=build /greeter-server /
EXPOSE 50051
CMD ["/greeter-server"]

grpc示例

ingress支持grpc示例,参考文章

卸载K8s

清理本体

1
2
3
4
# 卸载集群本体
kubeadm reset -f
# 清理本体文件夹
rm -rf ~/.kube/ /etc/kubernetes/ /etc/cni /opt/cni /var/lib/etcd

清理组件

CentOS主机

1
2
# 清理组件
yum autoremove -y kubelet kubeadm kubectl && rm -rf /usr/bin/kube*

Debian/Ubuntu主机

1
2
3
# 清理组件
apt-get remove kube*
rm -rf /usr/bin/kube*

清理相关镜像

1
2
3
4
5
# 由于从registry.cn-hangzhou.aliyuncs.com拉取的镜像,将镜像删除
docker image ls | grep -v grep | grep -v REPOSITORY | grep registry.cn-hangzhou.aliyuncs.com | awk '{print $3}' | xargs docker image rm -

# 将所有镜像全部删除
docker image ls | grep -v REPOSITORY | awk '{print $3}' | xargs docker image rm -

FAQ

bridge-nf-call-iptables异常

安装时报错[ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptables contents are not set to 1

参考文章

1
2
3
# 解决方案
echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables
echo 1 > /proc/sys/net/bridge/bridge-nf-call-ip6tables

flannel无法访问kubernetes资源10.96.0.1不可达

10.96.0.1地址是指向k8s集群default空间创建的kubernetes服务的,底层基础网络不通(tcpdump无法抓到到此IP的包,可以考虑更换工具或者增加参数)。虚拟机使用的是虚拟主机网络,怀疑是网络配置问题,将此网络包丢弃导致。虚拟机更换为普通桥接问题解决。

先参考资料

kube-proxy开启ipvs的前置条件(所有节点)

1
2
3
4
5
6
7
8
9
cat > /etc/sysconfig/modules/ipvs.modules << EOF
#!/bin/bash
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack_ipv4
EOF
chmod 755 /etc/sysconfig/modules/ipvs.modules

docker使用systemd的cgroup

1
2
3
/etc/docker/daemon.json中增加
"exec-opts": ["native.cgroupdriver=systemd"]
Cgroup Driver: cgroupfs

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kubeadm config print init-defaults > kubeadm-config.yaml

修改advertiseAddress值为本机IP
修改kubernetesVersion为k8s版本
修改dnsDomain为最高级域名--非必须
新增podSubnet: 10.244.0.0/16容器子网--非必须
修改serviceSubnet服务子网--非必须

# 镜像名需要改--
kubeadm init --config=kubeadm-config.yaml --upload-certs | tee kubeadm-init.log
# 简化初始化流程
kubeadm init --apiserver-advertise-address=192.168.208.3 --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers --service-dns-domain=imsv2

kubectl -n kube-system get all
1
2
# 日志中关键错误
E1228 14:03:55.799748 1 main.go:234] Failed to create SubnetManager: error retrieving pod spec for 'kube-system/kube-flannel-ds-rbzdn': Get "https://10.96.0.1:443/api/v1/namespaces/kube-system/pods/kube-flannel-ds-rbzdn": dial tcp 10.96.0.1:443: connect: network is unreachable

网络性能调优

参考文章

CentOS7内核版本低导致部分域名解析失败

参考文章

简介

xv6-public GitHub

xv6-riscv Gitee

麻省理工6.828课程

英文文档

中文文档

xv6 是 MIT 开发的一个教学用的完整的类 Unix 操作系统,并且在 MIT 的操作系统课程 6.828 中使用。通过阅读并理解 xv6 的代码,可以清楚地了解操作系统中众多核心的概念,对操作系统感兴趣的同学十分推荐一读!这份文档是中文翻译的 MIT xv6 文档,是阅读代码过程中非常好的参考资料。

qemu调试xv6环境

环境搭建

下载xv6源码(riscv版本)

安装编译环境

1
sudo apt-get install -y qemu-system-misc binutils-riscv64-linux-gnu gcc-riscv64-linux-gnu gdb-multiarch

直接make编译,make qemu启动环境。

若使用VSCode进行调试,参考附录进行配置。

QEMU virt简介

QEMU riscv 启动代码

QEMU virt有8个hart,通过汇编指令csrr a1, mhartid可以读取hart对应的值。所有的hart都会执行执行内核代码。

Bootloader在启动后,会将内核放置在0x80000000

附录

VSCode一键调试

xv6-riscv目录中放置xv6源码

  • 已知问题,启动过程中QEMU直接退出,需要修改GDB配置文件.gdbinit
1
2
3
4
5
6
set confirm off
set architecture riscv:rv64
@REM target remote 127.0.0.1:26000
symbol-file kernel/kernel
set disassemble-next-line auto
set riscv use-compressed-breakpoints yes
  • tasks.json内容
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
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"options": {
"cwd": "${workspaceFolder}/xv6-riscv"
},
"tasks": [
{
"label": "xv6-qemu-task",
"type": "shell",
// "command": "echo all done",
"isBackground": true,
"command": "make && make qemu-gdb",
"problemMatcher": [
{
"pattern": [
{
"regexp": ".",
"file": 1,
"location": 2,
"message": 3
}
],
"background": {
"beginsPattern": ".*Now run 'gdb' in another window.",
"endsPattern": "."
}
}
],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
  • launch.json内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "xv6-qemu-gdb",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/xv6-riscv/kernel/kernel",
"stopAtEntry": true,
"cwd": "${workspaceRoot}/xv6-riscv",
"miDebuggerServerAddress": "127.0.0.1:26000",
"miDebuggerPath": "/usr/bin/gdb-multiarch",
"MIMode": "gdb",
"preLaunchTask": "xv6-qemu-task",
}
]
}

简介

I2C驱动

开发板:CH32V307V-EVT-R1 RISC-V32

引脚复用:

1
2
3
4
5
6
7
8
9
10
11
参考:CH32V307DS0.PDF
PB5 <--> I2C1_SMBA
PB8 <--> I2C1_SCL
PB9 <--> I2C1_SDA
PB10 <--> I2C2_SCL
PB11 <--> I2C2_SDA
PB12 <--> I2C2_SMBA

I2C1 <--> I2C2
PB8 <--> PB10
PB9 <--> PB11

SPI驱动

USB驱动

为什么说指针是C语言的精髓? 指针有什么用 举例说明

C语言的指针和语言历史背景相关,这个得从在上个世纪60年代说起……

一位年轻小伙小丹(Dennis MacAlistair Ritchie),需要编写一个操作系统,但缺少合适的语言工具。那时B语言精炼且接近硬件,但过于简单且数据无类型。用汇编写引导程序合适,大型操作系统效率有点低。年轻人想法就是多,没有工具就自制工具上,于是小丹同学就顺手设计了C语言。

作为一门语言工具的目的很纯粹,为操作系统而生[旺柴]

  • 开发效率高的高级语言;
  • 能够直接操作硬件;
  • 编译后的代码执行效率高;

C作为高级语言,与同时期的高级语言比肯定不能弱,毕竟那时没有Java、C++、PHP、Go、Python、C#等高级语言,而且这一点和题目没什么关系[旺柴]


正式回到主题

1. 操作硬件

这个主要是应用在与硬件非常近的场景,主要用于读写特定寄存器,比如:嵌入式开发、驱动软件开发、操作系统开发等。

这里先以以简单的stm32f103为例。GPIOB8管脚有连接一个LED灯,此时若想点亮这个灯就需要控制管脚输出高电平:

1
2
3
4
5
6
7
8
9
#define GPIOB_BSRR *((volatile unsigned int *)(0x40010C00 + 0x10))

int main(void)
{
...
//点亮LED
GPIOB_BSRR = 1 << 8;
...
}

注意看,宏GPIOB_BSRR定义为*((volatile unsigned int *)0x40010C10),这里已经应用了指针的知识,这个地址就是GPIOB口的BSRR寄存器。至于为什么是这个地址和写入值的含义与处理器相关,具体可以查看STM32F103芯片手册。换一种写法就是

1
2
3
4
5
6
7
8
int main(void)
{
volatile unsigned int *pGPIO_BSRR = (volatile unsigned int *)0x40010C10;
...
//点亮LED
*pGPIO_BSRR = 0x100000000;
...
}

指针是一种变量,pGPIO_BSRR中存储的值是0x40010C10,则点亮LED语句就是向地址0x40010C10写入一个整数值0x100000000若不用指针用普通变量能表达向特定寄存器赋值的语义吗?显然是不能的

毕竟声明一个整数变量,它地址是0x40010C10概率还没我中大奖的概率大。每个特定处理器寄存器的地址是固定的,与内存地址范围没有交叠,所以编译器也不会给普通变量分配这样的地址。

部分同学可能不熟悉嵌入式,再找找上古Linux 0.11中有这么一个函数con_init,读取显示参数(0x90006地址存储显示参数在启动时获取的存储的)

1
2
3
4
5
6
7
8
#define ORIG_VIDEO_COLS (((*(unsigned short *)0x90006) & 0xff00) >> 8)

void con_init(void)
{
...
video_num_columns = ORIG_VIDEO_COLS;
...
}

换种写法就是

1
2
3
4
5
6
7
8
void con_init(void)
{
...
unsigned short *pORIG_VIDEO = (unsigned short *)0x90006;
unsigned short ORIG_VIDEO_COLS = ((*pORIG_VIDEO)>>8) & 0xff;
video_num_columns = ORIG_VIDEO_COLS;
...
}

这段就是将参数从内存地址0x90006中取出来,参数高字节就是显示列数。与嵌入式不同的是,这个地址就是普通内存地址,还是有运气遇到这个地址的[旺柴]

2. 执行效率高

其他老师不太清楚,我们像上课那会儿,老师怎么介绍指针的?

1
2
3
4
int a = 5;
int *pa = &a;
*pa = 6;
printf("a=%d\n", *pa);

本人:「老师,为啥不直接使用变量a

老师:「这里讲解指针pa的用途」

[旺柴]

其实,指针很多时候是为提高执行效率,避免大块数据拷贝。比如,在收到一个1.5K的网络二进制包,需要调用解析函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define PACKAGE_SIZE 1500

typedef struct net_raw_s
{
int data[PACKAGE_SIZE];
...
} net_raw_t;

int parse(const net_raw_t raw)
{
// 具体解析数据流程,保密
...
}

int main()
{
net_raw_t recv_net_data;
...
// 接收到网络数据准备解析
int ret = parse(recv_net_data);
...
}

每次调用parse函数都会拷贝net_raw_t类型数据,这个数据包有1.5K大小耗时非常多的。从执行效率考虑,此处非常适合使用指针方式,虽然要麻烦一点点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define PACKAGE_SIZE 1500

typedef struct net_raw_s
{
int data[PACKAGE_SIZE];
...
} net_raw_t;

int parse(net_raw_t *const raw)
{
// 具体解析数据流程,保密
...
}

int main()
{
net_raw_t recv_net_data;
...
// 接收到网络数据准备解析
int ret = parse(&recv_net_data);
...
}

本质上说,函数参数永远都是值传递,没有所谓的地址传递。函数参数raw指针变量只是赋值为recv_net_data变量的地址,没有所谓的地址传递。而指针的优势是,不管什么类型的指针,它自身都只占用4字节内存(32位),所以赋值效率高。若将函数parse修改为传递1.5K个指针,相信它的执行效率比最初版本parse还要慢。


还有一个抽象性应该很多高级语言都有,只是C语言需要利用指针来完成。

3. 抽象性

主要涉及函数指针,在模块设计时用处比较大,就是模块对外提供抽象接口。可以降低模块的耦合性,提升功能内聚性,提高协同开发效率,整体降低工程成本。这个特性在Linux内核中应用也非常广泛,下面主要以字符驱动为例进行说明。没接触过Linux字符驱动的建议先参考这篇文章

Linux设备驱动之字符设备驱动(超级详细~)

Linux字符设备驱动的通用操作

  • 初始化
  • 打开设备
  • 读数据
  • 写数据
  • 关闭设备
  • 等等操作

所有设备都按下面这个模板实现接口逻辑,就形成各种字符设备驱动。下面这个是并口打印机的驱动(源文件在linux-2.6.12/drivers/char/lp.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct file_operations {
...
int (*open) (struct inode *, struct file *);
...
};

static struct file_operations lp_fops = {
.owner = THIS_MODULE,
.write = lp_write,
.ioctl = lp_ioctl,
.open = lp_open,
.release = lp_release,
.read = lp_read,
};

static int lp_open(struct inode * inode, struct file * file)
{
...
}

在加载了这个驱动后,应用程序只需要调用openreadclose等接口就能操作这个字符设备。当然应用程序需要通过打印机打印信息时,需要先打开打印机设备,就要调用内核open函数。此时调用的open不是结构体struct file_operations中的函数指针,而是内核接口封装过的,但最终是调用lp_open,这样设备就初始化好就能继续操作。

在打开设备过程中,内核调用它们的都是结构体struct file_operations中的open函数指针。此时内核只关注要打开设备,而不关注设备如何打开,这些其实就是抽象。不管是打印机设备还是串口设备,只要需要打开动作,内核就负责找到设备的结构体struct file_operations中的open函数指针调用即可。

若没有这个抽象性,那编写内核时就要直接调用驱动接口。则一个开发人员既要掌握内核驱动管理逻辑细节,又要掌握设备驱动逻辑细节,最后软件维护成本必然很高。


上面说的这些都是了解的C语言指针的用处,只想说它太重要了,这也是这门上古语言能流传至今的秘宝。

以上都是个人对指针的理解,若有纰漏望轻喷[旺柴]

保护模式内存管理

vol-3 Chapter 3 Protected-Mode Memory Management

内存管理机制:段和页

开启保护模式会使能段机制,页机制是可选的。

<图片>Figure 3-1. Segmentation and Paging

完整内存管理机制开启后,地址转换:

Logical Address -段-> Linear Address -页-> Physical Address

详细转换流程

逻辑地址组成:段选择器 (线性地址)偏移

段选择器为全局描述表(GDT)的索引,就能找到段描述项,从中取出段基地址(线性地址空间),与偏移地址就能得到页描述项?

页描述组成:页目录索引 页表索引 (物理地址)偏移

页大小典型值为4K,当被访问的页(物理内存)不在当前内存中,则处理器会产生页错误(page-fault)异常

使用段机制

基本扁平模式(Basic Flat Model)

ROM分配在FFFF_FFF0H内存处,RAM在DS值为0时在内存底部

受保护扁平模式(Protected Flat Model)

访问不存在内存时,会触发通用保护(general-protection)异常

用户设置特权等级3的代码和数据段,管理员设置权限等级0的代码和数据段

多段模式(Multi-Segment Model)

逻辑与线性地址

图 Figure 3-5. Logical Address to Linear Address Translation

GDT或LDT

段选择器结构

图 Figure 3-6. Segment Selector

Index (位3-位15)可选择GDT或LDT中8K中的一项(每个段描述符大小为8字节)

TI (位2)表指示器,0使用GDT,1使用LDT

RPL (位0-1)特权选择器,0最高权限

6个段寄存器(CS DS SS ES FS GS),前三个必须加载有效的段选择器。每个寄存器都有可见的段选择器和不可见的基地址、限制和权限。

配置方式:

  • MOV, POP, LDS, LES, LSS, LGS, and LFS指令显示加载相应寄存器,MOV通常用作存储段寄存器可见区域
  • CALL, JMP, and RET, SYSENTER and SYSEXIT,IRET, INT n, INTO, INT3, and INT1指令隐形加载CS寄存器(有事有其他段寄存器)

段描述符

图 Figure 3-8. Segment Descriptor

Segment limit field 20位,指定段的大小。G==0,1Byte1MB,粒度1Byte;G==1,4KB4GB,粒度4KB;

Base address fields 24位,定义4GB线性地址空间的起始位置,推荐地址16字节对齐;

Type field 4位,指示段或门类型,并指定可以对段进行的访问类型和增长方向,功能由其他域决定;

S (descriptor type) flag 1位,指定段类型,0系统段,1代码或数据段;

DPL (descriptor privilege level) field 2位,指定段特权等级。0特权等级最高;

P (segment-present) flag 指示段是否在内存中,1存在,0不存在;

D/B (default operation size/default stack pointer size and/or upper bound) flag 1位,不同段描述符不同功能

G (granularity) flag 1位,指明段限制域缩放比例。0字节单位,1 4KB单位。

L (64-bit code segment) flag 1位

Available and reserved bits 1位,可供系统软件使用

保护

图 Figure 5-1. Descriptor Fields Used for Protection

中断和异常处理

表 Table 6-1. Protected-Mode Exceptions and Interrupts

0%