[PATCH] Adding support of RLIMIT_CPUNS

From: ÐÑÐÐÐÑÐÐ ÐÐÐÐÐÐÐÐ
Date: Thu Aug 17 2017 - 16:30:57 EST


To set time limit for process now we can use RLIMIT_CPU.
However, it has precision up to one second and it can be
too big for some purposes.

This patch adds support of RLIMIT_CPUNS, which works
almost as RLIMIT_CPU, but has nanosecond precision.

At the moment, RLIMIT_CPU and RLIMIT_CPUNS are two
independent values, because I don't see any nice way
for them to be together.

Signed-off-by: Grigory Reznikov <grikukan@xxxxxxx>
---
fs/proc/base.c | 1 +
include/asm-generic/resource.h | 1 +
include/linux/posix-timers.h | 1 +
include/uapi/asm-generic/resource.h | 4 +++-
kernel/fork.c | 20 +++++++++++++----
kernel/sys.c | 11 ++++++++-
kernel/time/posix-cpu-timers.c | 45 +++++++++++++++++++++++++++++++++----
7 files changed, 73 insertions(+), 10 deletions(-)

diff --git a/fs/proc/base.c b/fs/proc/base.c
index 719c2e9..1e3049e 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -567,6 +567,7 @@ static const struct limit_names lnames[RLIM_NLIMITS] = {
[RLIMIT_NICE] = {"Max nice priority", NULL},
[RLIMIT_RTPRIO] = {"Max realtime priority", NULL},
[RLIMIT_RTTIME] = {"Max realtime timeout", "us"},
+ [RLIMIT_CPUNS] = {"Max cpu time", "ns"},
};

/* Display limits for a process */
diff --git a/include/asm-generic/resource.h b/include/asm-generic/resource.h
index 5e752b9..ec7b0c5 100644
--- a/include/asm-generic/resource.h
+++ b/include/asm-generic/resource.h
@@ -25,6 +25,7 @@
[RLIMIT_NICE] = { 0, 0 }, \
[RLIMIT_RTPRIO] = { 0, 0 }, \
[RLIMIT_RTTIME] = { RLIM_INFINITY, RLIM_INFINITY }, \
+ [RLIMIT_CPUNS] = { RLIM_INFINITY, RLIM_INFINITY }, \
}

#endif
diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h
index 62839fd..0e22bde 100644
--- a/include/linux/posix-timers.h
+++ b/include/linux/posix-timers.h
@@ -110,6 +110,7 @@ void posix_cpu_timers_exit_group(struct task_struct *task);
void set_process_cpu_timer(struct task_struct *task, unsigned int clock_idx,
u64 *newval, u64 *oldval);

+void update_rlimit_cpu_ns(struct task_struct *task, unsigned long rlim_new);
void update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new);

void posixtimer_rearm(struct siginfo *info);
diff --git a/include/uapi/asm-generic/resource.h b/include/uapi/asm-generic/resource.h
index c6d10af..a86b2f4 100644
--- a/include/uapi/asm-generic/resource.h
+++ b/include/uapi/asm-generic/resource.h
@@ -45,7 +45,9 @@
0-39 for nice level 19 .. -20 */
#define RLIMIT_RTPRIO 14 /* maximum realtime priority */
#define RLIMIT_RTTIME 15 /* timeout for RT tasks in us */
-#define RLIM_NLIMITS 16
+#define RLIMIT_CPUNS 16 /* CPU time in ns,
+ doesn't depend on RLIMIT_CPU */
+#define RLIM_NLIMITS 17

