如何实现第一个单片机裸机程序(附汇编指令)

一、(8-2)

一个芯片上面有片内SRAM内存(4K),NOR Flash(2M) , Nand控制器(256M),GPIO控制器
启动过程:(大多数ARM芯片从0地址启动)
1、NOR 启动, NOR Flash基址为0 CPU读取NOR上第一个指令(前4字节)执行,CPU继续读取其他指令执行。

2、NAND 启动, 片内4K RAM基地址为0,它会把NandFlash前面4K的内容拷贝到RAM中来,然后CPU从0地址取出第一条指令执行。

总结:也就是我设为Nor启动的时候,我的NOR FLASH上面0地址才是基地址,而片内RAM的地址为0x4000 0000 = 1GB;而用到NAND FLASH的时候是用的片内RAM,4K的片内RAM的基地址0开始运行,NOR FLASH不可访问。

1KB = 1024B = 2^10B = 0x400
1MB = 1024 * 1024 = 2^20B = 0X10000 = 1 0000 0000 0000 0000 0000(二进制)
1GB = 2^30B

二、仅通过汇编指令实现点灯(8-3)

led_on.s

//假设点亮LED:gpf4.text //表示代码段
.global _start //第一条指令 ,  _start它表示一个标号_start:
/*
配置GPF4为输出引脚
把0x100写到地址0x56000050地址上
*/ldr r1 ,=0x56000050mov r0, #0x100 //或者使用ldr r0, =0x100str r0, [r1] //把r0的值写到r1的地址上去
/*
设置GPF4输出为高电平 
把0x10写到地址0x56000054  -- 0x10 =  0001 0000正好是第4个索引位置上(从0开始的)
由于是点亮led是写0
*/ldr r1 ,=0x56000054ldr r0, =0 //或者使用ldr r0, #0x100str r0, [r1] //把r0的值写到r1的地址上去/*加入一个死循环,因为我们生成.bin程序,如果后面不设置为死循环,后面的二进制的内容我们无法知道,我想的这应该就是单片机程序中的防止跑飞*/
halt: b halt

.text 部分是处理器开始执行代码的地方,指定了后续编译出来的内容放在代码段【可执行】,是arm-gcc编译器的关键词。
.global关键字用来让一个符号对链接器可见,可以供其他链接对象模块使用;告诉编译器后续跟的是一个全局可见的名字【可能是变量,也可以是函数名】
global _start 让 _start 符号成为可见的标识符,这样链接器就知道跳转到程序中的什么地方并开始执行。
_start是默认起始地址,也是编译、链接后程序的起始地址。由于程序是通过加载器来加载的,必须要找到 _start名字的函数,因此_start必须定义成全局的,以便存在于编译后的全局符号表中,供其它程序【如加载器】寻找到

arm-linux-gcc -c - o led_on.o led_on.S //编译
arm-linux-ld -Ttest 0 led_on.o -o led_on.elf //链接
arm-linux-objcopy - o binaru -S led_on.elf led_on.bin //生成bin文件
可以写在一个makefile中

all:arm-linux-gcc -c -o led_on.o led_on.S  arm-linux-ld -Ttest 0 led_on.o -o led_on.elf arm-linux-objcopy -o binaru -S led_on.elf led_on.bin 
clean:rm *.bin *.o *.elf

