mirror of
https://github.com/koverstreet/bcachefs-tools.git
synced 2025-01-22 00:04:31 +03:00
b5fd066153
We just wanted c sourcefiles out of the top level, not c source directories. Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
330 lines
6.8 KiB
C
330 lines
6.8 KiB
C
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <time.h>
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/timer.h>
|
|
|
|
/**
|
|
* timespec_add_ns - Adds nanoseconds to a timespec
|
|
* @a: pointer to timespec to be incremented
|
|
* @ns: unsigned nanoseconds value to be added
|
|
*
|
|
* This must always be inlined because its used from the x86-64 vdso,
|
|
* which cannot call other kernel functions.
|
|
*/
|
|
static struct timespec timespec_add_ns(struct timespec a, u64 ns)
|
|
{
|
|
a.tv_nsec += ns;
|
|
a.tv_sec += a.tv_nsec / NSEC_PER_SEC;
|
|
a.tv_nsec %= NSEC_PER_SEC;
|
|
return a;
|
|
}
|
|
|
|
#define DECLARE_HEAP(type) \
|
|
struct { \
|
|
size_t size, used; \
|
|
type *data; \
|
|
}
|
|
|
|
#define heap_init(heap, _size) \
|
|
({ \
|
|
size_t _bytes; \
|
|
(heap)->used = 0; \
|
|
(heap)->size = (_size); \
|
|
_bytes = (heap)->size * sizeof(*(heap)->data); \
|
|
(heap)->data = malloc(_bytes); \
|
|
(heap)->data; \
|
|
})
|
|
|
|
#define heap_free(heap) \
|
|
do { \
|
|
kvfree((heap)->data); \
|
|
(heap)->data = NULL; \
|
|
} while (0)
|
|
|
|
#define heap_swap(h, i, j) swap((h)->data[i], (h)->data[j])
|
|
|
|
#define heap_sift(h, i, cmp) \
|
|
do { \
|
|
size_t _r, _j = i; \
|
|
\
|
|
for (; _j * 2 + 1 < (h)->used; _j = _r) { \
|
|
_r = _j * 2 + 1; \
|
|
if (_r + 1 < (h)->used && \
|
|
cmp((h)->data[_r], (h)->data[_r + 1])) \
|
|
_r++; \
|
|
\
|
|
if (cmp((h)->data[_r], (h)->data[_j])) \
|
|
break; \
|
|
heap_swap(h, _r, _j); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define heap_sift_down(h, i, cmp) \
|
|
do { \
|
|
while (i) { \
|
|
size_t p = (i - 1) / 2; \
|
|
if (cmp((h)->data[i], (h)->data[p])) \
|
|
break; \
|
|
heap_swap(h, i, p); \
|
|
i = p; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define heap_add(h, d, cmp) \
|
|
({ \
|
|
bool _r = !heap_full(h); \
|
|
if (_r) { \
|
|
size_t _i = (h)->used++; \
|
|
(h)->data[_i] = d; \
|
|
\
|
|
heap_sift_down(h, _i, cmp); \
|
|
heap_sift(h, _i, cmp); \
|
|
} \
|
|
_r; \
|
|
})
|
|
|
|
#define heap_del(h, i, cmp) \
|
|
do { \
|
|
size_t _i = (i); \
|
|
\
|
|
BUG_ON(_i >= (h)->used); \
|
|
(h)->used--; \
|
|
if ((_i) < (h)->used) { \
|
|
heap_swap(h, _i, (h)->used); \
|
|
heap_sift_down(h, _i, cmp); \
|
|
heap_sift(h, _i, cmp); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define heap_pop(h, d, cmp) \
|
|
({ \
|
|
bool _r = (h)->used; \
|
|
if (_r) { \
|
|
(d) = (h)->data[0]; \
|
|
heap_del(h, 0, cmp); \
|
|
} \
|
|
_r; \
|
|
})
|
|
|
|
#define heap_peek(h) ((h)->used ? &(h)->data[0] : NULL)
|
|
#define heap_full(h) ((h)->used == (h)->size)
|
|
#define heap_empty(h) ((h)->used == 0)
|
|
|
|
#define heap_resort(heap, cmp) \
|
|
do { \
|
|
ssize_t _i; \
|
|
for (_i = (ssize_t) (heap)->used / 2 - 1; _i >= 0; --_i) \
|
|
heap_sift(heap, _i, cmp); \
|
|
} while (0)
|
|
|
|
struct pending_timer {
|
|
struct timer_list *timer;
|
|
unsigned long expires;
|
|
};
|
|
|
|
static inline bool pending_timer_cmp(struct pending_timer a,
|
|
struct pending_timer b)
|
|
{
|
|
return a.expires < b.expires;
|
|
}
|
|
|
|
static DECLARE_HEAP(struct pending_timer) pending_timers;
|
|
|
|
static pthread_mutex_t timer_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_cond_t timer_cond = PTHREAD_COND_INITIALIZER;
|
|
static pthread_cond_t timer_running_cond = PTHREAD_COND_INITIALIZER;
|
|
static unsigned long timer_seq;
|
|
|
|
static inline bool timer_running(void)
|
|
{
|
|
return timer_seq & 1;
|
|
}
|
|
|
|
static ssize_t timer_idx(struct timer_list *timer)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < pending_timers.used; i++)
|
|
if (pending_timers.data[i].timer == timer)
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
int del_timer(struct timer_list *timer)
|
|
{
|
|
ssize_t idx;
|
|
|
|
pthread_mutex_lock(&timer_lock);
|
|
idx = timer_idx(timer);
|
|
if (idx >= 0)
|
|
heap_del(&pending_timers, idx, pending_timer_cmp);
|
|
|
|
timer->pending = false;
|
|
pthread_mutex_unlock(&timer_lock);
|
|
|
|
return idx >= 0;
|
|
}
|
|
|
|
void flush_timers(void)
|
|
{
|
|
unsigned long seq;
|
|
|
|
pthread_mutex_lock(&timer_lock);
|
|
seq = timer_seq;
|
|
while (timer_running() && seq == timer_seq)
|
|
pthread_cond_wait(&timer_running_cond, &timer_lock);
|
|
|
|
pthread_mutex_unlock(&timer_lock);
|
|
}
|
|
|
|
int del_timer_sync(struct timer_list *timer)
|
|
{
|
|
unsigned long seq;
|
|
ssize_t idx;
|
|
|
|
pthread_mutex_lock(&timer_lock);
|
|
idx = timer_idx(timer);
|
|
if (idx >= 0)
|
|
heap_del(&pending_timers, idx, pending_timer_cmp);
|
|
|
|
timer->pending = false;
|
|
|
|
seq = timer_seq;
|
|
while (timer_running() && seq == timer_seq)
|
|
pthread_cond_wait(&timer_running_cond, &timer_lock);
|
|
pthread_mutex_unlock(&timer_lock);
|
|
|
|
return idx >= 0;
|
|
}
|
|
|
|
int mod_timer(struct timer_list *timer, unsigned long expires)
|
|
{
|
|
ssize_t idx;
|
|
|
|
pthread_mutex_lock(&timer_lock);
|
|
timer->expires = expires;
|
|
timer->pending = true;
|
|
idx = timer_idx(timer);
|
|
|
|
if (idx >= 0 &&
|
|
pending_timers.data[idx].expires == expires)
|
|
goto out;
|
|
|
|
if (idx >= 0) {
|
|
pending_timers.data[idx].expires = expires;
|
|
|
|
heap_sift_down(&pending_timers, idx, pending_timer_cmp);
|
|
heap_sift(&pending_timers, idx, pending_timer_cmp);
|
|
} else {
|
|
if (heap_full(&pending_timers)) {
|
|
pending_timers.size *= 2;
|
|
pending_timers.data =
|
|
realloc(pending_timers.data,
|
|
pending_timers.size *
|
|
sizeof(struct pending_timer));
|
|
|
|
BUG_ON(!pending_timers.data);
|
|
}
|
|
|
|
heap_add(&pending_timers,
|
|
((struct pending_timer) {
|
|
.timer = timer,
|
|
.expires = expires,
|
|
}),
|
|
pending_timer_cmp);
|
|
}
|
|
|
|
pthread_cond_signal(&timer_cond);
|
|
out:
|
|
pthread_mutex_unlock(&timer_lock);
|
|
|
|
return idx >= 0;
|
|
}
|
|
|
|
static bool timer_thread_stop = false;
|
|
|
|
static int timer_thread(void *arg)
|
|
{
|
|
struct pending_timer *p;
|
|
struct timespec ts;
|
|
unsigned long now;
|
|
int ret;
|
|
|
|
pthread_mutex_lock(&timer_lock);
|
|
|
|
while (!timer_thread_stop) {
|
|
now = jiffies;
|
|
p = heap_peek(&pending_timers);
|
|
|
|
if (!p) {
|
|
pthread_cond_wait(&timer_cond, &timer_lock);
|
|
continue;
|
|
}
|
|
|
|
if (time_after_eq(now, p->expires)) {
|
|
struct timer_list *timer = p->timer;
|
|
|
|
heap_del(&pending_timers, 0, pending_timer_cmp);
|
|
BUG_ON(!timer_pending(timer));
|
|
timer->pending = false;
|
|
|
|
timer_seq++;
|
|
BUG_ON(!timer_running());
|
|
|
|
pthread_mutex_unlock(&timer_lock);
|
|
timer->function(timer);
|
|
pthread_mutex_lock(&timer_lock);
|
|
|
|
timer_seq++;
|
|
pthread_cond_broadcast(&timer_running_cond);
|
|
continue;
|
|
}
|
|
|
|
|
|
ret = clock_gettime(CLOCK_REALTIME, &ts);
|
|
BUG_ON(ret);
|
|
|
|
ts = timespec_add_ns(ts, jiffies_to_nsecs(p->expires - now));
|
|
|
|
pthread_cond_timedwait(&timer_cond, &timer_lock, &ts);
|
|
}
|
|
|
|
pthread_mutex_unlock(&timer_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct task_struct *timer_task;
|
|
|
|
__attribute__((constructor(103)))
|
|
static void timers_init(void)
|
|
{
|
|
heap_init(&pending_timers, 64);
|
|
BUG_ON(!pending_timers.data);
|
|
|
|
timer_task = kthread_run(timer_thread, NULL, "timers");
|
|
BUG_ON(IS_ERR(timer_task));
|
|
}
|
|
|
|
__attribute__((destructor(103)))
|
|
static void timers_cleanup(void)
|
|
{
|
|
get_task_struct(timer_task);
|
|
|
|
pthread_mutex_lock(&timer_lock);
|
|
timer_thread_stop = true;
|
|
pthread_cond_signal(&timer_cond);
|
|
pthread_mutex_unlock(&timer_lock);
|
|
|
|
int ret = kthread_stop(timer_task);
|
|
BUG_ON(ret);
|
|
|
|
put_task_struct(timer_task);
|
|
timer_task = NULL;
|
|
}
|