Linux内核实现ns级别定时器及应用层实现us级高精度定时

内核态ns/us定时器

内核态常规实现精准定时器再linux2.6版本提出hrtime模块能达到ns级别精准定时

实现方法如下(内核hrtime为我们提供了创建与运行接口):

#include 
#include 
#include 
#include 
#include 
#include 

static struct hrtimer timer;
ktime_t kt;
struct timespec oldtc;

static enum hrtimer_restart hrtimer_hander(struct hrtimer *timer)
{
        struct timespec tc;
 //     printk("I am in hrtimer hander : %lu... \r\n",jiffies);
        getnstimeofday(&tc); //获取新的当前系统时间

        printk("interval: %ld - %ld = %ld us\r\n",tc.tv_nsec/1000,oldtc.tv_nsec/1000,tc.tv_nsec/1000-oldtc.tv_nsec/1000);
        oldtc = tc;
    hrtimer_forward(timer,timer->base->get_time(),kt);
    return HRTIMER_RESTART;
}

static int __init test_init(void)
{
    printk("---------test start-----------\r\n");

    getnstimeofday(&oldtc);  //获取当前系统时间
    kt = ktime_set(0,1000000);//1ms
    hrtimer_init(&timer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);
    hrtimer_start(&timer,kt,HRTIMER_MODE_REL);
    timer.function = hrtimer_hander;
    return 0;
}

static void __exit test_exit(void)
{
    hrtimer_cancel(&timer);
    printk("------------test over---------------\r\n");
}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");

通过测试发现设置1ms定时,最后延时如下 :

root@ucalgo2:~/timer_pro# cat /proc/kmsg 
<4>[  769.824508] 001: interval: 668501 - 667502 = 999 us
<4>[  769.825508] 001: interval: 669502 - 668501 = 1001 us
<4>[  769.826509] 001: interval: 670502 - 669502 = 1000 us
<4>[  769.827509] 001: interval: 671502 - 670502 = 1000 us
<4>[  769.828509] 001: interval: 672502 - 671502 = 1000 us
<4>[  769.829508] 001: interval: 673501 - 672502 = 999 us
<4>[  769.830509] 001: interval: 674502 - 673501 = 1001 us
<4>[  769.831508] 001: interval: 675501 - 674502 = 999 us
<4>[  769.832508] 001: interval: 676501 - 675501 = 1000 us
<4>[  769.833508] 001: interval: 677501 - 676501 = 1000 us
<4>[  769.835051] 001: interval: 679044 - 677501 = 1543 us
<4>[  769.836112] 001: interval: 680105 - 679044 = 1061 us
<4>[  769.836509] 001: interval: 680502 - 680105 = 397 us
<4>[  769.837509] 001: interval: 681502 - 680502 = 1000 us
<4>[  769.838508] 001: interval: 682502 - 681502 = 1000 us
<4>[  769.839508] 001: interval: 683501 - 682502 = 999 us
<4>[  769.840508] 001: interval: 684501 - 683501 = 1000 us
<4>[  769.841509] 001: interval: 685502 - 684501 = 1001 us
<4>[  769.842508] 001: interval: 686502 - 685502 = 1000 us
<4>[  769.845509] 001: interval: 689502 - 688503 = 999 us

测试设置10us定时器,最后延时如下:

cat /proc/kmsg 
<4>[ 1139.536530] 000: interval: 380539 - 380451 = 88 us
<4>[ 1139.536542] 000: interval: 380551 - 380539 = 12 us
<4>[ 1139.536552] 000: interval: 380560 - 380551 = 9 us
<4>[ 1139.536561] 000: interval: 380570 - 380560 = 10 us
<4>[ 1139.536572] 000: interval: 380581 - 380570 = 11 us
<4>[ 1139.536582] 000: interval: 380590 - 380581 = 9 us
<4>[ 1139.536592] 000: interval: 380601 - 380590 = 11 us
<4>[ 1139.536602] 000: interval: 380610 - 380601 = 9 us
<4>[ 1139.536612] 000: interval: 380621 - 380610 = 11 us
<4>[ 1139.536622] 000: interval: 380631 - 380621 = 10 us
<4>[ 1139.536632] 000: interval: 380641 - 380631 = 10 us
<4>[ 1139.536642] 000: interval: 380651 - 380641 = 10 us
<4>[ 1139.536652] 000: interval: 380660 - 380651 = 9 us
<4>[ 1139.536662] 000: interval: 380671 - 380660 = 11 us
<4>[ 1139.536672] 000: interval: 380680 - 380671 = 9 us

应用层高精度定时器

方法一:注册时钟信号到内核hrtimer模块,时间到达触发调度,具体代码如下:

#include 
#include 
struct timeval old_time,new_time;
unsigned int val;
//事件1
void timeout1(union sigval sig)
{
        gettimeofday(&new_time, NULL);
        val = new_time.tv_sec*1000000 + new_time.tv_usec - (old_time.tv_sec*1000000 + old_time.tv_usec);
        printf("itvl:%d\n", val);
        old_time = new_time;
}

