基于linux多线程调试分析的ecos多任务调试设计

1      引言

1.1    编写目的

基于jtag的ecos多任务调试设计,希望借鉴gdb现已有的对Linux多线程调试的考察,预期从设计实现、软件层次设计等方面借鉴。

本文首先对linux多线程调试设计框架进行分析;然后,具体分析linux多线程调试的实现机制;最后,从实现原理与整体设计框架上进行总结,得出CSKY的多任务调试的总体设计方案。

1.2    概述

Gdb提供了一组多线程调试命令,如表1-1所示。

表格1‑1 gdb多线程命令列表

命令

说明

Info threads

显示当前可调式的所有线程(处于stop状态)

Thread ID

切换当前线程为指定ID的线程

Thread apply ID1 ID2 command

让一个或者多个线程执行command命令

Thread apply all command

让所有线程执行command命令

Set scheduler-locking off|on|step

设置locking schedule的模式:

On——只有当前线程会运行;

Off——所有线程都会运行;

Step——当执行step操作的时候只有当前线程会运行,执行continue操作的时候所有线程都会运行。

 

多线程调试的主要任务是准确及时地捕捉引起被调试程序线程状态的变化的事件,并且GDB针对根据捕捉到的事件做出相应的操作。最终的结果就是维护一个正确的thread list。表1-1中的调试命令都是基于thread list实现的。

2      代码框架分析

2.1    总体分析

Gdb的linux多线程调试的实现主要依赖下面三个文件,如表2-1所示。

 

文件名

说明

Thread.c

多线程调试命令子集的实现

Linux-nat.c

Linux应用程序调试的实现

Linux-thread-db.c

基于pthread库的多线程部分的实现

 

Libpthread库用来管理用户态线程的,用户线程的创建和消除都需要libpthread库的支持,gdb基于此维护好用户线程的thread_list。Gdb的多线程调试命令基于thread_list实现对用户态进程的操作。而不同的系统对于用户线程的实现方式不一样,有些系统用户态线程完全是线程库实现,在内核看来只有一个调度单元,这种可以看做是用户线程与内核线程多对一;而linux内核中,采用的是内核线程与用户线程一对一的方式,一个用户线程对内核来说是一个单独的调度单元。

用户态线程的信息光光靠libpthread库中的一些信息是不够的,对于内核相关的一些信息,比如线程状态、线程的信号等。必须找到用户态线程对应的内核线程,从中获取这些信息。

Gdb中使用structptid{pid,lwp,tid}来描述一个线程,这三个域分别表示:进程id、内核线程id、用户线程id。对于linux,内核线程和用户态线程是一对一的关系,所以lwp和tid是一样的。

下面简单介绍一下gdb中linux多线程应用程序本地调试主要的三个文件:

thread.c函数非常简单,它的任务非常简单,就是多线程调试命令子集的实现,比如info threads。当用户在gdb命令行敲入多线程调试命令子集中的命令时,就会调用thread.c中对应的函数。Thread.c中的实现都是基于thread list的。而gdb对于thread list的维护工作主要在另外两个文件中实现。

Linux-nat.c可以类比于CSKYjtag调试方式下的remote-csky.c文件。它实现了通常的调试功能,比如读写寄存器、读写内存、resume程序、wait程序、attach、detach等功能。更重要的是,在linux-nat.c中会维护一个lwp_list链表,表示当前进程所有的内核线程。

而linux-thread-db.c则是基于thread_db库(libpthreadlinux的多线程库,而libthread_db是和libpthread一起提供的,二者必须配套使用。Libthread_dbthread libraryinternals暴露给gdb)的一组功能函数。这么一组函数数量不多,可以分为两类:一类是对linux-nat.c已经实现的函数基础上套了一层,在原先的基础上增加一些libthread_db相关的操作,比如resume、wait等函数;另外一类是基于libthread_db库,增加linux-nat.c中没有实现的,仅仅用在多线程调试环境下的函数,比如to_get_thread_local_address。这些函数的任务就是获取用户态线程的产生、消亡事件,已经获取用户态线程相关的数据。Linux-thread-db.c获取用户线程的发生的事件和获取的信息、结合linux-nat.c中维护的lwp_list内核线程链表中提供的信息,以此维护一个完整的thread_list。里面包含gdb感兴趣的,关于线程所有的信息。

