工控网首页
>

应用设计

>

英创应用程序助手AppHelper使用说明

英创应用程序助手AppHelper使用说明

为保证系统稳定运行,系统CPU应避免长时间满负荷运作,应用程序CPU占用不宜过高。客户需要在调试阶段监测应用程序各个进程线程占用情况,对占用过高的进程线程进行优化。因CE自身不带进程线程系统占用查看工具,我们增加了AppHelper助手工具方便客户使用。

在之前的技术文章《CE应用程序助手简介》中简单介绍过英创AppHelper应用程序助手,本文将详细介绍AppHelper的使用方法。

AppHelper查看方法

客户在自制底板上只要引出了网络,USBOTG,DEBUG调试串口,或板子其它串口任意之一便可以查看AppHelper信息。

网络方式

通过telnet登录上板子,运行命令sysinfo,即可获得AppHelper打印的进程线程信息。

telnet模式打印示例图

USBOTG方式

使用AHC工具(使用方法见本文下一节)配置AppHelper输出为COM1。连接上板子USBOTG口,板子将以虚拟串口形式被PC识别。使用任意串口工具向该串口输出任意三个字符(任意波特率),即可获得AppHelper打印的进程线程信息。

USBOTG,DEBUG及其它串口打印示例图

DEBUG调试串口方式

使用AHC工具(使用方法见本文下一节)配置AppHelper输出为DEBUG。连接板子的DEBUG串口,PC端使用任意串口工具,设置波特率115200,向DEBUG口输出任意三个字符,即可获得AppHelper打印的进程线程信息。

串口方式

将底板上引出,且客户应用程序未使用的串口连接上PC。使用AHC工具(使用方法见本文下一节)配置好串口号及波特率。PC端使用任意串口工具,用设定的波特率向该串口输出任意三个字符,即可获得AppHelper打印的进程线程信息。

AHC工具使用介绍

AHC工具即AppHelper Config工具,用于设置AppHelper打印信息的输出位置。有两种办法进行设置。

控制面板方式

在板子控制面板中运行AHC工具。

  选择好输出信息的串口及波特率(其中COM1为USBOTG),点击OK键保存配置,板子重启后配置生效。

telnet方式

通过telnet登录上板子,执行命令AHC port [baud]

参数port:串口号,值为0-6,0表示DEBUG串口,1表示USBOTG转虚拟串口,2-6分别表示板子的COM2-COM6。

参数baud:波特率,可选参数,如果不填表示保持原波特率,支持1200,2400,4800,9600,19200,38400,57600,115200。当port为0时,baud固定为115200,当port为1时,baud值不生效。

命令执行后,DEBUG口可以看到打印提示信息。

打印格式说明

打印结果为数行,其中每行的格式均为:类型 ID号 占用情况 名称

以下图一次打印的部分截图为例:

类型

PID表示为process进程。TID表示为上面进程下的thread线程。

ID号

即进程ID值或线程ID值。

占用情况

显示格式为 K n% U m% total%

n值为该进程或线程在Kernel系统层的占用

m值为该进程或线程在User用户层的占用

total值为总占用,它应当等于n+m的和

进程下各个线程total占用和应当等于进程的total占用

名称

进程名即EXE的名称,线程默认没有名称,下一节会介绍如何给线程命名,从而能在AppHelper中显示出来。

进程及线程监视说明

AppHelper会打印系统下所有的进程的CPU占用信息。

只有在\NandFlash目录下的exe生成的进程会额外打印出它下面所有线程的CPU占用信息。

默认情况下,生成的线程只有ID号,没有名称,如果线程较多会不便于查看。我们可以通过简单代码给线程命名。

以光盘里的串口例程SPT_HEX为例:

添加一个结构体的定义

  typedef struct _THREAD_INDEX

  {

    DWORDdwSize;

    DWORDdwThreadID;

    TCHARszThreadName[32];

    _THREAD_INDEX*pNext;

    }THREAD_INDEX;

在创建线程后给线程命名

这里把串口接收线程命名为"CommRecvTread"

hRecvThread = CreateThread(0, 0, CommRecvTread, this, 0, &m_dwTID);

  HANDLE hHLP;

  DWORD dwLen;

  hHLP = CreateFile(L"HLP1:", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);

  THREAD_INDEXthreadIndex;

  wsprintf(threadIndex.szThreadName, L"CommRecvTread");

  threadIndex.dwThreadID = m_dwTID;

  threadIndex.dwSize = sizeof(THREAD_INDEX);

  WriteFile(hHLP, &threadIndex, sizeof(THREAD_INDEX), &dwLen, NULL);

  CloseHandle(hHLP);

在结束线程后取消命名

线程结束后应当手动将命名取消掉,避免不必要的显示错误,设置线程名为空,即可取消原命名。

  HANDLE hHLP;

  DWORD dwLen;

  hHLP = CreateFile(L"HLP1:", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);

  THREAD_INDEXthreadIndex;

  wsprintf(threadIndex.szThreadName, L"");

  threadIndex.dwThreadID = m_dwTID;

  threadIndex.dwSize = sizeof(THREAD_INDEX);

  WriteFile(hHLP, &threadIndex, sizeof(THREAD_INDEX), &dwLen, NULL);

  CloseHandle(hHLP);

