uptime: allow the optional limiting of kernel runtime

Introduce the ability to limit the limit the uptime of a kernel.
When enabled, these options set a maximum uptime on the kernel, and
(optionally) trigger a clean reboot at expiration.

This functionality may appear to be very close to the softdog watchdog
implementation. It is. But can't be the softdog for several reasons:

  - The soft watchdog should be available while this functionality is active
  - The duration range is different between this and the softdog. The
    timeout available here is potentially quite a bit longer.
  - At expiration, there are different expiration requirements and actions.
  - This functionality is specific to a particular use case and should
    not impact mainline functionality

To cleanly restart the kernel after one minute of uptime, the following
config items would be required:

  CONFIG_UPTIME_LIMITED_KERNEL=y
  CONFIG_UPTIME_LIMIT_DURATION=1
  CONFIG_UPTIME_LIMIT_KERNEL_REBOOT=y

Signed-off-by: Bruce Ashfield <bruce.ashfield@windriver.com>
This commit is contained in:
Bruce Ashfield 2011-07-12 10:26:50 -04:00
parent ce05be193a
commit ff29d80f66
3 changed files with 192 additions and 0 deletions

View File

@ -1085,6 +1085,31 @@ menuconfig EXPERT
environments which can tolerate a "non-standard" kernel.
Only use this if you really know what you are doing.
config UPTIME_LIMITED_KERNEL
bool "Create a kernel with uptime limitations"
default n
help
Limit the amount of time a kernel can run. The associated UPTIME_LIMIT*
kernel config options should be used to tune the behaviour.
config UPTIME_LIMIT_DURATION
int "Kernel uptime limit in minutes"
depends on UPTIME_LIMITED_KERNEL
range 0 14400
default 0
help
Define the uptime limitation on a kernel in minutes. Once
the defined time expires the kernel will emit a warning, cease
to be usable and eventually restart. The valid range is 0 (disable)
to 14400 (10 days)
config UPTIME_LIMIT_KERNEL_REBOOT
bool "Reboot a time limited kernel at expiration"
depends on UPTIME_LIMITED_KERNEL
default y
help
Reboot an uptime limited kernel at expiration.
config UID16
bool "Enable 16-bit UID system calls" if EXPERT
depends on HAVE_UID16 && MULTIUSER

View File

@ -47,6 +47,7 @@ obj-$(CONFIG_FREEZER) += freezer.o
obj-$(CONFIG_PROFILING) += profile.o
obj-$(CONFIG_STACKTRACE) += stacktrace.o
obj-y += time/
obj-$(CONFIG_UPTIME_LIMITED_KERNEL) += uptime_limit.o
obj-$(CONFIG_FUTEX) += futex.o
ifeq ($(CONFIG_COMPAT),y)
obj-$(CONFIG_FUTEX) += futex_compat.o

166
kernel/uptime_limit.c Normal file
View File

@ -0,0 +1,166 @@
/*
* uptime_limit.c
*
* This file contains the functions which can limit kernel uptime
*
* Copyright (C) 2011 Bruce Ashfield (bruce.ashfield@windriver.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*
* This functionality is somewhat close to the softdog watchdog
* implementation, but it cannot be used directly for several reasons:
*
* - The soft watchdog should be available while this functionality is active
* - The duration range is different between this and the softdog. The
* timeout available here is potentially quite a bit longer.
* - At expiration, there are different expiration requirements and actions.
* - This functionality is specific to a particular use case and should
* not impact mainline functionality
*
*/
#include <linux/kernel.h>
#include <linux/reboot.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#define UPTIME_LIMIT_IN_SECONDS (CONFIG_UPTIME_LIMIT_DURATION * 60)
#define MIN(X, Y) ((X) <= (Y) ? (X) : (Y))
#define TEN_MINUTES_IN_SECONDS 600
enum uptime_expiration_type {
uptime_no_action,
uptime_reboot
};
static enum uptime_expiration_type uptime_expiration_action = uptime_no_action;
static struct timer_list timelimit_timer;
static struct task_struct *uptime_worker_task;
static void timelimit_expire(unsigned long timeout_seconds)
{
char msg[128];
int msglen = 127;
if (timeout_seconds) {
if (timeout_seconds >= 60)
snprintf(msg, msglen,
"Uptime: kernel validity duration has %d %s remaining\n",
(int) timeout_seconds / 60, "minute(s)");
else
snprintf(msg, msglen,
"Uptime: kernel validity duration has %d %s remaining\n",
(int) timeout_seconds, "seconds");
printk(KERN_CRIT "%s", msg);
timelimit_timer.expires = jiffies + timeout_seconds * HZ;
timelimit_timer.data = 0;
add_timer_on(&timelimit_timer, cpumask_first(cpu_online_mask));
} else {
printk(KERN_CRIT "Uptime: Kernel validity timeout has expired\n");
#ifdef CONFIG_UPTIME_LIMIT_KERNEL_REBOOT
uptime_expiration_action = uptime_reboot;
wake_up_process(uptime_worker_task);
}
#endif
}
/*
* This thread starts and then immediately goes to sleep. When it is woken
* up, it carries out the instructions left in uptime_expiration_action. If
* no action was specified it simply goes back to sleep.
*/
static int uptime_worker(void *unused)
{
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
schedule();
if (kthread_should_stop())
break;
if (uptime_expiration_action == uptime_reboot) {
printk(KERN_CRIT "Uptime: restarting machine\n");
kernel_restart(NULL);
}
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
}
static int timeout_enable(int cpu)
{
int err = 0;
int warning_limit;
/*
* Create an uptime worker thread. This thread is required since the
* safe version of kernel restart cannot be called from a
* non-interruptible context. Which means we cannot call it directly
* from a timer callback. So we arrange for the timer expiration to
* wakeup a thread, which performs the action.
*/
uptime_worker_task = kthread_create(uptime_worker,
(void *)(unsigned long)cpu,
"uptime_worker/%d", cpu);
if (IS_ERR(uptime_worker_task)) {
printk(KERN_ERR "Uptime: task for cpu %i failed\n", cpu);
err = PTR_ERR(uptime_worker_task);
goto out;
}
/* bind to cpu0 to avoid migration and hot plug nastiness */
kthread_bind(uptime_worker_task, cpu);
wake_up_process(uptime_worker_task);
/* Create the timer that will wake the uptime thread at expiration */
init_timer(&timelimit_timer);
timelimit_timer.function = timelimit_expire;
/*
* Fire two timers. One warning timeout and the final timer
* which will carry out the expiration action. The warning timer will
* expire at the minimum of half the original time or ten minutes.
*/
warning_limit = MIN(UPTIME_LIMIT_IN_SECONDS/2, TEN_MINUTES_IN_SECONDS);
timelimit_timer.expires = jiffies + warning_limit * HZ;
timelimit_timer.data = UPTIME_LIMIT_IN_SECONDS - warning_limit;
add_timer_on(&timelimit_timer, cpumask_first(cpu_online_mask));
out:
return err;
}
static int __init timelimit_init(void)
{
int err = 0;
printk(KERN_INFO "Uptime: system uptime restrictions enabled\n");
/*
* Enable the timeout thread for cpu 0 only, assuming that the
* uptime limit is non-zero, to protect against any cpu
* migration issues.
*/
if (UPTIME_LIMIT_IN_SECONDS)
err = timeout_enable(0);
return err;
}
device_initcall(timelimit_init);