• 记一次代码小改进——读取按键与select、alarm等


          最近在做一个嵌入式项目,涉及按键值的读取部分,进程的CPU占用率比较高,达到50%以上,要改进一下。先用伪代码交代一下原来代码流程吧。

    //在while(1)大循环中
    //非阻塞方式
    打开设备文件,读按键值 if(vKey != -1) //有按键值, 返回 { return vKey; } else { usleep(10000); //10ms //心跳 //时间显示(有秒) }

          从代码中很明显可以知道,CPU占用率高是因为休眠的时间太少,把时间调到100ms,CPU降到30%左右。但是随之有个问题,按键反应很迟钝。休眠时间和按键反应这两者之间有矛盾。

    尝试改进一,select和timeout

    思路就是,让进程阻塞在select函数,有数据就返回,能及时反应;如果没有数据,时间超时也会返回,就去做其他事情(心跳、时间显示等)。

    tv.tv_sec = 0;
    tv.tv_usec = 500000;  //500ms
    
    int ret = select(fd_button+1, &rfds, NULL, NULL, &tv);
    
    if(ret>0)
    {
          n = read(fd_button, (char*)&bt, sizeof(BUTTON));
          //处理数据、返回键值
    }
    else
    {
          //设置了timeout时间,不休眠了
          //心跳
          //时间显示
    }

    结果不如我所料,分两种情况:(1)非阻塞打开设备文件,不按按键,没有等超时,select直接返回ret == 1,读按键数据n == 0;(2)阻塞打开设备文件,不按按键,没有等超时,select直接返回ret == 1,读按键数据,进程阻塞。

    很显然这都不是我想要,而为什么select没有等到超时再返回呢?

    上网查了select函数的实现原理才知道,原来select需要驱动程序的支持,要实现fops内的poll函数。具体的实现原理可以参考:

    Select函数实现原理分析 http://linux.chinaunix.net/techdoc/net/2009/05/03/1109887.shtml

    尝试改进二,定时器alarm和信号

    思路参考APUE第十章信号,涉及alarm函数,sigsetjmp和siglongjmp函数。具体思路就是,以阻塞方式打开设备文件,在读数据前设置定时器;当有数据读取时,读取数据,清除定时器;当没有数据读取,进程阻塞,如果定时器超时,从read阻塞中返回,进入信号处理函数,longjmp返回read之前,去做其他的事情。

    //信息处理函数
     static void dealSigAlarm(int signo)
    {
        longjmp(env_alrm,1);
    }
    
    //部分关键代码
    
    struct sigaction alrmact;
     bzero(&alrmact,sizeof(alrmact));
     alrmact.sa_handler = dealSigAlarm;
     alrmact.sa_flags = SA_NOMASK;//使用SA_RESTART将会阻塞在read函数
     alrmact.sa_restorer = NULL;
     sigaction(SIGALRM,&alrmact,NULL);
    
    if(setjmp(env_alrm) != 0)
    {
        printf("setjmp return\n");
        return -1;
    }
    
    alarm(1);   //1秒,
    
    if((n = read(fd_button, (char*)&bt, sizeof(BUTTON))) < 0)
     {
     …… 
    }
     …… 
    alarm(0);

    结果还是不如我所愿,进程直接阻塞在read函数,也没有进入定时器的信号处理函数(实验时有打log)。

    又上网查了很久,终于知道了原因:进程阻塞后,有两种睡眠状态(1)TASK_INTERRUPTIBLE(可中断的睡眠状态)(2)TASK_UNINTERRUPTIBLE(不可中断的睡眠状态)。所以这个按键驱动的read阻塞后极有可能是进入不可中断的睡眠状态,所以定时器的信号无法及时产生中断使进程恢复。从书上的一段话可以印证:在进程对某些硬件进行操作时(比如进程调用read系统调用对某个设备文件进行读操作,而read系统调用最终执行到对应设备驱动的代 码,并与对应的物理设备进行交互),可能需要使用TASK_UNINTERRUPTIBLE状态对进程进行保护,以避免进程与设备交互的过程被打断,造成 设备陷入不可控的状态。

    参与网文:http://andylin02.iteye.com/blog/858708

                  http://blog.csdn.net/li4850729/article/details/7554074

    尝试改进三,修改终端控制特性

    终端通常理解为平常登录的sell终端,进行输入输出的,不过我想串口或者按键也可以称为终端,作为输入。直接上一段网上的代码

    struct termios termios;
    
    tcgetattr(filedesc, &termios);
    termios.c_lflag &= ~ICANON;   /* Set non-canonical mode */
    termios.c_cc[VTIME] = 100;      /* Set timeout of 10.0 seconds */
    termios.c_cc[VMIN] = 0;
    tcsetattr(filedesc, TCSANOW, &termios);

    这段代码要打开文件之后,read之前,进行设置。思路就是设置文件描述符的控制特性来跳出阻塞,VTIME设置超时,VMIN设置最少读入字节数,在此设置为0。那么当没有数据输入,定时器超时,read返回0,终止阻塞。

    这方法也不错的,可惜我的尝试还是失败了,原因估计还是因为按键驱动进入了不可中断的睡眠状态。哎,超打击的…………

    参考网文:

    http://biancheng.dnbcw.info/c/430189.html

    http://stackoverflow.com/questions/2917881/how-to-implement-a-timeout-in-read-function-call

    尝试改进四,分而治之

    通过超时来解决这个问题已经是不可能了,只好另找方法。上网查查CPU占用率高的解决方法,看了其他一些领域的处理方法,居然给我找到了灵感,CPU占用率果然降下来了,占有5%左右。我叫这个方法为:分而治之。

    灵感来源:

    记一次代码优化(大数据量处理及存储)http://inter12.iteye.com/blog/593572

    修改代码后大概如此:

           //检查100次,每次休眠5ms(大概500ms,大部分在休眠)
            int i = 100;
            for(; i > 0; i--)
            {
                vKey = read_key();
    
                if(vKey != -1)    //有按键值, 跳出循环,准备去响应按键
                {
                    return vKey;
                }
                else
                {
                    usleep(5000);
                }
            }
    
            //到此for结束
            //心跳,显示时间等

    这样做的思路就是,把休眠的时间粒度分小,提高响应的灵敏度;把检查次数增大,使进程尽可能地休眠,降低CPU占用率。

    总结

    经过测试,上面前三种方法对于标准输入STDIN_FILENO都是可行的。但是对于按键设备文件却不行。

    虽然这个按键程序很小很小,不过五十行代码,但是却影响很大。在想办法改进的过程中,学到了不少知识,最终采取了一个不是方法的非常规方法,居然解决了,真是出乎预料。在工作的最后一天解决了一这个问题,感觉好爽,这周末是个好周末。

    当然有人会问,为什么不改按键驱动以支持select函数。这就涉及到部门之间的协调问题,我们部门只做嵌入式软件,驱动是另一个部门做,发了邮件给相关负责人反应,没见有回复(可能是年末了,忙着项目XXX),悲催啊,只能在自己这里找方法了。

  • 相关阅读:
    JavaScript 移动端拖动元素
    JavaScript轮播图
    JavaScript缓动动画函数封装
    JavaScript mouseenter和mouseover的区别
    类欧几里得算法
    AGC043 A~C 解题报告
    「UOJ495」新年的促销
    「BZOJ4842」Delight for a Cat
    Atcoder AGC002 解题报告
    Atcoder AGC001 解题报告
  • 原文地址:https://www.cnblogs.com/qinwanlin/p/2866735.html
Copyright © 2020-2023  润新知