命名线程后再使用AppHelper查看,启动接收线程后,就可以看到CommRecvTread这个线程,另外个没有命名的线程为SerialPort程序的主线程。

计算原理及误差说明

CPU占用时间是通过计算一段时间内(AppHelper设置为2000毫秒)CPU空闲tick值与这段时间里CPU运算周期tick值得出。

CPU空闲tick值 = CPU空闲tick计数t2 – CPU空闲tick计数t1

CPU总周期tick值 = CPU总周期tick计数t2 – CPU总周期tick计数t1

CPU占用 = 1 – (CPU空闲tick值/CPU总周期tick值)× 100%

进程或线程的CPU占用,是通过计算一段时间CPU运算周期tick值,和这段周期里Kernel或User运行线程或进程的tick值,通过相除得到。

进程/线程Kernel占用 = (进程/线程Kernel运行tick值/CPU总周期tick值)× 100%

进程/线程User占用 = (进程/线程User运行tick值/CPU总周期tick值)× 100%

进程/线程CPU占用 = 进程/线程Kernel占用 + 进程/线程User占用

打印结果可能会有少量误差,可能由于以下原因:

1、实验值计算到个位,小数部分四舍五入,所以可能产生细微的误差。

2、理想中的测量情况如下图

但是实际情况由于AppHelper本身也会产生系统消耗,所以测量情况为下图

在Δt值不为0的情况下,如果在Δt期间各个tick值产生较大跳动时,测试结果可能产生误差。

3、各个进程或线程的运行tick值并非完全实时变化,而是在进程或线程完成一个时间片挂起后才加上,所以查询函数获得值不一定非常精确。

测试程序及说明

test_prc_thd是一个简单的程序,用来测试AppHelper的进程线程监视功能。

“添加线程”按钮按下会创建一个新的线程。参数中传入线程编号,线程ID等信息。

void Ctest_prc_thdDlg::OnBnClickedButton1()

  {

    DWORD dwTID;

    HANDLE hTestThread;

    m_threadParam[m_dwCnt].dwCnt = m_dwCnt;

    m_threadParam[m_dwCnt].dwLv = m_dwCnt;

    m_threadParam[m_dwCnt].bThreadStop = FALSE;

    hTestThread = CreateThread(0, 0, TestTread, &m_threadParam[m_dwCnt], CREATE_SUSPENDED, &dwTID);

    m_threadParam[m_dwCnt].dwThreadID = dwTID;

    ResumeThread(hTestThread);

    CloseHandle(hTestThread);

  }

线程主函数里根据编号给线程自身命名,并且根据各自传入的参数执行负载率不等的计算。这里计算采用简单的循环计数,循环计算次数越多,线程CPU占用越多。

线程结束后,取消自身的命名。

  DWORD Ctest_prc_thdDlg::TestTread(LPVOID lparam)

  {

    THREAD_PARAM *pThreadParam = (THREAD_PARAM*)lparam;

    DWORDdwLen;

    THREAD_INDEXthreadIndex;

    HANDLE hHLP;

    volatile int n;

    hHLP = CreateFile(L"HLP1:", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);

    wsprintf(threadIndex.szThreadName, L"thread%d", pThreadParam->dwCnt);

    threadIndex.dwThreadID = pThreadParam->dwThreadID;

    threadIndex.dwSize = sizeof(THREAD_INDEX);

    WriteFile(hHLP, &threadIndex, sizeof(THREAD_INDEX), &dwLen, NULL);

    CloseHandle(hHLP);

    HANDLEhFile;

    WCHARwsFileName[64];

    inti;

    while(!pThreadParam->bThreadStop)

    {

      for (i=0; i<=pthreadparam->dwCnt; i++ )

      {

        int j;

        for (j=0;j<0xfffff;j++)< p="">

        {

          n++;

        }

      }

      Sleep(100);

    }

    hHLP = CreateFile(L"HLP1:", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);

    sprintf(threadIndex.szThreadName, L"");

    threadIndex.dwThreadID = pThreadParam->dwThreadID;

    threadIndex.dwSize = sizeof(THREAD_INDEX);

    WriteFile(hHLP, &threadIndex, sizeof(THREAD_INDEX), &dwLen, NULL);

    CloseHandle(hHLP);

    return 0;

  }

“结束所有线程”按钮按下,通知所有线程结束运行。

  void Ctest_prc_thdDlg::OnBnClickedButton2()

  {

    int i;

    for (i=0; i<m_dwCnt; i++)

    {

      m_threadParam[i].bThreadStop = TRUE;

      Sleep(100);

    }

    m_dwCnt = 0;

  }

测试例程,下图为该程序创建多个线程后的打印情况

该程序源码可以联系英创工程师获得,客户有疑问也可以向英创工程师咨询。

投诉建议

提交

查看更多评论
其他资讯

查看更多

Windows下STM32单片机eclipse编译环境搭建

Android Studio应用开发简介

WinCE工控主板WiFi解决方案

敬请关注成都英创微信公众号

WinCE工控主板通过PPI协议连接西门子PLC