《操作系统真象还原》学习笔记:第六章 完善内核

news2025/1/12 3:48:09

一.函数调用约定简介

调用约定,calling conventions,从字面上理解,它是调用函数时的一套约定,是被调用代码的接口,它体现在:

  • 参数的传递方式,是放在寄存器中?栈中?还是两者混合?
  • 参数的传递顺序,是从左到右传递?还是从右到左?
  • 是调用者保存寄存器环境,还是被调用者保存?保存哪些寄存器呢?

在这里插入图片描述

二、 实现自己的打印函数

1. 显卡的端口控制

在这里插入图片描述

  • 前四组寄存器属于分组,寄存器分组的原因是显卡上的寄存器太多,而系统端口有限。所以,对这类寄存器操作方法是先在Address Register中指定寄存器的索引值,用来确定所操作的寄存器是哪个,然后在Data Register 寄存器中对所索引的寄存器进行读写操作。

  • 上面CRT Controller Registers 寄存器组中的 Address Register 和 Data Register 的端口地址有些特殊,它的端口地址并不固定,具体值取决于 Miscellaneous Output Register 寄存器中的 Input/Output Address Select 字段
    在这里插入图片描述

  • I/OAS(Input/Output Address Select),此位用来选择 CRT controller 寄存器组的地址,这里是指 Address Register 和 Data Register 的地址。

  • 当此位为 0 时:并且为了兼容 monochrome 适配器(显卡),Input Status #1 Register 寄存器的端口地址被设置为 0x3BA

  • 当此位为 1 时:并且为了兼容 color/graphics 适配器(显卡),Input Status #1Register 寄存器的端口地址被设置为 0x3DA

Feature Control register 寄存器的写端口也是 3xAh 的形式,该端口地址取值以同样的方式受 I/OAS 位的影响。

  • 如果 I/OAS 位为 0,写端口地址为 3BAh。
  • 如果 I/OAS 位为 1,写端口地址为 3DAh。

默认情况下,Miscellaneous Output Register 寄存器的值为 0x67,其他字段不管,咱们只关注这最重要的 I/OAS 位,其值为 1。也就是说:

  • CRT controller 寄存器组的 Address Register 的端口地址为 0x3D4,Data Register 的端口地址 0x3D5
  • Input Status #1Register 寄存器的端口地址被设置为 0x3DA。
  • Feature Control register 寄存器的写端口是 0x3DA。

在这里插入图片描述



CRT Controller Data Registers
索引寄存器名称功能
00hHorizontal Total Register水平总寄存器,定义了一个完整水平扫描线所需要的总像素数
01hEnd Horizontal Display Register水平显示结束寄存器,定义了显示区的宽度,即一个水平扫描线上可见的像素数
02hStart Horizontal Blanking Register水平消隐开始寄存器,定义了水平消隐的起始像素位置
03hEnd Horizontal Blanking Register水平消隐结束寄存器,定义了水平消隐的结束像素位置
04hStart Horizontal Retrace Register水平回扫开始寄存器,定义了水平回扫的起始像素位置
05hEnd Horizontal Retrace Register水平回扫结束寄存器,定义了水平回扫的结束像素位置
06hVertical Total Register垂直总寄存器,定义了一个完整垂直扫描线所需的总行数
07hOverflow Register溢出寄存器,定义了垂直回扫时,超出垂直总行数范围的行数
08hPreset Row Scan Register预设行扫描寄存器,定义了显示器的初始扫描行
09hMaximum Scan Line Register最大扫描行数寄存器,定义了每个水平扫描线上的最大行数
0AhCursor Start Register光标开始寄存器,定义了光标显示的起始行
0BhCursor End Register光标结束寄存器,定义了光标显示的结束行
0ChStart Address High Register起始地址高位寄存器,定义了显示区的起始地址的高8位
0DhStart Address Low Register起始地址低位寄存器,定义了显示区的起始地址的低8位
0EhCursor Location High Register光标位置高位寄存器,定义了光标位置的高8位
0FhCursor Location Low Register光标位置低位寄存器,定义了光标位置的低8位
10hVertical Retrace Start Register垂直回扫开始寄存器,定义了垂直回扫的起始行数
11hVertical Retrace End Register垂直回扫结束寄存器,定义了垂直回扫的结束行数
12hVertical Display End Register垂直显示结束寄存器,定义了垂直显示的结束行数
13hOffset Register偏移寄存器,定义了每个字符在屏幕上占用的字节数
14hUnderline Location Register下划线位置寄存器,定义了下划线的位置
15hStart Vertical Blanking Register垂直消隐开始寄存器
16hEnd Vertical Blanking Register垂直消隐结束寄存器,定义了垂直消隐的结束行数
17hCRTC Mode Control RegisterCRTC模式控制寄存器,定义了CRTC控制器的模式和操作方式
18hLine Compare Register行比较寄存器,定义了当CRT控制器扫描到一个特定行时发出的比较信号,可用于产生中断或其他操作



