Linux内核add_timer的可靠性在一个jiffy的分辨率?

时间:2013-06-04 14:18:19

标签: linux timer linux-kernel

在下面给出的代码中,有一个简单的Linux内核模块(驱动程序),它使用add_timer以1 jiffy的分辨率重复调用一次函数(也就是说,计时器计划在{ {1}})。使用jiffies + 1脚本bash,然后我从rerun.sh中的打印输出中获取时间戳,并使用syslog对其进行可视化。

在大多数情况下,我得到gnuplot这样的输出:

syslog

...结果与时间序列和delta直方图一样:

_testjiffy_00001.png

这基本上是我对代码期望的时间质量。

然而 - 每隔一段时间,我就会得到一个类似的捕捉:

[ 7103.055787] Init testjiffy: 0 ; HZ: 250 ; 1/HZ (ms): 4
[ 7103.056044]  testjiffy_timer_function: runcount 1 
[ 7103.060045]  testjiffy_timer_function: runcount 2 
[ 7103.064052]  testjiffy_timer_function: runcount 3 
[ 7103.068050]  testjiffy_timer_function: runcount 4 
[ 7103.072053]  testjiffy_timer_function: runcount 5 
[ 7103.076036]  testjiffy_timer_function: runcount 6 
[ 7103.080044]  testjiffy_timer_function: runcount 7 
[ 7103.084044]  testjiffy_timer_function: runcount 8 
[ 7103.088060]  testjiffy_timer_function: runcount 9 
[ 7103.092059]  testjiffy_timer_function: runcount 10 
[ 7104.095429] Exit testjiffy

...结果如下:

_testjiffy_00002.png

......我喜欢:" WHOOOOOAAAAAA ......等一下......" - 序列中是否有脉冲掉落?意味着[ 7121.377507] Init testjiffy: 0 ; HZ: 250 ; 1/HZ (ms): 4 [ 7121.380049] testjiffy_timer_function: runcount 1 [ 7121.384062] testjiffy_timer_function: runcount 2 [ 7121.392053] testjiffy_timer_function: runcount 3 [ 7121.396055] testjiffy_timer_function: runcount 4 [ 7121.400068] testjiffy_timer_function: runcount 5 [ 7121.404085] testjiffy_timer_function: runcount 6 [ 7121.408084] testjiffy_timer_function: runcount 7 [ 7121.412072] testjiffy_timer_function: runcount 8 [ 7121.416083] testjiffy_timer_function: runcount 9 [ 7121.420066] testjiffy_timer_function: runcount 10 [ 7122.417325] Exit testjiffy 错过一个插槽,然后在接下来的4毫秒插槽中启动该功能?

有趣的是,在运行这些测试时,除了终端,Web浏览器和文本编辑器之外别无其他 - 所以我真的看不到任何运行,可能会占用操作系统/内核;因此,我真的看不出一个原因为什么内核会造成如此大的遗漏(整个jiffy时期)。当我读到Linux内核时序时,例如" The simplest and least accurate of all timers ... is the timer API",我读到了#34;最不准确" as:"不要期望完全 4毫秒时段" (按照这个例子) - 我不知道,我对(第一个)直方图中显示的方差很好;但我不希望错过整个时期!?

所以我的问题是:

  • 此解决方案是add_timer的预期行为(偶尔会错过一段时间)?
  • 如果是这样,有没有办法强迫"强迫" add_timer在每个4ms插槽中触发该功能,如此平台上的jiffy所指定的那样?
  • 我有可能得到一个错误的"时间戳 - 例如时间戳反映实际"打印"发生了syslog,而不是当函数实际触发时?
  • 请注意,我没有寻找低于jiffy(在这种情况下,4ms)的时间分辨率;当代码正常工作时,我也不希望减少delta方差。所以我看到它,我没有"高分辨率计时器"要求,也不要"硬实时"要求 - 我只想让add_timer可靠地解雇。这可能是在这个平台上实现的,而不是诉诸于特殊的"实时"内核的配置?

加分问题:在下面的add_timer中,您会注意到标有rerun.sh的两个sleep;如果其中任何一个被遗漏/注释,OS /内核冻结,并需要重启。我无法理解为什么 - 在bash的MUSTHAVE之后运行rmmod是否真的可能如此之快,它会与正常的模块加载/卸载过程冲突吗?


平台信息:

insmod

代码:

