一种非侵入的C程序死锁定位方法与流程

文档序号:17988856发布日期:2019-06-22 00:37阅读:237来源:国知局
本发明属于计算机
技术领域
:,尤其涉及linux操作系统中一种非侵入的c程序死锁定位方法。
背景技术
::国际标准化组织ieee为各种unix操作系统上运行的软件定义了一套应用程序编程接口api(applicationprogramminginterface)标准,这些标准统称为可移植操作系统接口posix(portableoperatingsysteminterfaceofunix)。在posix标准中,有1个专门用于多线程的api标准pthreads(posixthreads);在当前的各种linux操作系统中,c语言是使用较多的程序设计语言,该语言提供了遵循pthreads标准的多线程库。在linux操作系统中使用pthreads多线程库开发c程序时,如果程序设计不当,则当多个线程运行时可能发生死锁;当死锁发生后,如何对死锁进行检测就变得非常重要。目前有各种各样的死锁检测算法,针对不同的研究领域,有不同的实现方法。但在实际的应用过程中,不可避免地存在以下技术问题:(1)侵入式的死锁检测算法会对用户程序代码造成影响所谓侵入式,是指为了检测死锁需要修改用户编写的程序源代码,在程序源代码中插入新的代码来获取需要的检测数据,或者将需要的检测数据写入到文件,事后由死锁检测算法对数据进行分析以便发现死锁。这些新添加的冗余代码被称之为插桩代码。这种方式导致源代码与插桩代码混杂,使得用户难以区分两者,增加了代码管理难度。(2)非侵入式的死锁检测算法很难对导致死锁的文件名和代码行进行精确定位所谓非侵入式,是指不需要修改用户编写的程序源代码,就可以发现用户程序中的多线程死锁问题。非侵入式的死锁检测算法在linux操作系统中极少,目前能够看到的是一个叫做valgrind的软件。该软件虽然不对程序源代码进行插桩,但却对由源代码编译后生成的可执行码进行插桩,当程序运行时,这些插桩代码会输出需要的检测数据,或者将需要的数据写入文件供事后分析。这种方法由于在可执行码中添加了新的代码,使得源代码中的代码行与可执行码中的代码行并不一一对应,难以对死锁发生的代码行进行精确定位。中国发明专利“一种针对分布式系统程序死锁缺陷的测试系统及方法”(申请号201810799683x),该发明提供了一种针对分布式系统程序死锁缺陷的测试系统及方法,通过对分布式系统网络事件进行模拟,将其与单机上待测试分布式系统软件执行的锁事件相结合,之后使用基于优先级的线程调度模块将线程并行逻辑转为串行逻辑并通过死锁检测模块判断死锁,实现了在单机上对分布式系统死锁缺陷的有概率保证的检测,但该发明并不对死锁进行精确定位。中国发明专利“基于线程死锁的内存优化方法、移动终端及可读存储介质”(申请号2017112452130),该发明公开了一种基于线程死锁的内存优化方法、移动终端及可读存储介质,通过记录移动终端在每个预设使用时段内的熄屏待机时长,以建立移动终端的熄屏待机时长表,并将该熄屏待机时长表存储至预设存储区域,然后定时监测预设线程是否处于死锁状态。该发明并不涉及用户程序源代码的死锁定位问题。中国发明专利“包括死锁检测器的系统及其方法”(申请号2017109852320),该发明提供了一种基于硬件区块、死锁检测器及互连装置的死锁检测系统,但不用于对用户程序源代码的死锁检测。中国发明专利“线程检测方法、终端及计算机可读存储介质”(申请号2017104217887),该发明提供了一种线程检测方法,该方法包括以下步骤:在任务线程调用核心模块时,通过预设公共锁对所述核心模块进行锁定;开启检测线程,并通过所述检测线程周期性地尝试获取所述预设公共锁,以确定所述核心模块是否可用;若未能在预设超时时间内获取到所述预设公共锁,则确定所述核心模块不可用,并确定所述任务线程和核心模块发生了死锁。该发明用于检测出系统服务中的死锁情况,但不涉及用户程序源代码的死锁定位问题。对于那些在linux操作系统中基于pthreads编写的c程序来说,如果该程序的多个线程在运行期间发生死锁,如何设计一种既能及时发现死锁原因,又可不必修改c程序源代码(以下均简称“c代码”)就能够精准定位死锁发生的源代码文件名和代码行的非侵入式死锁检测算法,是非常有必要的和具有重要意义的。技术实现要素:本发明针对上述现有技术中存在的问题,提供一种非侵入的c程序死锁定位方法。对于那些在linux操作系统中基于pthreads编写的c程序来说,如果该程序的多个线程在运行期间发生死锁,那么不需要修改源代码,就能够准确地定位死锁发生时的源代码文件和代码行。从而有效地解决了如何能够准确地找到c程序中发生死锁的源代码文件名和代码行的问题,极大地方便了用户工作;用户根据本方法发出的死锁源代码文件和代码行的提示,对出现的问题及时进行修改,能有效地避免程序死锁。本发明的上述技术目的是通过以下技术方案实现的:一种非侵入的c程序死锁定位方法,包括如下步骤:s1:在linux操作系统中修改c程序所用的pthreads库,添加新的同步互斥函数,编译生成新的pthreads库;s2:在所述linux操作系统中修改与原同步互斥函数相关的系统头文件,使用宏定义将原同步互斥函数指向新的同步互斥函数;s3:用户使用原同步互斥函数和新的pthreads库共同编写、编译和运行多线程的c程序;s4:死锁检测程序从计算机共享内存中获取上述多线程的c程序的运行数据,若某一线程或某几个线程发生死锁,运行该死锁检测程序即可定位到该死锁线程具体的源代码文件名和代码行。进一步地,所述s1的具体步骤包括:s11:从所述linux操作系统的网站上下载c程序所用的pthreads库的源代码;s12:在所述s11的pthreads库的源代码中依次找出与临界区、读写锁和信号量相关的原同步互斥函数的实现文件;s13:分别在所述s12实现文件中设计出一个新的同步互斥函数;s14:根据所述s13中的每一个新的同步互斥函数,重新编译生成新的pthreads库;s15:将所述s14中新的pthreads库替换所述linux操作系统中原pthreads库。更进一步地,所述s13的具体步骤包括:s131:在所述实现文件中将所述s12中的每个原同步互斥函数重新拷贝一份代码并置于原同步互斥函数之后,对上述新拷贝的原同步互斥函数进行换名,形成新的同步互斥函数;s132:为所述s131中的每个新的同步互斥函数添加两个参数,分别是文件名和代码行;s133:为占用所述临界区、所述读写锁和所述信号量的每个新的同步互斥函数添加入口代码,将本线程等待所述临界区、所述读写锁和所述信号量的状态、文件名、代码行等信息写入共享内存;s134:为占用所述临界区、所述读写锁和所述信号量的每个新的同步互斥函数添加出口代码,将本线程在共享内存中等待所述临界区、所述读写锁和所述信号量的状态更改为使用状态;s135:为退出所述临界区、所述读写锁和所述信号量的每个新的同步互斥函数添加出口代码,删除本线程在所述s134的共享内存中的更新信息。进一步地,所述s2的具体步骤包括:s21:在所述linux操作系统上找到与原同步互斥函数相关的系统头文件;s22:使用c语言预处理指令#define宏定义将所述s21中原同步互斥函数重新指向所述s13中新的同步互斥函数;s23:调用原同步互斥函数,并将通过#define宏定义的原同步互斥函数的源代码文件名和代码行作为参数传给所述s22中新的同步互斥函数。进一步地,所述s3的具体步骤包括:s31:用户使用原同步互斥函数编写多线程的c代码;s32:使用新的pthreads库,将所述s31的c代码编译为可执行程序;s33:运行所述可执行程序。进一步地,所述s4的具体步骤包括:s41:设计一个死锁检测程序;s42:所述死锁检测程序从所述共享内存中获取所述s33中多线程c程序的运行数据;s43:对所述运行数据进行分析,判断运行着的多线程c程序是否发生死锁;s44:如果某一线程或某几个线程存在死锁,则输出包含该死锁线程的源代码文件名和代码行的死锁数据。更进一步地,所述s43的具体步骤包括:s431:根据所述s133的入口代码写入所述共享内存的数据查找所有处于等待状态的线程;s432:根据所述s134的出口代码写入所述共享内存的数据查找所有处于占用状态的线程;s433:如果有1个或者大于1个以上的线程处于循环占用和等待状态,则构成死锁。进一步地,所述临界区包括pthread_mutex_lock和pthread_mutex_unlock两个常用的同步互斥函数,分别表示请求进入临界区和释放临界区。进一步地,所述读写锁包括pthread_rwlock_rdlock、pthread_rwlock_wrlock和pthread_rwlock_unlock三个常用的同步互斥函数,分别表示读锁、写锁和释放锁。进一步地,所述信号量包括sem_wait和sem_post两个常用的同步互斥函数,分别表示等待信号量和释放信号量。与现有技术相比,本技术方案具有如下的优点和有益效果:当用户在linux操作系统中采用pthreads库编写的多线程c程序在运行时发生死锁,能够准确地定位c程序中发生死锁的源代码文件名和代码行;这种死锁定位方法是一种非侵入的方法,不需要在用户编写的c代码中插入其他代码,对用户程序代码没有任何影响;极大地提高了用户查找程序死锁的效率。附图说明图1是本发明一实施例的一种非侵入的c程序死锁定位方法的流程图;图2是本发明一实施例的死锁程序的原理图。具体实施方式下面结合实施例和附图对本发明做进一步说明:当前的计算机cpu通常采用多核设计技术,每个核都能独立地执行计算机指令;多个核就能并行处理多个计算机指令。随着以cpu为代表的计算机硬件技术并行化的发展,基于多线程的软件程序设计技术也日益发展和成熟。一个程序在运行时可以分成多个线程,这些线程能够同时运行在cpu的多个核中,从而有效地提高程序的运行效率。然而,当程序以多线程方式并行运行时,同步和互斥就成为了两大难题。所谓同步,是指一个线程在执行过程中要等待另外一个线程的执行结果才能继续执行。所谓互斥,是指一个程序的各个线程除了定义自己的局部变量,也可以定义由所有线程共享的全局变量;通常允许多个线程同时读一个全局变量,但任意时刻只能允许一个线程写一个全局变量,否则该变量的值就变得不确定,这种情况容易引起程序崩溃。同步和互斥会导致一个有设计缺陷的程序的多个线程发生死锁。譬如,当线程甲在使用全局变量a时,还需要使用正由线程乙使用的全局变量b;而线程乙在使用全局变量b时,还需要使用正由线程甲使用的全局变量a,这样两个线程因为互相等待对方的运行结果而会发生死锁;三个以上的线程在等待其他线程的运行结果而构成一个等待环时也会发生死锁。在c语言的pthreads多线程库中,提供了三种常用的方法来为线程间的同步和互斥提供支持,分别是临界区mutex、读写锁lock和信号量semaphore,相应的函数称之为同步互斥函数。(1)临界区mutex临界区mutex,包括pthread_mutex_lock和pthread_mutex_unlock两个常用的同步互斥函数,分别表示请求进入临界区和释放临界区。在任意时刻只能有1个线程进入临界区,进入临界区的进程可以读写全局变量,全局变量读写完成后则释放临界区。(2)读写锁lock读写锁lock,包括pthread_rwlock_rdlock、pthread_rwlock_wrlock、pthread_rwlock_unlock三个常用的同步互斥函数,分别表示读锁、写锁、释放锁。当一个线程准备读一个全局变量时,要事先调用pthread_rwlock_rdlock函数对该全局变量加1个读锁;当一个线程准备写一个全局变量时,要事先调用pthread_rwlock_wrlock函数对该全局变量加1个写锁。如果一个全局变量已经有1个读锁,则后续的写锁必须等待,但其他读锁则允许访问该全局变量;反之,如果一个全局变量已经有1个写锁,则后续的读锁和写锁必须等待,直到该写锁被释放。(3)信号量semaphore信号量semaphore,包括sem_wait和sem_post两个常用的同步互斥函数,分别表示等待和释放1个信号量。信号量常用整数表示,当该值大于0时,sem_wait请求成功,信号量值相应地减1,线程继续运行;当该值小于等于0时,线程处于等待状态。当线程调用sem_post释放信号量时,信号量值相应地加1。下面进一步介绍一种非侵入的c程序死锁定位方法,如图1所示,包括如下步骤:s1:在linux操作系统中修改c程序所用的pthreads库,添加新的同步互斥函数,编译生成新的pthreads库。当c程序调用这些新的同步互斥函数时,相应的每一个新的同步互斥函数的文件名和代码行会被写入计算机共享内存中。s2:在linux操作系统中修改与原同步互斥函数相关的系统头文件,使用宏定义将原同步互斥函数指向新的同步互斥函数。s3:用户使用原同步互斥函数和新的pthreads库共同编写、编译和运行多线程的c程序。s4:死锁检测程序从计算机共享内存中获取上述多线程的c程序的运行数据,若某一线程或某几个线程发生死锁,运行该死锁检测程序即可定位到该死锁线程具体的源代码文件名和代码行。进一步地,s1的具体步骤包括:s11:从linux操作系统的网站上下载c程序所用的pthreads库的源代码。s12:在s11的pthreads库的源代码中依次找出与临界区、读写锁和信号量相关的原同步互斥函数的实现文件,该实现文件为前述所说的同步互斥函数pthread_mutex_lock、pthread_mutex_unlock、pthread_rwlock_rdlock、pthread_rwlock_wrlock、pthread_rwlock_unlock、sem_wait和sem_post,这些同步互斥函数依次对应的实现文件为:pthread_mutex_lock.c、pthread_mutex_unlock.c、pthread_rwlock_rdlock.c、pthread_rwlock_wrlock.c、pthread_rwlock_unlock.c、sem_wait.c和sem_post.c。s13:分别在s12实现文件中设计出一个新的同步互斥函数。s14:根据s13中的每一个新的同步互斥函数,重新编译生成新的pthreads库。s15:将s14中新的pthreads库替换linux操作系统中原pthreads库。更进一步地,s13的具体步骤包括:s131:在实现文件中将s12中的每一个原同步互斥函数重新拷贝一份代码并置于原同步互斥函数之后,对上述新拷贝的原同步互斥函数进行换名,形成新的同步互斥函数。在本实施例中,新的同步互斥函数的名字可以在原同步互斥函数的名字之前添加前缀“new_”,构成新的同步互斥函数,如:new_pthread_mutex_lock、new_pthread_mutex_unlock、new_pthread_rwlock_rdlock、new_pthread_rwlock_wrlock、new_pthread_rwlock_unlock、new_sem_wait和new_sem_post。s132:为s131中的每个新的同步互斥函数添加两个参数,分别是文件名和代码行。s133:为占用临界区、读写锁和信号量的每个新的同步互斥函数添加入口代码,将本线程等待所述临界区、所述读写锁和所述信号量的状态、文件名、代码行等信息写入共享内存,即是入口代码将“线程id:资源类别:资源addr:文件名:代码行:wait”写入共享内存。“线程id”表示正在调用该函数的线程标识;“资源类别”表示4个字符串“mutex”、“readlock”、“writelock”、“semaphore”之一,分别代表临界区、读锁、写锁、信号量;“资源addr”表示一个程序中的临界区、读写锁和信号量的变量地址,用以区分每个资源,为简单起见,后面的例子中以“1”、“2”、“3”来表示;“文件名”表示调用该同步互斥函数的用户源代码文件名;“代码行”表示调用该函数的用户源代码文件中的行号;“wait”表示线程处于等待状态。这些函数包括new_pthread_mutex_lock、new_pthread_rwlock_rdlock、new_pthread_rwlock_wrlock、new_sem_wait等。s134:为占用临界区、读写锁和信号量的每个新的同步互斥函数添加出口代码,将本线程在共享内存中等待所述临界区、所述读写锁和所述信号量的状态更改为使用状态,即是出口代码将“线程id:资源类别:资源addr:文件名:代码行:use”写入共享内存。这里的“use”表示线程处于占用状态。这些同步互斥函数包括new_pthread_mutex_lock、new_pthread_rwlock_rdlock、new_pthread_rwlock_wrlock、new_sem_wait等。s135:为退出临界区、读写锁和信号量的每个新的同步互斥函数添加出口代码,删除本线程在s134的共享内存中的更新信息,即是该出口代码将“线程id:资源类别:资源addr:文件名:代码行:use”从共享内存中删除。这些同步互斥函数包括new_pthread_mutex_unlock、new_pthread_rwlock_unlock、new_sem_post等。进一步地,s2的具体步骤包括:s21:在linux操作系统上找到与原同步互斥函数相关的系统头文件。在linux操作系统中,通常为/usr/include/pthread.h和/usr/include/semaphore.h两个系统头文件。s22:使用c语言预处理指令#define宏定义将s21中原同步互斥函数重新指向s13中新的同步互斥函数,其中,所述#define宏定义是c语言中的一条预处理指令。s23:调用原同步互斥函数,并将通过#define宏定义的原同步互斥函数的源代码文件名和代码行作为参数传给s22中新的同步互斥函数。如“#definepthread_mutex_lock(x)new_pthread_mutex_lock(x,__file__,__line__)”,当用户在c程序中调用pthread_mutex_lock同步互斥函数时,其实真正调用的是new_pthread_mutex_lock同步互斥函数,除了参数x由原同步互斥函数传递给新的同步互斥函数外,调用原同步互斥函数的源代码文件名和代码行号可以由__file__和__line__两个宏定义获得,并指定给新的同步互斥函数。进一步地,所述s3的具体步骤包括:s31:用户使用原同步互斥函数编写多线程的c代码。s32:使用新的pthreads库,将s31的c代码编译为可执行程序。s33:运行所述可执行程序。进一步地,s4的具体步骤包括:s41:设计一个死锁检测程序。s42:所述死锁检测程序从共享内存中获取s33中多线程c程序的运行数据。s43:对上述运行数据进行分析,判断运行着的多线程c程序是否发生死锁。s44:如果某一线程或某几个线程存在死锁,则输出包含该死锁线程的源代码文件名和代码行的死锁数据。更进一步地,s43的具体步骤包括:s431:根据s133的入口代码写入所述共享内存的数据查找所有处于等待状态的线程,即是根据“线程id:资源类别:资源addr:文件名:代码行:wait”查找所有处于等待状态的线程。s432:根据所述s134的出口代码写入所述共享内存的数据查找所有处于占用状态的线程,即是根据“线程id:资源类别:资源addr:文件名:代码行:use”查找所有处于占用状态的线程。s433:如果有1个或者大于1个以上的线程处于循环占用和等待状态,则构成死锁。本申请不强调具体的死锁检测算法,使用保存在共享内存中的数据进行死锁检测的任何算法都可以。本实施例中,有一死锁状态如图2所示,由6条数据构成1个死锁,线程thread1已经占用临界区“mutex:1”,并且正在等待临界区“mutex:2”;线程thread2已经占用临界区“mutex:2”,并且正在等待临界区“mutex:3”;线程thread3已经占用临界区“mutex:3”,并且正在等待临界区“mutex:1”。从这6条数据可以看到,3个线程都已经占有1个临界区并且都想获得其它一个线程的已经占有的一个临界区,这样使得3个线程都无法满足想要的另一个临界区,从而发生死锁。“thread1:mutex:1:a.c:10:use”“thread1:mutex:2:b.c:20:wait”“thread2:mutex:2:b.c:25:use”“thread2:mutex:3:c.c:30:wait”“thread3:mutex:3:c.c:35:use”“thread3:mutex:1:a.c:15:wait”从上述6条数据中可以清楚地定位死锁发生时各个临界区所在的文件名分别为a.c、b.c、b.c、c.c、c.c、a.c,其相对应的代码行分别为10、20、25、30、35、15。本技术方案具有如下的优点和有益效果:当用户在linux操作系统中采用pthreads库编写的多线程c程序在运行时发生死锁,能够准确地定位c程序中发生死锁的源代码文件名和代码行;这种死锁定位方法是一种非侵入的方法,不需要在用户编写的c代码中插入其他代码,对用户程序代码没有任何影响;极大地提高了用户查找程序死锁的效率。以上对本发明实施例所提供的一种非侵入的c程序死锁定位方法,进行了详细介绍。以上实施例的说明只是用于帮助理解本发明的方法及其核心思想;同时,对于本领域的一般技术人员,依据本发明的思想,在具体实施方式及应用范围上均会有改变之处,综上所述,本说明书内容不应理解为对本发明的限制。如在说明书及权利要求当中使用了某些词汇来指称特定组件。本领域技术人员应可理解,不同机构可能会用不同名词来称呼同一个组件。本说明书及权利要求并不以名称的差异来作为区分组件的方式,而是以组件在功能上的差异来作为区分的准则。如在通篇说明书及权利要求当中所提及的“包含”为一开放式用语,故应解释成“包含但不限定于”。说明书后续描述为实施本发明的较佳实施方式,然所述描述乃以说明本发明的一般原则为目的,并非用以限定本发明的范围。本发明的保护范围当视所附权利要求所界定者为准。还需要说明的是,术语“包括”、“包含”或者其任何其他变体意在涵盖非排他性的包含,从而使得包括一系列要素的商品或者系统不仅包括那些要素,而且还包括没有明确列出的其他要素,或者是还包括为这种商品或者系统所固有的要素。在没有更多限制的情况下,由语句“包括一个……”限定的要素,并不排除在包括所述要素的商品或者系统中还存在另外的相同要素。上述说明示出并描述了本发明的若干优选实施例,但如前所述,应当理解本发明并非局限于本文所披露的形式,不应看作是对其他实施例的排除,而可用于各种其他组合、修改和环境,并能够在本文所述发明创造构想范围内,通过上述教导或相关领域的技术或知识进行改动。而本领域人员所进行的改动和变化不脱离本发明的精神和范围,则都应在本发明所附权利要求的保护范围内。当前第1页12当前第1页12
当前第1页1 2 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1