使用c语言实现自己的打印函数

先在Address Register中指定寄存器的索引值,用来确定所操作的寄存器是哪个,然后在Data Register 寄存器中对所索引的寄存器进行读写操作。
举例:

;;;;;;;;;  获取当前光标位置 ;;;;;;;;;
   ;先获得高8位
   mov dx, 0x03d4  ;索引寄存器
   mov al, 0x0e	   ;用于提供光标位置的高8位
   out dx, al
   mov dx, 0x03d5  ;通过读写数据端口0x3d5来获得或设置光标位置 
   in al, dx	   ;得到了光标位置的高8位
   mov ah, al

   ;再获取低8位
   mov dx, 0x03d4
   mov al, 0x0f
   out dx, al
   mov dx, 0x03d5 
   in al, dx


屏幕每行 80 个字符,共 25 行,咱们的滚屏实现比较简单,现在说一下用此方案实现滚屏的步骤:
(1)将第 1~24 行的内容整块搬到第 0~23 行,也就是把第 0 行的数据覆盖。
(2)再将第 24 行,也就是最后一行的字符用空格覆盖,这样它看上去是一个新的空行。
(3)把光标移到第 24 行也就是最后一行行首。
经过这三步,屏幕就像向上滚动了一行

print.c

#include "print.h"
#include "io.h"

//设置光标位置
void set_cursor(unsigned short cursor_pos);

//往显存写入字符。参数:显存位置,写入的字符
void write_char(unsigned short video_memory_pos,char ch);
//向下滚动一行
void roll_screen();



void put_str(char* str)
{
	while(*str != '\0')
	{
		put_char(*str);
		str++;	
	}
}



void put_char(char ch)
{
	
//获取当前光标位置
	unsigned short cursor_pos=0;
//高8位
	unsigned char high_8=0;
	outb(0x03d4,0x0e);
	high_8 = inb(0x03d5);
	
//低8位
	unsigned char low_8=0;
	outb(0x03d4,0x0f);
	low_8 = inb(0x03d5);
	cursor_pos =(high_8<<8) + low_8;
	
	
	//如果是换行键或者回车键
	if(ch==0xd || ch==0xa )  
	{
		cursor_pos=cursor_pos-(cursor_pos % 80)+80;  //光标值减去除80的余数便是取整
		if(cursor_pos<2000)
		{
			set_cursor(cursor_pos);
		}
		else
		{
			roll_screen();   //向下滚动一行
			
		}
	}
	//如果是退格键
	else if(ch==0x8)		
	{
		//当为退格时,本质上只要将光标向前一个显存位置即可,后面再输入的字符自然会覆盖此字符
		//但有可能在键入退格后不再键入新的字符,这时在光标已经向前移动到待删除的字符位置,但字符还在原处
		//;这就显得好怪异,所以此处添加了空格或空字符0
		cursor_pos--;
		write_char(cursor_pos*2,0x20);
		set_cursor(cursor_pos);
			
	}
	//普通字符
	else					
	{
		unsigned int video_memory_pos=cursor_pos*2;
		write_char(video_memory_pos,ch);
		cursor_pos++; 			//下一个光标值
		if(cursor_pos<2000)		//若光标值小于2000,表示未写到显存的最后,则去设置新的光标值
		{
			set_cursor(cursor_pos);
		}	
		else
		{
			roll_screen();  //向下滚动一行
		}		
	}
}