void create_timer_by_signal(void)
{
        static timer_t times;
        struct itimerspec ts;   //用于配置定时器时间
        struct sigevent evp;
        evp.sigev_notify            = SIGEV_THREAD;
        evp.sigev_notify_function           = timeout1;
        evp.sigev_value.sival_int   = 2;
        evp.sigev_notify_attributes = NULL;
        timer_create(CLOCK_REALTIME, &evp, ×);
        /*第一次调用后,每次调用间隔时间*/
        ts.it_interval.tv_sec = 0;
        ts.it_interval.tv_nsec = 0;  // 1000ns = 1us    1000us = 1ms   1000ms = 1s 
        /*第一次调用时间*/
        ts.it_value.tv_sec = 0;
        ts.it_value.tv_nsec = 10000;//10us
        //signal(SIGUSR1, timeout1);
        gettimeofday(&old_time, NULL);
        timer_settime(times, 0, &ts, NULL);
}

int main()
{

    int ret;

    create_timer_by_signal();
    while(1){

          usleep(300000);
    }
}

方法二:通过休眠的方式解决定时效果:

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 

struct timeval te;
unsigned int my_gettime()
{
	gettimeofday(&te, NULL);
	return (te.tv_sec * 1000000 + te.tv_usec);
}
int ble_npl_task_cpu_bind(int cpu,int thread_id)
{
	cpu_set_t mask;  //CPU˵ļ	
	CPU_ZERO(&mask);	 //ÿ  
	CPU_SET(cpu, &mask);   //׺ֵ	
	if (sched_setaffinity(thread_id, sizeof(mask), &mask) == -1)//߳CPU׺  
	{	
		return 1;
	}
	return 0;
}
void *testfunc(void *argv)
{
	struct timespec t;
	int i = 20;
	struct timeval tv, tv1, tv2;
	ble_npl_task_cpu_bind(2, 0);
	printf("rtc timer bind of cpu:%d\n", 2); //绑定cpu
	t.tv_sec = 0;
	t.tv_nsec = 27520;//32khz
	//TIMER_ABSTIME
	while(i--){
		gettimeofday(&tv, NULL);
		
		clock_nanosleep(CLOCK_MONOTONIC, 0, &t, NULL);	
				
		gettimeofday(&tv1, NULL);
		printf("%lu\n", (tv1.tv_sec *1000000 + tv1.tv_usec) - 
				(tv.tv_sec *1000000 + tv.tv_usec));
	}
}
int main()
{
	int i = 10, j = 1000000, z = 100;
	unsigned long long k = 10000000000;
	unsigned long itvl[100], itvl1[100], tmp = 0, err = 0, test2 = 1;
	struct timeval tv, tv1, tv2;
	struct timeval test[100], test1[100];
	struct sched_param param;
	param.sched_priority = 90;
	sched_get_priority_max(SCHED_FIFO);
 	sched_setscheduler(getpid(),SCHED_FIFO, ¶m);
    //测试在线程中定时器功能
#if 0 
	{
		pthread_attr_t attr;
		pthread_t               handle;
		struct sched_param param;
		err = pthread_attr_init(&attr);
		if (err) return err;
		err = pthread_attr_getschedparam (&attr, ¶m);
		if (err) return err;
		err = pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
		if (err) return err;
		param.sched_priority = 90;
		err = pthread_attr_setschedparam (&attr, ¶m);
		if (err) return err;
	//	err = pthread_create(&handle, &attr, testfunc, NULL);
	//	printf("err4:%d", err);
	}
#endif
    //实现150us定时器
	struct timespec t;
	t.tv_sec = 0;
	t.tv_nsec = 147000;//150us
	//TIMER_ABSTIME
	while(i--){
		gettimeofday(&tv, NULL);
		
		clock_nanosleep(CLOCK_MONOTONIC, 0, &t, NULL);	
				
		gettimeofday(&tv1, NULL);
		printf("%lu\n", (tv1.tv_sec *1000000 + tv1.tv_usec) - 
				(tv.tv_sec *1000000 + tv.tv_usec));
	}
	i = 100;
    //实现32khz定时器
	t.tv_sec = 0;
	t.tv_nsec = 20520;//32khz
	while(i--){
		itvl[i] = my_gettime();
		clock_nanosleep(CLOCK_MONOTONIC, 0, &t, NULL);
		itvl1[i] = my_gettime();
	}
	for (i =0; i< 100;i++) {
		printf("%lu\n", itvl1[i] - itvl[i]);
	}
	return 0;
}

以上所有实现需要满足:1)将linux打入实时补丁;

                                              2)设置CPU隔离,并将进程运行在隔离CPU上

实时补丁如文章:

Unbuntu16.04 安装实时内核之RT-PREEMP补丁_ubuntu实时补丁_w_melody的博客-CSDN博客

CPU隔离如文章:

linux CPU隔离_w_melody的博客-CSDN博客

你可能感兴趣的:(Linux研究篇,linux,运维,服务器)