From ebb1d65cf73b80063a36651c2519b84a02f71a32 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Tue, 1 Apr 2008 09:41:21 -0700 Subject: [PATCH] Allow timeout implementations to block, by using a kernel thread instead of a timer. This is required on the Quanta platform, which needs to down a semaphore inside the timeout implementation. Also, add a 2.4 compatibility implementation of kthread so that both 2.4 and 2.6 can use the kthread API. --- datapath/datapath.c | 38 ++- datapath/datapath.h | 1 + datapath/linux-2.4/compat-2.4/compat24.c | 2 + datapath/linux-2.4/compat-2.4/compat24.h | 2 + .../compat-2.4/include/linux/delay.h | 15 + .../compat-2.4/include/linux/kthread.h | 34 +++ datapath/linux-2.4/compat-2.4/kthread.c | 260 ++++++++++++++++++ 7 files changed, 337 insertions(+), 15 deletions(-) create mode 100644 datapath/linux-2.4/compat-2.4/include/linux/kthread.h create mode 100644 datapath/linux-2.4/compat-2.4/kthread.c diff --git a/datapath/datapath.c b/datapath/datapath.c index f2aa8625..32e51910 100644 --- a/datapath/datapath.c +++ b/datapath/datapath.c @@ -13,11 +13,12 @@ #include #include #include +#include #include #include +#include #include #include -#include #include #include #include @@ -38,8 +39,8 @@ #include "compat.h" -/* Number of seconds between runs of the flow expiration code. */ -#define EXPIRE_SECS 1 +/* Number of milliseconds between runs of the maintenance thread. */ +#define MAINT_SLEEP_MSECS 1000 #define BRIDGE_PORT_NO_FLOOD 0x00000001 @@ -71,7 +72,7 @@ int dp_dev_setup(struct net_device *dev); static struct datapath *dps[DP_MAX]; static DEFINE_MUTEX(dp_mutex); -static void dp_timer_handler(unsigned long arg); +static int dp_maint_func(void *data); static int send_port_status(struct net_bridge_port *p, uint8_t status); @@ -174,8 +175,9 @@ static int new_dp(int dp_idx) dp->miss_send_len = OFP_DEFAULT_MISS_SEND_LEN; - setup_timer(&dp->timer, dp_timer_handler, (unsigned long) dp); - mod_timer(&dp->timer, round_jiffies(jiffies + (EXPIRE_SECS * HZ))); + dp->dp_task = kthread_run(dp_maint_func, dp, "dp%d", dp_idx); + if (IS_ERR(dp->dp_task)) + goto err_free_dp; rcu_assign_pointer(dps[dp_idx], dp); mutex_unlock(&dp_mutex); @@ -288,10 +290,11 @@ static void del_dp(struct datapath *dp) rtnl_unlock(); #endif + kthread_stop(dp->dp_task); + /* Drop references to DP. */ list_for_each_entry_safe (p, n, &dp->port_list, node) del_switch_port(p); - del_timer_sync(&dp->timer); rcu_assign_pointer(dps[dp->dp_idx], NULL); /* Wait until no longer in use, then destroy it. */ @@ -301,18 +304,23 @@ static void del_dp(struct datapath *dp) module_put(THIS_MODULE); } -static void dp_timer_handler(unsigned long arg) +static int dp_maint_func(void *data) { - struct datapath *dp = (struct datapath *) arg; + struct datapath *dp = (struct datapath *) data; + + while (!kthread_should_stop()) { #if 1 - chain_timeout(dp->chain); + chain_timeout(dp->chain); #else - int count = chain_timeout(dp->chain); - chain_print_stats(dp->chain); - if (count) - printk("%d flows timed out\n", count); + int count = chain_timeout(dp->chain); + chain_print_stats(dp->chain); + if (count) + printk("%d flows timed out\n", count); #endif - mod_timer(&dp->timer, round_jiffies(jiffies + (EXPIRE_SECS * HZ))); + msleep_interruptible(MAINT_SLEEP_MSECS); + } + + return 0; } /* diff --git a/datapath/datapath.h b/datapath/datapath.h index cba2b793..b478eaef 100644 --- a/datapath/datapath.h +++ b/datapath/datapath.h @@ -42,6 +42,7 @@ struct datapath { struct timer_list timer; /* Expiration timer. */ struct sw_chain *chain; /* Forwarding rules. */ + struct task_struct *dp_task; /* Kernel thread for maintenance. */ /* Data related to the "of" device of this datapath */ struct net_device dev; diff --git a/datapath/linux-2.4/compat-2.4/compat24.c b/datapath/linux-2.4/compat-2.4/compat24.c index 13641ff1..bbacf311 100644 --- a/datapath/linux-2.4/compat-2.4/compat24.c +++ b/datapath/linux-2.4/compat-2.4/compat24.c @@ -15,6 +15,8 @@ int __init compat24_init(void) if (err) return err; + init_kthread(); + return genl_init(); } diff --git a/datapath/linux-2.4/compat-2.4/compat24.h b/datapath/linux-2.4/compat-2.4/compat24.h index 4e7038d8..48f4ec8e 100644 --- a/datapath/linux-2.4/compat-2.4/compat24.h +++ b/datapath/linux-2.4/compat-2.4/compat24.h @@ -6,6 +6,8 @@ void genl_exit(void); int random32_init(void); +void init_kthread(void); + void rcu_init(void); #endif /* compat24.h */ diff --git a/datapath/linux-2.4/compat-2.4/include/linux/delay.h b/datapath/linux-2.4/compat-2.4/include/linux/delay.h index 2db75187..a50f96c0 100644 --- a/datapath/linux-2.4/compat-2.4/include/linux/delay.h +++ b/datapath/linux-2.4/compat-2.4/include/linux/delay.h @@ -56,4 +56,19 @@ static inline unsigned long msecs_to_jiffies(const unsigned int m) #endif /* linux kernel < 2.4.29 */ +/** + * msleep_interruptible - sleep waiting for waitqueue interruptions + * @msecs: Time in milliseconds to sleep for + */ +static inline unsigned long msleep_interruptible(unsigned int msecs) +{ + unsigned long timeout = msecs_to_jiffies(msecs); + + while (timeout && !signal_pending(current)) { + set_current_state(TASK_INTERRUPTIBLE); + timeout = schedule_timeout(timeout); + } + return jiffies_to_msecs(timeout); +} + #endif diff --git a/datapath/linux-2.4/compat-2.4/include/linux/kthread.h b/datapath/linux-2.4/compat-2.4/include/linux/kthread.h new file mode 100644 index 00000000..15432a77 --- /dev/null +++ b/datapath/linux-2.4/compat-2.4/include/linux/kthread.h @@ -0,0 +1,34 @@ +#ifndef _LINUX_KTHREAD_H +#define _LINUX_KTHREAD_H +/* Simple interface for creating and stopping kernel threads without mess. */ +#include + +struct task_struct *kthread_create(int (*threadfn)(void *data), + void *data, + const char namefmt[], ...); + +/** + * kthread_run - create and wake a thread. + * @threadfn: the function to run until signal_pending(current). + * @data: data ptr for @threadfn. + * @namefmt: printf-style name for the thread. + * + * Description: Convenient wrapper for kthread_create() followed by + * wake_up_process(). Returns the kthread or ERR_PTR(-ENOMEM). + */ +#define kthread_run(threadfn, data, namefmt, ...) \ +({ \ + struct task_struct *__k \ + = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \ + if (!IS_ERR(__k)) \ + wake_up_process(__k); \ + __k; \ +}) + +int kthread_stop(struct task_struct *k); +int kthread_should_stop(void); + +int kthreadd(void *unused); +extern struct task_struct *kthreadd_task; + +#endif /* _LINUX_KTHREAD_H */ diff --git a/datapath/linux-2.4/compat-2.4/kthread.c b/datapath/linux-2.4/compat-2.4/kthread.c new file mode 100644 index 00000000..afc3b23c --- /dev/null +++ b/datapath/linux-2.4/compat-2.4/kthread.c @@ -0,0 +1,260 @@ +/* Kernel thread helper functions. + * Copyright (C) 2004 IBM Corporation, Rusty Russell. + * + * Creation is done via kthreadd, so that we get a clean environment + * even if we're invoked from userspace (think modprobe, hotplug cpu, + * etc.). + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "compat24.h" + +static DEFINE_SPINLOCK(kthread_create_lock); +static LIST_HEAD(kthread_create_list); +struct task_struct *kthreadd_task; + +struct kthread_create_info +{ + /* Information passed to kthread() from kthreadd. */ + int (*threadfn)(void *data); + void *data; + struct completion started; + + /* Result passed back to kthread_create() from kthreadd. */ + struct task_struct *result; + struct completion done; + + struct list_head list; +}; + +struct kthread_stop_info +{ + struct task_struct *k; + int err; + struct completion done; +}; + +/* Thread stopping is done by setthing this var: lock serializes + * multiple kthread_stop calls. */ +static DEFINE_MUTEX(kthread_stop_lock); +static struct kthread_stop_info kthread_stop_info; + +/** + * kthread_should_stop - should this kthread return now? + * + * When someone calls kthread_stop() on your kthread, it will be woken + * and this will return true. You should then return, and your return + * value will be passed through to kthread_stop(). + */ +int kthread_should_stop(void) +{ + return (kthread_stop_info.k == current); +} +EXPORT_SYMBOL(kthread_should_stop); + +static int kthread(void *_create) +{ + struct kthread_create_info *create = _create; + int (*threadfn)(void *data); + void *data; + int ret = -EINTR; + + /* Copy data: it's on kthread's stack */ + threadfn = create->threadfn; + data = create->data; + + /* OK, tell user we're spawned, wait for stop or wakeup */ + __set_current_state(TASK_UNINTERRUPTIBLE); + complete(&create->started); + schedule(); + + if (!kthread_should_stop()) + ret = threadfn(data); + + /* It might have exited on its own, w/o kthread_stop. Check. */ + if (kthread_should_stop()) { + kthread_stop_info.err = ret; + complete(&kthread_stop_info.done); + } + return 0; +} + +static void create_kthread(struct kthread_create_info *create) +{ + int pid; + + /* We want our own signal handler (we take no signals by default). */ + pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD); + if (pid < 0) { + create->result = ERR_PTR(pid); + } else { + wait_for_completion(&create->started); + read_lock(&tasklist_lock); + create->result = find_task_by_pid(pid); + read_unlock(&tasklist_lock); + } + complete(&create->done); +} + +/** + * kthread_create - create a kthread. + * @threadfn: the function to run until signal_pending(current). + * @data: data ptr for @threadfn. + * @namefmt: printf-style name for the thread. + * + * Description: This helper function creates and names a kernel + * thread. The thread will be stopped: use wake_up_process() to start + * it. See also kthread_run(), kthread_create_on_cpu(). + * + * When woken, the thread will run @threadfn() with @data as its + * argument. @threadfn() can either call do_exit() directly if it is a + * standalone thread for which noone will call kthread_stop(), or + * return when 'kthread_should_stop()' is true (which means + * kthread_stop() has been called). The return value should be zero + * or a negative error number; it will be passed to kthread_stop(). + * + * Returns a task_struct or ERR_PTR(-ENOMEM). + */ +struct task_struct *kthread_create(int (*threadfn)(void *data), + void *data, + const char namefmt[], + ...) +{ + struct kthread_create_info create; + + create.threadfn = threadfn; + create.data = data; + init_completion(&create.started); + init_completion(&create.done); + + spin_lock(&kthread_create_lock); + list_add_tail(&create.list, &kthread_create_list); + wake_up_process(kthreadd_task); + spin_unlock(&kthread_create_lock); + + wait_for_completion(&create.done); + + if (!IS_ERR(create.result)) { + va_list args; + va_start(args, namefmt); + vsnprintf(create.result->comm, sizeof(create.result->comm), + namefmt, args); + va_end(args); + } + return create.result; +} +EXPORT_SYMBOL(kthread_create); + +/** + * kthread_stop - stop a thread created by kthread_create(). + * @k: thread created by kthread_create(). + * + * Sets kthread_should_stop() for @k to return true, wakes it, and + * waits for it to exit. Your threadfn() must not call do_exit() + * itself if you use this function! This can also be called after + * kthread_create() instead of calling wake_up_process(): the thread + * will exit without calling threadfn(). + * + * Returns the result of threadfn(), or %-EINTR if wake_up_process() + * was never called. + */ +int kthread_stop(struct task_struct *k) +{ + int ret; + + mutex_lock(&kthread_stop_lock); + + /* Thread exit requires BKL, so prevent it while while we're messing + * with the thread. */ + lock_kernel(); + + /* Must init completion *before* thread sees kthread_stop_info.k */ + init_completion(&kthread_stop_info.done); + smp_wmb(); + + /* Now set kthread_should_stop() to true, and wake it up. */ + kthread_stop_info.k = k; + wake_up_process(k); + + unlock_kernel(); + + /* Once it dies, reset stop ptr, gather result and we're done. */ + wait_for_completion(&kthread_stop_info.done); + kthread_stop_info.k = NULL; + ret = kthread_stop_info.err; + mutex_unlock(&kthread_stop_lock); + + return ret; +} +EXPORT_SYMBOL(kthread_stop); + +void ignore_signals(struct task_struct *t) +{ + int i; + + for (i = 0; i < _NSIG; ++i) + t->sig->action[i].sa.sa_handler = SIG_IGN; + + flush_signals(t); +} + +/* This is a copy of the set_task_comm() function, which is not exported to + * modules. */ +static void do_set_task_comm(struct task_struct *tsk, char *buf) +{ + task_lock(tsk); + strncpy(tsk->comm, buf, sizeof(tsk->comm)); + tsk->comm[sizeof(tsk->comm)-1]='\0'; + task_unlock(tsk); +} + +int kthreadd(void *unused) +{ + struct task_struct *tsk = current; + + /* Setup a clean context for our children to inherit. */ + do_set_task_comm(tsk, "kthreadd"); + ignore_signals(tsk); + tsk->nice = -5; + set_cpus_allowed(tsk, CPU_MASK_ALL); + + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + if (list_empty(&kthread_create_list)) + schedule(); + __set_current_state(TASK_RUNNING); + + spin_lock(&kthread_create_lock); + while (!list_empty(&kthread_create_list)) { + struct kthread_create_info *create; + + create = list_entry(kthread_create_list.next, + struct kthread_create_info, list); + list_del_init(&create->list); + spin_unlock(&kthread_create_lock); + + create_kthread(create); + + spin_lock(&kthread_create_lock); + } + spin_unlock(&kthread_create_lock); + } + + return 0; +} + +void init_kthread(void) +{ + int pid; + + pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); + kthreadd_task = find_task_by_pid(pid); +} -- 2.30.2