生成 elf 文件并不是能直接用在嵌入式平台上面裸跑的,因为我们并没有操作系统,我们不需要elf文件头的那些指示信息提供给操作系统(在linux上头部会有魔数,告诉操作系统我是JAVA还是elf文件还是#!/bin/bash类型的文件),指示系统怎么去加载文件,在嵌入式上面的完全没有那个必要,只需要将实际的代码提取出来,直接运行就OK,也就是 objcopy的操作。

三、汇编与机器码(8-4)

前面说到ldr是一个伪指令,我们将elf文件反汇编一下查看一下真正汇编指令

all:arm-linux-gcc -c -o led_on.o led_on.S  arm-linux-ld -Ttest0 led_on.o -o led_on.elf arm-linux-objcopy -o binaru -S led_on.elf led_on.bin arm-linux-objcopy -D led_on.elf > led_on.dis
clean:rm *.bin *.o *.elf

查看反汇编dis文件的内容
在这里插入图片描述
第一列是地址,第二列是机器码,第三列是汇编码

pc = 当前指令地址+8
因为ARM是以流水线的方式运行的
当前执行地址A的指令,已经在对地址A+4 的指令进行译码,已经在读取A+8(PC的当前值)的指令。

0地址的指令可以变成 r1 = [ pc + 20 ] = [ 8 + 20 ] = [0x1c] 去0x1c地址去读取它的内存的值 = 0x5600 0050
8地址就是把0x100写到0x56000050地址中去
c地址上 r1 = [0xc + 8 + 12 ] = [ 32 ] = [ 0x20 ] 去0x20地址去读取它的内存的值 = 0x5600 0054

我们看到旁边的机器码,在bin文件通过16进制可以看到与反汇编中机器码是一致的,编译器把这些伪指令转换成真正的汇编码
在这里插入图片描述
如果想要电量GPF5的话需要写0x400到0x5600 0050
在这里插入图片描述
查看MOV的机器码。发现影响立即数的就是最后的0-11位
这12位如何表示:分为高4位(rotate移位数),低8位(immed_8)
立即数= immed_8 循环右移(2 * rotate)位 = 1 << (1100 = 12) = 1右移24位 = 31个0 1 右移24位后就是 0x100 = (23个0) 1 (8个0)
因此我们少移动2位的话就是0x400 = 1 循环右移22位

可以总结 C/汇编语言是给人类看的 —> bin文件是给机器看的,CPU只管机器码

四、用c语言实现点灯程序(8-7)

int main()
{unsigned int *pGPFCON = (unsigned int *)0x56000050;unsigned int *pGPFDAT = (unsigned int *)0x56000054;/* 配置GPF4为输出引脚 */*pGPFCON = 0x100;/* 设置GPF4输出0 */*pGPFDAT = 0;return 0;
}

a. 我们写出了main函数, 谁来调用它?
b. main函数中变量保存在内存中, 这个内存地址是多少?
答: 我们还需要写一个汇编代码, 给main函数设置内存, 调用main函数

还需要写一个汇编代码,给main函数设置内存,调用main函数

.text
.global _start_start: //前4行都是固定的语法/* 设置内存: sp 栈 */ldr sp, =4096  /* nand启动 */
//	ldr sp, =0x40000000+4096  /* nor启动 *//* 调用main */bl main/* 保存返回地址 */
halt:b halt

局部变量都应该存放在栈中

设置为NAND启动的时候使用片内的4K内存,也就是4096,我们把栈 设置在最顶部
makefile文件:

all:arm-linux-gcc -c -o led.o led.carm-linux-gcc -c -o start.o start.Sarm-linux-ld -Ttext 0 start.o led.o -o led.elfarm-linux-objcopy -O binary -S led.elf led.binarm-linux-objdump -D led.elf > led.dis
clean:rm *.bin *.o *.elf *.dis

五、解析C程序的内部机制,汇编和C如何调用起来的

首先我们知道

start.s作用: (1) 设置栈 (2)调用main并把返回地址保存在lr中

led.c : main : (1)定义两个局部变量 (2)设置变量 (3)return 0

那就有几个疑问了:
(1)为何要设置栈

因此C函数要用

(2)怎么使用栈

a.保存局部变量 b.保存lr等寄存器

(3)a.被调用如何把返回值返回给调用者?b.调用者如何把参数传给被调用者?

ATPCS:ARM-THUMB procedure call standard(ARM-Thumb过程调用标准)
调用者 通过r0-r3传给或传回 被调用者,r4-r11可能被使用,所以在函数的入口保存他们,在出口恢复他们。 (下面解析代码的时候main函数中的return 0就是保存在)

bl main 返回地址保存在lr寄存器里面,假设main函数也调用其他函数,调用完其他函数后,他也应该返回地址到lr上,main函数的返回地址就被子函数的返回地址覆盖了

dis反汇编文件(我们来开始一条一条的分析执行过程):


led.elf:     file format elf32-littlearmDisassembly of section .text:00000000 <_start>:0:	e3a0da01 	mov	sp, #4096	; 0x10004:	eb000000 	bl	c <main> /* bl c跳转到c地址也就是main段下面的c地址开始执行,同时保存lr = 8 */00000008 <halt>:8:	eafffffe 	b	8 <halt>  /* 这里是lr的返回地址即main函数的出口地址,对应汇编指令 halt: b halt循环*/0000000c <main>:c:	e1a0c00d 	mov	ip, sp  // ip = so = 409610:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}  //pc = 当前指令地址+8 (这一段前面说过流水线) = 10 + 8 = 0x18 ,lr = 8,ip = 4096,fp = 未定义,sp = 4096 - 4 * 4 = 4080(因为加了!所以需要修改后的sp值写会sp中),此时栈中存放的内存布局见下图,高地址存高位寄存器,即R15先存高地址14:	e24cb004 	sub	fp, ip, #4	; 0x4 //fp = ip - 4 = 409218:	e24dd008 	sub	sp, sp, #8	; 0x8 // sp = sp - 8 = 4080 - 8 = 40721c:	e3a03456 	mov	r3, #1442840576	; 0x56000000 // r3 = 0x5600000020:	e2833050 	add	r3, r3, #80	; 0x50 //r3 = 0x5600005024:	e50b3010 	str	r3, [fp, #-16] // r3保存在fp - 16 = 4092 - 16 = 4076 的地方,局部变量保存在栈中28:	e3a03456 	mov	r3, #1442840576	; 0x560000002c:	e2833054 	add	r3, r3, #84	; 0x54 //r3 = 0x5600005430:	e50b3014 	str	r3, [fp, #-20]// r3保存在fp - 20 = 4092 - 20 = 4072 的地方34:	e51b2010 	ldr	r2, [fp, #-16] // r2 = fp - 16 = 4076的地方取值 = 0x5600005038:	e3a03c01 	mov	r3, #256	; 0x100 // r3 = 0x1003c:	e5823000 	str	r3, [r2] // r3的值存入到r2的地址中去40:	e51b2014 	ldr	r2, [fp, #-20] // r2 = fp - 20 = 4072的地方取值 = 0x5600005444:	e3a03000 	mov	r3, #0	; 0x0 // r3 = 048:	e5823000 	str	r3, [r2] //这里同理把0写入0x56000054的地址中4c:	e3a03000 	mov	r3, #0	; 0x0 // 这里开始4c 50地址就是return 0的操作了,编译器没那么聪明,先把r3=050:	e1a00003 	mov	r0, r3 //再把r0 = r3,多此一举了,直接把r0 = 0就可以了54:	e24bd00c 	sub	sp, fp, #12	; 0xc //恢复栈 sp = fp - 12 = 4092 - 12 = 408058:	e89da800 	ldmia	sp, {fp, sp, pc} //从栈中恢复寄存器,从sp = 4080开始恢复,配合下图内存中的布局:fp = [4080] = 4080 , sp = [4084] = 4096, pc = [4088] = 8-->调回0x8的地址,main返回,返回到halt//具体stmdb、ldmia操作见下面汇编指令中ldm、stm  (8-8) 中 ia 、db的内容。
Disassembly of section .comment:00000000 <.comment>:0:	43434700 	cmpmi	r3, #0	; 0x04:	4728203a 	undefined8:	2029554e 	eorcs	r5, r9, lr, asr #10c:	2e342e33 	mrccs	14, 1, r2, cr4, cr3, {1}10:	Address 0x10 is out of bounds.

在这里插入图片描述
我们可以看到栈中前面几位是用来保存寄存器的值的,函数返回之前会从这里恢复寄存器,下面的内容就是局部变量,sp进去之前是4096,出来后还是4096

bin文件中最后一个确实是e89da800,elf中的comment段并没有放进去,comment的中文名字叫做注释,bin文件肯定不需要注释
在这里插入图片描述

现在的代码只涉及被调用者给调用者返回值,那调用者如何传参给被调用者呢?

直接通过r0传入就可以了,见下面的代码,都是通过r0传入来实现不同的形参传入效果

C代码:

void delay(volatile int d)
{while (d--);
}int led_on(int which)
{unsigned int *pGPFCON = (unsigned int *)0x56000050;unsigned int *pGPFDAT = (unsigned int *)0x56000054;if (which == 4){/* 配置GPF4为输出引脚 */*pGPFCON = 0x100;}else if (which == 5){/* 配置GPF5为输出引脚 */*pGPFCON = 0x400;}/* 设置GPF4/5输出0 */*pGPFDAT = 0;return 0;
}

汇编代码:

.text
.global _start_start:/* 设置内存: sp 栈 */ldr sp, =4096  /* nand flash启动 */
//	ldr sp, =0x40000000+4096  /* nor flash启动 */mov r0, #4  //把4作为形参传入到led_on函数中bl led_onldr r0, =100000 //把100000 作为形参传入到led_on函数中bl delaymov r0, #5 //把5作为形参传入到led_on函数中bl led_onhalt:b halt

因为我们的程序特别小,设置栈是向下生长的,只要跟我们的程序代码部分不冲突就可以了。

六、关看门口以及判断Nor或者Nand启动

看门口:通过定时器去保持系统稳定,他在倒数,当倒数到0之前你要去设置它,如果到0的话他就会复位我们的系统,避免系统卡死。
在这里插入图片描述

在这里插入图片描述

norflash可以理解成硬盘一样的东西,可以像内存一样读,但不能想内存那样去写,如果一个硬盘很容易去写的话,那岂不是很容易被破坏,所以要写的时候需要发送一定的格式才可以。所以根据这个特性,nor不可以去写,那我们去写一个值到0地址,如果读取出来不是0那就是nand如果是0那就是nor

.text
.global _start_start:/* 关闭看门狗 */ldr r0, =0x53000000ldr r1, =0str r1, [r0]/* 设置内存: sp 栈 *//* 分辨是nor/nand启动* 写0到0地址, 再读出来* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动* 否则就是nor启动*/mov r1, #0ldr r0, [r1] /* 读出原来的值备份 */str r1, [r1] /* 0->[0] */ ldr r2, [r1] /* r2=[0] */cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */ldr sp, =0x40000000+4096 /* 先假设是nor启动 */moveq sp, #4096  /* nand启动 */streq r0, [r1]   /* 恢复原来的值 */bl mainhalt:b halt

七、中断的引入

  • 查询方式
    通过大循环不断去判断事件有没有发生

  • 中断方式
    在大循环中做自己的事情,如果中断发生了就马上去处理中断事件。

  • 中断任务怎么被调用(核心:怎么进入到中断服务程序?)
    中断控制器来发送信号给CPU
    在这里插入图片描述

  • ARM对于异常(中断)的使用过程
    1、初始化

    a.设置中断源(硬件处理可以屏蔽某些中断源,比如某些按键中断。软件的中断无法屏蔽。),让他可以产生中断;
    b.设置中断控制器(屏蔽,优先级)
    c.设置CPU总开关(使能中断)

    2、执行其他程序
    3、产生中断

    产生中断,举例:按下按键—>中断控制器—>CPU

    4、cpu每执行完一条指令都会检查有无中断/异常产生

    5、发现有异常/中断产生,开始中断程序的处理:

    对于不同的异常跳去不同的地址(这些地址通常是排在一块的,叫做异常向量 )执行程序,这些地址上只是一条跳转指令,跳转执行某个函数;
    在这里插入图片描述
    发生中断时,CPU跳到对应的地址执行
    在这里插入图片描述

    6、这些中断函数处理过程:

    a.保存现场
    b.调用对应服务程序,处理异常(中断)
    c.恢复现场

  • 不同的芯片,不同的架构,在这方面的处理稍有差别:

    保存/恢复现场:cortex M3/M4是硬件实现的,cortex A7是软件实现的
    CPU中止当前执行,跳转去执行处理异常的代码:也有差异

    • cortex M3/M4在向量表上放置的是函数地址
    • cortex A7在向量表上放置的是跳转指令

八、ARM的CPU模式、状态(state)与寄存器

8.1 7种模式

1、a、usr:正常模式
2、b、sys:兴奋模式
3、异常模式:

c、und 未定义指令模式,当CPU不认识这个指令的时候进入这个模式
d、svc 管理模式
e、abt 中止模式

1)指令预取模式
2)数据访问模式

