Linux用户进程高精度定时器去抖动

来源:互联网 发布:永安 知乎 编辑:程序博客网 时间:2024/05/22 04:30

Linux用户进程高精度定时器去抖动【新手自学APUE】。


通过实践 itimer定时器去抖动 总结的几点注意事项:

  • 定时器精度要支持较高精度,定时器设置值要大于 【上确界】(最大值)(每 两次 抖动的时间间隔),这样定时器就不会因为超时而把抖动数据当做第一次有效数据读取;同时定时器设置值要小于用户 最短的【下确界】(驱动每 两次 向缓冲区写数据的时间的间隔)
  • 对于第一次读取有效数据要造成超时效果,可以第一次返回数据时等待一段时间(也许有更好的办法,暂时没想到)。
  • 根据实际情况,定时器不能是自动定时器,每次处理完抖动手动重新设置定时器,在读到数据时后取定时器的当前值判断是否超时,没有超时不处理读到的数据,若超时处理有效数据。最后重置定时器。
  • 定时器选用真正时间(real time,可以理解为进程启动后经过的时间)定时器工作模式,而不是CPU时间(user + system时间 或是 user时间)定时器,因为驱动返回数据经历的CPU时间不一定就是用户程序阻塞的时间,驱动工作时也有可能被抢占或是主动放弃CPU,或者被阻塞而进度等待态,所以虽然读数据阻塞了很久,但定时器实际上跑了很少的滴答次数。
  • 设置定时器后到读数据, 读数据到获取定时器超时时间都有可能被长时间抢占,导致定时器意外超时,因此可能会造成抖动产生的数据被当做有效数据处理。
  • 当判断为有效数据后处理数据及各种操作要特别小心,进程不能长时间的占用CPU或是长时间阻塞,原因是此时驱动可能随时会向内核缓冲区写数据,如果有效数据跟上次抖动产生的数据粘连在一起来不及处理下次读取时会被当做抖动数据忽略掉(可以通过调用协同线程处理,把有效数据放入一个队列可以很好处理需要复杂计算处理的场景,类似于中断的上下部工作原理…不过中断上半部运行过程中是不会被抢占的,因为它没有运行在进程上下文,不接受系统调度。现在用户进程则会随时被抢占,最恶劣的情况是系统高负荷下,驱动已经返回了数据但是用户进程长时间获取不到CPU时间会一直到不了运行态。所以用户定时器还是不能完全避免问题,在系统高负荷获得不到CPU时,会丢失掉部分有效数据,但是应付一般场景足够了,因为用户程序的抖动现象是有些驱动的中断程序没有处理好抖动情况,这是一个坑。。。).
  • 其实最好的办法是在驱动程序中就把抖动处理好,用户程序来处理这些问题真的很蛋疼,除了用来玩和学习。

itimer定时器的相关数据结构与C库函数:

  • int getitimer(int which, struct itimerval *curr_value);
  • int setitimer(int which, const struct itimerval *new_value,
    struct itimerval *old_value);
struct itimerval {     struct timeval it_interval; /* next value */     struct timeval it_value;    /* current value */};struct timeval {     long tv_sec;                /* seconds */     long tv_usec;               /* microseconds */ };

curr_value是一个结果参数,返回 定时器超时到现在经历的时间 的结构体 指针
whitch 指示当前定时器工作于何种模式,一共有三种模式,由三个常量指定:

  • ITIMER_REAL

    按程序运行经过的真实时间递减(包括被系统调用阻塞、被高优先级进程抢占等经历的时间), 超时产生SIGALRM信号.

  • ITIMER_VIRTUAL

    仅在程序工作在用户态时递减,超时产生SIGVTALRM 信号.

  • ITIMER_PROF

    在程序工作在用户态时或是陷入内核时递减,超时产生SIGVTALRM 信号.

old_value指向重新设置定时器后所保存之前的设置参数,是个结果参数(未曾试验)。
new_value就是现在要要设置定时器的参数,是个值参数。

返回值等 更多详细信息参考Linux编程手册第三章setitimer…

示例代码:

很多错误情况没有处理…

#include <stdio.h>#include <sys/time.h>#include <unistd.h>#include <fcntl.h>#include <string.h>#include <signal.h>#include <pthread.h>#define STRLEN(x) (strlen(#x) + 1) // yi ci du dao zi jie shu#define SHAKE_COUNT 3 //dou dong ci shuvoid signal_handle(int arg){        if(arg == SIGPROF)            printf("get signal SIGPROF\n");        else if(arg == SIGALRM)            printf("get signal SIGALRM\n");}/*void *th_io(void *arg){    printf("read pipe: %s\n", (char *)arg);}*/int main(){    signal(SIGPROF, signal_handle);    signal(SIGALRM, signal_handle);    struct itimerval itimer1, itimer2;    memset(&itimer1, 0, sizeof(itimer1));    memset(&itimer2, 0, sizeof(itimer2));    itimer1.it_interval.tv_sec = 0;    itimer1.it_value.tv_usec = 7;    int pipe_fd[2];    int ret = pipe(pipe_fd);    if(ret < 0){        perror("pipe error");        return -1;    }    pid_t shake_pid = fork();    if(!shake_pid){        usleep(30);// let first return data timeout                close(pipe_fd[0]);        int flags;        fcntl(pipe_fd[1], F_GETFL, flags);        fcntl(pipe_fd[1], F_GETFL, flags | O_NONBLOCK);        int i = 0;        while(1){            i = SHAKE_COUNT;            while(i--){                if( STRLEN(AAA) != \                    write(pipe_fd[1], "AAA", STRLEN(AAA)) ){                    perror("write error");                    return -1;                }            }            write(pipe_fd[1], "END", strlen("END") + 1);            usleep(999999);        }    }    else if(shake_pid < 0){        perror("fork error");        return -1;    }    close(pipe_fd[1]);    char buf[100];    //memset buf if buf is not end up with null.    pthread_t th_t_io;    while(1){        setitimer(ITIMER_REAL, &itimer1, NULL);        read(pipe_fd[0], buf, STRLEN(AAA));        getitimer(ITIMER_REAL, &itimer2);        if(itimer2.it_value.tv_sec == 0                && itimer2.it_value.tv_usec == 0)         printf("buf: %s\n", buf);//do someting quickly    }     return 0;}