void put_int(unsigned int num)
{
	unsigned char put_int_buffer[9] = {0};
	char hex_digits[] = "0123456789ABCDEF";
	
	//32位数字中,16进制数字的位数是8个
	//遍历每一位16进制数字
	int i;
	for(i = 0; i<8; i++)
	{
		 hex_str[i] = hex_digits[ (num>>i*4) & 0x0000000F ];
	}
	put_str(put_int_buffer);

	
}

 







void set_cursor(unsigned short cursor_pos)
{
	//先设置高8位
	outb(0x03d4,0x0e);
	outb(0x03d5,(cursor_pos>>8));
	
	//先设置低8位
	outb(0x03d4,0x0f);
	outb(0x03d5,(cursor_pos&0b0000000011111111));
	
	
}

//参数:显存位置,写入的字符
void write_char(unsigned short video_memory_pos,char ch)
{
	asm volatile ("movb %b1, %%gs:(%%bx);\
		inc %%bx; \
		movb $0x7,%%gs:(%%bx); \
		"::"b"(video_memory_pos),"a"(ch));
	
}


void roll_screen()
{
	unsigned short cursor_pos;
	
	//一共有2000-80=1920个字符要搬运,共1920*2=3840字节.一次搬4字节,共3840/4=960次 
	int *src  = (int*)0xb80a0;  //第1行行首
	int *dest = (int*)0xb8000;	//第0行行首
	int i;
	for(i = 0; i<960;i++)
	{

		*dest = *src;
		src++;
		dest++;
	}
	
	for(i = 3840; i<4000;i+=2)
	{
	
		write_char(i,0x20);			//最后一行填充空格
	}
	
	
	cursor_pos=1920;		//将光标值重置为1920,最后一行的首字符.
	
	set_cursor(cursor_pos);	
	

	
	
}



print.h
#ifndef __LIB_KERNEL_PRINT_H
#define __LIB_KERNEL_PRINT_H
void put_char(unsigned char ch);
void put_str(char* str);
void put_int(unsigned int num);	 // 以16进制打印

#endif


提前使用了下一节的内联汇编知识
io.h

/**************	 机器模式   ***************
	 b -- 输出寄存器QImode名称,即寄存器中的最低8位:[a-d]l。
	 w -- 输出寄存器HImode名称,即寄存器中2个字节的部分,如[a-d]x。

	 HImode
	     “Half-Integer”模式,表示一个两字节的整数。 
	 QImode
	     “Quarter-Integer”模式,表示一个一字节的整数。 
*******************************************/ 

#ifndef __LIB_IO_H
#define __LIB_IO_H

/* 向端口port写入一个字节*/
static inline void outb(unsigned short port,unsigned char data) {
/*********************************************************
 a表示用寄存器al或ax或eax,对端口指定N表示0~255, d表示用dx存储端口号, 
 %b0表示对应al,%w1表示对应dx */ 
   asm volatile ( "outb %b0, %w1" : : "a" (data), "d" (port));    
/******************************************************/
}

/* 将addr处起始的word_cnt个字写入端口port */
static inline void outsw(unsigned short port, void* addr,unsigned int word_cnt) {
/*********************************************************
   +表示此限制即做输入又做输出.
   outsw是把ds:esi处的16位的内容写入port端口, 我们在设置段描述符时, 
   已经将ds,es,ss段的选择子都设置为相同的值了,此时不用担心数据错乱。*/
   asm volatile ("cld; rep outsw" : "+S" (addr), "+c" (word_cnt) : "d" (port));
/******************************************************/
}

/* 将从端口port读入的一个字节返回 */
static inline char inb(unsigned short port) {
   char data;
   asm volatile ("inb %w1, %b0" : "=a" (data) : "d" (port));
   return data;
}

/* 将从端口port读入的word_cnt个字写入addr */
static inline void insw(unsigned short port, void* addr, unsigned int word_cnt) {
/******************************************************
   insw是将从端口port处读入的16位内容写入es:edi指向的内存,
   我们在设置段描述符时, 已经将ds,es,ss段的选择子都设置为相同的值了,
   此时不用担心数据错乱。*/
   asm volatile ("cld; rep insw" : "+D" (addr), "+c" (word_cnt) : "d" (port) : "memory");
/******************************************************/
}

#endif




