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

一.函数调用约定简介

调用约定,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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值