对于软件的结构,通常逻辑上都不直接以文件为单位进行分析,gdb中也一样。Gdb中对后端函数使用struct target_ops数据结构来组织。不同功能的函数集抽象成不同的target_ops。比如专门用于操作可执行文件的”exec”target_ops,用于处理coredump文件的”core” target_ops,以及我们熟悉的jtag调试方式的”csky”target_ops(一下称为linux_ops)。而linux-nat.c中实现的linux应用程序本地调试功能也抽象成一个ops”child” target_ops,linux-thread-db.c中实现的基于libpthread库的调试功能抽象成”multi-thread”target_ops。

整个linux多线程应用程序本地调试的结构框架如图2-1所示。


2.2    模块分析

Thread.c中的调试命令的实现,在此不作赘述。linux-nat.c中实现的linux_ops实现的是基本的linux应用程序本地调试的功能函数,本文着重分析多线程,故在本节模块分析中只分析linux-thread-db.c中实现的thread_db_ops。

Multi-threads ops中的函数和数据分析如表2-2所示。

 

成员名

说明

是否在linux_ops中有实现

To_shortname

名称

Multi-thread

\

To_stratum

该ops所属的层次

Thread_stratum

\

To_detach

Detach一个进程

Thread_db_detach

Y

To_wait

等待进程

Thread_db_wait

Y

To_resume

Resume进程

Thread_db_resume

Y

To_mourn_inferior

 

Thread_db_mourn_inferior

Y

To_pid_to_str

将pid转换成字符串

Thread_db_pid_to_str

Y

To_has_thread_control

 

Tc_shecdlock

Y

To_get_thread_local_address

找到该线程的tls的地址

Thread_db_get_thread_local_address

N

To_extra_thread_info

 

Thread_db_extra_thread_info

N

To_get_ada_task_ptid

 

Thread_db_get_ada_task_ptid

N

To_find_new_thread

通过pthread库,查找新的用户空间线程

Thread_db_find_new_thread

N

 

       当被调试程序是linux多线程程序时,就会使用thread_db_ops中的相应的函数。那么问题来了,对于resume和wait这些Linux_ops中也实现的函数,会调用哪个呢?为什么呢?Gdb中实现了很多的target_ops,有功能相近的比如linux_ops和csky_ops,也有完全不同功能的,比如linux_ops和file_ops。那么对于功能相近的target_ops怎样使用呢?功能不同的target_ops之间又有怎样的关系呢?这些问题gdb的target_ops的stratum分层机制能解释这些问题。

       Gdb中把target_ops分为了7层,每一层负责不同的功能。如图所示。

 

Gdb中有一个structtarget_ops类型的全局数组target_stack[],该数组的大小是7,对应7个stratum的target_ops。Arch_stratum在最顶端,dummy_stratum在最底端通过push_target()函数将target_ops添加到该数组中。一个stratum的target_ops只能有1个。如果push_target()调用时发现该stratum的target_ops已存在,那么就覆盖它。

当gdb中要调用target_ops中的某个功能函数时,会从target_stack的最顶端开始查询,如果该stratum的target_ops中实现了该函数的话,那么调用它,否则查询下一个stratum。由此可见,如果同一个函数在较顶层和较底层的target_ops中都实现的话,那么会调用较顶层的函数。那么,现在回到linux_ops和thread_db_ops的问题,linux_ops是process_stratum,而thread_db_ops是thread_stratum,所以对于二者中都实现的函数,会调用thread_db_ops中的函数。

需要注意的是gdb一开始并不会将所有的stratum的target_ops都push到target_stack中。因为一开始并不能确定某层次的stratum应该使用哪个target_ops,要根据具体使用到的时候才能确定。比如,当gdb通过file命令添加elf文件时,那么这时候需要一个处理elf文件格式的file_stratum,这时候就把”exec”target_ops通过push_target()函数push到target_stack中。那么”multi-thread” target_ops是什么时候push进来的呢?

