Necesito escribir una aplicación para Linux que envíe paquetes UDP periódicamente. Idealmente, la frecuencia debe ser cada 1 ms y el intervalo entre paquetes debe ser constante.
He intentado hacerlo a través de sockets habituales de esta manera:
while(counter < 4294967295)
{
for (k=0; k<4; k++) //Convert counter value to string
{
buf[k]=((unsigned char*)(&counter))[k];
}
sn = sendto(sender, &buf, sizeof(buf), 0, (struct sockaddr *)&srv_addr, sizeof(srv_addr)); // Sending UDP segment
if (sn < 0 ) error("UDP send fail!"); //Error handle
counter++;
nanosleep(&delay, NULL); //Sleep
}
En la aplicación anterior, solo llené el paquete UDP con un valor de contador, para poder distinguirlos en el extremo del receptor.
Básicamente, este código hace su trabajo, pero tiene estos problemas: 1. La frecuencia NO es lo suficientemente alta y se ve muy afectada por el rendimiento del host y otras aplicaciones. 2. El intervalo del paquete no es consistente, porque se usa RTC como referencia. Sin embargo, si traté de poner la verificación RTC, eso hizo que la tasa de paquetes fuera aún más lenta.
Supongo que debería haber una forma más elegante de lograr mis objetivos con un enfoque diferente. Por favor, amablemente asesorar.
Tenga en cuenta que incluso si logra que su caja de Linux envíe exactamente un paquete por milisegundo, no hay garantía (o incluso expectativa) de que el receptor reciba exactamente un paquete por milisegundo: los conmutadores Ethernet pueden introducir retrasos en la propagación de paquetes, y a menudo lo hacen. Si realmente necesita una sincronización exacta, puede buscar AVB y/o redes sensibles al tiempo, en las que el conmutador y los puntos finales se han diseñado para una sincronización de entrega consistente: en.wikipedia.org/wiki/Audio_Video_Puenteen.wikipedia.org/wiki/Time-Sensitive_Networking
–Jeremy Friesner
8 abr a las 18:11
chris dodd
La forma estándar de obtener un latido del corazón regular repetitivo en Linux (o cualquier UNIX para el caso) es usar setitimer (2):
Ahora recibirá una señal SIGALRM cada milisegundo, por lo que ejecuta su ciclo con un sigwait llamar:
sigset_t alarm_sig;
int signum;
sigemptyset(&alarm_sig);
sigaddset(&alarm_sig, SIGALRM);
while (1) {
sigwait(&alarm_sig, &signum); /* wait until the next signal */
... do your stuff to send a packet, or whatever.
}
Ahora estará enviando un paquete cada milisegundo MIENTRAS EL SISTEMA PUEDE SEGUIR EL RITMO. Esto último es importante: si el sistema está demasiado cargado (o su código para crear un paquete es demasiado lento), la siguiente señal llegará antes que la siguiente. sigwait llame y elimine su proceso. Si no quiere eso, coloque un controlador de señal para señales SIGALARM:
void missed_alarm(int signum) {
/* we missed a timer signal, so won't be sending packets fast enough!!! */
write(2, "Missed Alarm!\n", 14); /* can't use printf in a signal handler */
}
signal(SIGALRM, missed_alarm);
Ahora, si se pierde una alarma, los envíos de paquetes se ralentizarán (perderá una ranura), pero recibirá un mensaje al respecto en stderr.
Un problema importante con lo anterior es que depende de la resolución del temporizador de su sistema. En Linux, esto depende en gran medida de la configuración del núcleo CONFIG_HIGH_RES_TIMERS. Si eso no está habilitado, es probable que solo tenga una resolución de 10 ms, por lo que intentar usar un reloj de 1 ms fallará gravemente. Creo que está habilitado de forma predeterminada en la mayoría de las distribuciones x86_64, pero puede verificarlo en /proc/timer_list y ver cuál es la resolución del reloj ‘ktime_get_real’.
Esta solución funciona perfectamente para mis necesidades. Lo probé en la plataforma ARM de 1 Ghz bajo Debian 7 ARM, el resultado es bastante consistente. Con el temporizador de 1 ms no tengo alarmas perdidas y WireShark muestra que el intervalo entre paquetes es cercano a 1 ms. ¡Muchas gracias por ayudar!
–Xia
22 de agosto de 2014 a las 14:47
Si desea que sea coherente, debe vincular su proceso a un núcleo y “quemar” ese núcleo girando en un ciclo ocupado con _mm_pause() en el fondo. Luego verificará el reloj en cada iteración del bucle y, si ha pasado la cantidad de tiempo correcta, disparará un paquete.
Si desea ser bastante consistente, debe contar el tiempo desde el principio y aumentar su variable “próximo tiempo de espera” cada vez que envíe un paquete. Pero tenga cuidado: sería fácil volverse loco cuando la computadora se pone a dormir más o menos, luego se vuelve a encender y piensa que es hora de disparar 100000 paquetes (solo pregúntele a Adobe, tuvieron este error en Flash hace aproximadamente una década) .
Ah, y obviamente no hagas esto en un dispositivo de energía limitada como una computadora portátil o un teléfono.
Puede implementar un componente de modo kernel para enviar el paquete UDP que puede evitar los cambios de contexto entre el modo kernel y el usuario. Al mismo tiempo, puede obtener acceso a un temporizador de alta precisión para enviar los paquetes casi en tiempo real.
Esto, básicamente algo como un controlador de tarjeta de red personalizado, es lo único que funcionará (al menos en la medida en que lo controle; si el cable físico está ocupado, aún no lo enviará a tiempo). De lo contrario, no tiene forma de saber cuándo saldrá su paquete, incluso si lo envía exactamente a tiempo.
– Damon
15 de agosto de 2014 a las 15:28
nish
Usa 2 hilos. Hilo principal
pthread_mutex_lock(&sendLock);
/* create worker thread to send packet */
while (counter < 4294967295) {
pthread_mutex_unlock(&sendLock);
/* nano sleep for very small interval */
sched_yield();
pthread_mutex_lock(&sendLock);
/* nano sleep for remaining time interval */
}
mientras tanto en hilo de trabajo
while (1) {
pthread_mutex_lock(&sendLock);
pthread_mutex_unlock(&sendLock);
/* udp send code and counter increment*/
for (k=0; k<4; k++) //Convert counter value to string
{
buf[k]=((unsigned char*)(&counter))[k];
}
sn = sendto(sender, &buf, sizeof(buf), 0, (struct sockaddr *)&srv_addr, sizeof(srv_addr)); // Sending UDP segment
if (sn < 0 ) error("UDP send fail!"); //Error handle
counter++;
sched_yield();
}
esto asegurará un poco que se envíe 1 paquete por intervalo de tiempo
grapas gabriel
Cómo ejecutar fácilmente un bucle periódico de alta resolución y alta precisión en Linux, a cualquier frecuencia (por ejemplo, hasta 10 KHz~100 KHz) utilizando un programador suave en tiempo real y retrasos de nanosegundos
Hacer esto bien es bastante complicado. Pero, con algunas funciones auxiliares que escribí, es realmente fácil. Entonces, déjame mostrarte la parte fácil primero.
Aquí se explica cómo configurar un bucle en Linux que se ejecuta a una frecuencia fija de 10 kilociclos (Pueden ser posibles frecuencias de hasta 10 KHz a 100 KHz, dependiendo de su código y sistema). Conseguir timinglib.h aquí.
// My timing library, here:
// https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/c/timinglib.h
#include "timinglib.h"
// A task to start as a new pthread posix thread
void * pthread_task(void * argument)
{
// Activate the `SCHED_RR` Linux soft real-time round-robin scheduler, and
// turn on memory locking to lock your process's RAM so it can't be moved
// to swap space by the kernel, since that would mess up your program's
// timing.
use_realtime_scheduler();
// SET LOOP PERIOD (FREQUENCY) HERE!
// 10 us ( 0.01 ms) --> 100 KHz
// 100 us ( 0.10 ms) --> 10 KHz
// 1000 us ( 1.00 ms) --> 1 KHz
// 10000 us (10.00 ms) --> 100 Hz
const uint64_t PERIOD_US = 100; // 10 KHz loop frequency
// Seed the last wake time with the current time
uint64_t last_wake_time_us = micros();
while (true)
{
// Wait for the next cycle.
sleep_until_us(&last_wake_time_us, PERIOD_US);
// ---------------------------------------------------------------------
// Perform whatever action you want here at this fixed interval.
// ---------------------------------------------------------------------
}
}
¡Eso es todo! Súper simple. Pegue su código de socket para enviar paquetes UDP allí.
sleep_until_us() funciona igual que el Función FreeRTOS vTaskDelayUntil(). Me gustó lo fácil que era de usar, así que hice que mi función actuara como tal en Linux.
Para hacerlo, debo usar clock_nanosleep() con la bandera TIMER_ABSTIME dormir hasta un tiempo absoluto que establezca en el futuro (en el período deseado), en lugar de por un tiempo relativo a partir de ahora.
Vea mi llamada a esa función en mi implementación en timinglib.c:
void sleep_until_us(uint64_t * previous_wake_time_us, uint64_t period_us)
{
if (previous_wake_time_us == NULL)
{
printf("ERROR: NULL ptr.\n");
return;
}
uint64_t previous_wake_time_ns = US_TO_NS(*previous_wake_time_us);
uint64_t period_ns = US_TO_NS(period_us);
sleep_until_ns(&previous_wake_time_ns, period_ns);
*previous_wake_time_us = NS_TO_US(previous_wake_time_ns);
}
void sleep_until_ns(uint64_t * previous_wake_time_ns, uint64_t period_ns)
{
// See "sleep_nanosleep.c" and "sleep_nanosleep_minimum_time_interval.c" for sleep examples.
if (previous_wake_time_ns == NULL)
{
printf("ERROR: NULL ptr.\n");
return;
}
// Generate an absolute timestamp at a future point in time, at which point we want to
// wake up after sleeping.
uint64_t time_wakeup_ns = *previous_wake_time_ns + period_ns;
*previous_wake_time_ns = time_wakeup_ns; // update the user's input variable
const struct timespec TS_WAKEUP =
{
.tv_sec = (__time_t)(time_wakeup_ns / NS_PER_SEC),
.tv_nsec = (__syscall_slong_t)(time_wakeup_ns % NS_PER_SEC),
};
// If the sleep is interrupted, it may take a couple attempts to sleep the full
// amount--hence the while loop.
int retcode = EINTR; // force to run once
while (retcode == EINTR)
{
retcode = clock_nanosleep(CLOCK_TYPE, TIMER_ABSTIME, &TS_WAKEUP, NULL);
if (retcode != 0)
{
print_nanosleep_failed(retcode);
}
}
}
Pero, eso no es suficiente. Con el Linux normal SCHED_OTHER programador, clock_nanosleep() tiene un intervalo de sueño mínimo de aproximadamente 55 nosotroscon un error hasta 1 ms. Eso no es lo suficientemente bueno para bucles de 1~10+ KHz. Entonces, activamos el SCHED_RR programador rotativo suave en tiempo real para obtener intervalos de sueño mínimos de aproximadamente 4 nosotroscon un error hasta 0,4 ms. Eso está mucho mejor.
Estudia mi respuesta aquí: Cómo dormir por unos microsegundos
Aquí está mi programador y código de configuración de bloqueo de memoria desde timinglib.c:
// NB: for implementation details, see my examples inside the `set_scheduler()` func
// in "sleep_nanosleep_minimum_time_interval.c"
void use_realtime_scheduler()
{
int retcode;
pthread_t this_thread = pthread_self();
const struct sched_param priority_param =
{
// the priority must be from 1 (lowest priority) to 99
// (highest priority) for the `SCHED_FIFO` AND `SCHED_RR`
// (round robin) scheduler policies; see:
// https://man7.org/linux/man-pages/man7/sched.7.html
.sched_priority = REALTIME_SCHEDULER_PRIORITY_LOWEST,
};
retcode = pthread_setschedparam(this_thread, SCHED_RR, &priority_param);
if (retcode != 0)
{
printf("ERROR: in file %s: %i: Failed to set pthread scheduler. "
"retcode = %i: %s.\n",
__FILE__, __LINE__, retcode, strerror(retcode));
if (retcode == EPERM) // Error: Permissions
{
printf(" You must use `sudo` or run this program as root to "
"have proper privileges!\n");
}
}
else
{
// printf("`pthread_setschedparam()` successful.\n");
}
// Memory lock: also lock the memory into RAM to prevent slow operations
// where the kernel puts it into swap space. See notes above.
retcode = mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT);
if (retcode == -1)
{
printf("ERROR: in file %s: %i: Failed to lock memory into RAM. "
"errno = %i: %s.\n",
__FILE__, __LINE__, errno, strerror(errno));
if (errno == EPERM) // Error: Permissions
{
printf(" You must use `sudo` or run this program as root to "
"have proper privileges!\n");
}
}
else
{
// printf("`mlockall()` successful.\n");
}
}
Aquí hay una demostración completa con instrumentación de tiempo para probar las velocidades máximas posibles en su sistema: timinglib_pthread_periodic_loop.c.
Cuando se establece en un 100 nosotros período de bucle (10 kilociclos), aquí está la salida y el error. ¡Observa lo bueno que es el error! La mayoría de las iteraciones de bucle tienen un error de < 1 %, siendo el peor de los casos un error de +/- 20 % en ocasiones. Para 10 kilociclos¡genial!
[my answer] Cómo dormir unos microsegundos y configurar SCHED_RR – Como configurar Linux SCHED_RR suave programador rotativo en tiempo real para que clock_nanosleep() puede tener una mejor resolución de ~4 nosotros abajo de ~55 nosotros
[my answer] pthread_create no funciona correctamente con pthread_attr_setschedparam
Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos.
Configurar y más información
Tenga en cuenta que incluso si logra que su caja de Linux envíe exactamente un paquete por milisegundo, no hay garantía (o incluso expectativa) de que el receptor reciba exactamente un paquete por milisegundo: los conmutadores Ethernet pueden introducir retrasos en la propagación de paquetes, y a menudo lo hacen. Si realmente necesita una sincronización exacta, puede buscar AVB y/o redes sensibles al tiempo, en las que el conmutador y los puntos finales se han diseñado para una sincronización de entrega consistente: en.wikipedia.org/wiki/Audio_Video_Puente en.wikipedia.org/wiki/Time-Sensitive_Networking
–Jeremy Friesner
8 abr a las 18:11