$ cat /proc/cpuinfo | grep "processor\|model name\|MHz\|cores"
processor   : 0       # (same for 1)
model name  : Intel(R) Atom(TM) CPU N450   @ 1.66GHz
cpu MHz             : 1000.000
cpu cores   : 1
$ echo $(cat /etc/issue ; uname -a)
Ubuntu 11.04 \n \l Linux mypc 2.6.38-16-generic #67-Ubuntu SMP Thu Sep 6 18:00:43 UTC 2012 i686 i686 i386 GNU/Linux
$ echo $(lsb_release -a 2>/dev/null | tr '\n' ' ')
Distributor ID: Ubuntu Description: Ubuntu 11.04 Release: 11.04 Codename: natty

$ cd /tmp/testjiffy $ ls Makefile rerun.sh testjiffy.c

Makefile

obj-m += testjiffy.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

testjiffy.c

/* * [http://www.tldp.org/LDP/lkmpg/2.6/html/lkmpg.html#AEN189 The Linux Kernel Module Programming Guide] */ #include <linux/module.h> /* Needed by all modules */ #include <linux/kernel.h> /* Needed for KERN_INFO */ #include <linux/init.h> /* Needed for the macros */ #include <linux/jiffies.h> #include <linux/time.h> #define MAXRUNS 10 static volatile int runcount = 0; static struct timer_list my_timer; static void testjiffy_timer_function(unsigned long data) { int tdelay = 100; runcount++; if (runcount == 5) { while (tdelay > 0) { tdelay--; } // small delay } printk(KERN_INFO " %s: runcount %d \n", __func__, runcount); if (runcount < MAXRUNS) { my_timer.expires = jiffies + 1; add_timer(&my_timer); } } static int __init testjiffy_init(void) { printk(KERN_INFO "Init testjiffy: %d ; HZ: %d ; 1/HZ (ms): %d\n", runcount, HZ, 1000/HZ); init_timer(&my_timer); my_timer.function = testjiffy_timer_function; //my_timer.data = (unsigned long) runcount; my_timer.expires = jiffies + 1; add_timer(&my_timer); return 0; } static void __exit testjiffy_exit(void) { printk(KERN_INFO "Exit testjiffy\n"); } module_init(testjiffy_init); module_exit(testjiffy_exit); MODULE_LICENSE("GPL");

rerun.sh

编辑:受this comment by @granquet的启发,我尝试使用#!/usr/bin/env bash set -x make clean make # blank syslog first sudo bash -c 'echo "0" > /var/log/syslog' sleep 1 # MUSTHAVE 01! # reload kernel module/driver sudo insmod ./testjiffy.ko sleep 1 # MUSTHAVE 02! sudo rmmod testjiffy set +x # copy & process syslog max=0; for ix in _testjiffy_*.syslog; do aa=${ix#_testjiffy_}; ab=${aa%.syslog} ; case $ab in *[!0-9]*) ab=0;; # reset if non-digit obtained; else *) ab=$(echo $ab | bc);; # remove leading zeroes (else octal) esac if (( $ab > $max )) ; then max=$((ab)); fi; done; newm=$( printf "%05d" $(($max+1)) ); PLPROC='chomp $_; if (!$p) {$p=0;}; if (!$f) {$f=$_;} else { $a=$_-$f; $d=$a-$p; print "$a $d\n" ; $p=$a; };' set -x grep "testjiffy" /var/log/syslog | cut -d' ' -f7- > _testjiffy_${newm}.syslog grep "testjiffy_timer_function" _testjiffy_${newm}.syslog \ | sed 's/\[\(.*\)\].*/\1/' \ | perl -ne "$PLPROC" \ > _testjiffy_${newm}.dat set +x cat > _testjiffy_${newm}.gp <<EOF set terminal pngcairo font 'Arial,10' size 900,500 set output '_testjiffy_${newm}.png' set style line 1 linetype 1 linewidth 3 pointtype 3 linecolor rgb "red" set multiplot layout 1,2 title "_testjiffy_${newm}.syslog" set xtics rotate by -45 set title "Time positions" set yrange [0:1.5] set offsets graph 50e-3, 1e-3, 0, 0 plot '_testjiffy_${newm}.dat' using 1:(1.0):xtic(gprintf("%.3se%S",\$1)) notitle with points ls 1, '_testjiffy_${newm}.dat' using 1:(1.0) with impulses ls 1 binwidth=0.05e-3 set boxwidth binwidth bin(x,width)=width*floor(x/width) + width/2.0 set title "Delta diff histogram" set style fill solid 0.5 set autoscale xy set offsets graph 0.1e-3, 0.1e-3, 0.1, 0.1 plot '_testjiffy_${newm}.dat' using (bin(\$2,binwidth)):(1.0) smooth freq with boxes ls 1 unset multiplot EOF set -x; gnuplot _testjiffy_${newm}.gp ; set +x /proc/schedstat/proc/sched_debugdd获取调度程序统计信息。请注意,这大部分时间都会跳过&#34;跳过&#34; (也就是说,由于函数的第7,第6或第X次运行而丢失的文件);但是我设法获得了两次完整的运行,然后将它们发布在https://gist.github.com/anonymous/5709699中(因为我注意到gist可能更倾向于将其粘贴到SO上),因为输出有点大; call_usermodehelper文件记录正确的运行,*_11*文件使用&#34; drop&#34;记录运行。