f、IRQ 中断模式
g、FIQ 快中断

b-g6种模式为特权模式,可以编程操作CPSR直接进入其他模式;
a为用户模式,不可直接进入其他模式;
在这里插入图片描述
灰色表示模式专属的寄存器,发现R13、R14他们都有的专属寄存器,R13:sp栈,R14:LR链接寄存器(保存发生异常时的指令地址)

中断处理流程

a.保存现场

保存被中断模式的寄存器。
比如我在用户模式下,假设我保存r0-r14等寄存器,然后去处理异常,回来后再恢复这些寄存器。
如果我发生的是快中断,我就不需要保存被中断模式下的R8-R12了,因为我有自己专属模式下的R8-R12寄存器,并不会影响到你(这就是在FIQ模式下备份寄存器特别多的原因),linux下并不会使用FIQ模式,作为单片机情况下才会使用

 b.调用对应服务程序,处理异常(中断)  		c.恢复现场

8.2 状态state

ARM state:ARM指令集,每个指令4字节
Thumb state:Thumb指令集、每个指令2字节(减少程序存储的空间)
意思就是mov R0, R1 在ARM指令集下占4字节的机器码,Thumb占2字节的机器码

8.3 CPSR/SPSR

(1) CPSR状态位分析

在这里插入图片描述
1、 M4-M0表示当前CPU处于那种模式,也可以修改模式位进入那种模式(用户模式下没有权限修改)
在这里插入图片描述
2、bit5 T 表示当前处于ARM state还是Thumb state
3、bit6/bit7为1时,表示禁止所有的FIQ/IRQ
3、bit8-27保留位
4、bit31-28表示状态位

