首页 > 其他分享 >CSAPP Lab-4 Architecture Lab

CSAPP Lab-4 Architecture Lab

时间:2024-04-24 13:34:17浏览次数:27  
标签:src CSAPP val list Lab Architecture quad rdi rax

本次实验是有关书上第四章设计的 Y86-64 处理器的,实验分为三个部分,分别是编写几个简单的 Y86-64 程序、使用一条新指令扩展 SEQ 模拟器以及优化 Y86-64 的基准测试程序和处理器设计。

实验准备

需要简单复习一下 Y86-64 的指令集架构以及处理器架构呢。

指令集架构

指令集:

./20230904-csapp-archlab/image-20230904203030200

指令功能码:

./20230904-csapp-archlab/image-20230904203218950

程序员可见状态:

./20230904-csapp-archlab/image-20230904203138458

以及,书上这一章似乎没有提到,但是根据上一章的内容,我们一般将 %rdi, %rsi, %rdx, %rcx 用作参数的传递,而 %rbx, %rbp, %r12~%r15 是被调用者保存寄存器,被调用的函数有义务在使用之前保存寄存器的内容。

处理器架构

./20230904-csapp-archlab/image-20230904204101080

./20230904-csapp-archlab/image-20230904204258146

这两张图是 PIPE 的硬件结构和流水线控制逻辑,当然并不完整。在我们需要的时候,我们会补充相应的示意图。

Y86-64 的程序结构

书本给我们提供了一个典型的 Y86-64 汇编程序,这将是我们完成 Part A 的参考。

./20230904-csapp-archlab/image-20230904204852913

其中,程序从 0x0 开始执行,首先将栈地址(在程序的末尾标识出来)放入 %rsp 寄存器,然后调用 main 函数,main 函数运行结束后终止程序。下面是预定义的全局数据,然后就是各个函数过程。

Part A

Part A 需要我们写三个 Y86-64 的程序,每个程序分别完成了三个函数,并发起对这些函数的调用。

这个 Part 不是很难,只需要根据汇编程序的书写语法、分支循环的转化方法以及 Y86-64 指令集的一些约束来写就可以了。

sum.ys: Iteratively sum linked list elements

要被我们转译的函数如下:

/* sum_list - Sum the elements of a linked list */
long sum_list(list_ptr ls)
{
    long val = 0;
    while (ls) {
		val += ls->val;
		ls = ls->next;
    }
    return val;
}

这个程序是对一个链表的迭代求和。实验给我们提供了一组示例数据:

# Sample linked list
.align 8
ele1:
    .quad 0x00a
    .quad ele2
ele2:
    .quad 0x0b0
    .quad ele3
ele3:
    .quad 0xc00
    .quad 0

这个部分很简单,我的程序是:

# sum_list - Sum the elements of a linked list
# by Irilsy

# Execution begins at address 0
    .pos 0
    irmovq stack, %rsp  # Set up stack pointer
    call main           # Execute main program
    halt                # Terminate program

# Sample linked list
.align 8
ele1:
    .quad 0x00a
    .quad ele2
ele2:
    .quad 0x0b0
    .quad ele3
ele3:
    .quad 0xc00
    .quad 0

main:
    irmovq ele1, %rdi
    call sum_list
    ret

sum_list:
    xorq %rax, %rax
    jmp test
loop:
    mrmovq (%rdi), %rsi
    addq %rsi, %rax
    mrmovq 8(%rdi), %rdi
test:
    andq %rdi, %rdi
    jne loop
    ret

# Stack starts here and grows to lower addresses
    .pos 0x200
stack:

main 函数中,我们示例数据的地址放入 %rdi,然后调用 sum_list 函数。