注意我也在模块中切换到*_17*,但它没有多大帮助(使用此功能的模块获得了主要日志)。这些是mod_timer_pinned

中的更改
testjiffy.c

...在#include <linux/kmod.h> // usermode-helper API ... char fcmd[] = "of=/tmp/testjiffy_sched00"; char *dd1argv[] = { "/bin/dd", "if=/proc/schedstat", "oflag=append", "conv=notrunc", &fcmd[0], NULL }; char *dd2argv[] = { "/bin/dd", "if=/proc/sched_debug", "oflag=append", "conv=notrunc", &fcmd[0], NULL }; static char *envp[] = { "HOME=/", "TERM=linux", "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL }; static void testjiffy_timer_function(unsigned long data) { int tdelay = 100; unsigned long tjnow; runcount++; if (runcount == 5) { while (tdelay > 0) { tdelay--; } // small delay } printk(KERN_INFO " %s: runcount %d \n", __func__, runcount); if (runcount < MAXRUNS) { mod_timer_pinned(&my_timer, jiffies + 1); tjnow = jiffies; printk(KERN_INFO " testjiffy expires: %lu - jiffies %lu => %lu / %lu\n", my_timer.expires, tjnow, my_timer.expires-tjnow, jiffies); sprintf(fcmd, "of=/tmp/testjiffy_sched%02d", runcount); call_usermodehelper( dd1argv[0], dd1argv, envp, UMH_NO_WAIT ); call_usermodehelper( dd2argv[0], dd2argv, envp, UMH_NO_WAIT ); } }

rerun.sh

我将使用此帖子进行详细回复。

@CL.:非常感谢你的回答。很高兴它确认了你的计时器功能可能会在以后的jiffy中被调用&#34 ;;通过记录jiffies,我也意识到计时器函数会在以后被调用 - 除此之外,它没有任何东西&#34;错误&#34;本身。

很了解时间戳;我想知道是否有可能:定时器函数在正确的时间点击,但是内核抢占了内核日志记录服务(我相信它是... set +x for ix in /tmp/testjiffy_sched*; do echo $ix | tee -a _testjiffy_${newm}.sched cat $ix >> _testjiffy_${newm}.sched done set -x ; sudo rm /tmp/testjiffy_sched* ; set +x cat > _testjiffy_${newm}.gp <<EOF ... ),所以我得到一个延迟的时间戳?但是,我试图创建一个循环的&#34; (或者更确切地说,定期)定时器功能写入硬件,我首先注意到这一点&#34; drop&#34;通过实现PC不会在USB总线上以特定间隔写入数据;并且鉴于时间戳确认了这种行为,这可能不是问题(我猜)。

我修改了计时器功能,使其相对于上一个计时器的预定时间(klogd)触发 - 再次通过my_timer.expires而不是mod_timer_pinned

add_timer

......和前几次尝试,它无可挑剔地工作 - 但是,最终,我得到了这个:

static void testjiffy_timer_function(unsigned long data)
{
  int tdelay = 100;
  unsigned long tjlast;
  unsigned long tjnow;

  runcount++;
  if (runcount == 5) {
    while (tdelay > 0) { tdelay--; } // small delay
  }

  printk(KERN_INFO
    " %s: runcount %d \n",
    __func__, runcount);

  if (runcount < MAXRUNS) {
    tjlast = my_timer.expires;
    mod_timer_pinned(&my_timer, tjlast + 1);
    tjnow = jiffies;
    printk(KERN_INFO
      " testjiffy expires: %lu - jiffies %lu => %lu / %lu last: %lu\n",
      my_timer.expires, tjnow, my_timer.expires-tjnow, jiffies, tjlast);
  }
}

......呈现如此:

_testjiffy_00027

...所以,基本上我有一个延迟/&#34; drop&#34;在+ 8ms插槽(应该是@ 3272446 jiffies),然后在+ 12ms插槽运行两个功能(这将是@ 3272447 jiffies);你甚至可以看到情节上的标签为&#34;更加粗体&#34;因为这些。这是更好的,从&#34; drop&#34;序列现在正在同步到一个正确的非丢弃序列(正如你所说的那样:&#34;以避免一个后期定时器功能转移所有后续定时器调用&#34;) - 但是,我仍然错过了一个节拍;因为我必须在每次节拍时将字节写入硬件,所以我保持一个持续的,恒定的传输速率,但遗憾的是这对我没什么帮助。