cmp R0,R1 表示如果两个相等,Zero位为1
beq xxx(表示跳转到xxx,但是得先判断第Zero位是否为1,如果为1就跳转)

cmp R0,R1 这条指令会影响到Z位
beq xxx这条指令会使用Z位,如果Z位为1,则跳转

(2) SPSR分析

SPSR:S表示save,用来保存被中断模式的CPSR,比如发生IRQ打断用户模式,那么这个时候IRQ的SPSR就会保存被中断的用户程序的CPSR。

(3)进入异常时的处理流程(硬件实现的)

1、把被中断模式下的下一条指令的地址保存在LR寄存器中 = PC + 4/8
2、把CPSR保存到即将进入到异常模式下的SPSR
3、修改CPSR的M4-M0模式位,进入异常模式
4、跳到向量表

(4)退出异常时的处理流程

1、让LR寄存器减去某个值,赋值给PC,PC = LR_异常 - offset,规则如下图所示
在这里插入图片描述

2、把CPSR = SPSR_异常,也就是进入程序时的CPSR把他恢复回来
3、如果是中断模式下,清中断操作

九、自己实现und_exception

对应的向量表
在这里插入图片描述

.text
.global _start_start:b reset ./* 中断向量表0地址 0:reset */b do_und		 /* 中断向量表4地址 4:und   */do_und:
reset:/* 关闭看门狗 */ldr r0, =0x53000000ldr r1, =0str r1, [r0]/* 设置内存: sp 栈 *//* 分辨是nor/nand启动* 写0到0地址, 再读出来* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动* 否则就是nor启动*/mov r1, #0ldr r0, [r1] /* 读出原来的值备份 */str r1, [r1] /* 0->[0] */ ldr r2, [r1] /* r2=[0] */cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */ldr sp, =0x40000000+4096 /* 先假设是nor启动 */moveq sp, #4096  /* nand启动 */streq r0, [r1]   /* 恢复原来的值 */bl mainhalt:b halt