/*
* SuS says limits have to be unsigned.
diff --git a/kernel/fork.c b/kernel/fork.c
index e075b77..33f9bbf 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1348,11 +1348,23 @@ void __cleanup_sighand(struct sighand_struct *sighand)
*/
static void posix_cpu_timers_init_group(struct signal_struct *sig)
{
- unsigned long cpu_limit;
-
+ unsigned long cpu_limit, cpuns_limit, total_limit;
+ /* RLIMIT_CPU timeout, RLIMIT_CPUNS timeout and time
+ * to closest timeout
+ */
+
cpu_limit = READ_ONCE(sig->rlim[RLIMIT_CPU].rlim_cur);
- if (cpu_limit != RLIM_INFINITY) {
- sig->cputime_expires.prof_exp = cpu_limit * NSEC_PER_SEC;
+ cpuns_limit = READ_ONCE(sig->rlim[RLIMIT_CPUNS].rlim_cur);
+
+ total_limit = RLIM_INFINITY;
+
+ if (cpu_limit != RLIM_INFINITY)
+ total_limit = cpu_limit * NSEC_PER_SEC;
+ if (cpuns_limit != RLIM_INFINITY && cpuns_limit < total_limit)
+ total_limit = cpuns_limit;
+
+ if (total_limit != RLIM_INFINITY) {
+ sig->cputime_expires.prof_exp = total_limit;
sig->cputimer.running = true;
}

diff --git a/kernel/sys.c b/kernel/sys.c
index 2855ee7..539b110 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -1504,6 +1504,8 @@ int do_prlimit(struct task_struct *tsk, unsigned int resource,
*/
new_rlim->rlim_cur = 1;
}
+ if (resource == RLIMIT_CPUNS && new_rlim->rlim_cur == 0)
+ new_rlim->rlim_cur = NSEC_PER_SEC;
}
if (!retval) {
if (old_rlim)
@@ -1519,10 +1521,17 @@ int do_prlimit(struct task_struct *tsk, unsigned int resource,
* very long-standing error, and fixing it now risks breakage of
* applications, so we live with it
*/
- if (!retval && new_rlim && resource == RLIMIT_CPU &&
+ if (!retval && new_rlim && resource == RIMIT_CPU &&
new_rlim->rlim_cur != RLIM_INFINITY &&
IS_ENABLED(CONFIG_POSIX_TIMERS))
update_rlimit_cpu(tsk, new_rlim->rlim_cur);
+
+ if (!retval && new_rlim && resource == RLIMIT_CPUNS &&
+ new_rlim->rlim_cur != RLIM_INFINITY &&
+ IS_ENABLED(CONFIG_POSIX_TIMERS))
+ update_rlimit_cpu_ns(tsk, new_rlim->rlim_cur);
+
+
out:
read_unlock(&tasklist_lock);
return retval;
diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c
index a3bd5db..e4830f6 100644
--- a/kernel/time/posix-cpu-timers.c
+++ b/kernel/time/posix-cpu-timers.c
@@ -19,20 +19,26 @@
static void posix_cpu_timer_rearm(struct k_itimer *timer);

/*
- * Called after updating RLIMIT_CPU to run cpu timer and update
+ * Called after updating RLIMIT_CPUNS to run cpu timer and update
* tsk->signal->cputime_expires expiration cache if necessary. Needs
* siglock protection since other code may update expiration cache as
* well.
*/
-void update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new)
+void update_rlimit_cpu_ns(struct task_struct *task, unsigned long rlim_new)
{
- u64 nsecs = rlim_new * NSEC_PER_SEC;
+ u64 nsecs = rlim_new;

spin_lock_irq(&task->sighand->siglock);
set_process_cpu_timer(task, CPUCLOCK_PROF, &nsecs, NULL);
spin_unlock_irq(&task->sighand->siglock);
}

+/* Same function for RLIMIT_CPU */
+void update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new)
+{
+ update_rlimit_cpu(task, rlim_new * NSEC_PER_SEC);
+}
+
static int check_clock(const clockid_t which_clock)
{
int error = 0;
@@ -938,6 +944,9 @@ static void check_process_timers(struct task_struct *tsk,
SIGPROF);
check_cpu_itimer(tsk, &sig->it[CPUCLOCK_VIRT], &virt_expires, utime,
SIGVTALRM);
+ /*
+ * RLIMIT_CPU check
+ */
soft = READ_ONCE(sig->rlim[RLIMIT_CPU].rlim_cur);
if (soft != RLIM_INFINITY) {
unsigned long psecs = div_u64(ptime, NSEC_PER_SEC);
@@ -974,7 +983,35 @@ static void check_process_timers(struct task_struct *tsk,
if (!prof_expires || x < prof_expires)
prof_expires = x;
}
-
+ /*
+ * RLIMIT_CPUNS check
+ */
+ soft = READ_ONCE(sig->rlim[RLIMIT_CPUNS].rlim_cur);
+ if (soft != RLIM_INFINITY) {
+ unsigned long hard =
+ READ_ONCE(sig->rlim[RLIMIT_CPUNS].rlim_max);
+ if (ptime >= hard) {
+ if (print_fatal_signals) {
+ pr_info("RT Watchdog Timeout (hard): %s[%d]\n",
+ tsk->comm, task_pid_nr(tsk));
+ }
+ __group_send_sig_info(SIGKILL, SEND_SIG_PRIV, tsk);
+ return;
+ }
+ if (ptime >= soft) {
+ if (print_fatal_signals) {
+ pr_info("CPU Watchdog Timeout (soft): %s[%d]\n",
+ tsk->comm, task_pid_nr(tsk));
+ }
+ __group_send_sig_info(SIGXCPU, SEND_SIG_PRIV, tsk);
+ if (soft < hard) {
+ soft += NSEC_PER_SEC;
+ sig->rlim[RLIMIT_CPUNS].rlim_cur = soft;
+ }
+ }
+ if (!prof_expires || soft < prof_expires)
+ prof_expires = soft;
+ }
sig->cputime_expires.prof_exp = prof_expires;
sig->cputime_expires.virt_exp = virt_expires;
sig->cputime_expires.sched_exp = sched_expires;
--
2.7.4