至于其他建议,使用十个计时器&#34; - 因为我的最终目标(使用周期性的lo-res定时器功能写入硬件);我一开始认为它不适用 - 但如果没有别的可能(除了做一些特殊的实时内核准备),那么我当然会尝试一种方案,我有10个(或N个)定时器(可能存储在数组中)一个接一个地定期发射。


编辑:只需添加剩余的相关评论:

  

USB传输要么提前安排(等时),要么没有时间保证(异步)。如果您的设备没有使用等时传输,则会严重错误设计。 - CL。 6月5日10:47

     
    

感谢评论,@ CL。 - &#34; ...提前安排(等时)......&#34;清除了我的困惑。我(最终)瞄准了FT232,它只有BULK模式 - 只要每个定时器点击的字节数很少,我实际上可以&#34;作弊&#34;我通过&#34;流媒体&#34; add_timer的数据;然而,当我将大量字节转移到接近消耗带宽时,那么这些&#34;会失败&#34;开始变得明显的下降。所以我有兴趣测试它的极限,为此我需要一个可靠的重复&#34;计时器&#34;功能 - 还有什么我可以尝试拥有一个可靠的&#34;计时器&#34;? - sdaau 6月5日12:27

         
      

@sdaau批量转移不适合流式传输。您无法使用其他类型的软件计时器来修复硬件协议中的缺陷。 - CL。 6月5日13:50

    
  

......以及我对@CL的回应。 :我知道我不能解决缺点;我对观察这些缺点更感兴趣 - 比方说,如果内核函数进行周期性的USB写操作,我可以在示波器/分析仪上观察信号,并希望看到散装模式在哪种意义上是不合适的。但首先,我必须相信该功能可以(至少在某种程度上)以周期性的速率可靠地重复(即&#34;生成&#34;时钟/滴答) - 而且我不知道到目前为止,我无法真正相信[13389.775508] Init testjiffy: 0 ; HZ: 250 ; 1/HZ (ms): 4 [13389.776051] testjiffy_timer_function: runcount 1 [13389.776063] testjiffy expires: 3272445 - jiffies 3272444 => 1 / 3272444 last: 3272444 [13389.780053] testjiffy_timer_function: runcount 2 [13389.780068] testjiffy expires: 3272446 - jiffies 3272445 => 1 / 3272445 last: 3272445 [13389.788054] testjiffy_timer_function: runcount 3 [13389.788073] testjiffy expires: 3272447 - jiffies 3272447 => 0 / 3272447 last: 3272446 [13389.788090] testjiffy_timer_function: runcount 4 [13389.788096] testjiffy expires: 3272448 - jiffies 3272447 => 1 / 3272447 last: 3272447 [13389.792070] testjiffy_timer_function: runcount 5 [13389.792091] testjiffy expires: 3272449 - jiffies 3272448 => 1 / 3272448 last: 3272448 [13389.796044] testjiffy_timer_function: runcount 6 [13389.796062] testjiffy expires: 3272450 - jiffies 3272449 => 1 / 3272449 last: 3272449 [13389.800053] testjiffy_timer_function: runcount 7 [13389.800063] testjiffy expires: 3272451 - jiffies 3272450 => 1 / 3272450 last: 3272450 [13389.804056] testjiffy_timer_function: runcount 8 [13389.804072] testjiffy expires: 3272452 - jiffies 3272451 => 1 / 3272451 last: 3272451 [13389.808045] testjiffy_timer_function: runcount 9 [13389.808057] testjiffy expires: 3272453 - jiffies 3272452 => 1 / 3272452 last: 3272452 [13389.812054] testjiffy_timer_function: runcount 10 [13390.815415] Exit testjiffy 在jiffies分辨率(因为它能够相对容易地跳过整个时期)。但是,似乎转向Linux&#39;在这个意义上,高分辨率计时器(add_timer)确实给了我一个可靠的周期函数 - 所以我想这解决了我的问题(发布在我的answer below中)。

1 个答案:

答案 0 :(得分:2)

确实有可能你的计时器函数被调用的时间比expires所设置的更快。 可能的原因是调度延迟,其他驱动程序禁用中断太长时间(图形和WLAN驱动程序通常是罪魁祸首),或者一些糟糕的BIOS执行SMI代码。

如果你想避免一个后期定时器功能转移所有后续定时器调用,你必须安排相应的下一个定时器而不是相对于当前时间(jiffies),但相对到最后一个计时器的预定时间(my_timer.expires)。 或者,使用您在jiffies + 123开始时安排的十个计时器......

日志中的时间戳是该字符串打印到日志的时间。

您的模块有问题:您必须确保在卸载模块之前计​​时器未处于暂挂状态。