十、如何实现一个按键中断

1、设置中断,让按键能够发出中断
2、设置中断控制器,让他能发出中断给CPU
3、设置CPU,控制CPSR的I位,它是总开关
在这里插入图片描述

附:汇编指令

LDR 读内存

LDR R0,[R1] //假设R1的值是x,读取地址x上的数据(4字节),保存到R0

STR 写内存命令

STR R0, [R1] 假设R1的值是x,把R0的值写到地址x(4字节)

B 跳转

MOV

MOV R0 ,R1 // 把R1的值赋值给R0 MOV R0, #100
// R0 = 0x100

MOV和LDR的区别

R0, = 0x12345678 // R0 = 0x12345678

伪指令,它会被拆分为几条真正的ARM指令,如果用MOV对于32位的指令,他有位表示MOV有几位表示R0,那剩下的不足32位村放不下0x12345678,不可以表示任意值,只能表示简单值比如0x100就是简单值(这个简单值叫做立即数),所以如果用MOV R0,0x12345678这是错误的。

add

add r0, r1 ,#4 // r0 = r1 + 4
add r0, r1, r2 // r0 = r1 + r2

sub

sub r0, r1 ,#4 // r0 = r1 - 4
sub r0, r1, r2 // r0 = r1 - r2

BL: brarch and link

