diff --git a/Documentation/scheduler/sched-preempt-delay.txt b/Documentation/scheduler/sched-preempt-delay.txt
new file mode 100644
index 0000000..4c9e111
--- /dev/null
+++ b/Documentation/scheduler/sched-preempt-delay.txt
@@ -0,0 +1,112 @@
+=================================
+What is preemption delay feature?
+=================================
+
+There are times when a userspace task is executing a critical section
+which gates a number of other tasks that want access to the same
+critical section. If the task holding the lock that guards this critical
+section happens to grab the lock just before its timeslice is up and is
+preempted by the scheduler, scheduler ends up scheduling other
+tasks which immediately try to grab the lock to enter the critical
+section. This only results in lots of context switches as tasks wake up
+and go to sleep immediately. If on the other hand, the original task
+were allowed to run for an extra timeslice, it could have completed
+executing its critical section allowing other tasks to make progress
+when they get scheduled. Preemption delay feature allows a task to
+request scheduler to let it borrow one extra timeslice, if possible.
+
+
+==================================
+Using the preemption delay feature
+==================================
+
+This feature is compiled in the kernel by setting
+CONFIG_SCHED_PREEMPT_DELAY in kernel configuration. By default, the
+kernel boots up with this feature disabled. Enable it using sysctl
+tunable kernel.preempt_delay_available. Once this feature is
+enabled, the userspace process communicates with the kernel using a
+4-byte memory location in its address space. This location must be
+aligned to 4-byte boundary. It first gives the kernel address for this
+memory location by making a prctl() system call with PR_SET_PREEMPT_DELAY
+option. This memory location is interpreted as the following data
+structure (defined in linux/sched.h):
+
+struct sched_delay_req {
+ unsigned char nopreempt; /* flag to request preemption delay */
+ unsigned char yield; /* flag from kernel indicating */
+ /* preemption delay was granted */
+ unsigned char rsvd[2]; /* reserved */
+};
+
+Task requests a preemption delay by writing a non-zero value to the
+first byte - nopreempt. Scheduler checks this value before preempting
+the task. Scheduler can choose to grant one and only an additional
+time slice to the task for each delay request but this delay is not
+guaranteed. If scheduler does grant an additional timeslice, it will
+set the flag in second byte. Upon completion of the section of code
+where the task wants preemption delay, task should check the second byte.
+If the flag in second byte is set, it should clear this flag and call
+sched_yield() so as to not hog the processor. If a thread was granted
+additional timeslice and it fails to call sched_yield(), scheduler
+will penalize it by denying its next request for additional timeslice.
+Following sample code illustrates how to use this feature:
+
+#include <linux/sched.h>
+
+int main()
+{
+ unsigned char buf[256];
+ struct sched_delay_req delay;
+
+ bzero(&delay, sizeof(delay));
+
+ /* Tell kernel where the flag lives */
+ prctl(PR_SET_PREEMPT_DELAY, &delay);
+
+ while (/* some condition is true */) {
+ /* do some work and get ready to enter critical section */
+ delay.nopreempt = 1;
+ /*
+ * Obtain lock for critical section
+ */
+ /*
+ * critical section
+ */
+ /*
+ * Release lock for critical section
+ */
+ delay.nopreempt = 0;
+ /* Give the CPU up if required */
+ if (delay.yield) {
+ delay.yield = 0;
+ sched_yield();
+ }
+ /* do some more work */
+ }
+ /*
+ * Tell kernel we are done asking for preemption delay
+ */
+ prctl(PR_SET_PREEMPT_DELAY, NULL);
+}
+
+
+====================
+Scheduler statistics
+====================
+
+Preemption delay features adds a new field to scheduler statictics -
+nr_preempt_delayed. This is a per thread statistic that tracks the
+number of times a thread was granted amnesty from preemption when it
+requested for one. "cat /proc/<pid>/task/<tid>/sched" will list this
+number along with other scheduler statistics.
+
+
+=====
+Notes
+=====
+
+1. If the location of shared flag is not aligned to 4-byte boundary,
+ prctl() will terminate with EFAULT.
+2. Userspace app should zero out the sched_delay_req structure before
+ giving kernel the address of this structure. Stale data in this
+ structure could cause unintended requests for preemption delay.diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 41a503c..6c24167 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -852,6 +852,17 @@ config SCHED_MC
making when dealing with multi-core CPU chips at a cost of slightly
increased overhead in some places. If unsure say N here.
+config SCHED_PREEMPT_DELAY
+ def_bool n
+ prompt "Scheduler preemption delay support"
+ ---help---
+ Say Y here if you want to be able to delay scheduler preemption
+ when possible by setting a flag in a memory location after
+ sharing the address of this location with kernel using
+ PR_SET_PREEMPT_DELAY prctl() call. See
+ Documentation/scheduler/sched-preempt-delay.txt for details.
+ If in doubt, say "N".
+
source "kernel/Kconfig.preempt"
config X86_UP_APIC
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 5e344bb..0b2f911 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1116,6 +1116,7 @@ struct sched_statistics {
u64 nr_wakeups_affine_attempts;
u64 nr_wakeups_passive;
u64 nr_wakeups_idle;
+ u64 nr_preempt_delayed;
};
#endif
@@ -1232,6 +1233,14 @@ enum perf_event_task_context {
perf_nr_task_contexts,
};
+#ifdef CONFIG_SCHED_PREEMPT_DELAY
+struct preempt_delay {
+ struct sched_delay_req *delay_req; /* delay request flag pointer */
+ unsigned char delay_granted; /* currently in delay */
+ unsigned char yield_penalty; /* failure to yield penalty */
+};
+#endif
+
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;@@ -1324,6 +1333,9 @@ struct task_struct {
/* Revert to default priority/policy when forking */
unsigned sched_reset_on_fork:1;
unsigned sched_contributes_to_load:1;
+#ifdef CONFIG_SCHED_PREEMPT_DELAY
+ struct preempt_delay sched_preempt_delay;
+#endif
unsigned long atomic_flags; /* Flags needing atomic access. */
@@ -3031,4 +3043,30 @@ static inline unsigned long rlimit_max(unsigned int limit)
return task_rlimit_max(current, limit);
}
+#ifdef CONFIG_SCHED_PREEMPT_DELAY
+static inline void task_init_preempt_delay(struct task_struct *p)
+{
+ memset(&p->sched_preempt_delay, 0, sizeof(struct preempt_delay));
+}
+static inline void task_clear_preempt_yield(struct task_struct *p)
+{
+ p->sched_preempt_delay.yield_penalty = 0;
+}
+extern int preempt_delay_write(struct task_struct *task,
+ unsigned long preempt_delay_addr);
+#define SCHED_SET_PREEMPT_DELAY(a) preempt_delay_write(current, a)
+#define SCHED_GET_PREEMPT_DELAY(a) \
+ put_user((unsigned long)current->sched_preempt_delay.delay_req,\
+ (unsigned long __user *)a)
+#else
+static inline void task_init_preempt_delay(struct task_struct *p)
+{
+}
+static inline void task_clear_preempt_yield(struct task_struct *p)
+{
+}
+#define SCHED_SET_PREEMPT_DELAY(a) (-EINVAL)
+#define SCHED_GET_PREEMPT_DELAY(a) (-EINVAL)
+#endif /* CONFIG_SCHED_PREEMPT_DELAY */
+
#endifdiff --git a/include/linux/sched/sysctl.h b/include/linux/sched/sysctl.h
index 596a0e0..516f74e 100644
--- a/include/linux/sched/sysctl.h
+++ b/include/linux/sched/sysctl.h
@@ -107,4 +107,8 @@ extern int sysctl_numa_balancing(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp,
loff_t *ppos);
+#ifdef CONFIG_SCHED_PREEMPT_DELAY
+extern int sysctl_preempt_delay_available;
+#endif
+
#endif /* _SCHED_SYSCTL_H */
diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h
index 513df75..ecfd2cd 100644
--- a/include/uapi/linux/prctl.h
+++ b/include/uapi/linux/prctl.h
@@ -179,4 +179,7 @@ struct prctl_mm_map {
#define PR_SET_THP_DISABLE 41
#define PR_GET_THP_DISABLE 42
+#define PR_SET_PREEMPT_DELAY 43
+#define PR_GET_PREEMPT_DELAY 44
+
#endif /* _LINUX_PRCTL_H */diff --git a/include/uapi/linux/sched.h b/include/uapi/linux/sched.h
index b932be9..66a2f67 100644
--- a/include/uapi/linux/sched.h
+++ b/include/uapi/linux/sched.h
@@ -49,4 +49,13 @@
*/
#define SCHED_FLAG_RESET_ON_FORK 0x01
+/*
+ * struct for requesting preemption delay from scheduler
+ */
+struct sched_delay_req {
+ unsigned char nopreempt;
+ unsigned char yield;
+ unsigned char rsvd[2];
+};
+
#endif /* _UAPI_LINUX_SCHED_H */diff --git a/kernel/fork.c b/kernel/fork.c
index 9b7d746..aea655b 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1672,6 +1672,8 @@ long do_fork(unsigned long clone_flags,
get_task_struct(p);
}
+ task_init_preempt_delay(p);
+
wake_up_new_task(p);
/* forking complete and child started to run, tell ptracer */
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 24beb9b..a926eea 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -4201,6 +4201,7 @@ SYSCALL_DEFINE0(sched_yield)
{
struct rq *rq = this_rq_lock();
+ task_clear_preempt_yield(current);
schedstat_inc(rq, yld_count);
current->sched_class->yield_task(rq);
diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c
index ce33780..618d2ac 100644
--- a/kernel/sched/debug.c
+++ b/kernel/sched/debug.c
@@ -597,6 +597,7 @@ void proc_sched_show_task(struct task_struct *p, struct seq_file *m)
P(se.statistics.nr_wakeups_affine_attempts);
P(se.statistics.nr_wakeups_passive);
P(se.statistics.nr_wakeups_idle);
+ P(se.statistics.nr_preempt_delayed);
{
u64 avg_atom, avg_per_cpu;diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index ef2b104..a880c6f 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -428,6 +428,129 @@ find_matching_se(struct sched_entity **se, struct sched_entity **pse)
#endif /* CONFIG_FAIR_GROUP_SCHED */
+#ifdef CONFIG_SCHED_PREEMPT_DELAY
+int sysctl_preempt_delay_available;
+
+int
+preempt_delay_write(struct task_struct *task, unsigned long preempt_delay_addr)
+{
+ /*
+ * Do not allow write if preemption delay feature is disabled
+ */
+ if (!sysctl_preempt_delay_available)
+ return -EPERM;
+
+ if ((void *)preempt_delay_addr == NULL) {
+ task->sched_preempt_delay.delay_req = NULL;
+ return 0;
+ }
+
+ /*
+ * Validate the pointer. It should be naturally aligned
+ */
+ if (unlikely((preempt_delay_addr % sizeof(u32)) != 0))
+ return -EFAULT;
+ if (unlikely(!access_ok(rw, preempt_delay_addr,
+ sizeof(struct sched_delay_req))))
+ return -EFAULT;
+
+ task->sched_preempt_delay.delay_req =
+ (struct sched_delay_req *) preempt_delay_addr;
+ return 0;
+}
+
+/*
+ * delay_resched_rq(): Check if the task about to be preempted has
+ * requested an additional time slice. If it has, grant it additional
+ * timeslice once.
+ */
+static void
+delay_resched_rq(struct rq *rq)
+{
+ struct task_struct *curr = rq->curr;
+ struct sched_entity *se;
+ struct sched_delay_req *delay_req, delay_flag;
+ int ret;
+
+ if (!sysctl_preempt_delay_available)
+ goto resched_now;
+
+ /*
+ * Check if task is using pre-emption delay feature. If address
+ * for preemption delay request flag is not set, this task is
+ * not using preemption delay feature, we can reschedule without
+ * any delay
+ */
+ delay_req = curr->sched_preempt_delay.delay_req;
+ if (delay_req == NULL)
+ goto resched_now;
+
+ /*
+ * Pre-emption delay will be granted only once. If this task
+ * has already been granted delay, rechedule now
+ */
+ if (curr->sched_preempt_delay.delay_granted) {
+ curr->sched_preempt_delay.delay_granted = 0;
+ goto resched_now;
+ }
+
+ /*
+ * Get the value of preemption delay request flag from userspace.
+ * Task had already passed us the address where the flag is stored
+ * in userspace earlier. If there is a page fault accessing this
+ * flag in userspace, that means userspace has not touched this
+ * flag recently and we can assume no preemption delay is needed.
+ *
+ * If task is not requesting additional timeslice, resched now
+ */
+ pagefault_disable();
+ ret = __copy_from_user_inatomic(&delay_flag, delay_req,
+ sizeof(u32));
+ pagefault_enable();
+ if (ret || !delay_flag.nopreempt)
+ goto resched_now;
+
+ /*
+ * Current thread has requested preemption delay and has not
+ * been granted an extension yet. If this thread failed to yield
+ * processor after being granted amnesty last time, penalize it
+ * by not granting this delay request, otherwise give it an extra
+ * timeslice.
+ */
+ if (curr->sched_preempt_delay.yield_penalty) {
+ curr->sched_preempt_delay.yield_penalty = 0;
+ goto resched_now;
+ }
+
+ se = &curr->se;
+ curr->sched_preempt_delay.delay_granted = 1;
+
+ /*
+ * Set the penalty flag for failing to yield the processor after
+ * being granted immunity. This flag will be cleared in
+ * sched_yield() if the thread indeed calls sched_yield
+ */
+ curr->sched_preempt_delay.yield_penalty = 1;
+
+ /*
+ * Let the thread know it got amnesty and it should call
+ * sched_yield() when it is done to avoid penalty next time
+ * it wants amnesty.
+ */
+ delay_flag.nopreempt = 0;
+ delay_flag.yield = 1;
+ schedstat_inc(curr, se.statistics.nr_preempt_delayed);
+ __copy_to_user_inatomic(delay_req, &delay_flag, sizeof(u32));
+
+ return;
+
+resched_now:
+ resched_curr(rq);
+}
+#else
+#define delay_resched_rq(rq) resched_curr(rq)
+#endif /* CONFIG_SCHED_PREEMPT_DELAY */
+
static __always_inline
void account_cfs_rq_runtime(struct cfs_rq *cfs_rq, u64 delta_exec);
@@ -2951,7 +3074,7 @@ check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
ideal_runtime = sched_slice(cfs_rq, curr);
delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
if (delta_exec > ideal_runtime) {
- resched_curr(rq_of(cfs_rq));
+ delay_resched_rq(rq_of(cfs_rq));
/*
* The current task ran long enough, ensure it doesn't get
* re-elected due to buddy favours.@@ -2975,7 +3098,7 @@ check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
return;
if (delta > ideal_runtime)
- resched_curr(rq_of(cfs_rq));
+ delay_resched_rq(rq_of(cfs_rq));
}
static void
@@ -4792,7 +4915,7 @@ static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int wake_
return;
preempt:
- resched_curr(rq);
+ delay_resched_rq(rq);
/*
* Only set the backward buddy when the current task is still
* on the rq. This can happen when a wakeup gets interleaved
diff --git a/kernel/sys.c b/kernel/sys.c
index 1eaa2f0..a8b1eff 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -2203,6 +2203,12 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
me->mm->def_flags &= ~VM_NOHUGEPAGE;
up_write(&me->mm->mmap_sem);
break;
+ case PR_SET_PREEMPT_DELAY:
+ error = SCHED_SET_PREEMPT_DELAY(arg2);
+ break;
+ case PR_GET_PREEMPT_DELAY:
+ error = SCHED_GET_PREEMPT_DELAY(arg2);
+ break;
default:
error = -EINVAL;
break;
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 15f2511..c1cd344 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -1104,6 +1104,15 @@ static struct ctl_table kern_table[] = {
.proc_handler = proc_dointvec,
},
#endif
+#ifdef CONFIG_SCHED_PREEMPT_DELAY
+ {
+ .procname = "preempt_delay_available",
+ .data = &sysctl_preempt_delay_available,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
+#endif
{ }
};
--
1.9.1