精华内容
下载资源
问答
  • 汇编语言(王爽)实验十 编写子程序

    万次阅读 多人点赞 2016-07-28 16:09:21
    标 题:汇编实验10—— 编写子程序 作 者: XHS_12302 时 间: 链 接: 实验10编写子程序 在这次实验中,我们将要编写3个子程序,通过它们来认识几个常见的问题和掌握解决这些问题的方法。同前面的所有实验一样,...

    标 题: 汇编实验10—— 编写子程序
    作 者: XHS_12302
    时 间: 2016_7_28 16:56

     

     

     

     

     

    实验10编写子程序


      在这次实验中,我们将要编写3个子程序,通过它们来认识几个常见的问题和掌握解决这些问题的方法。同前面的所有实验一样,这个实验是必须要独立完成的,在后面的课程中,将要用到这个实验中编写的3个子程序。
    1.  显示字符串
    问题
    显示字符串是现实工作中经常要用到的功能,应该编写一个通用的子程序来实现这个功能。我们应该提供灵活的调用接口,使调用者可以决定显示的位置(行、列)、内容和颜色。

    提示
    (1)  子程序的入口参数是屏幕上的行号和列号,注意在子程序内部要将它们转化为显存中的地址,首先要分析一下屏幕上的行列位置和显存地址的对应关系:
    (2)  注意保存子程序中用到的相关寄存器:
    (3)  这个子程序的内部处理和显存的结构密切相关,但是向外提供了与显存结构无关的接口。通过调用这个子程序,进行字符串的显示时可以不必了解显存的结构,为编程提供了方便。在实验中,注意体会这种设计思想。

    子程序描述
    名称:show_str
    功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
    参数:(dh)=行号(取值范围0~24),(dl)=列号(取值范围0~79),
        (cl)=颜色,ds:si指向字符串的首地址
    返回:无
    就用举例:在屏幕的8行3列,用绿色显示data段中的字符串。

     

     

     

     

    代码:

     

    assume cs:code
       data segment
       db 'Welcome to masm!',0
       data ends
    
     code segment
       start:  mov dh,8
               mov dl,3
               mov cl,2
               mov ax,data
               mov ds,ax
               mov si,0
               call show_str
               mov ax,4c00h
               int 21h
       show_str: push dx
                 push cx
                 push ds
                 push si
    
         mov ax,0b800h
         mov es,ax
         
         mov al,160
         mul dh
         mov bx,ax
         mov al,2
         mul dl
         add bx,ax
        mov al,cl
        
             s: mov cl,[si] 
                  jcxz ok
                  mov dx,[si]
                  mov es:[bx],dx
                  mov es:[bx+1],al
                  inc si
                  add bx,2
                  loop s
    
     ok:             
      pop si
      pop ds
      pop cx
      pop dx
    ret
    code ends
    end start 

     

     

    实验截图

     

     

     

    2.  解决除法溢出的问题
    问题
    前面讲过,div指令可以做除法。当进行8位除法的时候,用al存储结果的商,ah存储结果的余数:进行16位除法的时候,用ax存储结果的商,dx存储结果的余数。可是,现在有一个问题,如果结果的商大于ah或ax所能存储的最大值,那么将如何?
    比如,下面的程序段:
     mov bh,1
     mov ax,1000
     div bh
    进行的是8位除法,结果的商为1000,而1000在ah中放不下,
    又比如,下面的程序段:
    mov ax,1000h
    mov dx,1
    mov bx,1
    div bx
    进行的是16位除法,结果的商为11000H,而11000H在ax中存放不下。
    我们在用div指令做除法的时候,很可能发生上面的情况:结果的商过大,超出了寄存器所能存储的范围。当CPU执行div等除法指令的时候。如果发生这样的情况,将引发CPU的一个内部错误。这个错误被称为:除法溢出。我们可以通过特殊的程序来处理这个错误, 这里我们不讨论这个错误的处理,这是后面的课程中要涉及的内容。

    好了,我们已经清楚了问题的所在:用div指令做除法的时候可能产生除法溢出。由于有这样的问题,在进行除法运算的时候要注意除数和被除数的值,比如1000000/10就不能用div指令来计算。那么怎么办呢?我们用下面的子程序divdw解决。


    子程序描述
    名称:divdw
    功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型。
    参数:(ax)=dword型数据的低16位
        (dx)=dword型数据的高16位
        (cx)=除数
    返回:(dx)=结果的高16位,(ax)=结果的低16位
        (cx)=余数
    应用举例:计算1000000/10(F4240H/0AH)

      mov ax,4240h
      mov v dx,000fh
      mov cx,0ah
      call divdw


    提示
    给出一个公式:
    X:被除数,范围:[0,FFFF FFFF]
    N:除数,范围:[0,FFFF]
    H:X高16位,范围:[0,FFFF]
    L:X低16位,范围:[0,FFFF]
    int():描述性运算符,取商,比如:rem(38/10)=8
    rem():描述性运算符,取答数,比如:rem(38/10)=8
    公式:X/N=int(H/N)*65536+[rem(H/N)*65536+L]/N
    这个公式将可能产生溢出的除法运算:X/N,转变为多个不会产生溢出的除法运算。
    公式中,等号右边的所有除法运算都可以用div指令来做,肯定不会导致除法溢出。

    代码:

     

    assume cs:code
        code segment
        start:mov ax,4240h
              mov dx,000fh
              mov cx,0Ah
              call divdw
              mov ax,4c00H
              int 21h
    divdw:
          push ax
          mov ax,dx
          mov dx,0
          div cx
          mov bx,ax
          pop ax
          div cx
          mov cx,dx
          mov dx,bx
          ret    
        code ends
    end start

     

     

     

    程序截图:

     

    3.数值显示
    问题
    编程,将data段中的数据以十进制的形式显示出来。
    data segment
    dw 123,12666,1,8,3,38
    data ends
      这些数据在内存中都是二进制信息,标记了数值的大小。要把它们显示到屏幕上,成为我们能够读懂的信息,需要进行信息的转化。比如,数值12666,在机器中存储为二进制信息:0011000101111010B(317AH),计算机可以理解它。而我们要在显示器上读到可以理解的数值12666,我们看到的应该是一串字符:“12666”。由于 显卡遵循的是ASCII编码,为了让我们能在显示器上看到这串字符,它在机器中应以ASCII码的形式存储为:31H、32H、36H、36H、36H(字符“0”~“9”对应的ASCII码为30H~39H)。
      通过上面的分析可以看到,在概念世界中,有一个抽象的数据12666,它表示了一个数值的大小。在现实世界中它可以有多种表示形式,可以在电子机器中以高低电平(二进制)的形式存储,也可以在纸上、黑板上、屏幕上以人类的语言“12666”来书写。现在,我们面临的问题就是,要将同一抽象的数据,从一种表示形式转化为另一种表示形式。
      可见,要将数据用十进制形式显示到屏幕上,要进行两步工作:
    (1)  将用二进制信息存储的数据转变为十进制形式的字符串:
    (2)  显示十进制形式的字符串。
    第二步我们在本次实验的第一个子程序中已经实现,在这里只要调用一下show_str即可。我们来讨论第一步,因为将二进制信息转变为十进制形式的字符串也是经常要用到的功能,我们应该为它编写一个通用的子程序。


    子程序描述
    名称:dtoc
    功能:将word型数据转变为表示十进制数的字符串,字符串以0为结尾符。
    参数:(ax)=word型数据
        ds:si指向字符串的首地址
    返回:无
    应用举例:编程,将数据12666以十进制的形式在屏幕的8行3列,用绿色显示出来。
    在显示时我们调用本次实验中的第一个子程序show-str。



    提示
      下面我们对这个问题进行一下简单地分析。
    (1)  要得到字符串“12666”,就是要得到一列表示该字符的ASCII码:31H、32H、36H、36H、36H。
    十进制数码字符对应的ASCII码=十进制数码值+30H
    要得到表示十进制数的字符串,先求十进制数每位的值。
    例:对于12666,先求得每位的值:1、2、6、6、6。再将这些数分别加上30H,便得到了表示12666的ASCII码串:31H、32H、36H、36H、36H。
    (2)  那么,怎样得到每位的值呢?采用如图10.2所示的方法。

    图10.2除10取余法示意


      可见,用10除12666,共除5次,记下每次的余数,就得到了每位的值。
    (3)  综合以上分析,可得出处理过程如下:
    用12666除以10,循环5次,记下每次的余数;将每次的余数分别加30H,便得到了表示十进制数的ASCII码串,如图10.3所示。



    图10.3得到十进制每位数字字符的方法示意

     


    (4)  对(3)的质疑:
    在已知数据是12666的情况下,知道进行5次循环。可在实际问题中,数据的值是多少
    程序员并不知道,也就是说,程序员不能事先确定循环次数。
      那么,如何确定数据各位的值已经全部求出了呢?我们可以看出,只要是除到商为0,各位的值就已经全部求出。可以使用jcxz指令来实现相关的功能。

     

     

     

     

     

    代码:

     

    assume cs:code
      data segment
      db 16 dup(0)
      data ends
    
     
      code segment
        start:mov ax,12666
              mov bx,data
              mov ds,bx
              mov si,0
              call dtoc
              mov dh,8
              mov dl,3
              mov cl,2
              call show_str
              mov ax,4c00h
              int 21h
    dtoc:
           mov cx,ax    ;17
           jcxz bk
           push ax
           mov al,ah
           mov ah,0
           mov bl,10
           div bl
           mov cl,al
           mov ch,ah
           pop ax
           mov ah,ch
           div bl
           mov dl,ah
           mov dh,0
           push dx
           mov ah,cl
           jmp short dtoc   ;29
         bk:pop ax 
            add ax,30h
            mov [si],al
            
            pop ax 
            add ax,30h
            mov [si+1],al
           
            pop ax 
            add ax,30h
            mov [si+2],al
            
            pop ax 
            add ax,30h
            mov [si+3],al    ;44
            
            pop ax 
            add ax,30h
            mov [si+4],al
            mov byte ptr [si+5],0
            ret
           
         
    show_str:
         mov si,0
         mov ax,0b800h
         mov es,ax
         
         mov al,160
         mul dh
         mov bx,ax
         mov al,2
         mul dl
         add bx,ax
         mov al,cl
        
             s: mov cl,[si] 
                  jcxz ok
                  mov dx,[si]
                  mov es:[bx],dx
                  mov es:[bx+1],al
                  inc si
                  add bx,2
                  loop s
    
     ok: ret    
      code ends
    end start


    程序运行截图

     

     

    至此    实验完成    

                               题中有不足之处  希望不吝赐教

                                             文中有的 文字是借鉴别人的    除了代码和截图

                                                 完成实验参考过别的资料,在第二个实验中受过小甲鱼视频(328/2)的启发

                                                                                                   视频资料上云盘找:链接: http://pan.baidu.com/s/1nvpQiLz 密码: et5r

                                                                                                                                                                                                                                     日期:2016_7_28

    展开全文
  • Mit6.S081-实验3-Page tables

    万次阅读 热门讨论 2020-11-11 11:36:22
    Mit6.S081-实验2-System calls一、Print a page table1,实验准备2,实验要求3,system call调用链路4,trace system call具体实现4,执行效果5,测试效果 一、Print a page table 1,实验准备 1)阅读xv6 book章节3...

    一、Print a page table

    1,实验准备

    1)阅读xv6 book章节3
    2)内存布局代码:kern/memlayout.h
    3)虚拟内存代码:kernel/vm.c
    4)分配、释放物理内存代码:kernel/kalloc.c

    2,实验要求

    为了帮助学习RISC-V page tables,也可能是帮助未来的调试,第一个任务是写一个函数来打印页表内容。
    定义一个函数vmprint()。它应该接收一个pagetable_t参数,并且用下面格式打印页表。
    在exec.c中return argc之前,插入if(p->pid==1) vmprint(p-pagetable),来打印第一个进程的页表。
    当启动xv6时,应该打印以下内容,描述第一个进程的页表信息,在刚执行完exec()时。
    

    在这里插入图片描述

    第一行显示了vmprint()的参数。在那之后每行对应一个PTE,包含树中指向page-table pages的PTE。
    每个PTE行由一些".."(表明树的深度)缩进。
    每个PTE行显示PTE在page-table page中的索引、pte地址、pte中的物理页地址。不要打印无效PTE。
    在上面的例子中,有顶级page-table page:0、255。entry 0的下一级仅有索引0, 这个索引0的下一级有0、1、2三个PTE。
    你的代码可能生成与上面那些代码不同的物理地址。entry和虚拟地址的数量应该一样。
    

    3,具体实现

    1)在kernel/vm.c中定义
    在这里插入图片描述
    2)修改kernel/defs.h中增加vmprint()的声明
    在这里插入图片描述
    3)在kernel/exec.c中增加对vmprint()的调用
    在这里插入图片描述

    4,执行效果

    在这里插入图片描述

    5,测试效果

    在xv6-labs-2020中,执行下面指令,测试程序

    make grade
    

    在这里插入图片描述

    二、A kernel page table per process

    1,实验要求

    无论何时在内核执行时,xv6使用同一个内核页表。内核页表是一个物理地址的直接映射,因此内核虚拟地址x对应物理地址x。
    xv6也有一个单独的页表给每个进程的用户地址空间,仅包含那个进程用户内存的映射,起始于虚拟地址0。
    因为内核页表不包含这些映射,用户地址在内核无效。因此,当内核需要使用一个用户指针传到system call时,内核必须首先翻译指针到物理地址。
    这个和下个实验的目的是为了允许内核直接解析用户指针。
    第一个任务是更改内核,为了当在内核执行时,每个进程使用它自己的内核页表拷贝。
    更改struct proc来让每个进程保持一个内核页表,更改scheduler(),当切换进程时切换内核页表。
    对于这一步,每个进程的内核页表应该和已存在的全局内核页表完全相同。
    读xv6书、理解作业一开始提到的代码如何起作用,将更容易正确地更改虚拟内存代码。
    页表设置中的bug会因为缺少映射导致缺陷,导致加载、存储会影响到不可预期的物理内存页,也会导致执行不正确的物理内存页。
    

    2,具体实现

    1)在kernel/proc.h的struct proc中新加kernelPageTable
    在这里插入图片描述
    2)修改kernel/vm.c,新增一个vmmake()方法可以创建一个内核页表(不包含CLINT的映射)
    在这里插入图片描述
    3)在kernel/vm.c,修改vminit()方法,内部由vmmake()实现,此处为全局内核页表创建过程,另外加上CLINT的映射。
    在这里插入图片描述
    4)在kernel/proc.c,修改procinit()方法,不再于此方法中为每个进程分配内核栈
    在这里插入图片描述
    5)在kernel/proc.c,修改allocproc(),在此时创建内核页表,并在内核页表上分配一个内核栈
    在这里插入图片描述
    6)在kernel/proc.c,修改scheduler(),在swtch()切换进程前修改satp,保证进程执行期间用的是进程内核页表,切换完后再修改satp为全局内核页表
    在这里插入图片描述
    7)在kernel/vm.c,kvmpa()方法会在进程执行期间调用,此时需要修改为获取进程内核页表,而不是全局内核页表
    在这里插入图片描述
    myproc()方法调用,需要在proc.c头部添加头文件引用
    在这里插入图片描述
    8)在kernel/proc.c,修改freeproc()方法,添加对进程内核页表的资源释放
    在这里插入图片描述
    8)在kernel/proc.c,新增proc_free_kernel_pagetable()方法,用于释放进程内核页表指向的物理内存,以及进程内核页表本身
    在这里插入图片描述
    9)在kernel/vm.c,新增uvmfree2()方法,用于释放内核页表上的内核栈
    在这里插入图片描述

    3,测试效果

    启动xv6,成功执行usertests
    在这里插入图片描述

    三、Simplify copyin/copyinstr

    1,实验要求

    内核的copyin函数读取用户指针指向的内存。它先将它们翻译为物理地址(内核可以直接用)。通过代码walk进程页表实现翻译。
    在此实验中,你的工作是给每个进程的内核页表添加用户映射,使得copyin可以直接使用用户指针。
    

    2,具体实现

    1)在kernel/proc.c中,修改fork方法
    在这里插入图片描述
    2)在kernel/exec.c中,修改exec(),在用户进程页表重新生成完后,取消进程内核页表之前的映射,在进程内核页表,建立新进程页表的映射
    在这里插入图片描述
    并添加用户空间地址不能大于PLIC的判断
    在这里插入图片描述
    3)在sysproc.c中,修改sys_sbrk(),在内存扩张、缩小时,相应更改进程内核页表
    在这里插入图片描述
    4)在kernel/defs.h中,添加copyin_new()、copyinstr_new()的声明
    在这里插入图片描述
    5)在kernel/vm.c中,替换copyin()、copyinstr()为copyin_new()、copyinstr_new()
    在这里插入图片描述

    3,测试结果

    在xv6-labs-2020下执行make grade
    在这里插入图片描述

    展开全文
  • Mit6.S081-实验7-Multithreading

    万次阅读 2021-03-04 11:05:35
    Mit6.S081-实验7-Multithreading一、实验准备二、Uthread:switching between threads1,实验要求2,一些提示3,具体实现4,执行效果 一、实验准备 本实验将带你熟悉多线程。 你将实现线程(用户级线程包)切换,...

    一、实验准备

    本实验将带你熟悉多线程。
    你将实现线程(用户级线程包)切换,使用多线程加速程序,实现一个barrier。
    在写代码前,你应该确定你已经读过xv6 book的章节7:scheduling,并且读过相关代码。
    

    开始本lab前,先切换到cow分支

       git fetch、git checkout cow、make clean
    

    二、Uthread:switching between threads

    1,实验要求

    在本练习中,你将为用户级线程系统设计context切换机制,然后实现它。
    开始前,xv6有两个文件user/uthread.c和user/uthread_switch.S,和一个在Makefile中的规则,来构建一个uthread程序。
    uthread.c包含绝大多数用户级线程包,以及3个简单的测试线程代码。
    线程包缺少一些代码来创建一个线程和在线程之间切换。
    
    你的工作是提出一个计划:创建线程、存储/恢复寄存器来进行多线程切换,然后实现该计划。
    当你做完时,make grade应该说你的方案通过uthread test。
    
    一旦你已经结束了,当你在xv6上执行uthread时,你应该看到下面输出(3个线程可能以不同顺序启动):
    

    在这里插入图片描述

    这个输出来自3个测试线程,每个测试线程有一个循环(打印一行然后让出CPU到其他线程)。
    基于这点,如果没有context切换代码,你将看不到输出。
    你将需要添加代码到user/uthread.c中的thread_creat()和thread_schedule(),user/uthread_switch.S的thread_switch。
    目标一是确保当thread_schedule()首次运行一个给定线程时,线程执行传到thread_create()中的函数,在它自己的栈上。
    另外目标是确保thread_switch保存切换前线程的寄存器,恢复要切换线程的寄存器,返回到切换后的线程上次离开时的指令。
    你将不得不决定哪里存储/恢复寄存器;更改struct thread来保存寄存器是一个好计划。
    你将需要在thread_schedule添加thread_switch调用;你能传递任何需要的参数到thread_switch,但目的是从一个线程切换到下个线程。
    

    2,一些提示

    thread_switch仅需要存储/恢复callee-save寄存器。为什么?
    你可以在user/uthread.asm中看到uthread汇编代码,这可能对调试有帮助。
    对于测试你的代码,使用riscv64-linux-gnu-gdb单步thread_switch可能是有帮助的。
    (gdb) file user/_uthread
    Reading symbols from user/_uthread...
    (gdb) b uthread.c:60
    这会在uthread.c第60行设置一个断点。
    这个断点可能会(也可能不会)在你运行uthread之前触发。这如何发生?
    一旦你的xv6 shell运行,输入”uthread”,gdb将在60行停住。现在你可以如下输入命令来检测uthread的状态:
    (gdb) p/x *next_thread
    用”x”,你可以检测内存位置的内容:
    (gdb) x/x next_thread->stack
    你可以跳到thread_switch开始处:
    (gdb) b thread_switch
    (gdb) c
    你可以使用si来单步汇编指令:
    (gdb) si
    

    3,具体实现

    1)修改user/uthread.c,新增头文件引用。
    在这里插入图片描述
    2)修改user/uthread.c,struct thread增加成员struct context,用于线程切换时保存/恢复寄存器信息。此处struct context即kernel/proc.h中定义的struct context。
    在这里插入图片描述
    3)修改user/uthread.c,修改thread_switch函数定义。
    在这里插入图片描述
    4)修改user/uthread.c,修改thread_create函数,修改ra保证初次切换到线程时从何处执行,修改sp保证栈指针位于栈顶(地址最高)。
    在这里插入图片描述
    5)修改user/uthread.c,修改thread_schedule,调用thread_switch调用,保存当前线程上下文,恢复新线程上下文。
    在这里插入图片描述
    6)修改user/uthread_switch.S,实现保存/恢复寄存器。
    在这里插入图片描述

    4,执行效果

    在这里插入图片描述
    在这里插入图片描述

    三、Using threads

    1,实验要求

    在本次作业中,你将使用哈希表探索并行程序(带有线程和锁)。
    你应该在一个真正的Linux或MacOS多核计算机上做此次作业(并非xv6、并非qemu)。
    现在的笔记本绝大多数拥有多处理器。
    本次作业使用Unix pthread threading库。
    你可以从说明页找到关于它的信息,with man pthreads。
    

    你可以在web上看到三个例子:例一例二例三

    文件notxv6/ph.c包含一个简单的哈希表,如果单线程使用,它是正确的,但多线程使用是不正确的。
    在你的xv6主目录,输入:
    
    $ make ph
    $ ./ph 1
    
    注意:构建ph,Makefile使用OS的gcc,而不是6.S081工具。
    对程序ph的参数明确了,哈希表执行put和get操作的线程数目。
    执行一会后,ph1将产生输出,类似:
    
    100000 puts, 3.991 seconds, 25056 puts/second
    0: 0 keys missing
    100000 gets, 3.981 seconds, 25118 gets/second
    
    你看到的数目可能不同于这个输出,相差2倍或更多,这取决于你的电脑有多快,是否它有多核,是否它正忙于做其他事。
    ph执行两个评测。首先它通过调用put()添加大量key到哈希表,打印每秒put的数量。
    它用get()从哈希表获取keys。它打印,应该已经因put而存在于哈希表中,但却不在的key数量,打印每秒get数量。
    你可以告知ph,同一时间,通过给一个大于1的参数用多线程使用它的哈希表,尝试ph 2:
    
    $ ./ph 2
    100000 puts, 1.885 seconds, 53044 puts/second
    1: 16579 keys missing
    0: 16579 keys missing
    200000 gets, 4.322 seconds, 46274 gets/second
    
    ph 2输出的第一行表明:当两个线程并发地添加entry到哈希表,它们实现每秒53044次插入。
    这两倍于单线程执行ph 1。这是一个卓越的parallel speedup,2倍于单个(每单元时间,两倍核产生两倍工作)。
    然而,两行显示16579 keys missing:表明大量应该在哈希表中的key并不在。
    put被期望添加那些key到哈希表,但某些地方出错了。
    看notxv6/ph.c,特别是put()和insert()。
    为什么两个线程时missing keys,1个线程时没有?说明2个线程的一系列事件(导致missing keys)。
    用一个简单的说明(位于answers-thread.txt)提交你的序列。
    为了避免一系列事件,插入锁和释放锁语句放在notxv6/ph.c的put和get中,以至于两个线程missing keys的数量总是0。
    

    相关pthread调用是:

    pthread_mutex_t lock;    //定义一个锁
    pthread_mutex_init(&lock, NULL); //初始化锁
    pthread_mutex_lock(&lock);    //获取锁
    pthread_mutext_unlock(&lock);  //释放锁
    
    make grade说你的代码通过ph_safe测试时,那么可以了,需要两个线程0 missing keys。
    在此处没通过ph_fast测试没关系。
    不要忘记调用pthread_mutex_init()。
    用1个线程测试你的代码,然后用两个线程测试你的代码,正确么(消除了missing keys?)?
    与单线程版本相比,两个线程版本实现了并行加速(每单元时间处理更多工作)?
    有些情况并发put不会在内存中重叠(读写哈希表时),因此无需锁来保护排斥另外一个。
    你可以修改ph.c利用这个情况,来获取put并行加速?提示:每个hash bucket一把锁如何?
    更改你的代码以便于一些put操作在保证正确的情况下并行执行。
    当make grade说你的代码通过ph_safe和ph_fast测试时,就成功了。
    ph_fast测试需要:两个线程比单个线程产生至少1.25倍的每秒put次数。
    

    2,具体实现(可通过ph_safe)

    1)修改notxv6/ph.c,定义互斥锁。
    在这里插入图片描述
    2)修改notxv6/ph.c,初始化互斥锁。
    在这里插入图片描述
    3)修改notxv6/ph.c,对put操作加锁。
    在这里插入图片描述

    3,执行效果

    未加锁:
    在这里插入图片描述
    简单加锁:
    在这里插入图片描述

    4,具体实现(可通过ph_fast)

    1)修改notxv6/ph.c,定义互斥锁,每个bucket一个锁。
    在这里插入图片描述
    2)修改notxv6/ph.c,初始化互斥锁。
    在这里插入图片描述
    3)修改notxv6/ph.c,在put中对数组某个bucket加锁。
    在这里插入图片描述

    5,执行效果

    每个bucket一个锁
    在这里插入图片描述

    三、Barrier

    1,实验要求

    在本作业中,你将实现barrier(应用中的一个点):所有参与线程必须等待,直到所有其他参与线程也到达该点。
    你将使用线程condition变量,一种协作技术,与xv6的sleep和wakeup相似。
    你应该在一个真实电脑上做此作业(并非xv6、并非qemu)。
    文件notxv6/barrier.c包含一个损坏的barrier。
    
    $ make barrier
    $ ./barrier 2
    barrier: notxv6/barrier.c:42: thread: Assertion `i == t' failed.
    
    2明确了在barrier同步的线程数(barrier.c中的nthread)。
    每个线程执行一个循环。
    在每个循环中,迭代一个线程调用barrier(),然后sleep几(一个随机数)毫秒。
    断言触发,因为一个线程在另外一个线程到达barrier之前离开barrier。
    期望的行为是:每个线程阻塞在barrier()中,直到所有线程调用barrier()。
    
    你的目的是实现期望的barrier行为。
    除了ph作业中看到的锁之外,你将需要下面新pthread方法;
    

    看更多细节:例一例二

    pthread_cond_wait(&cond, &mutex);  // go to sleep on cond, releasing lock mutex, acquiring upon wake up
    pthread_cond_broadcast(&cond);     // wake up every thread sleeping on cond
    调用pthread_cond_wait时,释放mutex,返回前重新获取mutex
    我们已经给你barrier_init()。
    你的工作是实现barrier(),以致于panic不会发生。
    我们已经为你定义struct barrier;它的属性供你使用。
    

    有两个问题会让你的任务复杂:

    你不得不处理连续的barrier调用,我们称之为一轮。
    bstate.round记录当前round。你应该提升bstate.round,每次所有线程到达barrier。
    
    你不得不处理这种情况:在其他线程退出barrier之前一个线程进入循环。
    特别是,你正在从一轮到下一轮重复使用bstate.nthread变量。
    确保,离开barrier、进入循环的线程不会提升bstate.nthread,当之前的round仍在使用它时。
    

    用1、2、更多线程来测试你的代码。

    2,具体实现

    1)修改notxv6/barrier.c,实现barrier()方法。
    在这里插入图片描述

    3,执行效果

    在这里插入图片描述

    展开全文
  • Mit6.S081-实验8-locks

    万次阅读 2021-03-10 16:40:39
    Mit6.S081-实验8-locks一、实验准备二、Memory allocator1,实验要求2,一些提示3,具体实现 一、实验准备 在本实验,你将在 重构代码以提升并行 上获得经验。 多核机器低并行的常见症状是high lock contention。 ...

    一、实验准备

    在本实验,你将在 重构代码以提升并行 上获得经验。
    多核机器低并行的常见症状是high lock contention。
    提高并行,通常包括改变数据结构和锁策略来减少争用。
    你将为xv6 memory allocator和block cache这么做。
    在写代码之前,确保阅读xv6 book的以下部分:
    Chapter6:"Locking"和对应代码
    Section3.5:"Code:Physical memory allocator"
    Section8.1到8.3:"Overview","Buffer cache layer","Code:Buffer cache"
    

    切换到lock分支

    git fetch、git checkout lock、make clean
    

    二、Memory allocator

    1,实验要求

    程序user/kalloctest压测xv6的memory allocator:
    3个进程grow、shrink它们的地址空间,导致一些调用:kalloc和kfree。
    kalloc和kfree获取kmem.lock。
    kalloctest打印(”#fetch-and-add”)acquire中的循环迭代编号,由于尝试获取另外一个核已经持有的锁,kem lock和少量其它锁。
    acquire循环迭代编号是一个lock contention的粗略测量。
    kalloctest的输出看起来类似之前完成的实验。
    
    $ kalloctest
    start test1
    test1 results:
    --- lock kmem/bcache stats
    lock: kmem: #fetch-and-add 83375 #acquire() 433015
    lock: bcache: #fetch-and-add 0 #acquire() 1260
    --- top 5 contended locks:
    lock: kmem: #fetch-and-add 83375 #acquire() 433015
    lock: proc: #fetch-and-add 23737 #acquire() 130718
    lock: virtio_disk: #fetch-and-add 11159 #acquire() 114
    lock: proc: #fetch-and-add 5937 #acquire() 130786
    lock: proc: #fetch-and-add 4080 #acquire() 130786
    tot= 83375
    test1 FAIL
    
    对于每个锁,acquire持有:为了该锁,acquire的调用次数;
    acquire循环中,尝试获取但没能设置锁的次数。
    kalloctest调用一个system call,让kernel打印那些对于kmem、bcache lock计数,(这是本实验的关注点),也打印5个争用最强烈的锁。
    如果存在锁争用,acquire循环迭代中的计数将很多大。
    system call返回 为了获取kmem和bcache lock的循环迭代总数。
    
    对于本实验,你必须使用一个专用unloaded多核机器。
    如果你使用一个正在做其他事的机器,kalloctest打印的计数将是无用的。
    你可以使用专用Athena workstation,或你的笔记本电脑,但不要使用dialup机器。
    
    kalloctest中锁争用的根本原因是kalloc()只有一个free list,受单lock保护。
    为了移除锁争用,你将不得不重新设计memory allocator来避免一个单独lock和list。
    基本想法是:每个CPU持有一个free list,每个list有其自己的锁。
    不同CPU上的分配和释放可以并行执行,因为每个CPU将在不同的list上操作。
    主要挑战将是处理这样的场景:
    某个CPU上的free list空了,但另外CPU list有free memory。
    在这种情况下,cpu必须steal另外CPU free list的一部分。
    stealing可能引发锁争用,但不常见。
    
    你的工作是实现每个CPU freelist,当CPU free list为空时stealing。
    你必须让所有锁名以kmem开头。
    你应该为每把锁调用initlock,传入一个以kmem开头的名字。
    执行kalloctest来看是否你的实现已经减少了锁争用。
    为了检查仍可以分配所有内存,执行usertests sbrkmuch。
    你的输出将和下面显示的相似,减少了kmem lock争用总数,尽管具体数字不同。
    确保usertests中所有测试通过。make grade应该表明kalloctests通过。
    
    $ kalloctest
    start test1
    test1 results:
    --- lock kmem/bcache stats
    lock: kmem: #fetch-and-add 0 #acquire() 42843
    lock: kmem: #fetch-and-add 0 #acquire() 198674
    lock: kmem: #fetch-and-add 0 #acquire() 191534
    lock: bcache: #fetch-and-add 0 #acquire() 1242
    --- top 5 contended locks:
    lock: proc: #fetch-and-add 43861 #acquire() 117281
    lock: virtio_disk: #fetch-and-add 5347 #acquire() 114
    lock: proc: #fetch-and-add 4856 #acquire() 117312
    lock: proc: #fetch-and-add 4168 #acquire() 117316
    lock: proc: #fetch-and-add 2797 #acquire() 117266
    tot= 0
    test1 OK
    start test2
    total free number of pages: 32499 (out of 32768)
    .....
    test2 OK
    $ usertests sbrkmuch
    usertests starting
    test sbrkmuch: OK
    ALL TESTS PASSED
    $ usertests
    ...
    ALL TESTS PASSED
    

    2,一些提示

    你可以使用kernel/param.h中常量NCPU。
    让freerange把所有空闲内存给正在执行freerange的CPU。
    函数cpuid返回当前cpu核编号,但仅在中断关闭时可以调用它并使用它的结果。
    你应该使用push_off()和pop_off()来关闭、开启中断。
    看kernel/sprintf.c中的snprintf函数,为string formatting找灵感。
    可以将所有锁命名为kmem。
    

    3,具体实现

    1)修改kernel/kalloc.c,修改原有结构体声明kmem,定义结构体数组kmemArray[NCPU]。
    在这里插入图片描述
    2)修改kernel/kalloc.c,修改kinit方法,对NCPU个kmem结构体初始化。
    在这里插入图片描述
    3)修改kernel/kalloc.c,修改kfree方法,获取当前cpuid,释放对应freelist。
    在这里插入图片描述
    4)修改kernel/kalloc.c,修改kalloc方法,当前CPU freelist为空时,从其他CPU freelist处获取。
    在这里插入图片描述

    4,测试效果

    在这里插入图片描述
    在这里插入图片描述

    三、Buffer cache

    1,实验要求

    这半个作业独立于上半个;不管你是否完成了上半个,你都可以处理这半个并通过测试。
    如果多进程集中地使用文件系统,它们可能会竞争获取bcache.lock,保护kernel/bio.c中的磁盘block cache。
    bcachetest创建多个进程重复读取不同文件,产生bcache.lock上的竞争。它的输出如下(在你完成本实验之前):
    
    $ bcachetest
    start test0
    test0 results:
    --- lock kmem/bcache stats
    lock: kmem: #fetch-and-add 0 #acquire() 33035
    lock: bcache: #fetch-and-add 16142 #acquire() 65978
    --- top 5 contended locks:
    lock: virtio_disk: #fetch-and-add 162870 #acquire() 1188
    lock: proc: #fetch-and-add 51936 #acquire() 73732
    lock: bcache: #fetch-and-add 16142 #acquire() 65978
    lock: uart: #fetch-and-add 7505 #acquire() 117
    lock: proc: #fetch-and-add 6937 #acquire() 73420
    tot= 16142
    test0: FAIL
    start test1
    test1 OK
    
    你可能会看到不同的输出,为获取bcache lock,acquire循环迭代次数将会很高。
    如果你看到kernel/bio.c中的代码,你将看到bacache.lock保护cached block buffers list、每个block buffer的引用数量(b->refcnt)、cached block的标识(b->dev和b->blockno)。
    修改block cache,以致于执行bcachetest时,在bcache中所有锁的acquire循环迭代次数接近0。
    理想状态下,与block cache相关的所有锁计数之和应该为0,但如果和小于500也是可以的。
    更改bget和brelse,以致于对bcache不同block的并发查看和释放不会在锁上冲突(例如,无需等待bcache.lock)。
    你必须保证不变性:每个block最多一个副本被缓存。
    当你完成时,你的输出应该和下面所示相似(尽管不完全一样)。
    确保usertests仍然通过。当你完成时,make grade应该通过所有测试。
    
    $ bcachetest
    start test0
    test0 results:
    --- lock kmem/bcache stats
    lock: kmem: #fetch-and-add 0 #acquire() 32954
    lock: kmem: #fetch-and-add 0 #acquire() 75
    lock: kmem: #fetch-and-add 0 #acquire() 73
    lock: bcache: #fetch-and-add 0 #acquire() 85
    lock: bcache.bucket: #fetch-and-add 0 #acquire() 4159
    lock: bcache.bucket: #fetch-and-add 0 #acquire() 2118
    lock: bcache.bucket: #fetch-and-add 0 #acquire() 4274
    lock: bcache.bucket: #fetch-and-add 0 #acquire() 4326
    lock: bcache.bucket: #fetch-and-add 0 #acquire() 6334
    lock: bcache.bucket: #fetch-and-add 0 #acquire() 6321
    lock: bcache.bucket: #fetch-and-add 0 #acquire() 6704
    lock: bcache.bucket: #fetch-and-add 0 #acquire() 6696
    lock: bcache.bucket: #fetch-and-add 0 #acquire() 7757
    lock: bcache.bucket: #fetch-and-add 0 #acquire() 6199
    lock: bcache.bucket: #fetch-and-add 0 #acquire() 4136
    lock: bcache.bucket: #fetch-and-add 0 #acquire() 4136
    lock: bcache.bucket: #fetch-and-add 0 #acquire() 2123
    --- top 5 contended locks:
    lock: virtio_disk: #fetch-and-add 158235 #acquire() 1193
    lock: proc: #fetch-and-add 117563 #acquire() 3708493
    lock: proc: #fetch-and-add 65921 #acquire() 3710254
    lock: proc: #fetch-and-add 44090 #acquire() 3708607
    lock: proc: #fetch-and-add 43252 #acquire() 3708521
    tot= 128
    test0: OK
    start test1
    test1 OK
    $ usertests
      ...
    ALL TESTS PASSED
    
    请你把所有锁名以“bcache”开头。即你该为每个锁调用initlock,传入一个以“bcache”开头的名字。
    减少block cache中的争用比kalloc更困难,因为bcache buffers确实在多进程间共享。
    对于kalloc,给每个CPU它自己的allocator可以减少绝大多数争用;它们不会对block cache起作用。
    我们建议你用哈希表(每个bucket一把锁)在cache中查看block序号。
    某些情况下,你的方案有lock conflict是可以的:
    1,当两个进程并发地使用相同block序号。bcachetest test0从不会这么做。
    2,当两个进程同时在cache中miss时,需要找到一个未使用的block来替换。bcachetest test0从不会这么做。
    3,无论你使用什么方案来区分blocks和locks,两个进程并发使用blocks都会产生conflict。
    例如,两个进程使用blocks(它的block号哈希到哈希表同一位置)。
    bcachetest.test0()可能这么做,取决于你的设计,但你应该尝试调整策略细节来避免confilcts(如,改变哈希表的尺寸)。
    bcachetest的test1使用块比缓冲区,练习一些文件系统代码路径。
    

    2,一些提示

    1,阅读xv6书中block cache的描述(章节:8.1-8.3)。
    2,使用固定数量的buckets,并不动态扩容hash表是可以的。使用素数(例如13)个buckets来减少哈希碰撞的可能性。
    3,当buffer没有被找到,在哈希表中找一个buffer,并给该buffer分配一个entry必须是原子的。
    4,移除list的所有buffers(bcache.head等等),用最后使用时间来标定buffers。(如,kernel/trap.c中用的ticks)。
    因为这个改变,brelse无需获取bcache lock,bget可以基于时间标定选择LRU block。
    5,bget中串行化删除是可以的。(例如,当在cache中没查到时,bget中选择一个buffer来重用)
    6,你的方案,在一些场景中可能需要持有两个锁;
    例如,在移除期间,你可能需要持有bcache lock和每个bucket的lock。确保你避免死锁。
    7,当取代一个block,你可能移动一个struct buf从一个bucket到另外一个bucket,因为新block哈希到一个不同的bucket。
    你可能有一个困难情况:新block可能哈希到与旧block一样的bucket。确保你在那种情况下避免死锁。
    8,一些调试提示:实现bucket锁,但在bget开始、结束时保留全局bcache.lock acquire/release来串行化代码。
    一旦确保无竞争情况正确时,移除全局锁,处理并发问题。你也可以执行make CPUS=1 qemu来测试单核。
    

    3,具体实现

    1)修改kernel/buf.h,新增tick,作为时间标记。
    在这里插入图片描述
    2)修改kernel/bio.c,新增NBUC,作为hash表bucket数量。
    在这里插入图片描述
    3)修改kernel/bio.c,修改bcache,去除head。
    在这里插入图片描述
    4)修改kernel/bio.c,定义结构体bMem,新增结构体数组bMem作为哈希表。
    在这里插入图片描述
    5)修改kernel/bio.c,在binit方法中新增哈希表中每个bucket lock初始化。
    在这里插入图片描述
    6)修改kernel/bio.c,新增replaceBuffer方法,用于LRU buffer赋值。
    在这里插入图片描述
    7)修改kernel/bio.c中的bget方法。

    static struct buf*
    bget(uint dev, uint blockno)
    {
        struct buf *b;
    	struct buf *lastBuf;
    
    	// Is the block already cached?
    	uint64 num = blockno%NBUC;
    	acquire(&(hashTable[num].lock));
    	for(b = hashTable[num].head.next, lastBuf = &(hashTable[num].head); b; b = b->next){
    		if (!(b->next)){
    			lastBuf = b;
    		}
    		if(b->dev == dev && b->blockno == blockno){
    			b->refcnt++;
    			release(&(hashTable[num].lock));
    			acquiresleep(&b->lock);
    			return b;
    		}
    	}
    
    	struct buf *lruBuf = 0;
    	acquire(&bcache.lock);
    	for(b = bcache.buf; b < bcache.buf + NBUF; b++){
    	    if(b->refcnt == 0) {
    	    	if (lruBuf == 0){
    	    		lruBuf = b;
    	    		continue;
    	    	}
    			if (b->tick < lruBuf->tick){
    				lruBuf = b;
    			}
    	    }
      	}
    
    	if (lruBuf){
    	  	uint64 oldTick = lruBuf->tick;
    		uint64 oldNum = (lruBuf->blockno)%NBUC;
    		if(oldTick == 0){
    			replaceBuffer(lruBuf, dev, blockno, ticks);
    			lastBuf->next = lruBuf;
    			lruBuf->prev = lastBuf;
    		}else {
    			if (oldNum != num){
    				acquire(&(hashTable[oldNum].lock));
    				replaceBuffer(lruBuf, dev, blockno, ticks);
    				lruBuf->prev->next = lruBuf->next;
    				if (lruBuf->next){
    					lruBuf->next->prev = lruBuf->prev;
    				}
    				release(&(hashTable[oldNum].lock));
    				lastBuf->next = lruBuf;
    				lruBuf->prev = lastBuf;
    				lruBuf->next = 0;
    			}else {
    				replaceBuffer(lruBuf, dev, blockno, ticks);
    			}
    		}
    	  	release(&bcache.lock);
    		release(&(hashTable[num].lock));
    		acquiresleep(&lruBuf->lock);
    		return lruBuf;
    	}
      	panic("bget: no buffers");
    }
    

    8)修改kernel/bio.c中的brelse方法。
    在这里插入图片描述
    9)修改kernel/bio.c中的bpin、bunpin方法。
    在这里插入图片描述

    4,测试效果

    1)bcachetest
    在这里插入图片描述
    2)usertests
    在这里插入图片描述

    展开全文
  • 使用8255A实现键盘显示控制实验(翻转法实现)。
  • Mit6.S081-实验4-Traps

    万次阅读 2020-12-14 16:24:16
    Mit6.S081-实验4-Traps一、RISC-V assembly1,实验准备2,实验要求3,相关问题 一、RISC-V assembly 1,实验准备 1)阅读xv6 book章节4 2)从user space过渡到kernel space,kernel space返回到user space的汇编代码...
  • Mit6.S081-实验5-xv6 lazy page allocation

    万次阅读 2020-12-23 12:01:16
    Mit6.S081-实验5-xv6 lazy page allocation一、Eliminate allocation from sbrk()1,实验准备2,实验要求3,具体实现二、Lazy allocation1,实验要求2,具体实现3,执行效果二、Lazytests and Usertests1,实验要求2...
  • Mit6.S081-实验2-System calls

    万次阅读 2020-10-19 20:32:10
    Mit6.S081-实验2-System calls前言一、System call tracing1,实验准备2,实验要求3,system call调用链路4,trace system call具体实现4,执行效果5,测试效果 前言 一、System call tracing 1,实验准备 1)阅读xv...
  • Hive实验

    万次阅读 2020-07-13 04:12:19
    像极了 sql 的 hive实验
  • Mit6.S081-实验1-Xv6 and Unix utilities

    万次阅读 多人点赞 2020-09-19 15:38:36
    Mit6.S081-实验1-Xv6 and Unix utilities前言一、Boot xv61,实验目的2,操作流程1)切换到xv6-labs-2020代码库的lab1分支2)启动xv63)测试xv64)过程分析5)其他操作二、在xv6中添加一个自己编写的程序1,源码准备...
  • Mit6.S081-实验6-Copy-on-Write Fork for xv6

    万次阅读 2020-12-30 14:10:41
    Mit6.S081-实验6-Copy-on-Write Fork for xv6一、前言二、The problem三、The solution四、Implement copy-on write1,实验要求2,进攻计划3,一些提示4,具体实现5,执行效果 一、前言 虚拟内存提供一定程度的...
  • Mit6.S081-实验环境搭建

    万次阅读 2020-09-19 14:41:27
    Mit6.S081-实验环境搭建前言一、Linux系统二、SSH连接工具三、环境搭建1,安装依赖2,克隆源码3,分支说明4,源码目录简析 前言 qemu(quick emulator):这是一个模拟硬件环境的软件,利用它可以运行我们编译好的...
  • DirectX修复工具强力修复实验

    万次阅读 2018-02-16 19:57:48
    DirectX修复工具API Sets强力修复实验包下载地址: https://pan.baidu.com/s/1viLPeKp8vtFCy8Pr1S9CWw 密码:5d6n 实验包使用说明: 1、实验包仅支持DirectX修复工具V3.6.6版及以上版本。 2、首先将上述下载...
  • 实验二 定时器实验

    千次阅读 2019-01-18 11:01:50
    实验二 定时器实验 一、实验要求 89C51内部定时计数器T0,用CPU内部定时器中断方式计时,实现每一秒钟输出状态发生一次反转. 二、实验目的 学习89C51内部定时/计数器使用方法,进一步掌握中断处理程序的编程。 ...
  • 实验三 定时器实验

    千次阅读 2019-01-10 15:41:06
    实验三 定时器实验(2学时)  实验目的: – 掌握MCS-51单片机定时器的使用及编程。  实验内容及要求: – ⑴ 用定时器1的方式1定时,P1.0接一发光二极管,使该发光二极管每秒钟闪烁5次(采用中断方式)。 – ⑵...
  • 基于Cisco Packet Tracer 6.2的校园网仿真实验

    万次阅读 多人点赞 2018-12-17 11:20:18
    前段时间,《计算机网络》这门课开始写实验了,虽然只有三个实验,但是一个一个比一个麻烦。 实验二的内容是用思科的模拟软件写一个仿真实验,我虽然知道思科,但是从来都不知道思科有什么仿真软件,老师更是任由...
  • 一、实验目的 熟悉存储器的电路构成,掌握存储器读写的编程方法。 二、实验设备 PC 计算机一台, Dais-86PRO+或 Dais-PRO163C 实验系统一套。 三、 实验内容及步骤 编写程序,将 03000H-03FFFH存储器单元...
  • 一、实验描述 专题地图是一个非常复杂的过程,地图数据的符号化与注记标注,都是地图编制准备基础的地理数据。然而,要将准备好的地图数据,通过一幅完整的地图表达出来,还有很多工作,包括布局纸张的设置、制图...
  • 实验一 外部中断实验

    千次阅读 2019-01-18 11:01:23
    实验一 外部中断实验 一、实验要求 用单次脉冲申请中断,在中断处理程序中对输出信号进行反转。 二、实验目的 1、学习外部中断技术的基本使用方法。 2、学习中断处理程序的编程方法。 三、实验原理 当89C51的...
  • 木马分析(隐藏分析)实验 木马分析(控制分析)实验 木马植入方法实验---冰河木马
  • 编译原理实验:词法分析

    万次阅读 多人点赞 2018-09-29 21:17:16
    实验题目:词法分析实验目的实验内容实验要求输入输出2. 设计思想3.算法流程4. 源程序5. 调试数据 1. 实验题目:词法分析 实验目的 根据PL/0语言的文法规范,编写PL/0语言的词法分析程序;或者调研词法分析...
  • 文章目录偶校验实验海明校验实验 偶校验实验 错误示范: 推荐连法: 海明校验实验 表1.7竖过来之后就是二进制数据了填表就行。
  • 实验1 BP神经网络实验

    千次阅读 2019-01-31 22:13:51
    传送门(所有的实验都使用python实现) 实验1 BP神经网络实验 实验2 som网实验 实验3 hopfield实现八皇后问题 实验4 模糊搜索算法预测薄冰厚度 实验5 遗传算法求解tsp问题 实验6 蚁群算法求解tsp问题 实验7 ...
  • 实验二 外部中断实验

    千次阅读 2019-01-10 15:40:32
    实验二 外部中断实验(2学时)  实验目的: – 掌握中断控制的设置方法及中断服务程序编写方法。  实验内容及要求: – ⑴用单脉冲信号申请中断,在中断服务程序中对输出信号进行翻转(可通过P1.0接一发光二极管...
  • 实验二 分类算法实验

    万次阅读 2017-12-24 16:32:06
    实验目的1.巩固4种基本的分类算法的算法思想:朴素贝叶斯算法,决策树算法,人工神经网络,支持向量机算法; 2.能够使用现有的分类器算法代码进行分类操作 3.学习如何调节算法的参数以提高分类性能;二、实验的硬件...
  • 微机实验课-实验三中断程序设计

    千次阅读 多人点赞 2018-11-07 18:02:39
    微机实验三操作参考 2018年11月 William 〇,实验目的 结合实验指导书,1)掌握中断概念,掌握中断程序设计方法;2)掌握中断程序调试方法。 一,建立本次实验的工程文件 如前面实验一样,复制实验指导书上的...
  • Python实验

    万次阅读 多人点赞 2019-02-16 14:14:35
    实验 一 Python基本数据类型和列表 一.实验目的及要求 (1)了解Python语言的基本数据类型。 (2)掌握Python语言基本数据类型的使用方法。 (3)掌握Python语言中列表的使用方法。 (4)编写实验报告。 二.实验...
  • Cisco Packet Tracer 实验教程

    万次阅读 多人点赞 2017-04-20 11:02:54
    Cisco Packet Tracer 实验教程 本笔记的实验来源是上学校计算机网路课程时的实验资料,我稍微整理了一些,在实现后的命令行添加了一些注释,实现不是唯一的,我的实现也不一定是最优的,如果有错谬,敬请指正...
  • 单片机实验-DA实验

    千次阅读 2018-07-22 16:23:23
    一、实验目的 1、了解 D/A 转换的基本原理。 2、了解 D/A 转换芯片 0832 的性能及编程方法。 3、了解单片机系统中扩展 D/A 转换的基本方法。 二.实验设备和器件 1.KEIL软件 2.实验箱 三.实验内容 利用 DAC...
  • 实验二:LED流水灯实验

    千次阅读 2020-04-16 10:41:17
    实验二:LED流水灯实验强调:实验二:LED流水灯实验使用的开发板原理图及本次使用的模块备注:实验二的代码部分 强调: 本文章为新手提供学习参考 实验二:LED流水灯实验 控制开发板上的LED灯,独立完成一个单片机...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 569,151
精华内容 227,660
关键字:

实验