bl xxx -> (1 )跳转到xxx (2) 把 返回地址(下一条指令的地址)保存到 lr 寄存器中

ldm、stm (8-8)

ldmia
stmdb
m表示many,ldr的时候一次只能操作一个寄存器

ldm 读内存,写入多个寄存器

stm 把多个寄存器的值写入内存

ia 、db

在这里插入图片描述

stmdb sp!{fp, ip, lr, pc}

对上面一行汇编代码进行解析:
假设sp = 4096,db表示预先减少

因为内存空间是0-4095才对,4096已经超出了,所以我们使用db预先减少
在这里插入图片描述
因此是先减后存,我们已经减完了到sp’ = sp - 4 = 4092,后面需要存储寄存器的值
规则:高编号的寄存器存在高寄存器,因此{}里面顺序随便
因此:4092-4095 存放PC = R15
在执行先减后存的操作 sp; = sp - 4 = 4092 - 4 = 4088
4088-4091 存放lr= R14
后面一次执行先减后存的操作得到:
4084-4087 存放ip= R12
4080-4083 存放fp = R11

ldmia sp!{fp, sp, pc}

在这里插入图片描述

ia表示后增,那就是先读后增 ,高编号寄存器存放高地址内存值 。

(1) 因此刚开始是fp = 4080- 4083的值 = 原来保存的fp (低编号寄存器存放低地址内容的值)
(2) 后增sp’ = sp + 4 = 4084
(3) 先读: sp = 4084 - 4087的值= 原来保存的ip
(4) 后增sp’ = sp + 4 = 4088
(5) 先读: sp = 4088 - 4091的值= 原来保存的p’c
(6) 后增sp’ = sp + 4 = 4092
这里的sp后面没有!:表示sp修改后的地址值不存入sp中,因此在第(3)步中已经把原来传入的ip赋值给sp了,而这个ip在程序的最开始的时候就是传入的最原始的sp = 4096,见下图。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/256370.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

程序是怎样跑起来的