main.c
#include "print.h" 
#include "io.h"

void main(void) 
{ 

	put_str("hello world");
	put_char('\n');
	put_int(0x12345678);
	put_char('\n');
	put_int(0x123);
	while(1); 
}

编译链接

nasm -I /home/abc/Desktop/bochs/code/boot/ /home/abc/Desktop/bochs/code/boot/mbr.s -o /home/abc/Desktop/bochs/code/boot/mbr.bin
dd if=/home/abc/Desktop/bochs/code/boot/mbr.bin  of=/home/abc/Desktop/bochs/hd60m.img bs=512 count=1 conv=notrunc

nasm -I /home/abc/Desktop/bochs/code/boot/ /home/abc/Desktop/bochs/code/boot/loader.s -o /home/abc/Desktop/bochs/code/boot/loader.bin 
dd if=/home/abc/Desktop/bochs/code/boot/loader.bin  of=/home/abc/Desktop/bochs/hd60m.img bs=512 count=4 seek=2 conv=notrunc

nasm -f elf32  ./code/lib/print.s -o  ./code/lib/print.o  

gcc -I ./code/lib/ -m32 -c  ./code/kernel/main.c -o ./code/kernel/main.o

gcc  -I ./code/lib/ -m32 -c   ./code/kernel/test.c -o ./code/kernel/test.o

ld -m elf_i386 ./code/kernel/main.o ./code/lib/print.o  ./code/kernel/test.o -Ttext 0xc0001500 -e main -o ./code/kernel/kernel.bin 

dd if=./code/kernel/kernel.bin of=/home/abc/Desktop/bochs/hd60m.img bs=512 count=200 seek=9 conv=notrunc

bin/bochs -f bochs.disk


效果
在这里插入图片描述







什么是内联汇编

  • 内联汇编称为inline assembly,GCC支持在C代码中直接嵌入汇编代码,所以称为GCC inline assembly。C语言不支持寄存器操作,汇编语言可以,所以自然就想到了再C语言中嵌入内联汇编提升“战斗力”的方式,通过内联汇编,C 程序员可以实现 C 语言无法表达的功能,这样使开发能力大为提升。
  • 内联汇编按格式分为两大类,一类是最简单的基本内联汇编,另一类是复杂一些的扩展内联汇编,内联汇编中所用的汇编语言,其语法是 AT&T,并不是咱们熟悉的 Intel 汇编语法,GCC 只支持它,所以咱们还得了解下 AT&T。
    在这里插入图片描述

基本内联汇编

基本内联汇编是最简单的内联形式,其格式为:
asm [volatile] ("assembly code")

  • asm:用于声明内联汇编表达式,这是内联汇编固定部分,不可少。asm和__asm__ 是一样的,是由gcc定义的宏:#define __asm__ asm

  • volatile:是可选项,它告诉gcc:“不要修改我写的汇编代码,请原样保留”。volatile 和__volatile__是一样的,是由gcc定义的宏:#define __volatile__ volatile

  • “assembly code” 是咱们所写的汇编代码,它必须位于圆括号中,而且必须用双引号括起来。规则如下:

    1. 指令必须用双引号引起来,无论双引号中是一条指令或多条指令
    2. 一对双引号不能跨行,如果跨行需要在结尾用反斜杠\转义。
    3. 指令之间用分号; 换行符\n或换行符加制表符\n \t分隔。
    4. 当指令在多个双引号中时,除最后一个双引号外,其余双引号中的代码最后一定要有分隔符,如:
      asm(“movl $9,%eax;””pushl %eax”) 正确
      asm(“movl $9,%eax””pushl %eax”)  错误
      
    5. 寄存器前面加前缀%,立即数前面加前缀$,操作数由左到右的顺序。

扩展内联汇编

