SCHEDULER(9)

okay bestie, pull up a chair, we're reading SCHEDULER(9) β€” yes it's from the Kernel Developer's Manual, section 9, and yes it's about the scheduler doing its little round-robin thing. it's giving ✨core kernel✨ and honestly? we're obsessed. no cap. πŸ’…

🌸 NAME 🌸

curpriority_cmp, maybe_resched, resetpriority, roundrobin, roundrobin_interval, sched_setup, schedclock, schedcpu, setrunnable, updatepri β€” perform round-robin scheduling of runnable processes πŸ¦‹


✨ SYNOPSIS ✨

#include <sys/param.h>
#include <sys/proc.h>

int
curpriority_cmp(struct proc *p);

void
maybe_resched(struct thread *td);

void
propagate_priority(struct proc *p);

void
resetpriority(struct ksegrp *kg);

void
roundrobin(void *arg);

int
roundrobin_interval(void);

void
sched_setup(void *dummy);

void
schedclock(struct thread *td);

void
schedcpu(void *arg);

void
setrunnable(struct thread *td);

void
updatepri(struct thread *td);

like, all the little scheduling girlies in one place. iconic behavior honestly.


πŸ’– DESCRIPTION πŸ’–

so every single process has three different priorities chilling inside struct proc: p_usrpri, p_nativepri, and p_priority. three! main character energy for real. πŸ‘‘

The p_usrpri member is the user priority of the process β€” calculated from the process' estimated CPU time and its nice level.

The p_nativepri member is the saved priority that propagate_priority() uses. Here's the vibe: when a process obtains a mutex, its priority gets saved into p_nativepri. While it's holding that mutex, its priority might get bumped by some other process that blocks on the mutex. and then?? when the process releases the mutex, its priority gets restored to whatever was saved in p_nativepri. cute, right? it's like a lil bookmark for your priority. πŸŽ€

The p_priority member is the actual priority of the process, and it's the one used to figure out what runqueue(9) it runs on, for example.

🫢 curpriority_cmp()

this function compares the cached priority of the currently running process with process p. the receipts:

  • currently running process has a higher priority β†’ returns a value less than zero
  • current process has a lower priority β†’ returns a value greater than zero
  • current process has the same priority as p β†’ returns zero

and where does that cached priority live? it gets updated when a process resumes from tsleep(9) or returns to userland in userret(), and it's stored in the private variable curpriority. πŸ’Ύ

🫢 maybe_resched()

compares the priorities of the current thread and td. if td has a higher priority than the current thread, then a context switch is needed, and KEF_NEEDRESCHED is set. 🚨 like a little alarm bell going off.

🫢 propagate_priority()

okay this one is the gossip chain fr fr. it looks at the process that owns the mutex that p is blocked on. that process's priority gets bumped to the priority of p if needed.

then it depends:

  • if that process is currently running, the function just returns. bye!
  • if the process is on a runqueue(9), it gets moved to the appropriate runqueue(9) for its new priority.
  • if the process is blocked on a mutex, its position in the list of processes blocked on the mutex in question is updated to reflect its new priority.

then the function repeats the whole procedure using the process that owns the mutex it just encountered. it's a chain reaction, bestie. πŸ¦‹

ngl, one very important note: a process's priorities are only bumped to the priority of the original process p β€” not to the priority of the previously encountered process. don't get it twisted. πŸ’…

🫢 resetpriority()

recomputes the user priority of the ksegrp kg (stored in kg_user_pri) and calls maybe_resched() to force a reschedule of each thread in the group if needed. group project but make it functional. 🫢

🫢 roundrobin()

used as a timeout(9) function to force a reschedule every sched_quantum ticks. it's the alarm clock of the kernel. ⏰

🫢 roundrobin_interval()

simply returns the number of clock ticks in between reschedules triggered by roundrobin(). so literally all it does is return the current value of sched_quantum. that's it. that's the whole function. lowkey iconic for that. ✨

🫢 sched_setup()

a SYSINIT(9) that gets called to start the callout driven scheduler functions. it just calls the roundrobin() and schedcpu() functions for the first time. after that initial call, the two functions propagate themselves by registering their callout event again at the completion of the respective function. self-sufficient queens. πŸ‘‘

🫢 schedclock()

called by statclock() to adjust the priority of the currently running thread's ksegrp. it updates the group's estimated CPU time and then adjusts the priority via resetpriority(). πŸ’–

🫢 schedcpu()

the big one. it updates all process priorities, and it does it in like four whole steps, so buckle up:

  1. first, it updates statistics that track how long processes have been in various process states. πŸ“Š
  2. secondly, it updates the estimated CPU time for the current process such that about 90% of the CPU usage is forgotten in 5 * load average seconds. for example, if the load average is 2.00, then at least 90% of the estimated CPU time for the process should be based on the amount of CPU time the process has had in the last 10 seconds. then it recomputes the priority of the process and moves it to the appropriate runqueue(9) if necessary.
  3. thirdly, it updates the %CPU estimate used by utilities such as ps(1) and top(1) so that 95% of the CPU usage is forgotten in 60 seconds. πŸ“
  4. once all process priorities have been updated, schedcpu() calls vmmeter() to update various other statistics including the load average. finally, it schedules itself to run again in hz clock ticks.

it's literally doing the most and we respect it. πŸ”₯

🫢 setrunnable()

used to change a process's state to be runnable. the process gets placed on a runqueue(9) if needed, and the swapper process is woken up and told to swap the process in if the process is swapped out. and if the process has been asleep for at least one run of schedcpu(), then updatepri() is used to adjust the priority of the process. πŸ˜΄βž‘οΈπŸƒ

🫢 updatepri()

used to adjust the priority of a process that has been asleep. it retroactively decays the estimated CPU time of the process for each schedcpu() event that the process was asleep. finally, it calls resetpriority() to adjust the priority of the process. healing era fr. 🌸


🌈 SEE ALSO 🌈

go read those too bestie, iykyk. πŸ’œ


πŸ’€ BUGS πŸ’€

okay so. the curpriority variable really should be per-CPU. in addition, maybe_resched() should compare the priority of chk with that of each CPU, and then send an IPI to the processor with the lowest priority to trigger a reschedule if needed. like, imagine not doing that. 😭
Priority propagation is broken and is thus disabled by default. the p_nativepri variable is only updated if a process does not obtain a sleep mutex on the first try. also, if a process obtains more than one sleep mutex in this manner, and had its priority bumped in between, then p_nativepri will be clobbered. it's giving tragic. πŸ’€

aaaand that's the whole page, bestie 🫢 this lil masterpiece came to you from Debian, dated November 3, 2000 β€” so yeah, it's been serving scheduling realness since before most of us could even read. slay. πŸ’…βœ¨πŸŽ€

πŸ“¦ where this page comes from