1. 对程序来说CPU是什么 问题&#xff1a; 程序是什么&#xff1f; 指示计算机每一步动作的一组指令程序是由什么组成的&#xff1f; 指令和数据什么是机器语言&#xff1f; CPU可以直接识别并使用的语言正在运行的程序存储在什么位置&#xff1f; 内存什么是内存地址&#x…

深入理解《hello world》是如何实现的

函数栈桢的创建和销毁 前言C语言是由函数构成的栈帧概念寄存器hello world是如何实现我们转到汇编代码&#x1f333; main函数栈帧的创建(开始调用main函数)&#x1f333; main函数栈帧的初始化&#x1f333;函数栈帧的销毁printf函数栈帧的销毁main函数的栈帧销毁 总结 ❤️ &…

【供应链架构day8】履约系统的架构长什么样子:从需求开始讲起

很多公司&#xff0c;除了自营商城以外&#xff0c;还有其它渠道&#xff08;如天猫、京东等&#xff09;&#xff0c;多个渠道的订单该如何集中履约&#xff1f;订单履约全流程是怎样的&#xff1f;接着小Q的故事&#xff0c;为您揭晓多平台订单履约系统的系统设计思路。 由于…

金融反欺诈-交易基础介绍

一、简介 如今&#xff0c;互联网金融比较火热&#xff0c;金融欺诈也变得非常普遍&#xff0c;金融反欺诈也应运而生。本文将主要介绍下金融交易中的一些基本内容&#xff0c;并简单介绍下历史悠久的并且还未淘汰的磁条卡的风险&#xff0c;这些也是了解金融欺诈需要的基本知识…

详解反调试技术

反调试技术&#xff0c;恶意代码用它识别是否被调试&#xff0c;或者让调试器失效。恶意代码编写者意识到分析人员经常使用调试器来观察恶意代码的操作&#xff0c;因此他们使用反调试技术尽可能地延长恶意代码的分析时间。为了阻止调试器的分析&#xff0c;当恶意代码意识到自…

stl文件的解析和在线3d打印

什么是stl文件 STL(Stereolithography)文件&#xff0c;由3D Systems于1987年创建&#xff0c;并且已被广泛用作全行业3D打印机模型的标准文件。它有一些别的首字母缩写词如“标准三角语言(Standard Triangle Language)”&#xff0c;“标准曲面细分语言(Standard Tessellatio…

\r \r\n \t的区别,是什么意思

分享一下我老师大神的人工智能教程吧。零基础&#xff0c;通俗易懂&#xff01;风趣幽默&#xff01;http://www.captainbed.net/ 也欢迎大家转载本篇文章。分享知识&#xff0c;造福人民&#xff0c;实现我们中华民族伟大复兴&#xff01; \n 软回车&#xff1a; 在Windo…

著名的 P=NP 问题到底是什么?

△点击上方“Python猫”关注 &#xff0c;回复“1”领取电子书 大家好&#xff0c;我是猫哥。我最近在追一部热播的电视剧《天才基本法》&#xff0c;它反复提到了“PNP”问题。这可是一个天大的难题&#xff0c;在 2000 年克雷数学研究所公布的千禧年七大数学难题中&#xff0…

PCIe 是什么 ? -- 基本知识

1. 概述 1&#xff09;PCIe(Peripheral Component Interconnect Express)是继ISA和PCI总线之后的第三代I/O总线。一般翻译为周边设备高速连接标准。 2&#xff09;PCIe协议是一种端对端的互连协议&#xff0c;提供了高速传输带宽的解决方案。目前PCIe已经发展到第四代PCIe4.0, …

反欺诈概念库-信用卡反欺诈管理

原文&#xff1a;http://www.cnki.com.cn/Article/CJFDTotal-XYKZ200508004.htm 2005年6月&#xff0c;美国爆出4000万张信用卡资料外泄的特大新闻。消息传来&#xff0c;舆论哗然。尽管我国只有数千个信用卡账户数据资料受波及&#xff0c;但一石激起千层浪&#xff0c;国内金…

打印DPI如何与计算机DPI一致,打印效果失真 你了解DPI与照片关系么?