格式:
asm [volatile] (“assembly code”:output : input : clobber/modify)

  • output:用来指定汇编代码的数据如何输出给C代码使用。内嵌的汇编指令运行结束后,如果想将运行结果存储到C变量中,就用此项指定输出的位置。output中每个操作数的格式为:“操作数修饰符约束名”(C 变量名),操作数修饰符通常为等号=。多个操作数之间用逗号,分隔。

  • input:用来指定C中数据如何输入给汇编使用。要向让汇编使用C中的变量作为参数,就要在此指定。input中每个操作数的格式为:“[操作数修饰符] 约束名”(C 变量名),操作数修饰符为可选项。多个操作数之间用逗号,分隔。

  • clobber/modify:汇编代码执行后会破坏一些内存和寄存器资源,通过此项通知编译器,可能造成寄存器或内存数据破坏,这样gcc就知道哪些寄存器或内存需要提前保护前来。

    1. 格式:用双引号把寄存器名称引起来,多个寄存器之间用逗号,分隔,寄存器不用再加两个%%,只要写名称即可,如:asm("movl %%eax, %0;movl %%eax,%%ebx":"=m" (ret_value)::"bx")
    2. 如果我们的内联汇编代码修改了标志寄存器 eflags 中的标志位,同样需要在 clobber/modify 中用”cc”声明
    3. 如果我们修改了内存,我们需要在 clobber/modify 中”memory”声明。
    4. 如果我们在 output 中使用了内存约束,gcc 自然会得到哪块内存被修改。但如果被修改的内容并未在output 中,我们就需要用”memory”告诉 gcc 啦。

  • 约束的作用:约束的作用是让C代码的操作数变成汇编代码能使用的操作数,所有的约束形式其实都是给汇编用的。约束是C语言中的操作数与汇编语言中的操作数之间的映射,他告诉gcc,同一个操作数在两种环境下如何变换身份,如何对接沟通。编译过程中C代码是要先变成汇编代码的,内联汇编中的约束就相当于gcc让咱们指定C中数据的编译形式。



以下是各种约束的解释:

  • 寄存器约束:寄存器约束就是要求gcc使用哪个寄存器,将input或output变量约束在某个寄存器中,常见的寄存器约束有:
    a:表示寄存器 eax/ax/al
    b:表示寄存器 ebx/bx/bl
    c:表示寄存器 ecx/cx/cl
    d:表示寄存器 edx/dx/dl
    D:表示寄存器 edi/di
    S:表示寄存器 esi/si
    q:表示任意这 4 个通用寄存器之一:eax/ebx/ecx/edx
    r:表示任意这 6 个通用寄存器之一:eax/ebx/ecx/edx/esi/edi
    g:表示可以存放到任意地点(寄存器和内存)。相当于除了同 q 一样外,还可以让 gcc 安排在内存中
    A:把 eax 和 edx 组合成 64 位整数
    f:表示浮点寄存器
    t:表示第 1 个浮点寄存器
    u:表示第 2 个浮点寄存器
#include<stdio.h> 
void main() 
{ 
	int in_a = 1, in_b = 2, out_sum; 
	asm("addl %%ebx, %%eax":"=a"(out_sum):"a"(in_a),"b"(in_b)); 
	printf("sum is %d\n",out_sum); 
}
  • 内存约束:内存约束是要求gcc直接将位于input和output中的C变量内存地址作为内联汇编代码的操作数,不需要寄存器做中转,直接进行内存读写,也就是汇编代码的操作数是C变量的指针。
    m:表示操作数可以使用任意一种内存形式
    o:操作数为内存变量,但访问它是通过偏移量的形式访问,即包括offset_address的格式。

  • 立即数约束:立即数即常数,此约束要求gcc在传值的时候不通过内存和寄存器,直接作为立即数传给汇编代码。由于立即数不是变量,只能作为右值,所以只能放在input中。
    i:表示操作数为整数立即数
    F:表示操作数为浮点数立即数
    I:表示操作数为 0~31 之间的立即数
    J:表示操作数为 0~63 之间的立即数
    N:表示操作数为 0~255 之间的立即数
    O:表示操作数为 0~32 之间的立即数
    X:表示操作数为任何类型立即数

  • 通用约束:
    0~9:此约束只用在 input 部分,但表示可与 output 和 input 中第 n 个操作数用相同的寄存器或内存。

