/* * Ouroboros - Copyright (C) 2016 - 2026 * * Packet scheduler component * * Dimitri Staessens * Sander Vrijders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., http://www.fsf.org/about/contact/. */ #if defined(__linux__) || defined(__CYGWIN__) #define _DEFAULT_SOURCE #else #define _POSIX_C_SOURCE 200112L #endif #include "config.h" #include #include #include #include "common/connmgr.h" #include "ipcp.h" #include "psched.h" #include #include #include #include #include #ifndef BUILD_CONTAINER static int qos_prio [] = { QOS_PRIO_BE, QOS_PRIO_VIDEO, QOS_PRIO_VOICE, }; #endif struct psched { fset_t * set[QOS_CUBE_MAX * IPCP_SCHED_THR_MUL]; next_packet_fn_t callback; read_fn_t read; pthread_t readers[QOS_CUBE_MAX * IPCP_SCHED_THR_MUL]; }; struct sched_info { struct psched * sch; qoscube_t qc; size_t idx; }; /* Map an FD to one reader's set: one FD, one thread (no shared FDs). */ static size_t fd_set_idx(int fd, qoscube_t qc) { return qc + ((size_t) fd % IPCP_SCHED_THR_MUL) * QOS_CUBE_MAX; } static void cleanup_reader(void * o) { fqueue_destroy((fqueue_t *) o); } /* * Per-reader deadline scheduler: a paced flow is served, then deferred * to its next-send deadline instead of blocking the thread, so it never * stalls its thread-mates. */ struct dsched { uint64_t deadline[PROC_MAX_FLOWS]; /* absolute ns, per tracked fd */ int active[PROC_MAX_FLOWS]; /* compact list of tracked fds */ int posn[PROC_MAX_FLOWS]; /* fd -> active index, -1 = none */ size_t n; }; static void cleanup_dsched(void * o) { free(o); } static void dsched_track(struct dsched * d, int fd, uint64_t deadline) { if (d->posn[fd] >= 0) return; d->deadline[fd] = deadline; d->posn[fd] = (int) d->n; d->active[d->n++] = fd; } static void dsched_untrack(struct dsched * d, int fd) { int i = d->posn[fd]; if (i < 0) return; d->active[i] = d->active[--d->n]; d->posn[d->active[i]] = i; d->posn[fd] = -1; } static uint64_t dsched_serve(struct dsched * d, struct psched * sched, qoscube_t qc, uint64_t now) { struct ssm_pk_buff * spb; uint64_t dmin = 0; size_t i; int fd; time_t wait; for (i = 0; i < d->n; ) { fd = d->active[i]; if (d->deadline[fd] > now) { if (dmin == 0 || d->deadline[fd] < dmin) dmin = d->deadline[fd]; ++i; continue; } if (sched->read(fd, &spb) < 0) { dsched_untrack(d, fd); continue; } wait = sched->callback(fd, qc, spb); if (wait == 0) continue; d->deadline[fd] = now + (uint64_t) wait; if (dmin == 0 || d->deadline[fd] < dmin) dmin = d->deadline[fd]; ++i; } return dmin; } static void dsched_events(struct dsched * d, fqueue_t * fq, uint64_t now) { int fd; while ((fd = fqueue_next(fq)) >= 0) { switch (fqueue_type(fq)) { case FLOW_DEALLOC: dsched_untrack(d, fd); notifier_event(NOTIFY_DT_FLOW_DEALLOC, &fd); break; case FLOW_DOWN: notifier_event(NOTIFY_DT_FLOW_DOWN, &fd); break; case FLOW_UP: notifier_event(NOTIFY_DT_FLOW_UP, &fd); break; case FLOW_PKT: dsched_track(d, fd, now); break; default: break; } } } static void * packet_reader(void * o) { struct psched * sched; struct dsched * d; fqueue_t * fq; qoscube_t qc; size_t idx; sched = ((struct sched_info *) o)->sch; qc = ((struct sched_info *) o)->qc; idx = ((struct sched_info *) o)->idx; ipcp_lock_to_core(); free(o); fq = fqueue_create(); if (fq == NULL) return (void *) -1; d = malloc(sizeof(*d)); if (d == NULL) { fqueue_destroy(fq); return (void *) -1; } memset(d, 0, sizeof(*d)); memset(d->posn, 0xFF, sizeof(d->posn)); /* -1: nothing tracked yet */ pthread_cleanup_push(cleanup_dsched, d); pthread_cleanup_push(cleanup_reader, fq); while (true) { struct timespec now_ts; struct timespec to; struct timespec * timeo; uint64_t now; uint64_t dmin; uint64_t delta; int ret; clock_gettime(PTHREAD_COND_CLOCK, &now_ts); now = TS_TO_UINT64(now_ts); dmin = dsched_serve(d, sched, qc, now); if (dmin == 0) { timeo = NULL; } else { delta = dmin > now ? dmin - now : 1; to.tv_sec = (time_t) (delta / BILLION); to.tv_nsec = (long) (delta % BILLION); timeo = &to; } ret = fevent(sched->set[idx], fq, timeo); if (ret < 0) continue; dsched_events(d, fq, now); } pthread_cleanup_pop(true); pthread_cleanup_pop(true); return (void *) 0; } struct psched * psched_create(next_packet_fn_t callback, read_fn_t read) { struct psched * psched; struct sched_info * infos[QOS_CUBE_MAX * IPCP_SCHED_THR_MUL]; int i; int j; assert(callback); psched = malloc(sizeof(*psched)); if (psched == NULL) goto fail_malloc; psched->callback = callback; psched->read = read; for (i = 0; i < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++i) { psched->set[i] = fset_create(); if (psched->set[i] == NULL) { for (j = 0; j < i; ++j) fset_destroy(psched->set[j]); goto fail_flow_set; } } for (i = 0; i < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++i) { infos[i] = malloc(sizeof(*infos[i])); if (infos[i] == NULL) { for (j = 0; j < i; ++j) free(infos[j]); goto fail_infos; } infos[i]->sch = psched; infos[i]->qc = i % QOS_CUBE_MAX; infos[i]->idx = i; } for (i = 0; i < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++i) { if (pthread_create(&psched->readers[i], NULL, packet_reader, infos[i])) { for (j = 0; j < i; ++j) pthread_cancel(psched->readers[j]); for (j = 0; j < i; ++j) pthread_join(psched->readers[j], NULL); for (j = i; j < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++j) free(infos[j]); goto fail_infos; } } #ifndef BUILD_CONTAINER for (i = 0; i < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++i) { struct sched_param par; int pol = SCHED_RR; int min; int max; min = sched_get_priority_min(pol); max = sched_get_priority_max(pol); min = (max - min) / 2; par.sched_priority = min + (qos_prio[i % QOS_CUBE_MAX] * (max - min) / 99); if (pthread_setschedparam(psched->readers[i], pol, &par)) goto fail_sched; } #endif return psched; #ifndef BUILD_CONTAINER fail_sched: for (j = 0; j < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++j) pthread_cancel(psched->readers[j]); for (j = 0; j < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++j) pthread_join(psched->readers[j], NULL); #endif fail_infos: for (j = 0; j < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++j) fset_destroy(psched->set[j]); fail_flow_set: free(psched); fail_malloc: return NULL; } void psched_destroy(struct psched * psched) { int i; assert(psched); for (i = 0; i < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++i) { pthread_cancel(psched->readers[i]); pthread_join(psched->readers[i], NULL); } for (i = 0; i < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++i) fset_destroy(psched->set[i]); free(psched); } void psched_add(struct psched * psched, int fd) { qoscube_t qc; assert(psched); ipcp_flow_get_qoscube(fd, &qc); fset_add(psched->set[fd_set_idx(fd, qc)], fd); } void psched_del(struct psched * psched, int fd) { qoscube_t qc; assert(psched); ipcp_flow_get_qoscube(fd, &qc); fset_del(psched->set[fd_set_idx(fd, qc)], fd); }