0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

深入浅出谈Android多线程及AsyncTask机制

电子设计 ? 2018-08-30 19:39 ? 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

一、Android当中的多线程

在Android当中,当一个应用程序的组件启动的时候,并且没有其他的应用程序组件在运行时,Android系统就会为该应用程序组件开辟一个新的线程来执行。默认的情况下,在一个相同Android应用程序当中,其里面的组件都是运行在同一个线程里面的,这个线程我们称之为Main线程。当我们通过某个组件来启动另一个组件的时候,这个时候默认都是在同一个线程当中完成的。当然,我们可以自己来管理我们的Android应用的线程,我们可以根据我们自己的需要来给应用程序创建额外的线程。

二、Main Thread 和 Worker Thread

在Android当中,通常将线程分为两种,一种叫做Main Thread,除了Main Thread之外的线程都可称为Worker Thread。

当一个应用程序运行的时候,Android操作系统就会给该应用程序启动一个线程,这个线程就是我们的Main Thread,这个线程非常的重要,它主要用来加载我们的UI界面,完成系统和我们用户之间的交互,并将交互后的结果又展示给我们用户,所以Main Thread又被称为UI Thread。

Android系统默认不会给我们的应用程序组件创建一个额外的线程,所有的这些组件默认都是在同一个线程中运行。然而,某些时候当我们的应用程序需要完成一个耗时的操作的时候,例如访问网络或者是对数据库进行查询时,此时我们的UI Thread就会被阻塞。例如,当我们点击一个Button,然后希望其从网络中获取一些数据,如果此操作在UI Thread当中完成的话,当我们点击Button的时候,UI线程就会处于阻塞的状态,此时,我们的系统不会调度任何其它的事件,更糟糕的是,当我们的整个现场如果阻塞时间超过5秒钟(官方是这样说的),这个时候就会出现 ANR (Application Not Responding)的现象,此时,应用程序会弹出一个框,让用户选择是否退出该程序。对于Android开发来说,出现ANR的现象是绝对不能被允许的。

另外,由于我们的Android UI控件是线程不安全的,所以我们不能在UI Thread之外的线程当中对我们的UI控件进行操作。因此在Android的多线程编程当中,我们有两条非常重要的原则必须要遵守:

- 绝对不能在UI Thread当中进行耗时的操作,不能阻塞我们的UI Thread

- 不能在UI Thread之外的线程当中操纵我们的UI元素

三、如何处理UI Thread 和 Worker Thread之间的通信

既然在Android当中有两条重要的原则要遵守,那么我们可能就有疑问了?我们既不能在主线程当中处理耗时的操作,又不能在工作线程中来访问我们的UI控件,那么我们比如从网络中要下载一张图片,又怎么能将其更新到UI控件上呢?这就关系到了我们的主线程和工作线程之间的通信问题了。在Android当中,提供了两种方式来解决线程直接的通信问题,一种是通过Handler的机制(这种方式在后面的随笔中将详细介绍),还有一种就是今天要详细讲解的 AsyncTask 机制。

四、AsyncTask

AsyncTask:异步任务,从字面上来说,就是在我们的UI主线程运行的时候,异步的完成一些操作。AsyncTask允许我们的执行一个异步的任务在后台。我们可以将耗时的操作放在异步任务当中来执行,并随时将任务执行的结果返回给我们的UI线程来更新我们的UI控件。通过AsyncTask我们可以轻松的解决多线程之间的通信问题。

怎么来理解AsyncTask呢?通俗一点来说,AsyncTask就相当于Android给我们提供了一个多线程编程的一个框架,其介于Thread和Handler之间,我们如果要定义一个AsyncTask,就需要定义一个类来继承AsyncTask这个抽象类,并实现其唯一的一个 doInBackgroud 抽象方法。要掌握AsyncTask,我们就必须要一个概念,总结起来就是: 3个泛型,4个步骤。

3个泛型指的是什么呢?我们来看看AsyncTask这个抽象类的定义,当我们定义一个类来继承AsyncTask这个类的时候,我们需要为其指定3个泛型参数:

- Params: 这个泛型指定的是我们传递给异步任务执行时的参数的类型

- Progress: 这个泛型指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型

- Result: 这个泛型指定的异步任务执行完后返回给UI线程的结果的类型

我们在定义一个类继承AsyncTask类的时候,必须要指定好这三个泛型的类型,如果都不指定的话,则都将其写成Void,例如:

4个步骤:当我们执行一个异步任务的时候,其需要按照下面的4个步骤分别执行

- onPreExecute(): 这个方法是在执行异步任务之前的时候执行,并且是在UI Thread当中执行的,通常我们在这个方法里做一些UI控件的初始化的操作,例如弹出要给ProgressDialog

- doInBackground(Params... params): 在onPreExecute()方法执行完之后,会马上执行这个方法,这个方法就是来处理异步任务的方法,Android操作系统会在后台的线程池当中开启一个worker thread来执行我们的这个方法,所以这个方法是在worker thread当中执行的,这个方法执行完之后就可以将我们的执行结果发送给我们的最后一个 onPostExecute 方法,在这个方法里,我们可以从网络当中获取数据等一些耗时的操作

- onProgressUpdate(Progess... values): 这个方法也是在UI Thread当中执行的,我们在异步任务执行的时候,有时候需要将执行的进度返回给我们的UI界面,例如下载一张网络图片,我们需要时刻显示其下载的进度,就可以使用这个方法来更新我们的进度。这个方法在调用之前,我们需要在 doInBackground 方法中调用一个 publishProgress(Progress) 的方法来将我们的进度时时刻刻传递给 onProgressUpdate 方法来更新


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    切多道切割工艺对晶圆 TTV 厚度均匀性的提升机制与参数优化

    TTV 厚度均匀性欠佳。切多道切割工艺作为一种创新加工方式,为提升晶圆 TTV 厚度均匀性提供了新方向,深入探究其提升机制与参数优化方法具有重要的现实意义。 二
    的头像 发表于 07-11 09:59 ?194次阅读
    <b class='flag-5'>浅</b>切多道切割工艺对晶圆 TTV 厚度均匀性的提升<b class='flag-5'>机制</b>与参数优化

    多线程的安全注意事项

    多线程安全是指多个线程同时访问或修改共享资源时,能够保证程序的正确性和可靠性。 开发者选择TaskPool或Worker进行多线程开发时,在TaskPool和Worker的工作线程中导
    发表于 06-20 07:49

    工控一体机多线程任务调度优化:聚徽分享破解工业复杂流程高效协同密码

    任务调度作为工控一体机管理和协调各项任务的关键机制,其优化程度直接关乎工业生产的效率、稳定性与精准度,宛如一把关键钥匙,解锁工业复杂流程高效协同的大门。 工业复杂流程与多线程任务调度的现状剖析 现代工业场景纷繁复杂,
    的头像 发表于 05-28 14:06 ?266次阅读

    深入浅出解析:为什么高精度测量要选择12bit示波器?

    示波器是电子工程师的核心工具,能够直观观察信号特性。本文通过对比实验,分析12bit和8bit示波器在信号捕捉和波形还原方面的关键差异,解释为何高精度测量工作中,12bit示波器能提供更可靠的测量结果。8bit与12bit:数字背后的真相什么是示波器的“bit数”时?简单来说,这是示波器ADC(模数转换器)的分辨能力,决定了仪器能够识别的最小电压变化。这个看
    的头像 发表于 05-22 11:40 ?436次阅读
    <b class='flag-5'>深入浅出</b>解析:为什么高精度测量要选择12bit示波器?

    程序设计与数据结构

    的地址)出发,采用推导的方式,深入浅出的分析了广大C程序员学习和开发中遇到的难点。 2. 从方法论的高度对C语言在数据结构和算法方面的应用进行了深入讲解和阐述。 3. 讲解了绝大多数C程序员开发
    发表于 05-13 16:45

    深入浅出解析低功耗蓝牙协议栈

    深入Bluetooth LE协议栈各个组成部分之前,我们先看一下Bluetooth LE协议栈整体架构。 如上图所述,要实现一个Bluetooth LE应用,首先需要一个支持Bluetooth
    的头像 发表于 04-09 14:49 ?635次阅读
    <b class='flag-5'>深入浅出</b>解析低功耗蓝牙协议栈

    2025 中国华东智能家居创新技术研讨会现场直击,高精度算法如何改变生活?--其利天下

    在此次展会上,我司技术总监冯建武先生带来了《智能家居的 “心脏” 革命:高精度电机驱动算法如何重塑未来生活》的演讲,深入浅出地阐述了我司目前在无刷马达自适应算法、FOC控制算法等技术上的优势,以及在智能家居领域上的发展战略。
    的头像 发表于 03-30 11:11 ?581次阅读
    2025 中国华东智能家居创新技术研讨会现场直击,高精度算法如何改变生活?--其利天下

    《零基础开发AI Agent——手把手教你用扣子做智能体》

    《零基础开发AI Agent——手把手教你用扣子做智能体》是一本为普通人量身打造的AI开发指南。它不仅深入浅出地讲解了Agent的概念和发展,还通过详细的工具介绍和实战案例,帮助读者快速掌握
    发表于 03-18 12:03

    请问如何在Python中实现多线程与多进程的协作?

    大家好!我最近在开发一个Python项目时,需要同时处理多个任务,且每个任务需要不同的计算资源。我想通过多线程和多进程的组合来实现并发,但遇到了一些问题。 具体来说,我有两个任务,一个是I/O密集型
    发表于 03-11 06:57

    请问rt-thread studio如何进行多线程编译?

    ,使用的是5800h+32g内存+sn550 ssd,开启16线程编译时cpu的占用率也只能到30%,编译完整个工程需要3分钟 感觉多线程编译设置没有生效,有办法提高编译速度吗
    发表于 02-19 08:30

    深居AutoCAD二次开发

    深居AutoCAD二次开发,net版
    发表于 01-06 14:12 ?9次下载

    深入浅出RISC-V调试

    一、JTAG简介 目前RISC-V官方支持的调试方式是JTAG(Joint Test Action Group),而ARM支持的调试方式有JTAG和SWD(Serial Wire Debug)这两种。 JTAG是一种国际标准的调试方式(IEEE1149.1),而SWD是ARM开发的。标准JTAG采用四线方式,分别是TCK、TMS、TDI和TDO,有一个可选的TRST引脚。 ● TCK:测试时钟输入。 ● TMS:测试模式选择。 ● TDI:测试数据输入。 ● TDO:测试数据输出。 在调试时需要用到一个工具,比如JLink或者CMSIS-DAP,对于这个工具,在这里称为JTAG主机(JTAG host),而嵌入在芯片内部的JTAG称为JTAG从机(JTAG slave),需要注意的是上面这些信号的输入输出方向是对于JTAG从机来说的。下文中如无特别说明,JTAG都是指JTAG从机。 一个JTAG主机可以同时对多个JTAG从机进行调试,这通过JTAG扫描链(JTAG Scan Chain)完成,如图1所示。 图1 一个JTAG主机连接多个JTAG从机 JTAG内部有一个TAP(Test Access Port)控制器(或者说状态机),通过TCK和TMS信号来改变状态机的状态。这个状态机的核心是两路SCAN,分别是IR SCAN和DR SCAN,TAP状态机如图2所示。 图2 TAP状态机 箭头上的0或1表示的是TMS信号的电平。JTAG在每一个TCK信号的上升沿采样TMS信号和TDI信号,决定状态机的状态是否发生变化,在每一个TCK信号的下降沿输出TDO信号。可以看到,无论TAP目前处于哪一个状态,只要TMS保持高电平并持续5个TCK时钟,则TAP一定会回到Test-Logic-Reset状态。 JTAG内部有一个IR(instruction register)寄存器和多个DR(data register)寄存器,IR寄存器决定要访问的是哪一个DR寄存器。DR寄存器有IDCODE、BYPASS等。在Test-Logic-Reset状态下IR寄存器默认选择的是IDCODE这个DR寄存器。 JTAG主机通过IR SCAN设置IR寄存器的值,然后通过DR SCAN来读、写相应的DR寄存器。 二、RISC-V调试Spec 调试模块在CPU芯片设计里是最为不起眼的,但又是最为复杂的模块之一,大部分开源的处理器IP都没有调试模块。 下面的内容基于RISC-V debug spec 0.13版本。 目前RISC-V的官方调试上位机是openocd,调试工具可以是JLink或者CMSIS-DAP,RISC-V调试系统框架如图3所示。 图3 RISC-V调试系统框架 可以看到主要分为3个部分,分别是Debug Host,可以理解为PC;Debug Hardware,可以理解为JLink或者CMSIS-DAP这样的调试工具;第三部分就是嵌入在芯片内部的调试模块。在调试模块内部,与调试工具直接交互的是DTM模块,DTM模块通过DMI接口与DM模块交互。 1>DTM模块 在DTM模块里实现了一个TAP控制器(状态机),其中IR寄存器的长度最少为5位,当TAP控制器复位时,IR的值默认为5\'b00001,即选择的是IDCODE寄存器。DTM模块的寄存器(DR寄存器)定义如图4所示。 图4 DTM寄存器 其中红色框起来的寄存器是必须要实现的。下面简单介绍一下这几个寄存器。 ① IDCODE寄存器(0x01) 当TAP状态机复位时,IR寄存器的值默认为0x01,即选择的是IDCODE寄存器。IDCODE寄存器的每一位含义如图5所示。IDCODE是只读寄存器。 图5 IDCODE寄存器 ● Version:只读,版本号,可为任意值。 ● PartNumber:只读,可为任意值。 ● Manufld:只读,厂商号,遵循JEP106标准分配,实际中可为任意值,只要不与已分配的厂商号冲突即可。 ② DTM控制和状态寄存器(dtmcs,0x10) dtmcs寄存器的每一位含义如图6所示。 图6 dtmcs寄存器 ● dmihardreset:DTM模块硬复位,写1有效。 ● dmireset:清除出错,写1有效。 ● idle:只读,JTAG 主机在Run-Test-Idle状态停留的时钟周期数,0表示不需要进入Run-Test-Idle状态,1表示进入Run-Test-Idle状态后可以马上进入下一个状态,以此类推。 ● dmistat:只读,上一次操作的状态。0表示无出错,1或者2表示操作出错,3表示操作还未完成。 ● abits:只读,dmi寄存器中address域的大小(位数)。 ● version:只读,实现所对应的spec版本,0表示0.11版本,1表示0.13版本。 ③ DM模块接口访问寄存器(dmi,0x11) dmi寄存器的每一位含义如图7所示。 图7 dmi寄存器 ● address:可读可写,DM寄存器的长度(位数)。 ● data:可读可写,往DM寄存器读、写的数据,固定为32位。 ● op:可读可写,读或者写这个域时有不同的含义。当写这个域时,写0表示忽略address和data的值,相当于nop操作;写1表示从address指定的寄存器读数据;写2表示把data的数据写到address指定的寄存器。写3为保留值。当读这个域时,0表示上一个操作正确完成;1为保留值;2表示上一个操作失败,这个状态是会被记住的,因此需要往dtmcs寄存器的dmireset域写1才能清除这个状态。3表示上一个操作还未完成。 在Update-DR状态时,DTM开始执行op指定的操作。在Capture-DR状态时,DTM更新data域。 ④ BYPASS寄存器(0x1f) 只读,长度为1,值固定为0。 2>DM模块 从图3可知,DM模块访问RISC-V Core有两种方式,一种是通过abstract command,另一种是通过system bus。abstract command方式是必须要实现的,system bus的方式是可选的。 DM模块的寄存器都为32位,定义如图8所示。 图8 DM寄存器 下面介绍一下红色框起来这几个重要的寄存器。 ① data寄存器(data0-data11,0x04-0x0f) 这12个寄存器是用于abstract command的数据寄存器,长度为32位,可读可写。 ② DM控制寄存器(dmcontrol,0x10) dmcontrol寄存器的每一位含义如图9所示。 图9 dmcontrol寄存器 ● haltreq:只写,写1表示halt(暂停)当前hart(hart表示CPU核,存在多核的情况)。 ● resumereq:只能写1,写1表示resume(恢复)当前hart,即go。 ● hartreset:可读可写,写1表示复位DM模块,写0表示撤销复位,这是一个可选的位。 ● ackhavereset:只能写1,写1表示清除当前hart的havereset状态。 ● hasel:可读可写,0表示当前只有一个已经被选择了的hart,1表示当前可能有多个已经被选择了的hart。 ● hartsello:可读可写,当前选择的hart的低10位。1位表示一个hart。 ● hartselhi:可读可写,当前选择的hart的高10位。1位表示一个hart。如果只有一个hart,那么hasel的值为0,hartsello的值为1,hartselhi的值为0。 ● setresethaltreq:只能写1,写1表示当前选择的hart复位后处于harted状态。 ● clrresethaltreq:只能写1,写1表示清除setresethaltreq的值。 ● ndmreset:可读可写,写1表示复位整个系统,写0表示撤销复位。 ● dmactive:可读可写,写0表示复位DM模块,写1表示让DM模块正常工作。正常调试时,此位必须为1。 ③ DM状态寄存器(dmstatus,0x11) dmstatus寄存器是一个只读寄存器,每一位含义如图10所示。 图10 dmstatus寄存器 ● impebreak:1表示执行完progbuf的指令后自动插入一条ebreak指令,这样就可以节省一个progbuf。当progbufsize的值为1时,此值必须为1。 ● allhavereset:1表示当前选择的hart已经复位。 ● anyhavereset:1表示当前选择的hart至少有一个已经复位。 ● allresumeack:1表示当前选择的所有hart已经应答上一次的resume请求。 ● anyresumeack:1表示当前选择的hart至少有一个已经应答上一次的resume请求。 ● allnonexistent:1表示当前选择的hart不存在于当前平台。 ● anynonexistent:1表示至少有一个选择了的hart不存在于当前平台。 ● allunavail:1表示当前选择的hart都不可用。 ● anyunavail:1表示至少有一个选择了的hart不可用。 ● allrunning:1表示当前选择的hart都处于running状态。 ● anyrunning:1表示至少有一个选择了的hart处于running状态。 ● allhalted:1表示当前选择的hart都处于halted状态。 ● anyhalted:1表示至少有一个选择了的hart处于halted状态。 ● authenticated:0表示使用DM模块之前需要进行认证,1表示已经通过认证。 ● authbusy:0表示可以进行正常的认证,1表示认证处于忙状态。 ● hasresethaltreq:1表示DM模块支持复位后处于halted状态,0表示不支持。 ● confstrptrvalid:1表示confstrptr0~3寄存器保存了配置字符串的地址。 ● version:0表示DM模块不存在,1表示DM模块的版本为0.11,2表示DM模块的版本为0.13。 ④ abstract控制和状态寄存器(abstractcs,0x16) abstractcs寄存器定义如图11所示。 图11 abstractcs寄存器 ● progbufsize:只读,program buffer的个数,取值范围为0~16,每一个的大小为32位。 ● busy:只读,1表示abstract命令正在执行,当写command寄存器后该位应该马上被置位直到命令执行完成。 ● cmderr:可读、只能写1,cmderr的值仅当busy位为0时有效。0表示无错误,1表示正在操作command、abstractcs、data或者progbuf寄存器,2表示不支持当前命令,3表示执行命令时出现异常,4表示由于当前hart不可用,或者不是处于halted/running状态而不能被执行,5表示由于总线出错(对齐、访问大小、超时)导致的错误,7表示其他错误。写1清零cmderr。 ● datacount:只读,所实现的data寄存器的个数。 ⑤ abstract命令寄存器(command,0x17) 当写这个寄存器时,相应的操作就会被执行。command寄存器只能写,定义如图12所示。 图12 command寄存器 ● cmdtype:只写,命令类型,0为表示访问寄存器,1表示快速访问,2表示访问内存。 ● control:只写,不同的命令类型有不同的含义,说明如下。 当cmdtype为0时,control定义如图13所示。 图13 访问寄存器 ● cmdtype:值为0。 ● aarsize:2表示访问寄存器的最低32位,3表示访问寄存器的最低64位,4表示访问寄存器的最低128位。如果大于实际寄存器的大小则此次访问是失败的。 ● aarpostincrement:1表示成功访问寄存器后自动增加regno的值。 ● postexec:1表示执行progbuf里的内容(指令)。 ● transfer:0表示不执行write指定的操作,1表示执行write指定的操作。 ● write:0表示从指定的寄存器拷贝数据到arg0指定的data寄存器。1表示从arg0指定的data寄存器拷贝数据到指定的寄存器。 ● regno:要访问的寄存器。 综上,可知: Ⅰ. 当write=0,transfer=1时,从regno指定的寄存器拷贝数据到arg0对应的data寄存器。 Ⅱ. 当write=1,transfer=1时,从arg0对应的data寄存器拷贝数据到regno指定的寄存器。 Ⅲ. 当aarpostincrement=1时,将regno的值加1。 Ⅳ. 当postexec=1时,执行progbuf寄存器里的指令。 arg对应的data寄存器如图14所示。 图14 arg对应的data寄存器 即当访问的寄存器位数为32位时,arg0对应data0寄存器,arg1对应data1寄存器,arg2对应data2寄存器。 当cmdtype为1时,control定义如图15所示。 图15 快速访问 ● cmdtyte:值为1。 此命令会执行以下操作: 1)halt住当前hart。 2)执行progbuf寄存器里的指令。 3)resume当前hart。 当cmdtype为2时,control定义如图16所示。 图16 访问内存 ● cmdtype:值为2。 ● aamvirtual:0表示访问的是物理地址,1表示访问的是虚拟地址。 ● aamsize:0表示访问内存的低8位,1表示访问内存的低16位,2表示访问内存的低32位,3表示访问内存的低64位,4表示访问内存的低128位。 ● aampostincrement:1表示访问成功后,将arg1对应的data寄存器的值加上aamsize对应的字节数。 ● write:0表示从arg1指定的地址拷贝数据到arg0指定的data寄存器,1表示从arg0指定的data寄存器拷贝数据到arg1指定的地址。 ● target-specific:保留。 综上,可知: Ⅰ. 当write=0时,从arg1指定的地址拷贝数据到arg0指定的data寄存器。 Ⅱ. 当write=1时,从arg0指定的data寄存器拷贝数据到arg1指定的地址。 Ⅲ. 当aampostincrement=1时,增加arg1对应的data寄存器的值。 ⑥ 系统总线访问控制和状态寄存器(sbcs,0x38) sbcs寄存器定义如图17所示。 图17 sbcs寄存器 ● sbversion:只读,0表示system bus是2018.1.1之前的版本,1表示当前debug spec的版本,即0.13版本。 ● sbbusyerror:只读,写1清零,当debugger要进行system bus访问操作时,如果上一次的system bus访问还在进行中,此时会置位该位。 ● sbbusy:只读,1表示system bus正在忙。在进行system bus访问前必须确保该位为0。 ● sbreadonaddr:可读可写,1表示每次往sbaddress0寄存器写数据时,将会自动触发system bus从新的地址读取数据。 ● sbaccess:可读可写,访问的数据宽度,0表示8位,1表示16位,2表示32位,3表示64位,4表示128位。 ● sbautoincrement:可读可写,1表示每次system bus访问后自动将sbaddress的值加上sbaccess的大小(字节)。 ● sbreadondata:可读可写,1表示每次从sbdata0寄存器读数据后将自动触发system bus从新的地址读取数据。 ● sberror:可读,写1清零,0表示无错误,1表示超时,2表示访问地址错误,3表示地址对齐错误,4表示访问大小错误,7表示其他错误。 ● sbasize:只读,system bus地址宽度(位数),0表示不支持system bus访问。 ● sbaccess128:只读,1表示system bus支持128位访问。 ● sbaccess64:只读,1表示system bus支持64位访问。 ● sbaccess32:只读,1表示system bus支持32位访问。 ● sbaccess16:只读,1表示system bus支持16位访问。 ● sbaccess8:只读,1表示system bus支持8位访问。 ⑦ 系统总线地址0寄存器(sbaddress0,0x39) 可读可写,如果sbcs寄存器中的sbasize的值为0,那么此寄存器可以不用实现。 当写该寄存器时,会执行以下流程: Ⅰ. 设置sbcs.sbbusy的值为1。 Ⅱ. 从新的sbaddress地址读取数据。 Ⅲ. 如果读取成功并且sbcs.sbautoincrement的值为1,则增加sbaddress的值。 Ⅳ. 设置sbcs.sbbusy的值为0。 ⑧ 系统总线数据0寄存器(sbdata0,0x3c) 可读可写,如果sbcs寄存器中的所有sbaccessxx的值都为0,那么此寄存器可以不用实现。 当写该寄存器时,会执行以下流程: Ⅰ. 设置sbcs.sbbusy的值为1。 Ⅱ. 将sbdata的值写到sbaddress指定的地址。 Ⅲ. 如果写成功并且sbcs.sbautoincrement的值为1,则增加sbaddress的值。 Ⅳ. 设置sbcs.sbbusy的值为0。 当读该寄存器时,会执行以下流程: Ⅰ. 准备返回读取的数据。 Ⅱ. 设置sbcs.sbbusy的值为1。 Ⅲ. 如果sbcs.sbautoincrement的值为1,则增加sbaddress的值。 Ⅳ. 如果sbcs.sbreadondata的值为1,则开始下一次读操作。 Ⅴ. 设置sbcs.sbbusy的值为0。 三、RISC-V调试上位机分析 RISC-V官方支持的调试器上位机是openocd。openocd是地表最强大(没有之一)的开源调试上位机,支持各种target(ARM(M、A系列)、FPGA、RISC-V等),支持各种调试器(Jlink、CMSIS-DAP、FTDI等),支持JTAG和SWD接口。 这里不打算详细分析整个openocd的实现,只是重点关注针对RISC-V平台的初始化、读写寄存器和读写内存这几个流程。 1>openocd启动过程 openocd启动时需要通过-f参数制定一个cfg文件,比如: openocd.exe -f riscv.cfg riscv.cfg文件的内容如下: adapter_khz1000 reset_config srst_only adapter_nsrst_assert_width 100 interface cmsis-dap transport select jtag set _CHIPNAME riscv jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x1e200a6d set _TARGETNAME $_CHIPNAME.cpu target create $_TARGETNAME riscv -chain-position $_TARGETNAME ■ 第一行设置TCK的时钟为1000KHz。 ■ 第二行表示不支持通过TRST引脚复位,只支持TMS为高电平并持续5个TCK时钟这种方式的复位。 ■ 第三行是复位持续的延时。 ■ 第四行指定调试器为CMSIS-DAP。 ■ 第五行指定调试接口为JTAG。 ■ 第六行指定调试的target类型为riscv。 ■ 第七行指定生成一个IR寄存器长度为5位、IDCODE为0x1e200a6d的JTAG TAP。 ■ 第八、九行指定生成一个riscv target。 openocd启动时的主要流程如图18所示。 图18 openocd启动流程 下面重点关注一下examine target这个流程。 这里的target是指riscv,对于riscv,首先会读取dtmcontrol这个寄存器,因为openocd支持0.11和0.13版本的DTM,通过这个寄存器可以知道当前调试的DTM是哪一个版本。这里选择0.13版本来分析。通过读取dtmcontrol,还可以知道idle、abits这些参数。接下来会将dmcontrol这个寄存器的dmactive域写0后再写1来复位DM模块。接下来再读取dmstatus,判断version域是否为2。接下来还会读取sbcs和abstractcs寄存器,最后就是初始化每一个hart的寄存器。 2>read register过程 读寄存器时,先构建command寄存器的内容,首先将cmdtype的值设为0,aarsize的值设为2(寄存器的宽度为32位),transfer的值设为1,regno的值设为要读的寄存器的number,其他值设为0,然后写到command寄存器里。然后一直读取abstractcs寄存器,直到abstractcs寄存器的busy位为0或者超时。然后再判断abstractcs寄存器的cmderr的值是否为0,如果不为0则表示此次读取寄存器失败,如果为0则继续读取data0寄存器,这样就可以得到想要读的寄存器的值。 3>write register过程 写寄存器时,先将需要写的值写到data0寄存器,然后构建command寄存器的内容,首先将cmdtype的值设为0,aarsize的值设为2(寄存器的宽度为32位),transfer的值设为1,write的值设为1,regno的值设为要写的寄存器的number,其他值设为0,然后写到command寄存器里。然后一直读取abstractcs寄存器,直到abstractcs寄存器的busy位为0或者超时。然后再判断abstractcs寄存器的cmderr的值是否为0,如果不为0则表示此次写寄存器失败,如果为0则表示写寄存器成功。 4>read memory过程 如果progbufsize的值大于等于2,则会优先使用通过执行指令的方式来读取内存。这里不分析这种方式,而是分析使用system bus的方式。通过前面的分析可知,system bus有两个版本V0和V1,这里以V1版本来说明。 先将sbcs寄存器的sbreadonaddr的值设为1,sbaccess的值设为2(32位),然后将要读内存的地址写入sbaddress0寄存器。接着读sbdata0寄存器,最后读sbcs寄存器,如果其中的sbbusy、sberror和sbbusyerror都为0,则从sbdata0读取到的内容就是要读的内存的值。 5>write memory过程 和read memory类似,同样以V1版本来说明。 先将要写的内存地址写到sbaddress0寄存器,然后将要写的数据写到data0寄存器,最后读sbcs寄存器,如果其中的sbbusy、sberror和sbbusyerror都为0,则此次写内存成功。 四、RISC-V JTAG的实现 通过在STM32F103C8T6上实现(模拟)RISC-V调试标准,进一步加深对RISC-V JTAG调试的理解。 使用STM32的四个GPIO作为JTAG信号的四根线,其中TCK所在的引脚设为外部中断,即上升沿和下降沿触发方式,实现了可以通过openocd以RISC-V的调试标准来访问STM32的寄存器和内存。程序流程如图19所示。 图19 JTAG实现的程序流程 五、参考资料 1、在STM32上模拟RISC-V JTAG的实现:stm32_riscv_jtag_slave 2、一个从零开始写的易懂的RISC-V处理器核:tinyriscv
    发表于 11-28 22:00

    socket 多线程编程实现方法

    在现代网络编程中,多线程技术被广泛应用于提高服务器的并发处理能力。Socket编程是网络通信的基础,而将多线程技术应用于Socket编程,可以显著提升服务器的性能。 多线程编程的基本概念 多线
    的头像 发表于 11-12 14:16 ?1156次阅读

    Python中多线程和多进程的区别

    Python作为一种高级编程语言,提供了多种并发编程的方式,其中多线程与多进程是最常见的两种方式之一。在本文中,我们将探讨Python中多线程与多进程的概念、区别以及如何使用线程池与进程池来提高并发执行效率。
    的头像 发表于 10-23 11:48 ?1130次阅读
    Python中<b class='flag-5'>多线程</b>和多进程的区别

    CPU线程和程序线程的区别

    CPU的线程与程序的线程在概念、作用、实现方式以及性能影响等方面存在显著差异。以下是对两者区别的详细阐述,旨在深入探讨这一技术话题。
    的头像 发表于 09-02 11:18 ?2326次阅读