sum_list 函数中,我们用 %rax 代表 val,第一句指令将 val 清空。while 循环我们使用跳转到中间的方法实现,直接 jmp 到条件判断的位置。在循环内部,我们仍旧使用 %rdi 代表 ls 指针,(%rdi) 就是 ls->val8(%rdi) 就是 ls->next。循环体的三句话按照含义模拟即可。在条件判断的地方,我们使用 andq 作为与 \(0\) 的比较。

./20230904-csapp-archlab/image-20230904210917180

rsum.ys: Recursively sum linked list elements

这次要被我们转写的函数是:

/* rsum_list - Recursive version of sum_list */
long rsum_list(list_ptr ls)
{
    if (!ls)
    return 0;
    else {
        long val = ls->val;
        long rest = rsum_list(ls->next);
    	return val + rest;
    }
}

示例数据与前一个程序相同。这个版本是递归版的 sum_list,使用递归的方法对一个链表求和。

也很容易,按照含义模拟即可:

# rsum_list - Sum the elements of a linked list
# by Irilsy

# Execution begins at address 0
    .pos 0
    irmovq stack, %rsp  # Set up stack pointer
    call main           # Execute main program
    halt                # Terminate program

# Sample linked list
.align 8
ele1:
    .quad 0x00a
    .quad ele2
ele2:
    .quad 0x0b0
    .quad ele3
ele3:
    .quad 0xc00
    .quad 0

main:
    irmovq ele1, %rdi
    call rsum_list
    ret

rsum_list:
    xorq %rax, %rax
    andq %rdi, %rdi
    je end
    pushq %rbx
    mrmovq (%rdi), %rbx
    mrmovq 8(%rdi), %rdi
    call rsum_list
    addq %rbx, %rax
    popq %rbx
end:
    ret

# Stack starts here and grows to lower addresses
    .pos 0x200
stack:

sum.ys 基本一致,区别在于 rsum_list 函数的写法。仍然使用 %rax 代表 val。在函数的开头就把 %rax 清零,方便下一句的判断中如果 ls 指针为空可以直接返回。

然后,我们从内存中取出 (%rdi)ls->val),但是需要递归调用,这个时候有两种处理方法,第一种是不把这个数据留在寄存器中,而是存储在内存里,在递归调用结束后再取出。还有一种是使用被调用者保存的寄存器 %rbx,在使用之前将 %rbx 的内容保存进栈中,然后将我们取出的数据放进 %rbx,在调用前不用特别保存,因为任何函数在使用这个寄存器前都会保存的。我是用的是第二种方法。

在调用结束后,我们将返回值和 %rbx 中的 ls->val 加起来返回即可。

./20230904-csapp-archlab/image-20230904211009323

copy.ys: Copy a source block to a destination block

这次要我们转译的函数是下面这个:

/* copy_block - Copy src to dest and return xor checksum of src */
long copy_block(long *src, long *dest, long len)
{
    long result = 0;
    while (len > 0) {
        long val = *src++;
        *dest++ = val;
        result ^= val;
        len--;
    }
    return result;
}

这个函数的功能是将从 src 开始的 lenlong 型数据复制到 dest 处,并返回它们的异或和。

实验提供的示例数据是:

# Source block
src:
    .quad 0x00a
    .quad 0x0b0
    .quad 0xc00

# Destination block
dest:
    .quad 0x111
    .quad 0x222
    .quad 0x333

我的写法如下:

# copy_block - Sum the elements of a linked list
# by Irilsy

# Execution begins at address 0
    .pos 0
    irmovq stack, %rsp  # Set up stack pointer
    call main           # Execute main program
    halt                # Terminate program

.align 8
# Source block
src:
    .quad 0x00a
    .quad 0x0b0
    .quad 0xc00

# Destination block
dest:
    .quad 0x111
    .quad 0x222
    .quad 0x333

main:
    irmovq src, %rdi
    irmovq dest, %rsi
    irmovq $3, %rdx
    call copy_block
    ret

copy_block:
    xorq %rax, %rax
    irmovq $8, %r8
    irmovq $-1, %r9
    jmp test