要push”multi-thread”target_ops,那么我们就必须确定我们调试的是多线程程序。那么怎样才能确定这件事情呢?gdb是通过被调试程序是否状态libpthread库来判断的。也就是一旦被调试程序装在libpthread库,我们就应该将”multi-thread” target_ops push进来。Gdb需要一定的机制来捕捉装载libpthread库这个事件,这个机制叫做observer-notify机制:gdb采用一种事件的observer机制,在gdb中对不同的事件都有相应care的observer_list,在observer_list中各个observer注册了对改事件的处理函数notify(),当该事件发生时,就会遍历这个observer_list,依次调用各个observer的notify()处理函数。Gdb内部的事件由很多种,包括:线程的创建、objfile的添加。而这里我们multi-thread ops的添加利用的就是,Objfile(包括main file和so file)添加事件。这里添加new objfile事件在gdb中有一个对应的observer_list叫new_objfile_subject。在observer_notify_new_objfile(objfile)函数相当于这么一个new objfile的入口函数,在该函数中遍历new_objfile_subject依次执行各个notify()处理函数,其中有个notify()函数便是thread_db_new_file(objfile)。而thread_db_new_file(objfile)就会将”multi-thread”target_ops push到target_stack中。

3      Linux多线程调试机制分析

本节主要分析linux多线程程序本地调试实现的机制。包括:如何建立被调试进程并且使其进入调试模式、如何维护内核线程列表lwp_list、如何维护用户线程列表thread_list。

3.1    调试进程建立

Gdb在调试linux应用程序时,必须以run命令开始,因为gdb为被调试程序创建进程的过程就在run命令中完成。具体的流程如图3-1所示。

3.2    Lwp_list的维护

被调试进程创建线程最终是通过clone()系统调用实现的。要维护内核线程就是要捕捉子线程的创建和死亡事件,以此来维护被调试进程的内核线程。这个捕捉事件由ptrace提供的机制实现。具体机制如图3-2所示。


       在3.1节中创建好被调试进程之后,gdb通过ptrace(PTRACE_SETOPTIONS)设置gdb会对被调试进程的哪些事情感兴趣。这里设置PTRACE_O_TRACEFORK

PTRACE_O_TRACEVFORKPTRACE_O_TRACECLONEPTRACE_O_TRACEFORKDONE。设置过后,当被调试进程创建线程的时候,就会给自己发送一个SIGTRAP信号,让被调试进程进入stop状态,使得gdb能够捕捉到这些事件。

       用户可能会疑问,从来没有见过gdb捕捉子线程创建事件,维护lwp_list过程。这是因为,Gdb对于这些事件的处理用户是不可见的,相当于是后台进行的。当完成lwp_list添加后,gdb会让程序继续运行,直到被调试程序发生一些需要通知gdb用户的事件,比如触发了用户设置的断点。

3.3    Thread_list维护

Thread_list是structthread_info类型的一个链表,记录的是被调试进程的所有线程的信息。是最终呈现给用户的信息,里面包含线程用户态和内核态的一些信息。线程用户态信息的捕获基于libpthread库实现。但是libpthread库并没有提供调试接口。对应的有一个libthread_db库,该库和libpthread一样,只不过提供了一组调试接口。这么一组libpthread_db调试接口在gdb中使用struct thread_db_info进行管理。

该数据结构的具体信息如下表3-2所示。

数据项

说明

Int pid

该数据结构描述的process id

Void *handle

Libpthread.so的dlopen的handle

Struct ps_prochandle proc_handle

该数据结构用来identify用来的<proc_service.h> interface的child process

Td_thragent_t *thread_agent

Connection to the libthread_db library

Int need_stale_parent_threads_check

 

CORE_ADDR td_create_bp_addr

Thread creation event breakpoint的位置,该地址处的代码在thread被创建时,一定会被pthread库函数调用。Gdb通过在这个地方设置断点,这样gdb就能捕获thread的创建。

CORE_ADDR td_death_bp_addr

Thread death event breakpoint的地址,同上。

一组指向libthread_db 函数的函数指针

Td_err_e(*td_init_p)

初始化函数

Td_err_e(*td_ta_new_p)

 

Td_err_e(*td_ta_map_id2thr_p)

根据pid找到对应用户态线程

Td_err_e(*td_ta_map_lwp2thr_p)

根据lwp找到对应用户态线程

Td_err_e(*td_ta_thr_iter_p)

枚举所有的用户态线程

Td_err_e(*td_ta_event_addr_p)

获取线程发生event的地址

Td_err_e(*td_ta_set_event_p)

设置care的线程事件

Td_err_e(*td_ta_event_getmsg_p)

