摘要
在Linux和Windows系统上开发应用程序的过程中,经常会使用到调试器来进行软件的调试。大部分时候我们打开调试器就是“打断点”和“单步跟”。其实有很多细节是容易被忽略的,本文关注其中下面几个问题:
1 大家都知道线程是操作系统的调度执行单位,进程是线程的载体。那么当程序运行到调试断点处停下来的时候,是整个进程都停止运行,还是只有当前执行的线程停止,其它线程还在照常运行?
2 当程序运行到调试断点处停下来了以后,程序创建的定时器和sleep睡眠函数还在计时吗?还会调用到超时回调函数吗?程序恢复运行后只能调用一次还是可以调多次?
如果您可以将这些问题的答案脱口而出,那我要为您点个赞,这篇文章对您来说可能太easy了。如果您觉得需要去实验一下才能给出答案,那么请往下看,相信一定会有收获。楼主会在Linux+GDB和Windows+VS2010两套平台上进行实验,并给出实验结果和分析。
Linux + GDB
先进行的是Linux平台上的实验,测试代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include <signal.h>
#include<sys/time.h>
void sigalrm_fn(int signo)
{
static int cnt = 0;
struct timeval tv;
gettimeofday(&tv,NULL);
printf("Timer is running %d %d\n", ++cnt,tv.tv_sec);
alarm(10);
}
void *Thread1(void *arg)
{
static int cnt = 0;
struct timeval tv;
while (1)
{
gettimeofday(&tv,NULL);
printf("Thread1 is running %d %d\n",++cnt,tv.tv_sec);
sleep(7);
}
return NULL;
}
int main(int agrc,char* argv[])
{
pthread_t tidp;
struct timeval tv;
static int cnt = 0;
pthread_create(&tidp,NULL,Thread1,NULL);
signal(SIGALRM, sigalrm_fn);
alarm(10);
while (1)
{
gettimeofday(&tv,NULL);
printf("main thread is running %d %d\n",++cnt,tv.tv_sec);
sleep(5);
}
return 0;
}
编译命令如下:(-g选项表示增加调试信息,给gdb使用;-lpthread是在链接pthread库)
gcc test.c -o test -g -lpthread
./test执行
Linux是基于信号来实现定时器的,不管是秒级的alarm还是据称可以精确到微秒级的settimer,都是由linux内核来进行计数,并在到达超时时间后,由内核发送一个信号到应用程序。由于对精度要求不高,楼主这里使用了alarm来实现定时器,每10秒超时一次。运行起来的log输出是这个样子的,每一行的后面那一长串数字是时间,以秒为单位
main_thread每5秒输出一次打印,Thread1每7秒输出一次打印,timer每10秒输出一次打印
所有线程都会被停住吗?
在程序运行的过程中,启动gdb,并attach到该进程上。由于gdb attach上去之后会直接把程序停住,所以和打断点停住的效果是一样的。
这时候所有的log输出都停了,这也说明了,整个进程里所有的线程全都被停止了,下面的这张图也给出了证据,上面两个红色的框里,29032是test启动时的主线程,29033是程序里创建的线程(LWP就是Light Weight Process,Linux对于线程就叫这个称呼),它们都被gdb touch到了。下面的那个红色的框表明当面进程的PC指针位置(运行到了哪里,nanosleep就是sleep函数的系统调用)
timer和sleep的计时还在继续吗?
使用gdb把程序停住之前,给一张程序log输出的截图,
图片里第一条红线上面的部分是gdb继续运行前输出的log,第一条红线和第二条红线间的3条log都是continue命令执行下去就会立刻输出的log,它们的时间都是1468563254,几乎是同时输出,这也说明了sleep函数和timer的计时还在继续,而且早已超时,但始终得不到执行。实际上,熟悉Linux的朋友肯定都知道,这些计时操作都是在内核里完成的,而gdb只是停住了一个应用程序,内核当然还是在正常运转的。
PS:gdb里有一条命令叫set scheduler-locking on它的作用如字面意思,禁止调度器,就是只允许当前选择的线程继续运行调试,其它所有的线程都停止运行。
Window +VS2010
#include "stdafx.h"
#include "windows.h"
#include <stdio.h>
#include <Mmsystem.h>
#pragma comment(lib, "Winmm.lib")
void WINAPI onTimeFunc(UINT wTimerID, UINT msg,DWORD dwUser,DWORD dwl,DWORD dw2)
{
static int cnt;
SYSTEMTIME sys;
GetLocalTime(&sys);
printf("Timer is running %d, min: %d sec: %d \n",++cnt,sys.wMinute,sys.wSecond);
}
DWORD WINAPI Thread1(LPVOID pM)
{
static int cnt = 0;
while (1)
{
SYSTEMTIME sys;
GetLocalTime(&sys);
printf("Thread1 is running %d, min: %d sec: %d \n",++cnt,sys.wMinute,sys.wSecond);
Sleep(7000);
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
static int cnt = 0;
CreateThread(NULL,0,Thread1,NULL,0,NULL);
timeSetEvent(10000, 1, (LPTIMECALLBACK)onTimeFunc, DWORD(1), TIME_PERIODIC);
while (1)
{
SYSTEMTIME sys;
GetLocalTime(&sys);
printf("main thread is running %d, min: %d sec: %d \n",++cnt,sys.wMinute,sys.wSecond);
Sleep(5000);
}
return 0;
}
在vs2010里编译通过,按F5启动调试模式。程序运行起来的log输出是这样的:(后面两个min和sec是当前系统时间的分和秒)