loop:
    mrmovq (%rdi), %rcx
    addq %r8, %rdi
    rmmovq %rcx, (%rsi)
    addq %r8, %rsi
    xorq %rcx, %rax
    addq %r9, %rdx
test:
    andq %rdx, %rdx
    jg loop

# Stack starts here and grows to lower addresses
    .pos 0x200
stack:

首先是 main 函数中,src, dest, $3 分别作为 %rdi, %rsi, %rdx 传入 copy_block 函数,也就是前三个参数。

copy_block 函数中,我们使用 %rax 代表 resultval 中间变量存储在 %rcx 中。具体的代码按照含义模拟即可。需要注意的是,因为 Y86-64 指令集不支持使用立即数作为 OPq 运算操作的参数,因此我们使用 %r8 存储 \(8\) 这个值,%r9 存储 \(-1\) 这个值。

./20230904-csapp-archlab/image-20230904211511036

Part B

Part B 的任务看上去也不算复杂,就是实现 iaddq 指令,将立即数与寄存器相加。

这个任务可以结合书上 SEQ 结构的设计过程,一边翻书一边完成,不算太难。

首先,我们仿照下图的格式,也为 iaddq 指令做一个表格。

./20230904-csapp-archlab/image-20230904212608014
阶段 iaddq V, rB
取指 Icde: ifun ← M1[PC]
rA:rB ← M1[PC + 1]
valC ← M8[PC + 2]
valP ← PC + 10
译码 valB ← R[rB]
执行 valE ← valB + valA
Set CC
访存
写回 R[rB] ← valE
更新 PC PC ← valP

然后我们来一个个看每个阶段有哪些信号需要修改。

取指阶段

取指阶段涉及的 HCL 信号如下:

信号 含义
icode, ifun 指令码和功能码
instr_valid 指令是否合法
need_regids 指令是否需要寄存器指示符字节
need_valC 指令是否包含立即数

最后的三个信号都需要修改,修改如下:

bool instr_valid = icode in 
	{ INOP, IHALT, IRRMOVQ, IIRMOVQ, IRMMOVQ, IMRMOVQ,
	       IOPQ, IJXX, ICALL, IRET, IPUSHQ, IPOPQ, IIADDQ };
bool need_regids =
	icode in { IRRMOVQ, IOPQ, IPUSHQ, IPOPQ, 
		     IIRMOVQ, IRMMOVQ, IMRMOVQ, IIADDQ };
bool need_valC =
	icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ, IJXX, ICALL, IIADDQ };

译码阶段和写回阶段

信号 含义
srcA, srcB 产生值 valA, valB 的寄存器
dstE 计算结果需要作为目的写入的寄存器
dstM 从内存中读取的值需要作为目的写入的寄存器

本阶段需要修改的是 srcBdstE

word srcB = [
	icode in { IOPQ, IRMMOVQ, IMRMOVQ, IIADDQ } : rB;
	icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RRSP;
	1 : RNONE;  # Don't need register
];
word dstE = [
	icode in { IRRMOVQ } && Cnd : rB;
	icode in { IIRMOVQ, IOPQ, IIADDQ } : rB;
	icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RRSP;
	1 : RNONE;  # Don't write any register
];

执行阶段

信号 含义
aluA, aluB ALU 的两个输入
alufun ALU 需要执行的功能
set_cc 是否更新条件码

除了 alufun 都需要修改。

word aluA = [
	icode in { IRRMOVQ, IOPQ } : valA;
	icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ, IIADDQ } : valC;
	icode in { ICALL, IPUSHQ } : -8;
	icode in { IRET, IPOPQ } : 8;
	# Other instructions don't need ALU
];
word aluB = [
	icode in { IRMMOVQ, IMRMOVQ, IOPQ, ICALL, 
		      IPUSHQ, IRET, IPOPQ, IIADDQ } : valB;
	icode in { IRRMOVQ, IIRMOVQ } : 0;
	# Other instructions don't need ALU
];
bool set_cc = icode in { IOPQ, IIADDQ };