序号占位符

  • 占位符:为了方便对操作数的引用,扩展内联汇编提供了占位符,他的作用是代表约束指定的操作数(寄存器、内存、立即数),我们更多的是在内联汇编中使用占位符来引用操作数。占位符分为序号占位符和名称占位符:

    1. 序号占位符:序号占位符是对在output和input中的操作数,按照它们从左到右的次序从0开始编号,一直到9,也就是说最多支持10个序号占位符,引用它的格式是%0~9。占位符所表示的操作数默认情况下为 32 位数据,在%和序号之间插入字符’h’来表示操作数为ah(第 8~15 位),或者插入字符’b’来表示操作数为 al(第 0~7 位)。
      h –输出寄存器高位部分中的那一字节对应的寄存器名称,如 ah、bh、ch、dh。
      b –输出寄存器中低部分 1 字节对应的名称,如 al、bl、cl、dl。
      w –输出寄存器中大小为 2 个字节对应的部分,如 ax、bx、cx、dx。
      k –输出寄存器的四字节部分,如 eax、ebx、ecx、edx。
      如:
      asm("addl %%ebx, %%eax":"=a"(out_sum):"a"(in_a),"b"(in_b));
      等价于
      asm("addl %2, %1":"=a"(out_sum):"a"(in_a),"b"(in_b));
      其中:
      “=a”(out_sum)序号为 0,%0 对应的是 eax。
      “a”(in_a)序号为 1,%1 对应的是 eax。
      “b”(in_b)序号为 2,%2 对应的是 ebx。

    2. 名称占位符:名称占位符与序号占位符不同,序号占位符靠本身出现在output和input中的位置就能被编译器识别出来。而名称占位符需要在output和input中把操作数显式地起个名字,它用这样的格式来标识操作数:[名称]”约束名”(C 变量)。这样,该约束对应的汇编操作数便有了名字,在 assembly code 中引用操作数时,采用%[名称]的形式就可以了。

  • 由于扩展内联汇编中的占位符要有前缀%,为了区别占位符和寄存器,只好在寄存器前用两个%做前缀啦,这就是本节前面解释在扩展内联汇编中寄存器前面要有两个%做前缀的原因。

  • 在约束中还有操作数类型修饰符,用来修饰所约束的操作数:内存、寄存器,分别在ouput 和 input中有以下几种。
    在output中:
    =:表示操作数是只写,相当于为 output 括号中的 C 变量赋值,如=a(c_var),此修饰符相当于 c_var=eax。
    +:表示操作数是可读写的,告诉 gcc 所约束的寄存器或内存先被读入,再被写入。
    &:表示此 output 中的操作数要独占所约束(分配)的寄存器,只供 output 使用,任何 input 中所分配的寄存器不能与此相同。注意,当表达式中有多个修饰符时,&要与约束名挨着,不能分隔。
    在input中:
    %:input 中的输入可以和下一个 input 操作数互换

  • 一般情况下,input 中的 C 变量是只读的,output 中的 C 变量是只写的。

  • 修饰符’='只用在 output 中,表示 C 变量是只写的,功能相当于 output 中的 C 变量=约束的汇编操作数,如”=a”(c_var),相当于 c_var=eax 的值。

  • 修饰符’+'也只用在 output 中,但它具备读、写的属性,也就是它既可作为输入,同时也可以作为输出,所以省去了在 input 中声明约束。

#include <stdio.h> 
void main() 
{ 
	int in_a = 1, in_b = 2; 
	asm("addl %%ebx, %%eax;":"+a"(in_a):"b"(in_b)); 
	printf("in_a is %d\n", in_a); 
}

扩展内联汇编之机器模式简介

  • 机器模式用来在机器层面上指定数据的大小及格式。
  • 由于各种约束均不能确切地表达具体的操作数对象,所以引用了机器模式,用来从更细的粒度上描述数据对象的大小及其指定部分

寄存器按是否可单独使用,可分成几个部分,拿 eax 举例:

  • 低部分的一字节:al
  • 高部分的一字节:ah
  • 两字节部分:ax
  • 四字节部分:eax

h :输出寄存器高位部分中的那一字节对应的寄存器名称,如 ah、bh、ch、dh。
b :输出寄存器中低部分 1 字节对应的名称,如 al、bl、cl、dl。
w :输出寄存器中大小为 2 个字节对应的部分,如 ax、bx、cx、dx。
k :输出寄存器的四字节部分,如 eax、ebx、ecx、edx。




举例:

1 #include<stdio.h> 
2 void main() 
3 { 
4 	int in_a = 0x1234, in_b = 0; 
5 	asm("movw %1, %0":"=m"(in_b):"a"(in_a)); 
6 	printf("in_b now is 0x%x\n", in_b);
}

这段代码目的是把in_a的低16位复制到in_b中,第五行中,变量in_a的约束是a,这表示由gcc把in_a的值分配给寄存器al、ax或eax,这很模糊,到底gcc把in_a的值分配给谁了呢?之后的movw指令也很模糊,我们只能这样理解:movw指令将al、ax或eax中的2个字节复制到in_b所在的内存中

修改后:

1 #include<stdio.h> 
2 void main() 
3 { 
4 	int in_a = 0x1234, in_b = 0; 
5 	asm("movw %w1, %0":"=m"(in_b):"a"(in_a));
6 	printf("in_b now is 0x%x\n", in_b);
}

添加w前缀就可以确定源操作数为ax

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

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

相关文章

JavaSE知识点整理---集合篇

文章目录 1、数组与集合的区别?2、List、Set、Map的区别&#xff1f;3、ArrayList、LinkedList、Vector的区别&#xff1f;4、Java集合的快速失败机制【fail-fast】5、List接口常用方法6、List的三种遍历方式 1、数组与集合的区别? 在Java中&#xff0c;数组和集合都是容器&a…

如何使用 reqwest 包

GET 请求 向连接发起一个 GET 请求&#xff1a;https://hacker-news.firebaseio.com/v0/topstories.json&#xff0c;并解析返回的内容。 尝试发起请求 因为是 GET 请求&#xff0c;可以先在浏览器中进行查看&#xff0c;浏览器可以正常显示一个 id 列表&#xff0c;如下所示…

24届华东理工大学近5年自动化考研院校分析

今天给大家带来的是华东理工大学控制考研分析 满满干货&#xff5e;还不快快点赞收藏 一、华东理工大学 学校简介 华东理工大学原名华东化工学院&#xff0c;1956年被定为全国首批招收研究生的学校之一&#xff0c;1960年起被中共中央确定为教育部直属的全国重点大学&#…

【Winform学习笔记(七)】Winform无边框窗体拖动功能

Winform无边框窗体拖动功能 前言正文1、设置无边框模式2、无边框窗体拖动方法1、通过Panel控件实现窗体移动2、通过窗体事件实现窗体移动3、调用系统API实现窗体移动4、重写WndProc()实现窗体移动 前言 在本文中主要介绍 如何将窗体设置成无边框模式、以及实现无边框窗体拖动功…

Java中的enum枚举类型

知识介绍 在Java中&#xff0c;enum&#xff08;枚举&#xff09;是一种特殊的数据类型&#xff0c;用于定义一组命名的常量。枚举类型允许你在代码中创建一个固定的、预定义的值集合&#xff0c;这些值可以在整个程序中使用。使用枚举可以使代码更加清晰、可读&#xff0c;并…

Bootload U-Boot分析

Bootloader是在操作系统运行之前执行的一段小程序。通过这段小程序可以初始化硬件设备、建立内存空间的映射表&#xff0c;从而建立适当的系统软硬件环境&#xff0c;为最终调用操作系统内核做好准备。 对于嵌入式系统&#xff0c;Bootloader是基于特定硬件平台来实现的。因此…

【Hystrix技术指南】(5)Command创建和执行实现

创建流程 构建HystrixCommand或者HystrixObservableCommand对象 *使用Hystrix的第一步是创建一个HystrixCommand或者HystrixObservableCommand对象来表示你需要发给依赖服务的请求。 若只期望依赖服务每次返回单一的回应&#xff0c;按如下方式构造一个HystrixCommand即可&a…

FreeRTOS(vTaskList与vTaskGetRunTimeStats)

目录 1、Cube配置 ①配置SYS ②配置TIM3 ③配置USART2 ④配置FreeRTOS ⑤配置中断优先级 2、代码添加改动 ①在main函数合适位置开启TIM3中断 ②修改HAL_TIM_PeriodElapsedCallback函数 ③完善两个相关函数 ④vTaskList与vTaskGetRunTimeStats的使用 vTaskList&#xff…

C# 有效的字母异位词

