LINUX.ORG.RU

Периодический вызов функции в Си


1

3

Имеется самодельный девайс. Он ждёт пока к нему по USART придёт запрос данных и начинает их отдавать как только его получит.

Имеется консольная программа на Си. Она должна опрашивать девайс каждую секунду и писать ответ в файл. Данных много, поэтому USART работает почти на пределе своей пропускной способности, но точно должен будет справляться (данных всегда одно и то же количество). Поэтому нельзя сделать while (1) { write(); read(); sleep(); }, потому что задержка будет достаточно маленькая и ОС может не вернуть управление моей программе вовремя.

Решение: не синхронизировать чтение и запись. Чтобы каждую секунду гарантированно сделался write, а read будет делаться по мере получения данных (можно, например, открыть последовательный порт в блокирующем режиме или использовать select). В итоге буферизация ядра Linux и USB-переходника сделают всё за меня (программе не страшно, если она будет читать данные кусками, у меня есть возможность определить начало новых данных, ведь каждый пакет имеет фиксированный размер).

Но как в чистом Си сделать чтобы некая функция гарантированно вызывалась каждую секунду? Интервал достаточно большой, поэтому даже не на риалтаймовой ОС это должно быть реально.

Желательно чтобы способ был более-менее кросс-платформенный и тянул поменьше зависимостей.

★★★★★

Последнее исправление: KivApple (всего исправлений: 2)

а read будет делаться по мере получения данных
Но как в чистом Си сделать чтобы некая функция гарантированно вызывалась каждую секунду?

epoll + timerfd?

Stil ★★★★★
()

select с таймаутом.

Deleted
()

sigaction это не то, что интересует? правда не уверен насчёт «гарантированно»

wakuwaku ★★★★
()
Последнее исправление: wakuwaku (всего исправлений: 1)

Но как в чистом Си сделать чтобы некая функция гарантированно вызывалась каждую секунду?

На чистом си никак. Нужны nanosleep() и clock_gettime(), т.е. нужно высчитывать типичное время выполнения самой ф-ии и делать nanosleep() на (1 сек - avg_exec_time). Если хочется чтобы разброс был минимальным, то нужно будет ещё поднимать приоритет процессу.

mashina ★★★★★
()

Но как в чистом Си сделать чтобы некая функция гарантированно вызывалась каждую секунду?

Насколько гарантированно?

invy ★★★★★
()
Ответ на: комментарий от Stil

Попробовал для теста такой код, но он не работает - не пишется каждую секунду «Timer!» в консоль. Где я ошибся?

#include <stdio.h> 
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/timerfd.h>

int open_timer(int interval) {
	int fd = timerfd_create(CLOCK_REALTIME, 0);
	if (fd == -1) return -1;
	struct itimerspec timerspec;
	timerspec.it_interval.tv_sec = interval / 1000;
	timerspec.it_interval.tv_nsec = (interval % 1000) * 1000000;
	timerspec.it_value.tv_sec = 0;
	timerspec.it_value.tv_nsec = 0;
	if (timerfd_settime(fd, 0, &timerspec, NULL) < 0) {
		close(fd);
		return -1;
	}
	return fd;
}

int main(int argc, char **argv) {
	int timer_fd = open_timer(1000);
	if (timer_fd == -1) {
		fprintf(stderr, "Failed to create timer\n");
		return -1;
	}
	fd_set fd_list;
	FD_ZERO(&fd_list);
	FD_SET(timer_fd, &fd_list);
	while (1) {
		select(1, &fd_list, NULL, NULL, NULL);
		printf("Timer!\n");
	}
	close(timer_fd);
	return 0;
}

KivApple ★★★★★
() автор топика
Ответ на: комментарий от Stil

Прочитал мануал внимательнее и изменил строчку:

timerspec.it_value.tv_nsec = 1;

Но всё равно не работает - программа просто висит в бесконечном ожидании, но не пишет каждую секунду сообщение.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Не помогло. Да и если select портит fd_list, то хотя бы в первый раз вывелось сообщение, а оно вообще не выводится.

Заменил select на read и всё заработало - значит timerfd работает. Однако мне нужно читать не только timerfd, но и последовательный порт, поэтому нужно таки разобраться с select.

KivApple ★★★★★
() автор топика

Пусть девайс сыпет данными сам. Это твой девайс, ведь так? Например по событиям (сильное изменение или просто таймер).

ziemin ★★
()

Желательно чтобы способ был более-менее кросс-платформенный

Предложенный timerfd есть только в linux.

В Вашем случае будет даже проще сделать ежесекундную запись из обработчика таймера - при помощи posix'ных функций sigaction(), timer_create(), timer_settime(). А в main() достаточно читать только из последовательного порта.

Sorcerer ★★★★★
()
Ответ на: комментарий от Sorcerer

Всё понял, поставил там FD_SETSIZE, заработало. Спасибо.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от Sorcerer

Какой-нибудь из этих двух способов будет возможно запустить на Windows с помощью чего-нибудь типа cygwin?

KivApple ★★★★★
() автор топика
Ответ на: комментарий от Sorcerer

Тогда перепишу на нём, тем более что из-за кривого переходника из-за поисков несуществующей ошибки в коде исходник стал страшным. А что насчёт работы с последовательным портом в cygwin? open сможет открыть его? А tcsetattr сможет настроить скорость?

KivApple ★★★★★
() автор топика
Ответ на: комментарий от Eddy_Em

Нет уж, поддержка Linux для меня более приоритетная. Не заработает на винде ну и ладно, но если заработает то будет хорошо.

Qt слишком жирный для маленькой консольной программы-бэкэнда.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от Eddy_Em

Меня сейчас больше интересует можно ли увеличить кэш write в неблокирующем режиме. А то он мне не даёт послать больше 4.5к данных по USART. Нужно ждать пока он хотя бы часть отправит. Нельзя ли его заставить кешировать любое количество данных (ну либо не любое, но раз в 10 больше), а отправлять уже как получится?

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от Sorcerer

После второго SIGALARM select перестаёт отдавать управление. Точнее так:

1) Я создаю таймер

2) Приходит первый SIGALARM

3) Я запускаю цикл с select. Всё отлично работает

4) Приходит второй SIGALARM

5) select вылетает с EINTR

6) Я игнорирую это и перезапускаю select

7) select больше не отдаёт управление, сигнал нормально приходит дальше

while (1) {
	fd_set fds_r, fds_w;
	FD_ZERO(&fds_r);
	FD_ZERO(&fds_w);
	FD_SET(device_fd, &fds_r);
	FD_SET(device_fd, &fds_w);
	int retval = select(FD_SETSIZE, &fds_r, &fds_w, NULL, NULL);
	if ((errno == EINTR) || (retval == 0)) continue;
	if (retval < 0) {
		perror("select() failed");
		timer_delete(timer);
		close(device_fd);
		return -1;
	}
	...
}
KivApple ★★★★★
() автор топика
Ответ на: комментарий от KivApple

Решил проблему. (errno == EINTR) && (retval < 0). Ибо errno не обнуляется.

KivApple ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.