获取发生上一个event的用户态线程id

Td_err_e(*td_thr_validate_p)

判断某用户态线程是否alive

Td_err_e(*td_thr_get_info_p)

获取用户态线程的信息

Td_err_e(*td_thr_event_enable_p)

使能用户态线程的事件监测

Td_err_e(*td_thr_tls_get_addr_p)

获取线程的tls的地址

 

在被调试进程加载libpthread库时,会为该进程创建这么一个struct thread_db_info记录该进程要使用到的libthread_db提供的调试接口。其中比较重要的是:

(1)             CORE_ADDR td_create_bp_addr和CORE_ADDRtd_death_bp_addr。这两个地址是对应libpthread库中的某个位置。当调用libpthread库创建线程或者线程死亡时,一定会分别调用这么两个addr处的代码。Gdb通过在这两个位置设置断点来捕获libpthread库的线程创建和死亡事件。

(2)             一组函数指针。这么一组函数指针对应libpthread库中的相关函数。Gdb使用dlopen()打开libthread_db库以后,以此通过dlsym()方式找到对应函数符号在libthread_db中的位置(也就是在libpthread库中的位置),然后赋值给struct thread_db_info中相应的指针。这样gdb在要调用libpthread库中的函数来获取用户态的线程信息的时候直接调用struct thread_db_info中的函数指针即可。

对于用户线程create和death事件的捕获非常简单:

I. gdb在CORE_ADDRtd_create_bp_addr和CORE_ADDR td_death_bp_addr设置断点。断点的类型为特殊类型bp_thread_event.

II. 被调试程序创建子进程或者子进程死亡,会执行到libpthread库的CORE_ADDR td_create_bp_addr或CORE_ADDRtd_death_bp_addr地址处,触发断点。线程进入stop状态

III.  gdb 通过waitpid()监测到被调试进程的状态改变,分析子进程发生的事件,判断为bp_thread_event的断点触发。如果是create,获取新创建线程struct thread_info的相关的信息,并且加入到thread_list中;如果是death,从thread_list中删除该线程。

4      Ecos多任务调试机制分析

eCos(EmbeddedConfigurable Operation System)是一款开源的实时操作系统。相对linux系统,ecos更精简,可配置。Ecos像linux一样支持多任务,具有任务调度的功能,但是ecos和linux的区别在于ecos系统的多任务并不支持独立的进程空间,也没有内核态和用户态的概念。Ecos内核和多任务代码编译在一起,共享内存空间,合起来组成一个程序。

Ecos多任务调试即基于任务调度功能,获取任务(以下称为线程)的状态。Ecos中使用Cyg_Thread来描述一个线程。里面包含的信息有:

 

数据域

说明

State

线程状态

Suspend_count

挂起次数

Wakeup_count

唤醒次数

Wait_info

等待的原因

Unique_id

线程的id

Stack_base

栈的基址

Stack_size

栈的大小

Stack_ptr

当前栈针

Entry_point

线程入口地址

Entry_argument

线程参数

Name

线程的名称

HAL_SavedRegisters*saved_context

寄存器(包括pc)

List_next

下一个线程

 

该类还有一个重要的静态成员staticCyg_Thread *thread_list。该指针指向ecos的线程链表。所以我们主要获取该thread_list指针,就能找到线程链表,再通过读内存的方式,以此获取每个线程的线程信息。

现在关键是要找到staticCyg_Thread *thread_list这个指针。这就要借助最终编译出来的elf文件的符号信息,通过查找符号表中Cyg_Thread ::thread_list符号的地址,就ok了。

       有时候不仅要知道整个线程链表,要明确current_thread也很重要。Ecos中有个类class Cyg_Scheduler_Base,它提供了一个static方法current_thread()返回当前线程,那么同样通过elf文件的符号表找到符号Cyg_Scheduler_Base::current_thread所在的地址,即可获得当前线程的class Cyg_Thread的对象。

       在jtag调试方式下,当触发断点后,cpu进入调试模式,gdb此时通过读内存的方式能够获取当前线程、线程列表。然后执行多线程命令。

5      Ecos多线程调试设计

5.1    框架设计

本设计基于jtag调试方式,gdb通过在线仿真器和cpu的had调试模块,实现调试功能。参照linux多线程调试实现。

(1)   Thread.c文件仍然作为多线程调试的命令的实现文件;