今天我们来聊一下关于分辨率的问题,分辨率这个词看似遥远,但是却和你的照片息息相关,文章开始之前我先向大家抛出一个问题,“我们要打印A4尺寸的照片,照片的像素至少要多大?”如果您能轻松的回答上来这个问题,那么这篇文章对于您的意义就不大了。 其实关于打印照片尺寸的…

常说的监听某个端口,是什么意思?怎么理解?

▲ 点击上方“分布式实验室”关注公众号 回复“1”抽取纸质技术书 今天这篇文章&#xff0c;想用一个故事和你讲讲端口监听是怎么回事。耐心往下看。 在学生会大楼的角落里&#xff0c;有一家咖啡店&#xff0c;在咖啡店的角落里有两个学生。利兹敲打着她哥哥在她搬到大学时给她…

RWKV – transformer 与 RNN 的强强联合

在 NLP (Natural Language Processing, 自然语言处理) 领域&#xff0c;ChatGPT 和其他的聊天机器人应用引起了极大的关注。每个社区为构建自己的应用&#xff0c;也都在持续地寻求强大、可靠的开源模型。自 Vaswani 等人于 2017 年首次提出 Attention Is All You Need 之后&am…

chatgpt赋能python:Python中的或运算:学习这个重要概念

Python中的或运算&#xff1a;学习这个重要概念 或运算是Python编程语言中一个重要的概念。了解如何使用或运算可以帮助程序员编写更有效和有意义的代码。在此文章中&#xff0c;我们将介绍Python中或运算的基础知识以及如何使用它来编写各种类型的代码。 什么是或运算&#…

智慧工厂主题 Meetup 线下报名+福利开启!IoTDB X EMQ 构建数据平台赋能智能制造...

随着全球制造业的竞争日益激烈&#xff0c;智慧工厂成为当今制造业的重要趋势之一。智慧工厂采用了先进的物联网、大数据等科技手段&#xff0c;以期通过智能化、数字化管理和生产&#xff0c;实现高度自动化和高效生产。因此&#xff0c;如何通过计算分析挖掘生产数据价值&…

《计算机组成原理》唐朔飞 第8章 CPU的结构和功能 - 学习笔记

写在前面的话&#xff1a;此系列文章为笔者学习计算机组成原理时的个人笔记&#xff0c;分享出来与大家学习交流。使用教材为唐朔飞第3版&#xff0c;笔记目录大体与教材相同。 网课 计算机组成原理&#xff08;哈工大刘宏伟&#xff09;135讲&#xff08;全&#xff09;高清_…

常用方法——7.JS 给数组排序 es6

let arrObj[{"name": "银行转账","value": 2}, {"name": "支付宝支付","value": 1}, {"name": "微信支付","value": 0} ] arrObj.sort((a,b)>{ return a.value-b.value})//升序…

JS数组对象排序(es6)

效果&#xff1a;升序&#xff1a; 降序&#xff1a; 升序是&#xff1a;a.value-b.value 降序是&#xff1a;b.value-a.value 代码&#xff1a; let arrObj[{"name": "银行转账","value": 2},{"name": "支付宝支付","…

微信小程序根据日期和时间进行排序

一、前言 最近接手了一个小程序的项目&#xff0c;有这样一个需求要对列表进行日期和时间的排序&#xff0c;于是小试牛刀&#xff0c;操作了一番&#xff0c;终于搞出来&#xff0c;在这里给大家总结分享一下经验&#xff0c;希望对大家有一定的帮助。 二、需求分析&#xf…

代码覆盖率

在做单元测试时&#xff0c;代码覆盖率常常被拿来作为衡量测试好坏的指标&#xff0c;甚至&#xff0c;用代码覆盖率来考核测试任务完成情况&#xff0c;比如&#xff0c;代码覆盖率必须达到80&#xff05;或 90&#xff05;。于是乎&#xff0c;测试人员费尽心思设计案例覆盖代…