【发布时间】:2018-02-08 10:56:52
【问题描述】:
在基于 Linux 驱动的单核嵌入式 Cortex-A8 机器上,我遇到了timerfd 的问题:我需要每隔几毫秒触发一些 IO,到目前为止,我以这种方式创建的计时器一切正常:
int _timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
int _flags = 0;
itimerspec _new_timer;
_new_timer.it_interval.tv_sec = interval / 1000000;
_new_timer.it_interval.tv_nsec = (interval % 1000000) * 1000;
_new_timer.it_value.tv_sec = _new_timer.it_interval.tv_sec;
_new_timer.it_value.tv_nsec = _new_timer.it_interval.tv_nsec;
timerfd_settime(_timer_fd, _flags, &_new_timer, NULL);
.. 和select()ing 在文件描述符上。
CPU 默认运行在 800MHz,可以缩小到 300MHz。即使在最低频率下,即使在高系统负载和大量 IO 的情况下,定时器也会定期触发。
现在问题来了:当我将 CPU 频率调节器设置为 ondemand 时,定时器在切换频率时错过唤醒几秒钟(我看到最多 2800 毫秒)。
我所说的 IO 涉及上传大文件(网络 IO、提取/CPU、写入闪存)。仅创建/提取大型存档似乎不是问题。
我修改了this handy little Python script,它使用timerfd每100ms打印一次CPU频率和时间差异,我可以重现这个问题!运行 test.py 并开始上传(大量 IO)给我以下输出:
f=300000 t=0.100021, count=01 *
f=600000 t=0.099609, count=01 * <== switch, but no problem
f=600000 t=0.099989, count=01 *
f=300000 t=0.100388, count=01 * <== switch, but no problem
f=300000 t=0.099874, count=01 *
f=300000 t=0.099944, count=01 *
f=300000 t=0.100000, count=01 *
f=600000 t=0.099615, count=01 * <== switch, but no problem
f=600000 t=0.100033, count=01 *
f=600000 t=0.099958, count=01 *
f=600000 t=0.100003, count=01 * <== IO starts
f=600000 t=0.100062, count=01 *
f=600000 t=0.100318, count=01 *
f=800000 t=0.418505, count=04 **** <== 3 misses
f=800000 t=0.081735, count=01 *
f=800000 t=0.100019, count=01 *
f=800000 t=0.099284, count=01 *
f=800000 t=0.100584, count=01 *
f=800000 t=0.100089, count=01 *
f=800000 t=0.099623, count=01 *
f=720000 t=1.854099, count=18 ****************** <== 17 misses
f=720000 t=0.046591, count=01 *
f=720000 t=0.099038, count=01 *
f=720000 t=0.100744, count=01 *
f=720000 t=0.099240, count=01 *
f=720000 t=0.100029, count=01 *
f=720000 t=0.099985, count=01 *
f=720000 t=0.100007, count=01 *
f=800000 t=2.715434, count=27 *************************** <== 26 misses
f=800000 t=0.085148, count=01 *
f=800000 t=0.099992, count=01 *
f=800000 t=0.099648, count=01 *
f=800000 t=0.100367, count=01 *
f=800000 t=0.099406, count=01 *
f=800000 t=0.099984, count=01 *
f=720000 t=2.446585, count=24 ************************ <== 23 misses
f=720000 t=0.054219, count=01 *
f=720000 t=0.099947, count=01 *
f=720000 t=0.099284, count=01 *
f=720000 t=0.100721, count=01 *
f=720000 t=0.099975, count=01 *
f=720000 t=0.100089, count=01 *
f=800000 t=2.391552, count=23 *********************** <== 22 misses
f=800000 t=0.015058, count=01 *
f=800000 t=0.092592, count=01 *
f=800000 t=0.100651, count=01 *
f=800000 t=0.099982, count=01 *
f=800000 t=0.099967, count=01 *
我尝试了this 答案,它建议设置我的进程的优先级但没有效果。
这是我目前的结论:
- 问题不是由我的 C 程序引起的,因为我可以用一个小的 Python 脚本重现它
- CPU 性能不是问题,因为将频率固定为 300MHz 效果很好
- 产生重负载的进程必须满足一定的要求(见下文)——仅仅做网络 IO 或 CPU 密集型操作是行不通的
- 似乎只有在
gpg进程收到某些数据时才会出现计时器间隔
所以我的问题是:我需要一个间隔约为 10 毫秒的精确计时器(几毫秒的抖动是可以的)。我可以通过timerfd 实现这一目标吗?我的替代方案是什么?
使用的内核版本是 4.4.19 (OpenEmbedded/Yocto)
复制
目前我知道除了以下方法之外没有其他方法可以重现所描述的行为:
- 在具有网络访问权限的嵌入式设备上安装了
nginxproxy_passing 端口80到其他端口,例如8081 - 在将监听
POST请求的设备上运行receive.py,接收一个大文件并将其通过管道传送到GnuPG - 在设备上运行
test.py,观察CPU频率和定时器精度 - 将CPU调速器设置为
ondemand:echo ondemand > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor - 在另一台机器上使用
upload.py向内嵌发送一个10M的随机内容文件 - 上传数据的内容似乎很重要!
upload.py <ip/hostname> 10000000将生成一个随机字节流并将其存储到一个名为data-out的文件中,然后再进行POSTing 它 - 在大多数情况下,您不会看到计时器间隙 - 如果您可以观察到它们,您可以保留该文件并重复使用它稍后 - 从嵌入式设备运行
upload.py(无网络)或忽略nginx将不起作用!
文件
这是
test.py的修改版本,产生上面的输出import asyncore, time, timerfd.async class TestDispatcher(timerfd.async.dispatcher): def __init__(self, *args): super().__init__(*args) self._last_t = time.time() def handle_expire(self, count): t = time.time() f = open('/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq').readline().strip('\n') print("f=%s t=%.6f, count=%0.2d %s" % (f, t - self._last_t, count, '*' * count)) self._last_t = t dispatcher = TestDispatcher(timerfd.CLOCK_MONOTONIC) dispatcher.settime(0, timerfd.itimerspec(0.1, 1)) asyncore.loop()
receive.pyimport subprocess, http.server, socketserver class InstallationHandler(http.server.BaseHTTPRequestHandler): def do_POST(self): gpg_process = subprocess.Popen( ['gpg', '--homedir', '/home/root/.gnupg', '-u', 'Name', '-d'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) tar_process = subprocess.Popen( ['tar', '-C', '.', '-xzf', '-'], stdin=gpg_process.stdout, stderr=subprocess.PIPE) content_length = int(self.headers['content-length']) while content_length > 0: content_length -= gpg_process.stdin.write( self.rfile.read(min(1000, content_length))) gpg_process.stdin.close() self.send_response(201) self.end_headers() socketserver.TCPServer.allow_reuse_address = True socketserver.TCPServer(('', 8081), InstallationHandler).serve_forever()
upload.py- 提供要上传的文件名或要上传的字节数 生成import http.client, sys, os if os.path.exists(sys.argv[2]): print('read.. %r' % sys.argv[2]) b = open(sys.argv[2], 'rb').read() else: print('generate random data..') b = os.urandom(int(sys.argv[2])) open('data-out', 'wb').write(b) b = bytes(b) print('size=%d' % len(b)) h = http.client.HTTPConnection(sys.argv[1]) h.request('POST', '/upload/calibration_data', b) print(h.getresponse().read())
【问题讨论】:
-
你能发布你的平台和修改后的脚本吗?
-
@Ezequel:我已经发布了脚本——你需要系统的哪些细节?这是一个带有 OpenEmbedded Linux 内核 4.4.19 的 Phytec phyCORE AM3359
-
我只是想知道 SoC。每个 SoC 都有不同的 cpufreq 和计时器实现。
-
所以,这不仅仅是cpufreq转换,因为在空载时没有抖动。而且,这不仅仅是负载,因为您可以将调速器设置为
performance(固定频率)。对吗? -
没错 - 我什至可以将其设置为
powersave或设置固定频率,并且计时器按预期工作。而且很难找到显示不良行为的任务:目前我必须通过nginx上传随机数据并让GnuPG尝试解密它。单独上传数据或解密数据对计时器没有影响。
标签: linux timer linux-kernel cpu frequency