(2)   相对linux-nat.c,我们基于现有的jtag调试方式的实现xxx-remote.c文件,里面实现一个process_stratum的target_ops;

(3)   相对linux-thread-db.c,我们设计一个ecos-thread-db.c,实现一个ecos_thread_db ops。

这样的框架设计的好处在于,ecos_thread_dbops和process_stratum target_ops分开,这样要实现其它实时系统比如ucos的多线程调试的时候只需要增加一个ucos-thread-db.c即可。

5.2    线程列表维护

对于线程列表的维护,现在有两个方案:

(1)   程序每次进入调试模式时,都扫描一遍ecos的thread_list,使得gdb中的thread_list和它同步。

(2)   能够像linux多线程事件捕捉一样,能不能通过信号或者断点的方式,gdb后台捕捉ecos的线程创建和线程死亡事件,以此维护thread_list(有待对ecos系统进行进一步考察)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。 Quartz的优势: 1、Quartz是一个任务调度框架(库),它几乎可以集成到任何应用系统中。 2、Quartz是非常灵活的,它让您能够以最“自然”的方式来编写您的项目的代码,实现您所期望的行为 3、Quartz是非常轻量级的,只需要非常少的配置 —— 它实际上可以被跳出框架来使用,如果你的需求是一些相对基本的简单的需求的话。 4、Quartz具有容错机制,并且可以在重启服务的时候持久化(”记忆”)你的定时任务,你的任务也不会丢失。 5、可以通过Quartz,封装成自己的分布式任务调度,实现强大的功能,成为自己的产品。6、有很多的互联网公司也都在使用Quartz。比如美团 Spring是一个很优秀的框架,它无缝的集成了Quartz,简单方便的让企业级应用更好的使用Quartz进行任务的调度。   课程说明:在我们的日常开发中,各种大型系统的开发少不了任务调度,简单的单机任务调度已经满足不了我们的系统需求,复杂的任务会让程序猿头疼, 所以急需一套专门的框架帮助我们去管理定时任务,并且可以在多台机器去执行我们的任务,还要可以管理我们的分布式定时任务。本课程从Quartz框架讲起,由浅到深,从使用到结构分析,再到源码分析,深入解析Quartz、Spring+Quartz,并且会讲解相关原理, 让大家充分的理解这个框架和框架的设计思想。由于互联网的复杂性,为了满足我们特定的需求,需要对Spring+Quartz进行二次开发,整个二次开发过程都会进行讲解。Spring被用在了越来越多的项目中, Quartz也被公认为是比较好用的定时器设置工具,学完这个课程后,不仅仅可以熟练掌握分布式定时任务,还可以深入理解大型框架的设计思想。
[入门数据分析的第一堂课]这是一门为数据分析小白量身打造的课程,你从网络或者公众号收集到很多关于数据分析的知识,但是它们零散不成体系,所以第一堂课首要目标是为你介绍:Ø  什么是数据分析-知其然才知其所以然Ø  为什么要学数据分析-有目标才有动力Ø  数据分析的学习路线-有方向走得更快Ø  数据分析的模型-分析之道,快速形成分析思路Ø  应用案例及场景-分析之术,掌握分析方法[哪些同学适合学习这门课程]想要转行做数据分析师的,零基础亦可工作中需要数据分析技能的,例如运营、产品等对数据分析感兴趣,想要更多了解的[你的收获]n  会为你介绍数据分析的基本情况,为你展现数据分析的全貌。让你清楚知道自己该如何在数据分析地图上行走n  会为你介绍数据分析分析方法和模型。这部分是讲数据分析的道,只有学会底层逻辑,能够在面对问题时有自己的想法,才能够下一步采取行动n  会为你介绍数据分析的数据处理和常用分析方法。这篇是讲数据分析的术,先有道,后而用术来实现你的想法,得出最终的结论。n  会为你介绍数据分析的应用。学到这里,你对数据分析已经有了初步的认识,并通过一些案例为你展现真实的应用。[专享增值服务]1:一对一答疑         关于课程问题可以通过微信直接询问老师,获得老师的一对一答疑2:转行问题解答         在转行的过程中的相关问题都可以询问老师,可获得一对一咨询机会3:打包资料分享         15本数据分析相关的电子书,一次获得终身学习
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值