242 有效的字母异位词 给定两个字符串 和 &#xff0c;编写一个函数来判断 是否是 的字母异位词。stts 注意&#xff1a;若 和 中每个字符出现的次数都相同&#xff0c;则称 和 互为字母异位词。stst 示例 1: 输入: s “anagram”, t “nagaram” 输出: true 示例 2: 输…

探索国产嵌入式Python解决方案的方法(开源)

大家好&#xff0c;今天我们要介绍一款适用于单片机的嵌入式Python开源项目 -- PikaPython。 第一&#xff1a;嵌入式Python的发展趋势 在嵌入式领域软硬件的发展趋势中&#xff0c;硬件的成本日益降低&#xff0c;性能逐渐提升。这种趋势使得Python在芯片上的运行难度已经大大…

学python的心得体会1000字,学python的心得体会2000字

这篇文章主要介绍了学python的心得体会2000字&#xff0c;具有一定借鉴价值&#xff0c;需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获&#xff0c;下面让小编带着大家一起了解一下。 1. 初学者应该从简单的练习开始&#xff0c;先掌握基本的语法和概念&#xff0c;…

Three.js光源

目录 Three.js入门 Three.js光源 本文我们将研究three.js中的灯光类型和JavaScript中的光源&#xff0c;探索不同的光源设置。我们的目标是全面理解光源设置和类型&#xff0c;比如环境光、半球光、矩形光、方向光、点光源和聚光灯。我们将逐个介绍它们的属性参数和使用方法…

深度相机常见技术:深度相机的相位求解

1.1 深度相机概述 深度相机是近几年兴起的新技术&#xff0c;相比较传统的相机&#xff0c;深度相机在功能上添加了一个深度测量&#xff0c;从而更方便准确的感知周围的环境及变化。深度相机有很多的应用场景&#xff0c;在我们日常生活中有很多深度相机的影子。那它可以用来…

在线对对联

对对联的起源可以追溯到中国古代,它与中国文化有着密切的关系。 1. 最早的对对联出现在汉朝,当时称为“对句”。它起源于民间,后来逐渐成为文人雅士的精神寄托。 2. 唐代时,对对联的格式更加规范,并被称为“春联”。它成为春节张贴的主要内容,寓意吉祥。 3. 宋代以后,对对联…

小程序商品如何开启返佣

​越来越多的商家开始意识到小程序平台的潜力和价值。开启分销返佣机制是一种有效的方式&#xff0c;可以吸引更多的用户和合作伙伴&#xff0c;提高销售额和品牌知名度。下面将介绍商城小程序商品如何开启返佣。 1. 开启系统分销设置。在管理员后台->营销设置 点击分销功能…

MYSQL --Austindatabases 历年文章合集

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

高速过孔同进同出后续来了!影响大不大由你们自己说

高速先生成员---黄刚 话说Chris在上篇文章的结尾留下的悬念&#xff0c;其实在上周的答题里&#xff0c;也有不少粉丝猜到了接下来要验证的内容。我们知道&#xff0c;任何两个结构如果距离变近了&#xff0c;容性就会增加&#xff0c;无论是孔和孔&#xff0c;线和线&#xf…

JMeter命令行执行+生成HTML报告

1、为什么用命令行模式 使用GUI方式启动jmeter&#xff0c;运行线程较多的测试时&#xff0c;会造成内存和CPU的大量消耗&#xff0c;导致客户机卡死&#xff1b; 所以一般采用的方式是在GUI模式下调整测试脚本&#xff0c;再用命令行模式执行&#xff1b; 命令行方式支持在…

MySQL(1)

MySQL创建数据库和创建数据表 创建数据库 1. 连接 MySQL mysql -u root -p 2. 查看当前的数据库 show databases; 3. 创建数据库 create database 数据库名; 创建数据库 4. 创建数据库时设置字符编码 create database 数据库名 character set utf8; 5. 查看和显示…

redis入门2-命令

Redis的基本数据类型 redis的基本数据类型&#xff08;value&#xff09;: string,普通字符串 hash&#xff08;哈希&#xff09;,适合存储对象 list(列表),按照插入顺序排序&#xff0c;可以由重复的元素 set(无序集合)&#xff0c;没有重复的元素 sorted set(有序集合)&…