mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
fix(freertos): Made select function non-blocking on Linux target
The select function wrapper was rewritten to be non-blocking on Linux systems, as it was stealing all the CPU time from lower priority tasks when called from a higher priority task. This is because the FreeRTOS scheduler does not know that the task thread is sleeping during the system call. This issue manifests all "slow" system calls on the Linux target, but handling the case of select fixes the problems for most ESP-IDF components. The FreeRTOS POSIX port documentation lists this as a known issue, so user code is responsible handling this case if other system calls are used, even if unknowingly. This closes GH issue #14395 "select() blocks the FreeRTOS scheduler on Linux target"
This commit is contained in:
parent
c9df77efbf
commit
8a39db3fae
@ -1,56 +1,84 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
#include <pthread.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <sys/types.h>
|
#include <sys/select.h>
|
||||||
#include "esp_err.h"
|
#include <errno.h>
|
||||||
#include "errno.h"
|
|
||||||
|
|
||||||
/** This module addresses the FreeRTOS simulator's coexistence with linux system calls from user apps.
|
/** This module addresses the FreeRTOS simulator's coexistence with Linux system calls from user apps.
|
||||||
* It's only included when building without lwIP, so we need to use linux system's select() which would receive
|
* It wraps select so that it doesn't block the FreeRTOS task calling it, so that the
|
||||||
* EINTR event on every FreeRTOS interrupt; we workaround this problem by wrapping select()
|
* scheduler will allow lower priority tasks to run.
|
||||||
* to bypass and silence these events.
|
* Without the wrapper, most components such as ESP-MQTT block lower priority tasks from running at all.
|
||||||
*/
|
*/
|
||||||
typedef int (*select_func_t)(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tval);
|
typedef int (*select_func_t)(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tval);
|
||||||
|
|
||||||
static inline int64_t get_us(void)
|
|
||||||
{
|
|
||||||
struct timespec spec;
|
|
||||||
clock_gettime(CLOCK_REALTIME, &spec);
|
|
||||||
return spec.tv_nsec / 1000 + spec.tv_sec * 1000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
int select(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tval)
|
int select(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tval)
|
||||||
{
|
{
|
||||||
int ret;
|
static select_func_t s_real_select = NULL;
|
||||||
struct timeval *tv = tval;
|
TickType_t end_ticks = portMAX_DELAY;
|
||||||
struct timeval timeval_local = {};
|
fd_set o_rfds, o_wfds, o_efds;
|
||||||
int64_t start = 0;
|
|
||||||
int64_t timeout_us = 0;
|
// Lookup the select symbol
|
||||||
select_func_t real_select = (select_func_t) dlsym(RTLD_NEXT, "select");
|
if (s_real_select == NULL) {
|
||||||
if (tv != NULL) {
|
s_real_select = (select_func_t)dlsym(RTLD_NEXT, "select");
|
||||||
start = get_us();
|
|
||||||
timeout_us = tval->tv_sec * 1000000 + tval->tv_usec;
|
|
||||||
timeval_local.tv_sec = tval->tv_sec;
|
|
||||||
timeval_local.tv_usec = tval->tv_usec;
|
|
||||||
tv = &timeval_local; // this (tv != NULL) indicates that we should handle timeouts
|
|
||||||
}
|
}
|
||||||
while ((ret = real_select(fd, rfds, wfds, efds, tv)) < 0 && errno == EINTR) {
|
|
||||||
if (tv != NULL) {
|
// Calculate the end_ticks if a timeout is provided
|
||||||
int64_t now = get_us();
|
if (tval != NULL) {
|
||||||
timeout_us -= now - start;
|
end_ticks = xTaskGetTickCount() + pdMS_TO_TICKS(tval->tv_sec * 1000 + tval->tv_usec / 1000);
|
||||||
if (timeout_us < 0) {
|
|
||||||
errno = 0;
|
|
||||||
ret = 0;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
start = now;
|
|
||||||
tv->tv_usec = timeout_us % 1000000;
|
// Preserve the original FD sets as select call will change them
|
||||||
tv->tv_sec = timeout_us / 1000000;
|
if (rfds) {
|
||||||
|
o_rfds = *rfds;
|
||||||
}
|
}
|
||||||
|
if (wfds) {
|
||||||
|
o_wfds = *wfds;
|
||||||
}
|
}
|
||||||
|
if (efds) {
|
||||||
|
o_efds = *efds;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
// Restore original FD sets before the select call
|
||||||
|
if (rfds) {
|
||||||
|
*rfds = o_rfds;
|
||||||
|
}
|
||||||
|
if (wfds) {
|
||||||
|
*wfds = o_wfds;
|
||||||
|
}
|
||||||
|
if (efds) {
|
||||||
|
*efds = o_efds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call select with a zero timeout to avoid blocking
|
||||||
|
struct timeval zero_tv = {0, 0};
|
||||||
|
int ret = s_real_select(fd, rfds, wfds, efds, &zero_tv);
|
||||||
|
|
||||||
|
// Return on success
|
||||||
|
if (ret > 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return on any error but EINTR
|
||||||
|
if (ret == -1 && errno != EINTR) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tval != NULL && xTaskGetTickCount() >= end_ticks) {
|
||||||
|
errno = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sleep for 10 tick(s) to allow other tasks to run.
|
||||||
|
* This can be any value greater than zero.
|
||||||
|
* 10 is a good trade-off between CPU time usage and timeout resolution.
|
||||||
|
*/
|
||||||
|
vTaskDelay(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user