作業系統實驗四實驗報告
實驗四:核心執行緒管理
練習1:分配並初始化一個程序控制塊
首先來看幾個比較重要的資料結構,kern/process/proc.h
中定義的程序控制塊及kern/trap/trap.h
中定義的中斷幀
struct proc_struct {
enum proc_state state; // Process state
int pid; // Process ID
int runs; // the running times of Proces
uintptr_t kstack; // Process kernel stack
volatile bool need_resched; // bool value: need to be rescheduled to release CPU?
struct proc_struct *parent; // the parent process
struct mm_struct *mm; // Process's memory management field
struct context context; // Switch here to run process即程序上下文
struct trapframe *tf; // Trap frame for current interrupt即中斷上下文
uintptr_t cr3; // CR3 register: the base addr of Page Directroy Table(PDT)
uint32_t flags; // Process flag
char name[PROC_NAME_LEN + 1]; // Process name
list_entry_t list_link; // Process link list
list_entry_t hash_link; // Process hash list
}
struct trapframe {
/* below here is a struct of general registers and defined by software */
// 當中斷異常發生時,此處結構內的通用暫存器資訊由軟體負責壓棧儲存
struct pushregs tf_regs;
/* below here are segement registers and defined by software */
// 當中斷異常發生時,此處的段暫存器資訊由軟體負責壓棧儲存
uint16_t tf_gs;
uint16_t tf_padding0;
uint16_t tf_fs;
uint16_t tf_padding1;
uint16_t tf_es;
uint16_t tf_padding2;
uint16_t tf_ds;
uint16_t tf_padding3;
uint32_t tf_trapno;
/* below here defined by x86 hardware */
// 當中斷異常發生時,此處的資訊由硬體壓棧儲存
uint32_t tf_err;
uintptr_t tf_eip;
uint16_t tf_cs;
uint16_t tf_padding4;
uint32_t tf_eflags;
/* below here only when crossing rings, such as from user to kernel, defined by hardware */
// 僅發生特權級改變時,此處額外的資訊由硬體壓棧儲存
uintptr_t tf_esp;
uint16_t tf_ss;
uint16_t tf_padding5;
}
struct context {
uint32_t eip;
uint32_t esp;
uint32_t ebx;
uint32_t ecx;
uint32_t edx;
uint32_t esi;
uint32_t edi;
uint32_t ebp;
}
這裡可以看到struct context
和struct trapframe
中有很多暫存器是一樣的,前者用於程序上下文切換,後者用於中斷上下文切換。注意這兩者的含義是不一樣的,在本實驗中一個程序開始執行需要系統進行初始化,此時tf
被用來儲存中斷幀,而程序執行時是通過context
來完成切換的,詳細見練習3的說明。
結構中一些重要的成員變數說明如下
mm
記憶體管理資訊,包括記憶體對映列表、頁表指標等state
程序所處的狀態,有PROC_UNINIT
、PROC_SLEEPING
、PROC_RUNNABLE
、PROC_ZOMBIE
四種,定義在enum proc_state
中parent
父程序,在所有程序中只有核心建立的第一個核心執行緒idleproc
沒有父程序,核心根據父子關係建立樹形結構維護一些特殊的操作context
程序的上下文,儲存暫存器,用於程序切換tf
中斷幀,總是指向核心棧的某個位置,當程序發生中斷異常,從使用者跳到核心時,中斷幀記錄了程序在中斷前的狀態,由於允許中斷巢狀,因此ucore
在核心棧上維護了tf
鏈,保證tf
總是能夠指向當前的trapframe
kstack
每個執行緒都有核心棧,並且位於核心地址空間的不同位置,對於核心執行緒,該棧就是執行時程式使用的棧,對於普通程序,該棧就是發生特權級改變時需儲存被打斷硬體資訊的棧
另外為了管理系統中所有的程序控制塊,ucore
維護了一些重要的全域性變數
static struct proc *current
當前佔用處理機並處於執行狀態的程序控制塊指標static list_entry_t hash_list[HASH_LIST_SIZE]
所有程序控制塊的雜湊表,hash_link
將基於pid
連結入這個雜湊表
根據實驗手冊及程式碼的提示,練習1的程式碼如下
把proc進行初步初始化(即把proc_struct中的各個成員變數清零)。但有些成員變數設定了特殊的值
// alloc_proc - alloc a proc_struct and init all fields of proc_struct
static struct proc_struct *
alloc_proc(void) {
struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
if (proc != NULL) {
proc->state = PROC_UNINIT; // 設定程序為初始態
proc->pid = -1; // 設定程序pid的未初始化值
proc->runs = 0;
proc->kstack = 0;
proc->need_resched = 0;
proc->parent = NULL;
proc->mm = NULL;
memset(&(proc->context), 0, sizeof(struct context));
proc->tf = NULL;
proc->cr3 = boot_cr3; // 使用核心頁目錄表的基址
proc->flags = 0;
memset(proc->name, 0, PROC_NAME_LEN + 1);
}
return proc;
}
之後proc_init
會進一步初始化idleproc
核心執行緒
練習2:為新建立的核心執行緒分配資源
do_fork
是建立執行緒的主要函式,kernel_thread
通過呼叫do_fork
函式來完成核心執行緒的建立工作,練習2中要求完成的do_fork
函式主要完成了6項工作
- 分配並初始化程序控制塊(
alloc_proc
) - 分配並初始化核心棧(
setup_stack
) - 根據
clone_flag
標誌複製或共享記憶體管理結構(copy_mm
) - 設定程序在核心正常執行和排程所需要的中斷幀和上下文(
copy_thread
) - 把設定好的程序控制塊放入
hash_list
和proc_list
兩個全域性程序連結串列中 - 將程序狀態設定為“就緒”態
/* do_fork - parent process for a new child process
* @clone_flags: used to guide how to clone the child process
* @stack: the parent's user stack pointer. if stack==0, It means to fork a kernel thread.
* @tf: the trapframe info, which will be copied to child process's proc->tf
*/
int
do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
int ret = -E_NO_FREE_PROC;
struct proc_struct *proc;
if (nr_process >= MAX_PROCESS) {
goto fork_out;
}
ret = -E_NO_MEM;
//LAB4:EXERCISE2 YOUR CODE
/*
* Some Useful MACROs, Functions and DEFINEs, you can use them in below implementation.
* MACROs or Functions:
* alloc_proc: create a proc struct and init fields (lab4:exercise1)
* setup_kstack: alloc pages with size KSTACKPAGE as process kernel stack
* copy_mm: process "proc" duplicate OR share process "current"'s mm according clone_flags
* if clone_flags & CLONE_VM, then "share" ; else "duplicate"
* copy_thread: setup the trapframe on the process's kernel stack top and
* setup the kernel entry point and stack of process
* hash_proc: add proc into proc hash_list
* get_pid: alloc a unique pid for process
* wakeup_proc: set proc->state = PROC_RUNNABLE
* VARIABLES:
* proc_list: the process set's list
* nr_process: the number of process set
*/
// 1. call alloc_proc to allocate a proc_struct
// 2. call setup_kstack to allocate a kernel stack for child process
// 3. call copy_mm to dup OR share mm according clone_flag
// 4. call copy_thread to setup tf & context in proc_struct
// 5. insert proc_struct into hash_list && proc_list
// 6. call wakeup_proc to make the new child process RUNNABLE
// 7. set ret vaule using child proc's pid
if((proc = alloc_proc()) == NULL){
goto fork_out; // 分配失敗,直接返回
}
if(setup_kstack(proc) != 0){
goto bad_fork_cleanup_kstack; // 堆疊初始化失敗,釋放已佔用的空間並返回
}
if(copy_mm(clone_flags, proc) != 0){
goto bad_fork_cleanup_proc; // 複製或共享記憶體管理結構失敗,釋放已佔用的空間並返回
}
copy_thread(proc, stack, tf); // 複製中斷幀和上下文
proc->pid = get_pid(); // 分配pid
hash_proc(proc); // 將新程序加入雜湊表
list_add(&proc_list, &(proc->list_link));
nr_process ++;
wakeup_proc(proc); // 喚醒程序
ret = proc->pid; // 返回程序pid
fork_out:
return ret;
bad_fork_cleanup_kstack:
put_kstack(proc);
bad_fork_cleanup_proc:
kfree(proc);
goto fork_out;
}
參考答案中有更多的考慮,不同部分及說明如下
proc->parent = current; // 設定父程序為當前的程序
bool intr_flag; // 由於要操作全域性資料結構,而ucore允許中斷巢狀,為了避免出現執行緒安全問題,這裡需要利用到kern/sync/sync.h中的鎖,至於執行緒安全問題可以參考相關的博文
local_intr_save(intr_flag);
{
proc->pid = get_pid();
hash_proc(proc);
list_add(&proc_list, &(proc->list_link));
nr_process ++;
}
local_intr_restore(intr_flag);
練習3:閱讀程式碼,理解proc_run函式和它呼叫的函式如何完成程序切換
參考piazza上同學的單步調式
1、kern_init
呼叫了proc_init
函式,後者啟動了建立核心執行緒的步驟
idleproc = alloc_proc()
通過kmalloc
函式獲得了proc_struct
作為第0個程序的控制塊,並初始化proc_init
函式對idleproc
核心執行緒進行進一步初始化
idleproc->pid = 0; // 設定pid=0,即第0個核心執行緒
idleproc->state = PROC_RUNNABLE; // 設定狀態為可執行,等待處理機排程執行該程序
idleproc->kstack = (uintptr_t)bootstack; // 直接將ucore啟動時的核心棧作為改執行緒的核心棧(以後其他執行緒的核心棧需要分配獲得)
idleproc->need_resched = 1; // 設定為可以被排程,根據cpu_idle函式,只要idleproc在執行且可以被排程,就立刻執行schedule函式切換到其他有用程序執行
set_proc_name(idleproc, "idle"); // 執行緒命名為idle
nr_process ++; // 執行緒數+1
current = idleproc; // 設定當前執行緒為idleproc
呼叫pid = kernel_thread(init_main, "Hello world!!", 0)
建立一個核心執行緒init_main
2、kernel_thread
函式建立了核心執行緒的臨時中斷幀,並呼叫do_fork
函式來進行進一步產生新的核心執行緒
// kernel_thread - create a kernel thread using "fn" function
// NOTE: the contents of temp trapframe tf will be copied to
// proc->tf in do_fork-->copy_thread function
int
kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags) {
struct trapframe tf; // 臨時變數tf來儲存中斷幀,並傳遞給do_fork
memset(&tf, 0, sizeof(struct trapframe)); // tf清零初始化
tf.tf_cs = KERNEL_CS; // 設定程式碼段為核心程式碼段KERNEL_CS
tf.tf_ds = tf.tf_es = tf.tf_ss = KERNEL_DS; // 設定資料段為核心資料段KERNEL_DS
tf.tf_regs.reg_ebx = (uint32_t)fn;
tf.tf_regs.reg_edx = (uint32_t)arg;
tf.tf_eip = (uint32_t)kernel_thread_entry; // 設定入口為函式kernel_thread_entry(定義在kern/process/entry.S中),該函式主要為函式fn做準備
return do_fork(clone_flags | CLONE_VM, 0, &tf); // 呼叫do_fork進一步完成建立工作,第二個引數為0代表建立的是核心執行緒
}
kernel_thread_entry: # void kernel_thread(void)
pushl %edx # push arg 將fn的引數壓棧
call *%ebx # call fn 呼叫fn
pushl %eax # save the return value of fn(arg) 將fn的返回值壓棧
call do_exit # call do_exit to terminate current thread 退出執行緒
3、do_fork
完成的工作在練習2中已充分說明,這裡詳細說明最重要的copy_thread
函式
// copy_thread - setup the trapframe on the process's kernel stack top and
// - setup the kernel entry point and stack of process
static void
copy_thread(struct proc_struct *proc, uintptr_t esp, struct trapframe *tf) {
proc->tf = (struct trapframe *)(proc->kstack + KSTACKSIZE) - 1; // proc->kstack指向給程序分配的KSTACKPAGE大小的空間,加上大小之後就指向最高地址,然後強制型別轉換後-1即在記憶體頂部空出一塊trap frame大小的空間,用於複製傳進來的臨時幀
*(proc->tf) = *tf; // 複製臨時幀
proc->tf->tf_regs.reg_eax = 0;
proc->tf->tf_esp = esp;
proc->tf->tf_eflags |= FL_IF;
proc->context.eip = (uintptr_t)forkret; // 設定指令暫存器為forkret函式,則進入子程序後就會呼叫forkret
proc->context.esp = (uintptr_t)(proc->tf); // 設定棧頂為核心棧存放了tf後的最大地址,即子程序棧頂從tf下面開始
}
隨後到do_fork
完成所有工作,返回到kernel_thread
再返回到proc_init
再返回到kern_init
4、此時在kern_init
後續有cpu_idle
和schedule
進行程序排程
// cpu_idle - at the end of kern_init, the first kernel thread idleproc will do below works
void
cpu_idle(void) {
while (1) {
if (current->need_resched) { // 顯然此時current = idleproc且已被設定為需要被排程,故進入schedule函式排程
schedule();
}
}
}
5、schedule
將返回之前建立的程序,並呼叫proc_run
進行執行
// proc_run - make process "proc" running on cpu
// NOTE: before call switch_to, should load base addr of "proc"'s new PDT
void
proc_run(struct proc_struct *proc) {
if (proc != current) {
bool intr_flag; // 由於要切換程序,同樣要注意執行緒安全問題,加鎖
struct proc_struct *prev = current, *next = proc;
local_intr_save(intr_flag);
{
current = proc; // 設定要切換的程序為接下來的正在執行程序
load_esp0(next->kstack + KSTACKSIZE); // 修改TSS任務狀態棧,將TSS的ts_esp0指向下一個程序的堆疊空間,引數值參考3中的註釋
lcr3(next->cr3); // 修改頁表基址
switch_to(&(prev->context), &(next->context)); // 切換,函式定義在kern\process\switch.S中
}
local_intr_restore(intr_flag);
}
}
6、proc_run
完成一些準備工作後,呼叫switch_to
最終完成切換
高地址
--------------------
|arg1:next->context|
--------------------
|arg0:prev->context|
--------------------
|return address | --->esp
--------------------
switch_to: # switch_to(from, to)
# save from's registers
movl 4(%esp), %eax # eax points to from [esp+4]即prev->context,將指向prev->context的指標放入eax中
popl 0(%eax) # save eip !popl 按照context的順序儲存原程序的各暫存器
movl %esp, 4(%eax) # save esp::context of from
movl %ebx, 8(%eax) # save ebx::context of from
movl %ecx, 12(%eax) # save ecx::context of from
movl %edx, 16(%eax) # save edx::context of from
movl %esi, 20(%eax) # save esi::context of from
movl %edi, 24(%eax) # save edi::context of from
movl %ebp, 28(%eax) # save ebp::context of from
高地址
--------------------
|arg1:next->context|
--------------------
|arg0:prev->context| --->esp
--------------------
# restore to's registers
movl 4(%esp), %eax # not 8(%esp): popped return address already 由於前面已經popl,故這裡不是8而是[esp+4]即next->context放入eax中
# eax now points to to 按照context的順序獲取新程序的各暫存器
movl 28(%eax), %ebp # restore ebp::context of to
movl 24(%eax), %edi # restore edi::context of to
movl 20(%eax), %esi # restore esi::context of to
movl 16(%eax), %edx # restore edx::context of to
movl 12(%eax), %ecx # restore ecx::context of to
movl 8(%eax), %ebx # restore ebx::context of to
movl 4(%eax), %esp # restore esp::context of to
pushl 0(%eax) # push eip 前面將原程序的返回地址彈出,現在將next->context的eip壓入棧
ret
注意,新程序就是前面建立的init_main
,參考3可以知道當時proc->context.eip = (uintptr_t)forkret
,當switch_to
返回時,把棧頂的內容賦值給EIP暫存器,此時跳轉到了forkret
進行執行
7、forkret
呼叫forkrets
完成準備並最終進入init_main
// forkret -- the first kernel entry point of a new thread/process
// NOTE: the addr of forkret is setted in copy_thread function
// after switch_to, the current proc will execute here.
static void
forkret(void) {
forkrets(current->tf);
}
# return falls through to trapret...
.globl __trapret
__trapret:
# restore registers from stack
popal
# restore %ds, %es, %fs and %gs
popl %gs
popl %fs
popl %es
popl %ds
# get rid of the trap number and error code
addl $0x8, %esp
iret
.globl forkrets
forkrets:
# set stack to this new process's trapframe
movl 4(%esp), %esp #esp指向當前程序的中斷幀即esp指向current->tf
jmp __trapret
注意參考3可以知道proc->context.esp = (uintptr_t)(proc->tf)
,而在6中switch_to
最後壓入了proc->context.eip
,故在forkrets
中[esp+4]即指向context.esp
,這裡就是中斷幀proc->tf
,參考棧的內容、struct trapframe
及__trapret
就會理解跳轉情況
switch_to之後的棧在記憶體中的存放:
高地址
-------------
|context.ebp|
|...........|
-------------
|context.esp| --->[esp+4] = proc->tf
-------------
|context.eip| --->esp
struct trapframe {
struct pushregs tf_regs; // 通用暫存器,對應__trapret: popal
uint16_t tf_gs; // 對應popl %gs
uint16_t tf_padding0;
uint16_t tf_fs; // 對應popl %fs
uint16_t tf_padding1;
uint16_t tf_es; // 對應popl %es
uint16_t tf_padding2;
uint16_t tf_ds; // 對應popl %ds
uint16_t tf_padding3;
uint32_t tf_trapno; // [esp]此時的esp指向這裡
/* below here defined by x86 hardware */
uint32_t tf_err; // [esp+4]
uintptr_t tf_eip; // [esp+8]對應addl $0x8, %esp
uint16_t tf_cs;
...
};
當iret
返回時就會進入此時的esp所指向的proc->tf.tf_eip
,在2中這個值被初始化為kernel_thread_entry
.text
.globl kernel_thread_entry
kernel_thread_entry: # void kernel_thread(void)
pushl %edx # push arg
call *%ebx # call fn
pushl %eax # save the return value of fn(arg)
call do_exit # call do_exit to terminate current thread
由此進入init_main
,當返回時呼叫do_exit
完成所有過程
完成程式碼後使用make qemu
檢視結果如下,程式正確
(THU.CST) os is loading ...
...省略部分輸出
check_alloc_page() succeeded!
check_pgdir() succeeded!
check_boot_pgdir() succeeded!
...省略部分輸出
kmalloc_init() succeeded!
check_vma_struct() succeeded!
...省略部分輸出
check_pgfault() succeeded!
check_vmm() succeeded.
...省略部分輸出
check_swap() succeeded!
++ setup timer interrupts
this initproc, pid = 1, name = "init"
To U: "Hello world!!".
To U: "en.., Bye, Bye. :)"
kernel panic at kern/process/proc.c:344:
process exit!!.
Welcome to the kernel debug monitor!!
Type 'help' for a list of commands.
相關推薦
作業系統實驗四實驗報告
實驗四:核心執行緒管理 練習1:分配並初始化一個程序控制塊 首先來看幾個比較重要的資料結構,kern/process/proc.h中定義的程序控制塊及kern/trap/trap.h中定義的中斷幀 struct proc_struct {
實驗四實驗報告
std 兩個 png int font src 初始 一次 數組a 實驗結論 Part 1 數組將類型相同的一組數據在內存中連續存放,由實驗可看出數組中元素的內存地址是連續的,不同類型數據計算機為其分配的內存空間是不同的。 Part 2 定義一維數組a,須指明它包含
物聯網-wemos D1 Mini (esp8266)實驗四 -- 實驗二中的丟失尋找器改進為手機控制水泵
材料: D1 Mini 1只 &nbs
20155217 實驗四《Java面向對象程序設計》實驗報告
是我 system oca thead temporary activity 免費 nds 系統文件 20155217 實驗四《Java面向對象程序設計》實驗報告 一、實驗內容 1.基於Android Studio開發簡單的Android應用並部署測試; 2.了解Andro
實驗四 圖的實現和應用 實驗報告 20162305
peek 有關 打印 隊列 廣度 dex 是否 深度優先 遍歷 實驗四 圖的實現和應用 實驗報告 20162305 實驗一 鄰接矩陣實現無向圖 實驗要求 用鄰接矩陣實現無向圖(邊和頂點都要保存),實現在包含添加和刪除結點的方法,添加和刪除邊的方法,size(),isEmp
2017-2018-2 20165315 實驗四《Android程序設計》實驗報告
idt 目的 不同 目錄 stat pic AC 定義 fan 2017-2018-2 20165315 實驗四《Android程序設計》實驗報告 第24章:初識Android Android Studio項目的目錄樹 1 build:該目錄包含了自動生成的文件,這些
2017-2018-2 20165202 實驗四《Android程序設計》實驗報告
delete 封面 enter header nexus options baseline flat 代碼實現 一、實驗報告封面 二、實驗內容 1.基於Android Studio開發簡單的Android應用並部署測試; 2.了解Android、組件、布局管理器的使用;
2017-2018-2 20165330 實驗四《Android程序設計》實驗報告
long this lin line tool 運行結果截圖 undle TP Coding 下載與安裝Android Studio 下載地址:Download Android Studio 安裝教程參考Android開發簡易教程 實驗內容 任務一 參考《Java和A
20165229 實驗四 《Android程序設計》 實驗報告
andro content toast getmenu length tex 啟示 urn 內容 20165229 實驗四 《Android程序設計》 實驗報告 實驗內容: 1.Android Stuidio的安裝測試 2.Activity測試 3.UI測試 4.布局測試
2017-2018-2 20165236 實驗四《Android開發基礎》實驗報告
pan and 發表 rda RoCE AS .cn main 博客 2017-2018-2 20165236 實驗四《Android開發基礎》實驗報告 一、實驗報告封面 課程:Java程序設計 班級:1652班 姓名:郭金濤 學號:20
2017-2018-2 20165215 實驗四《Android開發基礎》實驗報告
PE 對齊 ftw src 創建 信息 多個 界面 線性布局 2017-2018-2 20165215 實驗四《Android開發基礎》實驗報告 一、實驗報告封面 課程:Java程序設計 班級:1652班 姓名:張家佳 學號:20165215 指導教師:婁嘉鵬 實驗日期:2
20165328 實驗四《Andriid應用開發》實驗報告
com and alt mage 開發 TP 技術分享 inf ID 20165328 實驗四《Andriid應用開發》實驗報告
20165307 實驗四《Andriid應用開發》實驗報告
布局 main logs ava png 啟動 TP ID hello 1.實驗要求: 參考《Java和Android開發學習指南(第二版)(EPUBIT,Java for Android 2nd)》第二十四章 參考http://www.cnblogs.com/rocedu
2017-2018-2 20165218 實驗四《Android開發基礎》實驗報告
信息 and 修改 cti 自己的 res 鏈接 學習 main 實驗三 Android開發基礎 課程:java程序設計 姓名:趙冰雨 學號:20165218 指導教師:婁嘉鵬 實驗日期:2018.4.14 實驗內容: 1.基於Android Studio開發簡單的And
學號 2017-2018-20172309 《程序設計與數據結構》實驗四報告
日期 處理 技術分享 dia https 構建 new 解決 哈哈 學號 2017-2018-20172309 《程序設計與數據結構》實驗四報告 課程:《程序設計與數據結構》 班級: 1723 姓名: 王誌偉 學號:20172309 實驗教師:王誌強老師 實驗日期:2018
20172311 2017-2018-2 《程序設計與數據結構》實驗四報告
對話框 軟件 感想 epub 活動 日期 logs ima 圖片 20172311 2017-2018-2 《程序設計與數據結構》實驗四報告 課程:《程序設計與數據結構》 班級: 1723 姓名: 趙曉海 學號:20172311 實驗教師:王誌強 實驗日期:2018年5月3
20172303 2017-2018-2 《程序設計與數據結構》實驗四報告
打開 RoCE rep 結果 left android-s 遇到 IE 程序 20172303 2017-2018-2 《程序設計與數據結構》實驗四報告 課程:《程序設計與數據結構》 班級: 1723 姓名: 範雯琪 學號:20172303 實驗教師:王誌強 助教:張旭
2017-2018-2《程序設計與數據結構》實驗四報告
結果 數據結構 dea 怎麽加密 遇到 相關 cipher and AR 學號 2017-2018-2 《程序設計與數據結構》實驗四報告 課程:《程序設計與數據結構》 班級: 1723 姓名: 彭霖 學號:20172312 實驗教師:王誌強 實驗日期:2018年5月16日
實驗四 惡意程式碼技術 實驗報告 四
實驗報告 四 學 號 201521440029 中國人民公安大學 Chinese people’ public security university
實驗四報告
實驗名稱 實驗 4 [bx]和 loop 的使用 實驗日期 11/21學院 計軟院 專業 計科 年級 2017 級 4 班次 姓名 任心怡 學號 20171308152 一、實驗目的 1. 理解和掌握暫存器間接定址方式[bx] 2. 通過彙編指令 lo