fairsched + O(1) process scheduler
Subsystems (4)
This patch is a forward port of the fairsched-2.4.19 patch by Rik van Riel, which ensures all competing users get an equal share of cpu time.
Since the earlier patch applied to the classic O(n) process scheduler, and this one applies to the standard 2.5 O(1) one, the mapping isn't one-to-one but rather more complex.
Original 2.4.19 version: Rik van Riel, [email protected] Forward ported 2.5.66 version: Antonio Vargas, [email protected]
include/linux/sched.h | 4 ++++ include/linux/sysctl.h | 1 + kernel/sched.c | 11 +++++++++++ kernel/sysctl.c | 3 +++ kernel/user.c | 10 +++++++++- 5 files changed, 28 insertions(+), 1 deletion(-)
include/linux/sched.h renamed from include/linux/sched.h~fairsched-A0 +4 -0
@@ -280,6 +280,10 @@ struct user_struct {/* Hash table maintenance information */ struct list_head uidhash_list;uid_t uid; + +/* List maintenance information */ +struct list_head uid_list; +unsigned long ticks;/* How many ticks has received the user */ };#define get_current_user () ({ \
include/linux/sysctl.h renamed from include/linux/sysctl.h~fairsched-A0 +1 -0
@@ -129,6 +129,7 @@ enumKERN_CADPID =54 ,/* int: PID of the process to notify on CAD */ KERN_PIDMAX =55 ,/* int: PID # limit */ KERN_CORE_PATTERN =56 ,/* string: pattern for core-file names */ + KERN_FAIRSCHED=57, /* int: turn per-user fair cpu scheduler on/off */ };
kernel/sched.c renamed from kernel/sched.c~fairsched-A0 +11 -0
@@ -33,6 +33,13 @@#include <linux/timer.h> #include <linux/rcupdate.h> +/* +* turn per-user fair scheduler on/off +*/ + +int fairsched =1 ; + +#ifdef CONFIG_NUMA #define cpu_to_node_mask (cpu) node_to_cpumask(cpu_to_node(cpu))#else
@@ -316,6 +323,10 @@ static int effective_prio(task_t *p) bonus =MAX_USER_PRIO *PRIO_BONUS_RATIO *p->sleep_avg/MAX_SLEEP_AVG /100 -MAX_USER_PRIO *PRIO_BONUS_RATIO /100 /2 ; + +if (fairsched){ +/* special processing for per-user fair scheduler */ + } prio = p->static_prio - bonus;if (prio <MAX_RT_PRIO )
kernel/sysctl.c renamed from kernel/sysctl.c~fairsched-A0 +3 -0
@@ -57,6 +57,7 @@ extern char core_pattern[];extern int cad_pid;extern int pid_max;extern int sysctl_lower_zone_protection; +extern int fairsched;/* this is needed for the proc_dointvec_minmax for [fs_]overflow UID and GID */ static int maxolduid =65535 ;
@@ -263,6 +264,8 @@ static ctl_table kern_table[] = {#endif {KERN_PIDMAX ,"pid_max" , &pid_max,sizeof (int ), 0600, NULL, &proc_dointvec}, + {KERN_FAIRSCHED,"fairsched", + &fairsched,sizeof(int),0644,NULL,&proc_dointvec}, {0} };
kernel/user.c renamed from kernel/user.c~fairsched-A0 +9 -1
@@ -27,10 +27,13 @@ static kmem_cache_t *uid_cachep;static struct list_head uidhash_table[UIDHASH_SZ ];static spinlock_t uidhash_lock =SPIN_LOCK_UNLOCKED ; +static struct list_head user_list; +struct user_struct root_user = { .__count =ATOMIC_INIT (1 ), .processes =ATOMIC_INIT (1 ), - .files =ATOMIC_INIT (0 ) + .files =ATOMIC_INIT (0 ), + .ticks =NICE_TO_PRIO (0 ) };/*
@@ -39,10 +42,12 @@ struct user_struct root_user = {static inline void uid_hash_insert (struct user_struct *up,struct list_head *hashent) {list_add (&up->uidhash_list, hashent); +list_add (&up->uid_list, &user_list); }static inline void uid_hash_remove (struct user_struct *up) { +list_del (&up->uid_list);list_del (&up->uidhash_list); }
@@ -97,6 +102,7 @@ struct user_struct * alloc_uid(uid_t uidatomic_set (&new->__count,1 );atomic_set (&new->processes,0 );atomic_set (&new->files,0 ); +atomic_set (&new->ticks,NICE_TO_PRIO (0 ));/* * Before adding this, check whether we raced
@@ -146,6 +152,8 @@ static int __init uid_cache_init(void)for (n =0 ; n <UIDHASH_SZ ; ++n)INIT_LIST_HEAD (uidhash_table + n); + +INIT_LIST_HEAD (&user_list);/* Insert the root user immediately - init already runs with this */ uid_hash_insert (&root_user,uidhashentry (0 ));
_
On Tue, Apr 01, 2003 at 02:51:59PM +0200, Antonio Vargas wrote:
+
+ if(fairsched){
+ /* special processing for per-user fair scheduler */
+ }
I suspect something more needs to happen there. =)
I'd recommend a different approach, i.e. stratifying the queue into a user top level and a task bottom level. The state is relatively well encapsulated so it shouldn't be that far out.
-- wli
Quoted 7 lines · William Lee Irwin III
On Tue, Apr 01, 2003 at 02:51:59PM +0200, Antonio Vargas wrote:
kernel/sched.c renamed from kernel/sched.c~fairsched-A0 +4
-0+ +if (fairsched){ +/* special processing for per-user fair scheduler */ + }I suspect something more needs to happen there. =)
:) I haven't even compiled with this patch, I'm just trying
to get around my ideas and thus I posted so that:
a. We had an off-site backup
b. People with experience could shout out loud if they saw
some big-time silliness.
Quoted 6 lines
I'd recommend a different approach, i.e. stratifying the queue into a user top level and a task bottom level. The state is relatively well encapsulated so it shouldn't be that far out.
-- wli
I suspect you mean the scheduler runqueues?
My initial idea runs as follows:
a. On each array switch, we add some fixed value
to user->ticks. (HZ perhaps?)
b. When adding, we cap at some maximum value.
(2*HZ perhaps?)
c. On each timer tick, we decrement current->user->ticks.
d. When decrementing, if it's below zero we just
end this thread. (user has expired)
e. When switching threads, we take the first one that belongs
to a non-expired user. I think this can be done by sending the user-expired threads to the expired array, just like when they expire themselves.
I'll try to code this tonight, so I'll post later on if I'm lucky.
I think this needs much less complexity for a first version, but I would try what you propose if I get intimate enough with the scheduler.
Greets, Antonio.
Quoted 44 lines · Antonio Vargas
Quoted 7 lines · William Lee Irwin III
On Tue, Apr 01, 2003 at 02:51:59PM +0200, Antonio Vargas wrote:
(quoted diff) +4
-0+ + if(fairsched){ + /* special processing for per-user fair scheduler */ + }I suspect something more needs to happen there. =)
:) I haven't even compiled with this patch, I'm just trying
to get around my ideas and thus I posted so that:a. We had an off-site backup
b. People with experience could shout out loud if they saw
some big-time silliness.Quoted 6 lines
I'd recommend a different approach, i.e. stratifying the queue into a user top level and a task bottom level. The state is relatively well encapsulated so it shouldn't be that far out.
-- wli
I suspect you mean the scheduler runqueues?
My initial idea runs as follows:
a. On each array switch, we add some fixed value
touser->ticks. (HZ perhaps?)b. When adding, we cap at some maximum value.
(2*HZ perhaps?)c. On each timer tick, we decrement
current->user->ticks.d. When decrementing, if it's below zero we just
end this thread. (user has expired)e. When switching threads, we take the first one that belongs
to a non-expired user. I think this can be done by sending the user-expired threads to the expired array, just like when they expire themselves.
Quoted 8 lines
I'll try to code this tonight, so I'll post later on if I'm lucky.
I think this needs much less complexity for a first version, but I would try what you propose if I get intimate enough with the scheduler.
Greets, Antonio.
Ok, second try...
I have a doubt, do we need any locking for struct user when reading/writing his priority or when surfing the user_list?
Greets, Antnonio.
This patch is a forward port of the fairsched-2.4.19 patch by Rik van Riel, which ensures all competing users get an equal share of cpu time.
Since the earlier patch applied to the classic O(n) process scheduler, and this one applies to the standard 2.5 O(1) one, the mapping isn't one-to-one but rather more complex.
Original 2.4.19 version: Rik van Riel, [email protected] Forward ported 2.5.66 version: Antonio Vargas, [email protected]
A0: initial layout for code and data
A1: introduce runqueue handling related to per-user cpu share
include/linux/sched.h | 6 +++ include/linux/sysctl.h | 2 + kernel/sched.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++-- kernel/sysctl.c | 6 +++ kernel/user.c | 10 +++++- 5 files changed, 99 insertions(+), 3 deletions(-)
include/linux/sched.h renamed from include/linux/sched.h~fairsched-A1 +6 -0
@@ -280,6 +280,12 @@ struct user_struct {/* Hash table maintenance information */ struct list_head uidhash_list;uid_t uid; + +/* List maintenance information */ +struct list_head uid_list; + +/* Per-user timeslice management */ +unsigned int time_slice; };#define get_current_user () ({ \
include/linux/sysctl.h renamed from include/linux/sysctl.h~fairsched-A1 +2 -0
@@ -129,6 +129,8 @@ enumKERN_CADPID =54 ,/* int: PID of the process to notify on CAD */ KERN_PIDMAX =55 ,/* int: PID # limit */ KERN_CORE_PATTERN =56 ,/* string: pattern for core-file names */ +KERN_FAIRSCHED =57 ,/* turn per-user fair cpu scheduler on/off */ + KERN_MAX_TIMESLICE_USER=58, /* maximum user timeslice */ };
kernel/sched.c renamed from kernel/sched.c~fairsched-A1 +76 -2
131 lines not on this page · raw message
kernel/sysctl.c renamed from kernel/sysctl.c~fairsched-A1 +6 -0
@@ -57,6 +57,8 @@ extern char core_pattern[];extern int cad_pid;extern int pid_max;extern int sysctl_lower_zone_protection; +extern int fairsched; +extern int max_timeslice_user;/* this is needed for the proc_dointvec_minmax for [fs_]overflow UID and GID */ static int maxolduid =65535 ;
@@ -263,6 +265,10 @@ static ctl_table kern_table[] = {#endif {KERN_PIDMAX ,"pid_max" , &pid_max,sizeof (int ), 0600, NULL, &proc_dointvec}, + {KERN_FAIRSCHED, "fairsched", &pid_max, sizeof (int), + 0644, NULL, &proc_dointvec}, + {KERN_MAX_TIMESLICE_USER, "max_timeslice_user", &pid_max, sizeof (int), + 0644, NULL, &proc_dointvec}, {0} };
kernel/user.c renamed from kernel/user.c~fairsched-A1 +9 -1
@@ -27,10 +27,13 @@ static kmem_cache_t *uid_cachep;static struct list_head uidhash_table[UIDHASH_SZ ];static spinlock_t uidhash_lock =SPIN_LOCK_UNLOCKED ; +struct list_head user_list; +struct user_struct root_user = { .__count =ATOMIC_INIT (1 ), .processes =ATOMIC_INIT (1 ), - .files =ATOMIC_INIT (0 ) + .files =ATOMIC_INIT (0 ), + .time_slice =1 };/*
@@ -39,10 +42,12 @@ struct user_struct root_user = {static inline void uid_hash_insert (struct user_struct *up,struct list_head *hashent) {list_add (&up->uidhash_list, hashent); +list_add (&up->uid_list, &user_list); }static inline void uid_hash_remove (struct user_struct *up) { +list_del (&up->uid_list);list_del (&up->uidhash_list); }
@@ -97,6 +102,7 @@ struct user_struct * alloc_uid(uid_t uidatomic_set (&new->__count,1 );atomic_set (&new->processes,0 );atomic_set (&new->files,0 ); + new->time_slice =1 ;/* * Before adding this, check whether we raced
@@ -146,6 +152,8 @@ static int __init uid_cache_init(void)for (n =0 ; n <UIDHASH_SZ ; ++n)INIT_LIST_HEAD (uidhash_table + n); + +INIT_LIST_HEAD (&user_list);/* Insert the root user immediately - init already runs with this */ uid_hash_insert (&root_user,uidhashentry (0 ));
_
On Wed, Apr 02, 2003 at 02:46:43PM +0200, Antonio Vargas wrote:
+static inline void update_user_timeslices(void)
...
+ list_for_each(entry, &user_list) {
+ user = list_entry(entry, struct user_struct, uid_list);
+
+ if(!user) continue;
+
+ if(0){
+ user_time_slice = user->time_slice;
Hmm, this looks very O(n)... BTW, doesn't uidhash_lock lock user_list?
kernel/user.c struct user_struct root_user = { · line 46
Quoted 6 lines · Antonio Vargas
kernel/user.c renamed from kernel/user.c~fairsched-A1 +1
-0@@ -39,10 +42,12 @@ struct user_struct root_user = {static inline void uid_hash_insert (struct user_struct *up,struct list_head *hashent) {list_add (&up->uidhash_list, hashent); +list_add (&up->uid_list, &user_list); }
Okay, there are three or four problems:
(1) uidhash_lock can't be taken in interrupt context
(2) you aren't taking uidhash_lock at all in update_user_timeslices()
(3) you're not actually handing out user timeslices due to an if (0)
(4) walking user_list is O(n)
-- wli
Quoted 24 lines · William Lee Irwin III
On Wed, Apr 02, 2003 at 02:46:43PM +0200, Antonio Vargas wrote:
kernel/sched.c renamed from kernel/sched.c~fairsched-A1 +8
-0+static inline void update_user_timeslices (void ) + list_for_each(entry, &user_list) { + user = list_entry(entry, struct user_struct, uid_list); + +if (!user)continue ; + +if (0 ){ + user_time_slice = user->time_slice;Hmm, this looks very O(n)... BTW, doesn't
uidhash_locklockuser_list?kernel/user.c struct user_struct root_user = { · line 46
Quoted 6 lines · Antonio Vargas
kernel/user.c renamed from kernel/user.c~fairsched-A1 +1
-0@@ -39,10 +42,12 @@ struct user_struct root_user = {static inline void uid_hash_insert (struct user_struct *up,struct list_head *hashent) {list_add (&up->uidhash_list, hashent); +list_add (&up->uid_list, &user_list); }Okay, there are three or four problems:
Quoted 4 lines
(1)
uidhash_lockcan't be taken in interrupt context
(2) you aren't takinguidhash_lockat all inupdate_user_timeslices()
(3) you're not actually handing out user timeslices due to an if (0)
(4) walkinguser_listis O(n)
Yes I know there are problems, I'm just trying to make it run compile and run :)
I've been thinking about this thing a while ago, and I think I could do this:
a. Have a kernel thread which wakes up on each tick.
b. The thread just takes the first user_truct from the list, adds one tick to his
timeslice and sends it to the back. This makes the thread giveone tick to each user in turn and slowly, instead of trying to give all ticks at the same time.
Now, I don't know too much about the locking rules, but I think I could send a signal, semaphore o spinlock which wakes the thread from the scheduler_tick(), and the thread could take the uidhash_lock for the update.
Also, this locking rule means I can't even read current->user->time_slice? What if I changed the type to an atomic_int?
Greets, Antonio.
Quoted 3 lines · Antonio Vargas
I've been thinking about this thing a while ago, and I think I could do this:
a. Have a kernel thread which wakes up on each tick.
Why not use the timer tick itself? It already calls scheduler_tick()...
Oh, because you need to grab uidhash_lock? Ew. Needing a kernel thread for this is not pretty.
Quoted 2 lines
Also, this locking rule means I can't even read
current->user->time_slice? What if I changed the type to anatomic_int?
You can always read a single word-sized type atomically. No need for atomic_t's.
Robert Love
Quoted 10 lines · Robert Love
Quoted 3 lines · Antonio Vargas
I've been thinking about this thing a while ago, and I think I could do this:
a. Have a kernel thread which wakes up on each tick.
Why not use the timer tick itself? It already calls
scheduler_tick()...Oh, because you need to grab
uidhash_lock? Ew. Needing a kernel thread for this is not pretty.
Hmmm, we had some way for executing code just after an interrupt, but outside interrupt scope... was it a bottom half? Can you point me to some place where it's done?
Quoted 5 lines
Quoted 2 lines
Also, this locking rule means I can't even read
current->user->time_slice? What if I changed the type to anatomic_int?You can always read a single word-sized type atomically. No need for atomic_t's.
Ok, I did know m68k can do it, but wasn't sure about all other arches :)
Btw, I'm testing the patch using UML and besides I don't have any SMP machine, hope any of you can test it when it looks good :)
Greets, Antonio
Quoted 3 lines · Antonio Vargas
Hmmm, we had some way for executing code just after an interrupt, but outside interrupt scope... was it a bottom half? Can you point me to some place where it's done?
Unfortunately uidhash_lock cannot be used from a bottom half either.
You can push it into a work queue. See schedule_work() and the default events queue.
Quoted 1 line
Ok, I did know m68k can do it, but wasn't sure about all other arches :)
Yep. Everyone architecture I know of - and certainly all that Linux support - can do atomic read/writes to a word. Thinking about it, it would be odd if not (two writes to a single word interleaving?). There are places this assumption is used.
Anything more complicated, of course, needs atomic operations or locks.
Robert Love
Quoted 10 lines · Robert Love
Quoted 3 lines · Antonio Vargas
Hmmm, we had some way for executing code just after an interrupt, but outside interrupt scope... was it a bottom half? Can you point me to some place where it's done?
Unfortunately
uidhash_lockcannot be used from a bottom half either.You can push it into a work queue. See
schedule_work()and the default events queue.
I'll take a look.
Quoted 8 lines
Quoted 1 line
Ok, I did know m68k can do it, but wasn't sure about all other arches :)
Yep. Everyone architecture I know of - and certainly all that Linux support - can do atomic read/writes to a word. Thinking about it, it would be odd if not (two writes to a single word interleaving?). There are places this assumption is used.
Anything more complicated, of course, needs atomic operations or locks.
"word == basic unit of transfer from/to memory" on your reasoning, I suppose.
I'm coding this now, hope I can post a basic implementation.
Greets, Antonio.
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Quoted 5 lines · Robert Love
Yep. Everyone architecture I know of - and certainly all that Linux support - can do atomic read/writes to a word. Thinking about it, it would be odd if not (two writes to a single word interleaving?). There are places this assumption is used.
I believe this is true if the data is aligned. I don't think it's true for unaligned access on an x86.
- -Corey
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.0.6 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
iD8DBQE+jGwdIXnXXONXERcRAg7NAJ9p3/dhYIvyQrUSR9RQhzOHebJxcQCgnnIt TX41ng3GQo8SlGXLKmJvSKA=
=wjHE
-----END PGP SIGNATURE-----
Quoted 2 lines · Corey Minyard
I believe this is true if the data is aligned. I don't think it's true for unaligned access on an x86.
Yes, this is true. Sorry for not mentioning.
Unless Antonio plays games with pointers and types, though, all his accesses should be aligned.
Robert Love
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 10 lines · Hubertus Franke
Quoted 2 lines · Antonio Vargas
Quoted 1 line · William Lee Irwin III
On Tue, Apr 01, 2003 at 02:51:59PM +0200, Antonio Vargas wrote:
To add to my previous reply...
In your approach you might have a problem with starvation. High priority processes simply can eat all the ticks allocated to a user and when lower priority tasks pop up at the top of the active list they are move to the inactive list. This could happen over and over again, thus starving those tasks.
Sorry for not answering sooner, but I had my mail pointing at the lkml instead of my personal email.
Last night I continued coding up but I think I've it a deadlock:
it seems the system stops calling schedule_tick() when there are no more tasks to run, and it's there where I use a workqueue so that I can update the user timeslices out of interrupt context.
I think that because if I put printk's on my code, it continues to run OK, but if I remove them it freezes hard.
About giving ticks to users, I've got an idea: assuming there are N total processes in the system and an user has got M processes, we should give N * max_timeslice ticks to each user every M * max_timeslice * num_users ticks. I've been thinking about it and it seems it would balance the ticks correctly.
About the starvation for low-priority processes, I can get and example.. assume user A has process X Y and user B has process Z, and max_timeslice = 2:
max_timeslice = 2 and 4 total processes ==>
give 2 * 3 = 6 ticks to user A every 2 * 2 * 2 = 8 ticks give 2 * 3 = 6 ticks to user B every 1 * 2 * 2 = 4 ticks
running revised nr. of ticks for each
task user for each entity after executing
A B X Y Z
- - 6 6 2 2 2
X A1 5 6 1 2 2
X B1 4 6 0 2 2
Y A2 3 6 0 1 2
Y B2 2 6 0 0 2
Z A3 2 5 0 0 1
Z B3 2 4 0 0 0
reset_tasks - 2 4 2 2 2
X A4 1 4 1 2 2
X B4 0 4 0 2 2
reset_user B4 0 10 0 2 2
Z A5 0 9 0 2 1
Z B1 0 8 0 2 0
reset_tasks - 0 8 2 2 2
Z A6 0 7 2 2 1
Z B2 0 6 2 2 0
reset_tasks - 0 6 2 2 2
Z A7 0 5 2 2 1
Z B3 0 4 2 2 0
reset_tasks - 0 4 2 2 2
Z A8 0 3 2 2 1
reset_user A8 6 3 2 2 1
Z B4 6 2 2 2 0
reset_user B4 6 8 2 2 0
A A1 5 8 1 2 0
A B1 4 8 0 2 0
B A2 3 8 0 1 0
B B2 2 8 0 0 0
reset_tasks - 2 8 2 2 2
Z A3 2 7 2 2 1
Z B3 2 6 2 2 0
A A4 1 6 1 2 0
A B4 0 6 0 2 0
... ad infinitum ...
probably getting a graph out of this would be good :)
I will attach my current not-so-working patch and will try this scheme later at night.
Note there are some spourius calls to run my workqueue due to debugging tests.
Greets, Antonio.
This patch is a forward port of the fairsched-2.4.19 patch by Rik van Riel, which ensures all competing users get an equal share of cpu time.
Since the earlier patch applied to the classic O(n) process scheduler, and this one applies to the standard 2.5 O(1) one, the mapping isn't one-to-one but rather more complex.
Original 2.4.19 version: Rik van Riel, [email protected] Forward ported 2.5.66 version: Antonio Vargas, [email protected]
A0: initial layout for code and data
A1: introduce runqueue handling related to per-user cpu share
include/linux/sched.h | 6 +++ include/linux/sysctl.h | 2 + kernel/sched.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++- kernel/sysctl.c | 6 +++ kernel/user.c | 10 ++++- 5 files changed, 119 insertions(+), 2 deletions(-)
include/linux/sched.h renamed from include/linux/sched.h~fairsched-A1 +6 -0
@@ -280,6 +280,12 @@ struct user_struct {/* Hash table maintenance information */ struct list_head uidhash_list;uid_t uid; + +/* List maintenance information */ +struct list_head uid_list; + +/* Per-user timeslice management */ +unsigned int time_slice; };#define get_current_user () ({ \
include/linux/sysctl.h renamed from include/linux/sysctl.h~fairsched-A1 +2 -0
@@ -129,6 +129,8 @@ enumKERN_CADPID =54 ,/* int: PID of the process to notify on CAD */ KERN_PIDMAX =55 ,/* int: PID # limit */ KERN_CORE_PATTERN =56 ,/* string: pattern for core-file names */ +KERN_FAIRSCHED =57 ,/* turn per-user fair cpu scheduler on/off */ + KERN_MAX_TIMESLICE_USER=58, /* maximum user timeslice */ };
kernel/sched.c renamed from kernel/sched.c~fairsched-A1 +96 -1
150 lines not on this page · raw message
kernel/sysctl.c renamed from kernel/sysctl.c~fairsched-A1 +6 -0
@@ -57,6 +57,8 @@ extern char core_pattern[];extern int cad_pid;extern int pid_max;extern int sysctl_lower_zone_protection; +extern int fairsched; +extern int max_timeslice_user;/* this is needed for the proc_dointvec_minmax for [fs_]overflow UID and GID */ static int maxolduid =65535 ;
@@ -263,6 +265,10 @@ static ctl_table kern_table[] = {#endif {KERN_PIDMAX ,"pid_max" , &pid_max,sizeof (int ), 0600, NULL, &proc_dointvec}, + {KERN_FAIRSCHED, "fairsched", &fairsched, sizeof (int), + 0644, NULL, &proc_dointvec}, + {KERN_MAX_TIMESLICE_USER, "max_timeslice_user", &max_timeslice_user, sizeof (int), + 0644, NULL, &proc_dointvec}, {0} };
kernel/user.c renamed from kernel/user.c~fairsched-A1 +9 -1
@@ -27,10 +27,13 @@ static kmem_cache_t *uid_cachep;static struct list_head uidhash_table[UIDHASH_SZ ];static spinlock_t uidhash_lock =SPIN_LOCK_UNLOCKED ; +struct list_head user_list; +struct user_struct root_user = { .__count =ATOMIC_INIT (1 ), .processes =ATOMIC_INIT (1 ), - .files =ATOMIC_INIT (0 ) + .files =ATOMIC_INIT (0 ), + .time_slice =1 };/*
@@ -39,10 +42,12 @@ struct user_struct root_user = {static inline void uid_hash_insert (struct user_struct *up,struct list_head *hashent) {list_add (&up->uidhash_list, hashent); +list_add (&up->uid_list, &user_list); }static inline void uid_hash_remove (struct user_struct *up) { +list_del (&up->uid_list);list_del (&up->uidhash_list); }
@@ -97,6 +102,7 @@ struct user_struct * alloc_uid(uid_t uidatomic_set (&new->__count,1 );atomic_set (&new->processes,0 );atomic_set (&new->files,0 ); + new->time_slice =1 ;/* * Before adding this, check whether we raced
@@ -146,6 +152,8 @@ static int __init uid_cache_init(void)for (n =0 ; n <UIDHASH_SZ ; ++n)INIT_LIST_HEAD (uidhash_table + n); + +INIT_LIST_HEAD (&user_list);/* Insert the root user immediately - init already runs with this */ uid_hash_insert (&root_user,uidhashentry (0 ));
_
Quoted 8 lines · Antonio Vargas
Sorry for not answering sooner, but I had my mail pointing at the lkml instead of my personal email.
Last night I continued coding up but I think I've it a deadlock:
it seems the system stops callingschedule_tick()when there are no more tasks to run, and it's there where I use a workqueue so that I can update the user timeslices out of interrupt context. I think that because if I put printk's on my code, it continues to run OK, but if I remove them it freezes hard.
Use spin_lock_irq(&uidhash_lock) or you will deadlock if you hold it while you take a timer tick, but it's wrong anyway. it's O(N) with respect to number of users present. An O(1) algorithm could easily make use of reference counts held by tasks.
Quoted 11 lines · Antonio Vargas
About giving ticks to users, I've got an idea: assuming there are N total processes in the system and an user has got M processes, we should give N *
max_timesliceticks to each user every M *max_timeslice*num_usersticks. I've been thinking about it and it seems it would balance the ticks correctly.
About the starvation for low-priority processes, I can get and example.. assume user A has process X Y and user B has process Z, andmax_timeslice= 2:max_timeslice= 2 and 4 total processes ==>
give 2 * 3 = 6 ticks to user A every 2 * 2 * 2 = 8 ticks give 2 * 3 = 6 ticks to user B every 1 * 2 * 2 = 4 ticks
This isn't right, when expiration happens needs to be tracked by both user and task. Basically which tasks are penalized when the user expiration happens? The prediction is the same set of tasks will always be the target of the penalty.
-- wli
Quoted 29 lines · William Lee Irwin III
Quoted 8 lines · Antonio Vargas
Sorry for not answering sooner, but I had my mail pointing at the lkml instead of my personal email.
Last night I continued coding up but I think I've it a deadlock:
it seems the system stops callingschedule_tick()when there are no more tasks to run, and it's there where I use a workqueue so that I can update the user timeslices out of interrupt context. I think that because if I put printk's on my code, it continues to run OK, but if I remove them it freezes hard.Use
spin_lock_irq(&uidhash_lock)or you will deadlock if you hold it while you take a timer tick, but it's wrong anyway. it's O(N) with respect to number of users present. An O(1) algorithm could easily make use of reference counts held by tasks.Quoted 11 lines · Antonio Vargas
About giving ticks to users, I've got an idea: assuming there are N total processes in the system and an user has got M processes, we should give N *
max_timesliceticks to each user every M *max_timeslice*num_usersticks. I've been thinking about it and it seems it would balance the ticks correctly.
About the starvation for low-priority processes, I can get and example.. assume user A has process X Y and user B has process Z, andmax_timeslice= 2:max_timeslice= 2 and 4 total processes ==>
give 2 * 3 = 6 ticks to user A every 2 * 2 * 2 = 8 ticks give 2 * 3 = 6 ticks to user B every 1 * 2 * 2 = 4 ticks
Quoted 4 lines
This isn't right, when expiration happens needs to be tracked by both user and task. Basically which tasks are penalized when the user expiration happens? The prediction is the same set of tasks will always be the target of the penalty.
Just out of experimenting, I've coded something that looks reasonable and would not experience starvation.
In the normal scheduler, a non-interactive task will, when used all his timeslice, reset p->time_slice and queue onto the expired array. I'm now reseting p->reserved_time_slice instead and queuing the task onto a per-user pending task queue.
A separate kernel thread walks the user list, calculates the user timeslice and distributes it to it's pending tasks. When a task receives timeslices, it's moved from the per-user pending queue to the expired array of the runqueue, thus preparing it to run on the next array-switch.
If the user has many timeslices, he can give timeslices to many tasks, thus getting more work done.
While the implementation may not be good enough, due to locking problems and the use of a kernel-thread, I think the fundamental algorithm feels right.
William, should I take the lock on the uidhash_list when adding a task to a per-user task list?
Greets, Antonio.
This patch is a forward port of the fairsched-2.4.19 patch by Rik van Riel, which ensures all competing users get an equal share of cpu time.
Since the earlier patch applied to the classic O(n) process scheduler, and this one applies to the standard 2.5 O(1) one, the mapping isn't one-to-one but rather more complex.
Original 2.4.19 version: Rik van Riel, [email protected] Forward ported 2.5.66 version: Antonio Vargas, [email protected]
A0: initial layout for code and data
A1: introduce runqueue handling related to per-user cpu share
A2: add proper locking and O(1) behaviour
A3: perform all work with a kernel thread
include/linux/init_task.h | 2 include/linux/sched.h | 13 ++++ include/linux/sysctl.h | 1 kernel/fork.c | 1 kernel/sched.c | 53 +++++++++++++++++++- kernel/sysctl.c | 3 + kernel/user.c | 120 +++++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 191 insertions(+), 2 deletions(-)
include/linux/init_task.h renamed from include/linux/init_task.h~fairsched-A3 +2 -0
@@ -73,6 +73,8 @@ .active_mm = &init_mm, \ .run_list = LIST_HEAD_INIT(tsk.run_list), \ .time_slice = HZ, \ + .reserved_time_slice = HZ, \ + .user_tasks = LIST_HEAD_INIT(tsk.user_tasks), \ .tasks = LIST_HEAD_INIT(tsk.tasks), \ .ptrace_children= LIST_HEAD_INIT(tsk.ptrace_children), \ .ptrace_list = LIST_HEAD_INIT(tsk.ptrace_list), \
include/linux/sched.h renamed from include/linux/sched.h~fairsched-A3 +13 -0
@@ -280,6 +280,15 @@ struct user_struct {/* Hash table maintenance information */ struct list_head uidhash_list;uid_t uid; + +/* User list */ +struct list_head user_list; + +/* Task list for this user */ +struct list_head task_list; + +/* Per-user timeslice management */ +unsigned int time_slice; };#define get_current_user () ({ \
@@ -333,7 +342,11 @@ struct task_struct {unsigned long policy;unsigned long cpus_allowed;unsigned int time_slice, first_time_slice; +unsigned int reserved_time_slice; + +struct work_struct work_to_user; +struct list_head user_tasks;struct list_head tasks;struct list_head ptrace_children;struct list_head ptrace_list;
include/linux/sysctl.h renamed from include/linux/sysctl.h~fairsched-A3 +1 -0
@@ -129,6 +129,7 @@ enumKERN_CADPID =54 ,/* int: PID of the process to notify on CAD */ KERN_PIDMAX =55 ,/* int: PID # limit */ KERN_CORE_PATTERN =56 ,/* string: pattern for core-file names */ + KERN_FAIRSCHED=57, /* switch per-user fair cpu scheduler*/ };
kernel/fork.c renamed from kernel/fork.c~fairsched-A3 +1 -0
@@ -917,6 +917,7 @@ static struct task_struct *copy_process(* the parent if the child exits early enough. */ p->first_time_slice =1 ; + p->reserved_time_slice =0 ; current->time_slice >>=1 ; p->last_run = jiffies;if (!current->time_slice) {
kernel/sched.c renamed from kernel/sched.c~fairsched-A3 +52 -1
85 lines not on this page · raw message
kernel/sysctl.c renamed from kernel/sysctl.c~fairsched-A3 +3 -0
@@ -57,6 +57,7 @@ extern char core_pattern[];extern int cad_pid;extern int pid_max;extern int sysctl_lower_zone_protection; +extern int fairsched;/* this is needed for the proc_dointvec_minmax for [fs_]overflow UID and GID */ static int maxolduid =65535 ;
@@ -263,6 +264,8 @@ static ctl_table kern_table[] = {#endif {KERN_PIDMAX ,"pid_max" , &pid_max,sizeof (int ), 0600, NULL, &proc_dointvec}, + {KERN_FAIRSCHED, "fairsched", &fairsched, sizeof (int), + 0644, NULL, &proc_dointvec}, {0} };
kernel/user.c renamed from kernel/user.c~fairsched-A3 +119 -1
166 lines not on this page · raw message
_
Quoted 4 lines · William Lee Irwin III
Quoted 4 lines
Use
spin_lock_irq(&uidhash_lock)or you will deadlock if you hold it while you take a timer tick, but it's wrong anyway. it's O(N) with respect to number of users present. An O(1) algorithm could easily make use of reference counts held by tasks.
[...]
Quoted 4 lines
Quoted 4 lines
This isn't right, when expiration happens needs to be tracked by both user and task. Basically which tasks are penalized when the user expiration happens? The prediction is the same set of tasks will always be the target of the penalty.
Quoted 11 lines · Antonio Vargas
Just out of experimenting, I've coded something that looks reasonable and would not experience starvation.
In the normal scheduler, a non-interactive task will, when used all his timeslice, resetp->time_sliceand queue onto the expired array. I'm now resetingp->reserved_time_sliceinstead and queuing the task onto a per-user pending task queue.
A separate kernel thread walks the user list, calculates the user timeslice and distributes it to it's pending tasks. When a task receives timeslices, it's moved from the per-user pending queue to the expired array of the runqueue, thus preparing it to run on the next array-switch.
Hmm, priorities getting recalculated by a kernel thread sounds kind of scary, but who am I to judge?
Quoted 6 lines · Antonio Vargas
If the user has many timeslices, he can give timeslices to many tasks, thus getting more work done.
While the implementation may not be good enough, due to locking problems and the use of a kernel-thread, I think the fundamental algorithm feels right. William, should I take the lock on theuidhash_listwhen adding a task to a per-user task list?
Possible, though I'd favor a per-user spinlock.
The code looks reasonable now, modulo that race you asked about.
-- wli
Quoted 24 lines · William Lee Irwin III
Quoted 4 lines · William Lee Irwin III
Quoted 4 lines
Use
spin_lock_irq(&uidhash_lock)or you will deadlock if you hold it while you take a timer tick, but it's wrong anyway. it's O(N) with respect to number of users present. An O(1) algorithm could easily make use of reference counts held by tasks.[...]
Quoted 4 lines
Quoted 4 lines
This isn't right, when expiration happens needs to be tracked by both user and task. Basically which tasks are penalized when the user expiration happens? The prediction is the same set of tasks will always be the target of the penalty.
Quoted 11 lines · Antonio Vargas
Just out of experimenting, I've coded something that looks reasonable and would not experience starvation.
In the normal scheduler, a non-interactive task will, when used all his timeslice, resetp->time_sliceand queue onto the expired array. I'm now resetingp->reserved_time_sliceinstead and queuing the task onto a per-user pending task queue.
A separate kernel thread walks the user list, calculates the user timeslice and distributes it to it's pending tasks. When a task receives timeslices, it's moved from the per-user pending queue to the expired array of the runqueue, thus preparing it to run on the next array-switch.
Quoted 2 lines
Hmm, priorities getting recalculated by a kernel thread sounds kind of scary, but who am I to judge?
First and foremost, root user is handled as usual, so kernel tasks should also be handled as usual if they always run as root...
but I've yet to find out how to check if a task is a kernel thread or not, and skip these explicitely just to be safe.
Also, while I've coded it on a kernel thread, I'd settle perfectly to a periodic timer, but I don't know yet ask for one... perhaps a self-propagating workqueue entry with a delay?
Quoted 9 lines · Antonio Vargas
Quoted 6 lines
If the user has many timeslices, he can give timeslices to many tasks, thus getting more work done.
While the implementation may not be good enough, due to locking problems and the use of a kernel-thread, I think the fundamental algorithm feels right. William, should I take the lock on theuidhash_listwhen adding a task to a per-user task list?Possible, though I'd favor a per-user spinlock.
So, when trying to add a task to the per-user pending tasks, I'd have to do this:
1. spin_lock_irqsave(uidhash_lock, flags)
2. spin_lock(my_user->user_lock)
3. spin_unlock_restore(uidhash_lock, flags);
Is this any good?
Could I simply do "spin_unlock(my_user->user_lock)" at end without taking the uidhash_lock again?
Quoted 1 line
The code looks reasonable now, modulo that race you asked about.
What do I need to lock when I want to add a task to a runqueue? I'm doing a "spin_lock_irq(this_rq()->lock);"
As you can see, I'm not yet at speed with the locking rules... any online references to the latest locking rules? The BKL was really easy to understand in comparison! *grin*
Greets, Antonio.
Quoted 1 line · William Lee Irwin III
Quoted 1 line
Possible, though I'd favor a per-user spinlock.
Quoted 8 lines · Antonio Vargas
So, when trying to add a task to the per-user pending tasks, I'd have to do this:
1.spin_lock_irqsave(uidhash_lock, flags)
2.spin_lock(my_user->user_lock)
3.spin_unlock_restore(uidhash_lock, flags);
Is this any good?
Could I simply do "spin_unlock(my_user->user_lock)" at end without taking theuidhash_lockagain?
Well, you should unlock in the opposite order you acquired, and also unlock all the locks you acquired in general.
But you shouldn't need to do this. There's a trick with reference counting that saves you from the ostensible hierarchy:
You know the user won't go away b/c you hold a reference. You've now forked a child, and are looking to set it up. You get a reference for the child so it won't go away in the child's lifetime, and then take the per-user lock _only_. The per-user lock keeps two things looking at a user's task list from interfering with each other, but isn't needed for looking at the rest of the user. Now you can safely park a new task on the list or remove an old one, and when you're done, you drop the per-user lock only.
The per-user and uidhash locks shouldn't be taken simultaneously; the reference count on the user should be moved to or from zero,and the thing allocated or freed, and the uidhash_lock taken and dropped to hash or unhash without ever taking the per-user lock. Then we can just take the per-user lock when adding to it (we hold a reference) or not take any lock at all when freeing it (we were the last reference; no one will ever use this again without re-initializing it). Fresh allocations can also allow the task to add itself to the per-user tasklist without taking the per-user lock.
The actual tricky parts are setuid and so on, where you have to move a task between users. For this just compare the addresses of the two per-user locks and acquire them in address order, much like something in the scheduler, double_rq_lock(). Then you can modify both lists while holding both the locks to make the move happen, and when done, just unlock in the same order you acquired.
Quoted 1 line · William Lee Irwin III
Quoted 1 line
The code looks reasonable now, modulo that race you asked about.
Quoted 5 lines · Antonio Vargas
What do I need to lock when I want to add a task to a runqueue? I'm doing a "spin_lock_irq(this_rq()->lock);"
As you can see, I'm not yet at speed with the locking rules... any online references to the latest locking rules? The BKL was really easy to understand in comparison! *grin*
That's fine for the runqueue. Seems you've gotten it right. There are hard problems with using the BKL; be glad the lock you're taking has a specific relationship to what you're doing and what you're doing it to.
BTW, this probably won't actually be very efficient on SMP or with large numbers of users, but you're probably having fun writing it anyway. =) Best to get something running and tweak it later.
Hubertus & rml & others know more about scheduling policy in general, so you should probably ask them about the details of that; I just occasionally debug this stuff when it explodes.
-- wli
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 64 lines · Hubertus Franke
Quoted 50 lines · Antonio Vargas
Quoted 28 lines · William Lee Irwin III
Quoted 8 lines · Antonio Vargas
Sorry for not answering sooner, but I had my mail pointing at the lkml instead of my personal email.
Last night I continued coding up but I think I've it a deadlock:
it seems the system stops callingschedule_tick()when there are no more tasks to run, and it's there where I use a workqueue so that I can update the user timeslices out of interrupt context. I think that because if I put printk's on my code, it continues to run OK, but if I remove them it freezes hard.Use
spin_lock_irq(&uidhash_lock)or you will deadlock if you hold it while you take a timer tick, but it's wrong anyway. it's O(N) with respect to number of users present. An O(1) algorithm could easily make use of reference counts held by tasks.Quoted 11 lines · Antonio Vargas
About giving ticks to users, I've got an idea: assuming there are N total processes in the system and an user has got M processes, we should give N *
max_timesliceticks to each user every M *max_timeslice*num_usersticks. I've been thinking about it and it seems it would balance the ticks correctly.
About the starvation for low-priority processes, I can get and example.. assume user A has process X Y and user B has process Z, andmax_timeslice= 2:max_timeslice= 2 and 4 total processes ==>
give 2 * 3 = 6 ticks to user A every 2 * 2 * 2 = 8 ticks give 2 * 3 = 6 ticks to user B every 1 * 2 * 2 = 4 ticksQuoted 4 lines
This isn't right, when expiration happens needs to be tracked by both user and task. Basically which tasks are penalized when the user expiration happens? The prediction is the same set of tasks will always be the target of the penalty.
Just out of experimenting, I've coded something that looks reasonable and would not experience starvation.
In the normal scheduler, a non-interactive task will, when used all his timeslice, reset
p->time_sliceand queue onto the expired array. I'm now resetingp->reserved_time_sliceinstead and queuing the task onto a per-user pending task queue.A separate kernel thread walks the user list, calculates the user timeslice and distributes it to it's pending tasks. When a task receives timeslices, it's moved from the per-user pending queue to the expired array of the runqueue, thus preparing it to run on the next array-switch.
If the user has many timeslices, he can give timeslices to many tasks, thus getting more work done.
Quoted 8 lines
While the implementation may not be good enough, due to locking problems and the use of a kernel-thread, I think the fundamental algorithm feels right.
William, should I take the lock on the
uidhash_listwhen adding a task to a per-user task list?Greets, Antonio.
Antonio, I am going right now through some stuff.
Again I am coming from a class based approach, but fundamentally now difference.
Quoted 8 lines
First there are several issues that need to be addressed and they might have to be handled differently.
Shares can be observed under hard limits or soft limits (work preserving). The first proposal you put forward was clearly work preserving. The latter one (if I understood correctly reading it in 2 seconds) would facilitate hard working limits. The problem to simply do it at array switch time is that you could spent increased time inschedule()simply moving tasks between active and expired until ticks are regranted to the user.
The testing on pick_next_task is just a failsafe, it should not be done on the real working patch.
Quoted 7 lines
I have two concerns (and pardon me again if you already addressed them)
(A) starvation problem.
---------------------------------
One process could eat all the ticks
assigned to a user, leading that subsequent scheduled task will always move to the expired list.
Yes.
Quoted 2 lines
Effectively, the sum(timeslices) per user is the number of ticks I need to give (given all cpu bound) for a user to get all tasks execute.
Yes.
Quoted 7 lines
The ratio between the sums of the different users is effectively what is dictated by the shares.
One has effectively two means to manipulate that
(a) modifying thestatic_priorityof tasks belonging to a user
(b) modifying theeffective_priorityof tasks belonging to a user
(c) modifying thetime_sliceof tasks
Quoted 8 lines
all modifications are based on whether a user is overspending or underspending at some given rate.
To me (a) is not an option.
(b) tells me to modify the urgency of a task to be scheduled
(c) tells me how long task should run, its also depends on (a).What needs to be achieved is that starving tasks move up in the
effective_priorityabove the ones that keep eating allotted ticks.
I've it done correctly in the fairsched-A3 patch I sent. When a task uses up the timeslice, it goes to the end of a pending list. When the user receives slices, he gives it to the head of the pending list, quits it from the pending list and attaches to the expired array just like if it has gone on the non-fairsched path. So, the tasks just take a sleep while they wait their relaive per-user turn.
Quoted 6 lines
(B) O(1) issues
-------------------------By setting tasks aside to wait for replenishment of ticks for the users you might introduce an non O(1) effect, that Ingo might object against. One could argue, that you simply put the tasks asleep..
Yes, it's exactly that. Instead of passing a task from active to expired directly, they
take a detour until the user can "afford" to do it.
When handling more users, there is a natural inflation because each user can receives less slices per time unit.
----------------------------------
Quoted 11 lines
I am currently doing the math and coming up with a scheme. I should be hopefully close to something by Monday, which I can then share and if you want you can experiment around with. Need to do this anyway for the OLS paper we are presenting.
Also, I need to go through your latest patch in more detail, before I give you injustice, I just want to have the framework in my head first.Ping me on Monday...
-- Hubertus
Getting your linux-kernel subscription running would be great, I almost skipped your mail again because of it ;)
I'll try to code on this during the weekend, so see you on Monday.
Greets, Antonio.
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 2 lines · Hubertus Franke
Antonio, while you are coding here is some additional input.
Great!
Quoted 8 lines
By intoducing the user based pending queue and leaking the tasks back into the runqueue based on the per user ticks, you are changing semantics slightly.
If the task is reinserted back into the runqueue oit should be reinserted into the expired queue iff and only no expired/active queue switch has happened.
Otherwise it should be reinserted into the active list.
This sounds right :)
Quoted 2 lines
This will becoe important when we later distinguish between users that have limited share and those that have unlimited.
Yes, we should push the "p->user->uid == 0" test into the send_task_to_user() function then... and later on implement a "p->user->unlimited_cpu_share == 1" test.
Quoted 6 lines
Can be implemented by keeping a per runqueue array switch count and store it with the pending task. On reinsertion, if the task has the same as the current it goes to the expired. If its older than the current, then the task missed the array switch and it should go into the active queue.
Quoted 7 lines
Also, I don't follow necessarily your reason to put an INTERACTIVE task back on the active queue immediately, rather than going first through the pending queue again.
This way, a high priority job with even a small
sleep_avgalready being declared INTERACTIVE, will continue to suck up cycles beyond its user's limits. This can be as long as 8 secs currently.
Perhaps I'm not declaring this in any explicit way, but I feel that this type of control should only be applied to cpu hogs.
Quoted 4 lines
Instead, things to consider is to feed these as well through the pending queue and distinguish in the pending queue ? More thoughts required here... I let you know when any of them is successful at my end.
I'm somewhat deadlocked right now, so I'll have to wait until I have some think up some way to make it work as intended.
Greets, Antonio
kernel/sched.c static int effective_prio(task_t *p) · line 329
Quoted 3 lines · Antonio Vargas
kernel/sched.c renamed from kernel/sched.c~fairsched-A0 +3
-0+if (fairsched){ +/* special processing for per-user fair scheduler */ + }
Cute.
Robert Love