The Design and Implementation of the FreeBSD Operating System, Second Edition
Now available: The Design and Implementation of the FreeBSD Operating System (Second Edition)


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]

FreeBSD/Linux Kernel Cross Reference
sys/contrib/ck/src/ck_ec.c

Version: -  FREEBSD  -  FREEBSD-13-STABLE  -  FREEBSD-13-0  -  FREEBSD-12-STABLE  -  FREEBSD-12-0  -  FREEBSD-11-STABLE  -  FREEBSD-11-0  -  FREEBSD-10-STABLE  -  FREEBSD-10-0  -  FREEBSD-9-STABLE  -  FREEBSD-9-0  -  FREEBSD-8-STABLE  -  FREEBSD-8-0  -  FREEBSD-7-STABLE  -  FREEBSD-7-0  -  FREEBSD-6-STABLE  -  FREEBSD-6-0  -  FREEBSD-5-STABLE  -  FREEBSD-5-0  -  FREEBSD-4-STABLE  -  FREEBSD-3-STABLE  -  FREEBSD22  -  l41  -  OPENBSD  -  linux-2.6  -  MK84  -  PLAN9  -  xnu-8792 
SearchContext: -  none  -  3  -  10 

    1 #include <ck_ec.h>
    2 #include <ck_limits.h>
    3 
    4 #include "ck_ec_timeutil.h"
    5 
    6 #define DEFAULT_BUSY_LOOP_ITER 100U
    7 
    8 /*
    9  * The 2ms, 8x/iter default parameter hit 1.024 seconds after 3
   10  * iterations.
   11  */
   12 #define DEFAULT_INITIAL_WAIT_NS 2000000L  /* Start at 2 ms */
   13 /* Grow the wait time 8x/iteration. */
   14 #define DEFAULT_WAIT_SCALE_FACTOR 8
   15 #define DEFAULT_WAIT_SHIFT_COUNT 0
   16 
   17 struct ck_ec32_slow_path_state {
   18         struct ck_ec32 *ec;
   19         uint32_t flagged_word;
   20 };
   21 
   22 #ifdef CK_F_EC64
   23 struct ck_ec64_slow_path_state {
   24         struct ck_ec64 *ec;
   25         uint64_t flagged_word;
   26 };
   27 #endif
   28 
   29 /* Once we've waited for >= 1 sec, go for the full deadline. */
   30 static const struct timespec final_wait_time = {
   31         .tv_sec = 1
   32 };
   33 
   34 void
   35 ck_ec32_wake(struct ck_ec32 *ec, const struct ck_ec_ops *ops)
   36 {
   37         /* Spurious wake-ups are OK. Clear the flag before futexing. */
   38         ck_pr_and_32(&ec->counter, (1U << 31) - 1);
   39         ops->wake32(ops, &ec->counter);
   40         return;
   41 }
   42 
   43 int
   44 ck_ec32_wait_slow(struct ck_ec32 *ec,
   45     const struct ck_ec_ops *ops,
   46     uint32_t old_value,
   47     const struct timespec *deadline)
   48 {
   49         return ck_ec32_wait_pred_slow(ec, ops, old_value,
   50                                       NULL, NULL, deadline);
   51 }
   52 
   53 #ifdef CK_F_EC64
   54 void
   55 ck_ec64_wake(struct ck_ec64 *ec, const struct ck_ec_ops *ops)
   56 {
   57         ck_pr_and_64(&ec->counter, ~1);
   58         ops->wake64(ops, &ec->counter);
   59         return;
   60 }
   61 
   62 int
   63 ck_ec64_wait_slow(struct ck_ec64 *ec,
   64     const struct ck_ec_ops *ops,
   65     uint64_t old_value,
   66     const struct timespec *deadline)
   67 {
   68         return ck_ec64_wait_pred_slow(ec, ops, old_value,
   69                                       NULL, NULL, deadline);
   70 }
   71 #endif
   72 
   73 int
   74 ck_ec_deadline_impl(struct timespec *new_deadline,
   75     const struct ck_ec_ops *ops,
   76     const struct timespec *timeout)
   77 {
   78         struct timespec now;
   79         int r;
   80 
   81         if (timeout == NULL) {
   82                 new_deadline->tv_sec = TIME_MAX;
   83                 new_deadline->tv_nsec = NSEC_MAX;
   84                 return 0;
   85         }
   86 
   87         r = ops->gettime(ops, &now);
   88         if (r != 0) {
   89                 return -1;
   90         }
   91 
   92         *new_deadline = timespec_add(now, *timeout);
   93         return 0;
   94 }
   95 
   96 /* The rest of the file implements wait_pred_slow. */
   97 
   98 /*
   99  * Returns a timespec value for deadline_ptr. If deadline_ptr is NULL,
  100  * returns a timespec far in the future.
  101  */
  102 static struct timespec
  103 canonical_deadline(const struct timespec *deadline_ptr)
  104 {
  105 
  106         if (deadline_ptr == NULL) {
  107                 return (struct timespec) { .tv_sec = TIME_MAX };
  108         }
  109 
  110         return *deadline_ptr;
  111 }
  112 
  113 /*
  114  * Really slow (sleeping) path for ck_ec_wait.  Drives the exponential
  115  * backoff scheme to sleep for longer and longer periods of time,
  116  * until either the sleep function returns true (the eventcount's
  117  * value has changed), or the predicate returns non-0 (something else
  118  * has changed).
  119  *
  120  * If deadline is ever reached, returns -1 (timeout).
  121  *
  122  * TODO: add some form of randomisation to the intermediate timeout
  123  * values.
  124  */
  125 static int
  126 exponential_backoff(struct ck_ec_wait_state *wait_state,
  127     bool (*sleep)(const void *sleep_state,
  128         const struct ck_ec_wait_state *wait_state,
  129         const struct timespec *partial_deadline),
  130     const void *sleep_state,
  131     int (*pred)(const struct ck_ec_wait_state *state,
  132         struct timespec *deadline),
  133     const struct timespec *deadline)
  134 {
  135         struct timespec begin;
  136         struct timespec stop_backoff;
  137         const struct ck_ec_ops *ops = wait_state->ops;
  138         const uint32_t scale_factor = (ops->wait_scale_factor != 0)
  139             ? ops->wait_scale_factor
  140             : DEFAULT_WAIT_SCALE_FACTOR;
  141         const uint32_t shift_count = (ops->wait_shift_count != 0)
  142             ? ops->wait_shift_count
  143             : DEFAULT_WAIT_SHIFT_COUNT;
  144         uint32_t wait_ns = (ops->initial_wait_ns != 0)
  145             ? ops->initial_wait_ns
  146             : DEFAULT_INITIAL_WAIT_NS;
  147         bool first = true;
  148 
  149         for (;;) {
  150                 struct timespec now;
  151                 struct timespec partial_deadline;
  152 
  153                 if (check_deadline(&now, ops, *deadline) == true) {
  154                         /* Timeout. Bail out. */
  155                         return -1;
  156                 }
  157 
  158                 if (first) {
  159                         begin = now;
  160                         wait_state->start = begin;
  161                         stop_backoff = timespec_add(begin, final_wait_time);
  162                         first = false;
  163                 }
  164 
  165                 wait_state->now = now;
  166                 if (timespec_cmp(now, stop_backoff) >= 0) {
  167                         partial_deadline = *deadline;
  168                 } else {
  169                         do {
  170                                 partial_deadline =
  171                                     timespec_add_ns(begin, wait_ns);
  172                                 wait_ns =
  173                                     wait_time_scale(wait_ns,
  174                                                     scale_factor,
  175                                                     shift_count);
  176                         } while (timespec_cmp(partial_deadline, now) <= 0);
  177                 }
  178 
  179                 if (pred != NULL) {
  180                         int r = pred(wait_state, &partial_deadline);
  181                         if (r != 0) {
  182                                 return r;
  183                         }
  184                 }
  185 
  186                 /* Canonicalize deadlines in the far future to NULL. */
  187                 if (sleep(sleep_state, wait_state,
  188                           ((partial_deadline.tv_sec == TIME_MAX)
  189                            ? NULL :  &partial_deadline)) == true) {
  190                         return 0;
  191                 }
  192         }
  193 }
  194 
  195 /*
  196  * Loops up to BUSY_LOOP_ITER times, or until ec's counter value
  197  * (including the flag) differs from old_value.
  198  *
  199  * Returns the new value in ec.
  200  */
  201 #define DEF_WAIT_EASY(W)                                                \
  202         static uint##W##_t ck_ec##W##_wait_easy(struct ck_ec##W* ec,    \
  203                                                 const struct ck_ec_ops *ops, \
  204                                                 uint##W##_t expected)   \
  205         {                                                               \
  206                 uint##W##_t current = ck_pr_load_##W(&ec->counter);     \
  207                 size_t n = (ops->busy_loop_iter != 0)                   \
  208                     ? ops->busy_loop_iter                               \
  209                     : DEFAULT_BUSY_LOOP_ITER;                           \
  210                                                                         \
  211                 for (size_t i = 0;                                      \
  212                      i < n && current == expected;                      \
  213                      i++) {                                             \
  214                         ck_pr_stall();                                  \
  215                         current = ck_pr_load_##W(&ec->counter);         \
  216                 }                                                       \
  217                                                                         \
  218                 return current;                                         \
  219         }
  220 
  221 DEF_WAIT_EASY(32)
  222 #ifdef CK_F_EC64
  223 DEF_WAIT_EASY(64)
  224 #endif
  225 #undef DEF_WAIT_EASY
  226 /*
  227  * Attempts to upgrade ec->counter from unflagged to flagged.
  228  *
  229  * Returns true if the event count has changed. Otherwise, ec's
  230  * counter word is equal to flagged on return, or has been at some
  231  * time before the return.
  232  */
  233 #define DEF_UPGRADE(W)                                                  \
  234         static bool ck_ec##W##_upgrade(struct ck_ec##W* ec,             \
  235                                        uint##W##_t current,             \
  236                                        uint##W##_t unflagged,           \
  237                                        uint##W##_t flagged)             \
  238         {                                                               \
  239                 uint##W##_t old_word;                                   \
  240                                                                         \
  241                 if (current == flagged) {                               \
  242                         /* Nothing to do, no change. */                 \
  243                         return false;                                   \
  244                 }                                                       \
  245                                                                         \
  246                 if (current != unflagged) {                             \
  247                         /* We have a different counter value! */        \
  248                         return true;                                    \
  249                 }                                                       \
  250                                                                         \
  251                 /*                                                      \
  252                  * Flag the counter value. The CAS only fails if the    \
  253                  * counter is already flagged, or has a new value.      \
  254                  */                                                     \
  255                 return (ck_pr_cas_##W##_value(&ec->counter,             \
  256                                               unflagged, flagged,       \
  257                                               &old_word) == false &&    \
  258                         old_word != flagged);                           \
  259         }
  260 
  261 DEF_UPGRADE(32)
  262 #ifdef CK_F_EC64
  263 DEF_UPGRADE(64)
  264 #endif
  265 #undef DEF_UPGRADE
  266 
  267 /*
  268  * Blocks until partial_deadline on the ck_ec. Returns true if the
  269  * eventcount's value has changed. If partial_deadline is NULL, wait
  270  * forever.
  271  */
  272 static bool
  273 ck_ec32_wait_slow_once(const void *vstate,
  274     const struct ck_ec_wait_state *wait_state,
  275     const struct timespec *partial_deadline)
  276 {
  277         const struct ck_ec32_slow_path_state *state = vstate;
  278         const struct ck_ec32 *ec = state->ec;
  279         const uint32_t flagged_word = state->flagged_word;
  280 
  281         wait_state->ops->wait32(wait_state, &ec->counter,
  282                                 flagged_word, partial_deadline);
  283         return ck_pr_load_32(&ec->counter) != flagged_word;
  284 }
  285 
  286 #ifdef CK_F_EC64
  287 static bool
  288 ck_ec64_wait_slow_once(const void *vstate,
  289     const struct ck_ec_wait_state *wait_state,
  290     const struct timespec *partial_deadline)
  291 {
  292         const struct ck_ec64_slow_path_state *state = vstate;
  293         const struct ck_ec64 *ec = state->ec;
  294         const uint64_t flagged_word = state->flagged_word;
  295 
  296         /* futex_wait will only compare the low 32 bits. Perform a
  297          * full comparison here to maximise the changes of catching an
  298          * ABA in the low 32 bits.
  299          */
  300         if (ck_pr_load_64(&ec->counter) != flagged_word) {
  301                 return true;
  302         }
  303 
  304         wait_state->ops->wait64(wait_state, &ec->counter,
  305                                 flagged_word, partial_deadline);
  306         return ck_pr_load_64(&ec->counter) != flagged_word;
  307 }
  308 #endif
  309 
  310 /*
  311  * The full wait logic is a lot of code (> 1KB). Encourage the
  312  * compiler to lay this all out linearly with LIKELY annotations on
  313  * every early exit.
  314  */
  315 #define WAIT_SLOW_BODY(W, ec, ops, pred, data, deadline_ptr,            \
  316                        old_value, unflagged, flagged)                   \
  317         do {                                                            \
  318                 struct ck_ec_wait_state wait_state = {                  \
  319                         .ops = ops,                                     \
  320                         .data = data                                    \
  321                 };                                                      \
  322                 const struct ck_ec##W##_slow_path_state state = {       \
  323                         .ec = ec,                                       \
  324                         .flagged_word = flagged                         \
  325                 };                                                      \
  326                 const struct timespec deadline =                        \
  327                         canonical_deadline(deadline_ptr);               \
  328                                                                         \
  329                 /* Detect infinite past deadlines. */                   \
  330                 if (CK_CC_LIKELY(deadline.tv_sec <= 0)) {               \
  331                         return -1;                                      \
  332                 }                                                       \
  333                                                                         \
  334                 for (;;) {                                              \
  335                         uint##W##_t current;                            \
  336                         int r;                                          \
  337                                                                         \
  338                         current = ck_ec##W##_wait_easy(ec, ops, unflagged); \
  339                                                                         \
  340                         /*                                              \
  341                          * We're about to wait harder (i.e.,            \
  342                          * potentially with futex). Make sure the       \
  343                          * counter word is flagged.                     \
  344                          */                                             \
  345                         if (CK_CC_LIKELY(                               \
  346                                 ck_ec##W##_upgrade(ec, current,         \
  347                                         unflagged, flagged) == true)) { \
  348                                 ck_pr_fence_acquire();                  \
  349                                 return 0;                               \
  350                         }                                               \
  351                                                                         \
  352                         /*                                              \
  353                          * By now, ec->counter == flagged_word (at      \
  354                          * some point in the past). Spin some more to   \
  355                          * heuristically let any in-flight SP inc/add   \
  356                          * to retire. This does not affect              \
  357                          * correctness, but practically eliminates      \
  358                          * lost wake-ups.                               \
  359                          */                                             \
  360                         current = ck_ec##W##_wait_easy(ec, ops, flagged); \
  361                         if (CK_CC_LIKELY(current != flagged_word)) {    \
  362                                 ck_pr_fence_acquire();                  \
  363                                 return 0;                               \
  364                         }                                               \
  365                                                                         \
  366                         r = exponential_backoff(&wait_state,            \
  367                                                 ck_ec##W##_wait_slow_once, \
  368                                                 &state,                 \
  369                                                 pred, &deadline); \
  370                         if (r != 0) {                                   \
  371                                 return r;                               \
  372                         }                                               \
  373                                                                         \
  374                         if (ck_ec##W##_value(ec) != old_value) {        \
  375                                 ck_pr_fence_acquire();                  \
  376                                 return 0;                               \
  377                         }                                               \
  378                                                                         \
  379                         /* Spurious wake-up. Redo the slow path. */     \
  380                 }                                                       \
  381         } while (0)
  382 
  383 int
  384 ck_ec32_wait_pred_slow(struct ck_ec32 *ec,
  385     const struct ck_ec_ops *ops,
  386     uint32_t old_value,
  387     int (*pred)(const struct ck_ec_wait_state *state,
  388         struct timespec *deadline),
  389     void *data,
  390     const struct timespec *deadline_ptr)
  391 {
  392         const uint32_t unflagged_word = old_value;
  393         const uint32_t flagged_word = old_value | (1UL << 31);
  394 
  395         if (CK_CC_UNLIKELY(ck_ec32_value(ec) != old_value)) {
  396                 return 0;
  397         }
  398 
  399         WAIT_SLOW_BODY(32, ec, ops, pred, data, deadline_ptr,
  400                        old_value, unflagged_word, flagged_word);
  401 }
  402 
  403 #ifdef CK_F_EC64
  404 int
  405 ck_ec64_wait_pred_slow(struct ck_ec64 *ec,
  406     const struct ck_ec_ops *ops,
  407     uint64_t old_value,
  408     int (*pred)(const struct ck_ec_wait_state *state,
  409         struct timespec *deadline),
  410     void *data,
  411     const struct timespec *deadline_ptr)
  412 {
  413         const uint64_t unflagged_word = old_value << 1;
  414         const uint64_t flagged_word = unflagged_word | 1;
  415 
  416         if (CK_CC_UNLIKELY(ck_ec64_value(ec) != old_value)) {
  417                 return 0;
  418         }
  419 
  420         WAIT_SLOW_BODY(64, ec, ops, pred, data, deadline_ptr,
  421                        old_value, unflagged_word, flagged_word);
  422 }
  423 #endif
  424 
  425 #undef WAIT_SLOW_BODY

Cache object: fd24f383385cc360f62ad637209cf29d


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]


This page is part of the FreeBSD/Linux Linux Kernel Cross-Reference, and was automatically generated using a modified version of the LXR engine.