访存阶段

信号 含义
mem_read 是否从内存中读取
mem_write 是否向内存中写入
mem_addr 设置内存地址
mem_data 向内存写入的数据
Stat 状态码

不需要更新。

更新 PC 阶段

信号 含义
new_pc 新的 PC 地址

不需要更新。

构建和测试

按照实验的文档,我们在终端运行下面的命令构建我们的 SEQ 模拟器。

make VERSION=full

这时我们可能会遇到一个问题,提示 <tk.h> 头文件不存在。

./20230904-csapp-archlab/image-20230904222844700

必须要在这个文件夹下的 Makefile 文件中找到这三行注释掉即可。

GUIMODE=-DHAS_GUI
TKLIBS=-L/usr/lib -ltk -ltcl
TKINC=-isystem /usr/include/tcl8.5

编译成功后执行 ./ssim -t ../y86-code/asumi.yo 进行 ISA 测试:

./20230904-csapp-archlab/image-20230904223930655

测试成功!

接下来再使用基准程序进行测试:(cd ../y86-code; make testssim)

./20230904-csapp-archlab/image-20230904224134971

全部成功!

最后是回归测试,运行下面的命令测试除了 iaddq 的所有指令:(cd ../ptest; make SIM=../seq/ssim)

./20230904-csapp-archlab/image-20230904224513594

运行下面的命令测试 iaddq 指令:(cd ../ptest; make SIM=../seq/ssim TFLAGS=-i)

./20230904-csapp-archlab/image-20230904224641944

测试通关!

Part C

在这个部分中,实验给我们提供了一个 PIPE 结构的 HCL 实现 pipe-full.hcl,以及一个 Y86-64 程序 ncopy.ysncopy.ys 完成了和 Part A 中 copy.ys 类似的功能,不过返回的不是异或和,而是正数的个数。

这个任务的得分是用程序的 CPE 衡量的,如果拷贝 \(N\) 个元素需要 \(C\) 个时钟周期,那么这个程序的 CPE 就是 \(\frac CN\),也就是每元素周期数。如果 CPE 为 \(c\),那么这个部分的得分就是:

\[S = \left\{ \begin{aligned} &0,& &c > 10.5\\ &20\cdot (10.5 - c),& & 7.5 \leq c \leq 10.5\\ &60,& & c < 7.5 \end{aligned} \right. \]

也就是说,只有让 CPE 小于 \(7.5\) 才能得到满分!

首先记录一下在什么修改也没有做的情况下,我们的 CPE 和得分:

./20230904-csapp-archlab/image-20230904235624417

使用 iaddq

原始程序中所有的加法运算都是先将立即数存储到一个寄存器中,然后进行寄存器的加法的。如果我们添加一个 iaddq 指令实现立即数与寄存器相加,那么可以减少一定的操作代价吧。

(不得不说,原始程序在每个循环内部才将立即数存储进寄存器挺傻的……在循环外部预处理以下也不至于那么慢)

pipe-full.hcl 文件修改的方法与 Part B 的部分很类似,这里不再赘述。

修改后的程序如下:

# %rdi = src, %rsi = dst, %rdx = len
ncopy:
	xorq %rax,%rax		# count = 0;
	andq %rdx,%rdx		# len <= 0?
	jle Done		# if so, goto Done:
Loop:	mrmovq (%rdi), %r10	# read val from src...
	rmmovq %r10, (%rsi)	# ...and store it to dst
	andq %r10, %r10		# val <= 0?
	jle Npos		# if so, goto Npos:
	iaddq $1, %rax		# count++
Npos:
	iaddq $-1, %rdx		# len--
	iaddq $8, %rdi		# src++
	iaddq $8, %rsi		# dst++
	andq %rdx,%rdx		# len > 0?
	jg Loop			# if so, goto Loop:
Done:
	ret

修改后的 CPE:

./20230904-csapp-archlab/image-20230905113721286

还是零分,但是进步不小!

循环展开

循环展开可以减少索引改变的次数,提升计算效率。

我采用了 \(8\) 路循环展开。先写出循环展开的 C 程序版本。

word_t ncopy(word_t *src, word_t *dst, word_t len)
{
    word_t count = 0;
    word_t val;

    while (len >= 8) {
        val = *src, *dst = val;
        if (val > 0) count++;
        val = *(src + 1), *(dst + 1) = val;
        if (val > 0) count++;
        val = *(src + 2), *(dst + 2) = val;
        if (val > 0) count++;
        val = *(src + 3), *(dst + 3) = val;
        if (val > 0) count++;
        val = *(src + 4), *(dst + 4) = val;
        if (val > 0) count++;
        val = *(src + 5), *(dst + 5) = val;
        if (val > 0) count++;
        val = *(src + 6), *(dst + 6) = val;
        if (val > 0) count++;
        val = *(src + 7), *(dst + 7) = val;
        if (val > 0) count++;
        len -= 8, src += 8, dst += 8;
    }
    while (len) {
        val = *src++, *dst++ = val;
        if (val > 0) count++;
    }
    return count;
}

转写为汇编程序:

ncopy:
	xorq %rax,%rax		# count = 0;
	jmp Npos
Loop0:
	mrmovq (%rdi), %r10	# read val from src...
	rmmovq %r10, (%rsi)	# ...and store it to dst
	andq %r10, %r10		# val <= 0?
	jle Loop1			# if so, goto Npos:
	iaddq $1, %rax		# count++
Loop1:
	mrmovq 8(%rdi), %r10	# read val from src...
	rmmovq %r10, 8(%rsi)	# ...and store it to dst
	andq %r10, %r10		# val <= 0?
	jle Loop2			# if so, goto Npos:
	iaddq $1, %rax		# count++
Loop2:
	mrmovq 16(%rdi), %r10	# read val from src...
	rmmovq %r10, 16(%rsi)	# ...and store it to dst
	andq %r10, %r10		# val <= 0?
	jle Loop3			# if so, goto Npos:
	iaddq $1, %rax		# count++
Loop3:
	mrmovq 24(%rdi), %r10	# read val from src...
	rmmovq %r10, 24(%rsi)	# ...and store it to dst
	andq %r10, %r10		# val <= 0?
	jle Loop4			# if so, goto Npos:
	iaddq $1, %rax		# count++
Loop4:
	mrmovq 32(%rdi), %r10	# read val from src...
	rmmovq %r10, 32(%rsi)	# ...and store it to dst
	andq %r10, %r10		# val <= 0?
	jle Loop5			# if so, goto Npos:
	iaddq $1, %rax		# count++
Loop5:
	mrmovq 40(%rdi), %r10	# read val from src...
	rmmovq %r10, 40(%rsi)	# ...and store it to dst
	andq %r10, %r10		# val <= 0?
	jle Loop6			# if so, goto Npos:
	iaddq $1, %rax		# count++
Loop6:
	mrmovq 48(%rdi), %r10	# read val from src...
	rmmovq %r10, 48(%rsi)	# ...and store it to dst
	andq %r10, %r10		# val <= 0?
	jle Loop7			# if so, goto Npos:
	iaddq $1, %rax		# count++
Loop7:
	mrmovq 56(%rdi), %r10	# read val from src...
	rmmovq %r10, 56(%rsi)	# ...and store it to dst
	iaddq $64, %rdi		# src += 8
	iaddq $64, %rsi		# dst += 8
	andq %r10, %r10		# val <= 0?
	jle Npos		# if so, goto Npos:
	iaddq $1, %rax		# count++
Npos:
	iaddq $-8, %rdx		# len -= 8
	jge Loop0			# if so, goto Loop:
	iaddq $8, %rdx
	jle Done
Small:
	mrmovq (%rdi), %r10	# read val from src...
	rmmovq %r10, (%rsi)	# ...and store it to dst
	andq %r10, %r10		# val <= 0?
	jle Test			# if so, goto Npos:
	iaddq $1, %rax		# count++\
Test:
	iaddq $8, %rdi
	iaddq $8, %rsi
	iaddq $-1, %rdx
	jg Small
Done:
	ret

成功把 CPE 降低到了 \(9.30\)!

./20230904-csapp-archlab/image-20230905125225744

避免加载/使用冒险

注意到下面这样的语句会引起加载/使用冒险,导致一定会引起一个气泡。

mrmovq (%rdi), %r10	# read val from src...
rmmovq %r10, (%rsi)	# ...and store it to dst

我们可以将两次从内存中读取和两次向内存写入分别合并起来,以此来避免这样的冒险。

mrmovq (%rdi), %r10
mrmovq 8(%rdi), %r11
rmmovq %r10, (%rsi)
rmmovq %r11, 8(%rsi)

这一步的成果非常显著,我们的 CPE 一下子降低到了 \(8.52\)。

./20230904-csapp-archlab/image-20230905131212954

再补一个循环展开

注意到我们上一个版本的循环展开中,因为对于剩余的数据没有进行循环展开,导致这个剩余数据的处理中,依然存在加载/使用冲突。我们在这个版本中,将剩余的数据也进行了循环展开,以及消除气泡。

Npos:
	iaddq $-8, %rdx		# len -= 8
	jge Loop0			# if so, goto Loop:
	iaddq $8, %rdx
	jmp Test
L2_1:
	mrmovq (%rdi), %r10	# read val from src...
	mrmovq 8(%rdi), %r11	# read val from src...
	rmmovq %r10, (%rsi)	# ...and store it to dst
	rmmovq %r11, 8(%rsi)	# ...and store it to dst
	andq %r10, %r10		# val <= 0?
	jle L2_2			# if so, goto Npos:
	iaddq $1, %rax		# count++
L2_2:
	iaddq $16, %rdi
	iaddq $16, %rsi
	andq %r11, %r11		# val <= 0?
	jle Test			# if so, goto Npos:
	iaddq $1, %rax		# count++
Test:
	iaddq $-2, %rdx
	jge L2_1
	iaddq $2, %rdx
	jle Done
	mrmovq (%rdi), %r10	# read val from src...
	rmmovq %r10, (%rsi)	# ...and store it to dst
	andq %r10, %r10		# val <= 0?
	jle Done			# if so, goto Npos:
	iaddq $1, %rax		# count++

效果依然十分显著,CPE 降低到 \(8.08\),得分提升到 \(48.3\)。

./20230904-csapp-archlab/image-20230905134739914

其它优化

将开头的 xorq %rax, %rax 删去,因为程序运行时 %rax 自动被初始化为 \(0\) 了。但是这个优化不具备普适性,只能在这个程序中使用。

./20230904-csapp-archlab/image-20230905153446004

提升并不显著,只有 \(1\) 分左右。

另外,还有一种优化的方法是将循环展开剩余的部分全部展开写出,使用二分查找的方式定位到展开的代码中。这种方法我尝试过,但是不知道为什么实现的效果并不好。还是等以后有空再尝试继续优化这个方面吧!

标签:src,CSAPP,val,list,Lab,Architecture,quad,rdi,rax
From: https://www.cnblogs.com/hankeke303/p/18155092/csapp-archlab

相关文章

  • CSAPP Lab5 Cache Lab
    到实验5啦!这次的实验是有关高速缓存的。让我们先来复习一下高速缓存的基础知识吧!复习高速缓存的结构在一个存储器地址有\(m\)位的系统上,一共有\(M=2^m\)个地址。假设高速缓存被组织成一个有\(S=2^s\)个高速缓存组的数组,其中每个组包括\(E\)个高速缓存行,每行存......
  • CSAPP Lab6 Shell Lab
    本次实验的任务很清晰,实现一个简单的UnixShell。需要用到基础的进程控制、信号处理等知识。简单来说,实验已经提供了一些简单的功能,我们需要在此基础上,实现下面的功能:eval:解析和解释命令行的主例程。[70行]builtin_cmd:识别并解释内置命令quit(退出)、fg(前台运行某个作业)、bg(后......
  • CSAPP Lab-2 BOMBLAB
    第二个Lab就比较有趣了。这一个Lab的任务是,我们得到了一个bomb炸弹程序,这个炸弹程序有\(6\)个phase,每个phase都会读取我们的输入,判断我们的输入是否符合要求,如果正确这个phase的炸弹就会被拆除,否则炸弹就会爆炸。我们需要借助各种工具,对程序进行反汇编等等,获得能够......
  • 伯克利大学 CS61B Lab配置教程
    基本过程:首先将伯克利大学的代码框架下载到自己的电脑,然后我们直接在框架里修改就行将自己的代码上传到github上,然后使用伯克利大学的Gradescope评测自己写的代码下载代码在自己电脑桌面新建一个文件夹,这里我命名为:cs61b,打开gitbash,使用cd进入我们新创建的文件夹,注意路径......
  • 读《我和Labview》5条件结构和顺序结构
    5条件结构和顺序结构条件结构布尔类型条件选择结构其它数据类型的条件选择是否要设置默认分支?合理设置悬着条件隧道避免把控件放在条件结构内选择函数顺序结构程序执行顺序创建顺序结构层叠式顺序结构平铺式顺序结构无形胜有形的最高境界6用户自定义控件7控件的局......
  • Linux服务器中Docker部署的GitLab镜像访问出现500错误
    一背景这几天发现在Linux服务器中Docker部署的GitLab镜像访问出现500错误,在重启服务器后大概10分钟再次出现该情况,后面登录服务器一步步排查最终解决问题,现在将解决问题的过程做一个总结。二过程分析首先第一步就是看看我们Docker目录下文件占用的情况,因为我们的Linux服务......
  • 如何将Docker中GitLab数据备份到宿主Linux上
    一宿主机准备存放备份文件的目录建议以年月日进行命名使用putty.exe或者PowerShell登录远程服务器cdshare(如果没有当前目录请创建该共享目录)mkdir20220930(在共享目录下创建备份文件夹)二进入Docker容器内部备份数据1.执行命令sudodockerexec-itgitlab/bin/......
  • feign调用接口报错No qualifying bean of type '***HttpMessageConverters' available
    在整合springcloudgeateway时,调用了feign接口,报错Noqualifyingbeanoftype'org.springframework.boot.autoconfigure.http.HttpMessageConverters'available报错信息feign.codec.EncodeException:Noqualifyingbeanoftype'org.springframework.boot.autocon......
  • MIT6.S081 - Lab2: system calls
    Lab2:systemcalls预备知识执行一次系统调用的流程:USERMODEstep1:系统调用声明user/user.h:系统调用函数(如intfork(void))step2:ecall进入内核态user/usys.S(该文件由user/usys.pl生成,后续添加函数可以在这里添加):执行如下命令.globalforkfork:lia7,SYS_f......
  • 读《我和Labview》3.5-3.6路径和数据平化
    3.5路径3.5.1路径数据3.5.2相对路径3.5.3路径常量3.5.4路径与其他数据类型的转换3.6数据平化3.6.1数据平化至字符串3.6.2数据平化至XML3.6.3数据平化至JSON4图形化显示数据5条件结构和顺序结构6用户自定义控件7控件的局部变量和属性8按自己的喜好设置编程环境......