ยง9.1โ€“9.3Tickets โ€ฆ Implementation

Part I OSTEP pp. 89โ€“92 ยท ~6 min read

  • proportional share
  • ticket
  • lottery scheduling
  • ticket currency
  • ticket transfer
  • ticket inflation

Chapters 7 and 8 optimized turnaround and response. This chapter wants something else entirely: guarantee each job a slice of the pie โ€” proportional-share (fair-share) scheduling. The early classic is Waldspurger and Weihlโ€™s lottery scheduling (1994), and its core idea fits in a sentence: every so often, hold a lottery; jobs that deserve more CPU get more chances to win.

The Crux: How To Share The CPU Proportionally

How can we design a scheduler to share the CPU in a proportional manner? What are the key mechanisms for doing so? How effective are they?

9.1 Basic Concept: Tickets Represent Your Share

Everything rests on the ticket : a token representing a share of a resource. Hold 75 of the 100 tickets in play, and you should receive 75% of the CPU. Lottery scheduling delivers that share probabilistically: every time slice, the scheduler draws a winning ticket (0 to totalโˆ’1) and runs its holder:

A holds 75 tickets, B holds 25 โ€” hold the lottery yourself (๐ŸŽฒ re-roll for a different run)
AarrruntixBarrruntix
CPUB: 0โ€“3BA: 3โ€“5B: 5โ€“6A: 6โ€“7B: 7โ€“9A: 9โ€“10B: 10โ€“11A: 11โ€“13B: 13โ€“14A: 14โ€“19AB: 19โ€“20A: 20โ€“21B: 21โ€“22A: 22โ€“23B: 23โ€“24A: 24โ€“28AB: 28โ€“29A: 29โ€“34AB: 34โ€“38BA: 38โ€“40B: 40โ€“41A: 41โ€“44AB: 44โ€“45A: 45โ€“49AB: 49โ€“52BA: 52โ€“55AB: 55โ€“56A: 56โ€“57B: 57โ€“59A: 59โ€“61B: 61โ€“62A: 62โ€“66AB: 66โ€“67A: 67โ€“69B: 69โ€“70A: 70โ€“71B: 71โ€“72A: 72โ€“73B: 73โ€“74A: 74โ€“76B: 76โ€“77A: 77โ€“78B: 78โ€“79A: 79โ€“80B: 80โ€“81A: 81โ€“83B: 83โ€“84A: 84โ€“86B: 86โ€“89BA: 89โ€“92AB: 92โ€“93A: 93โ€“97AB: 97โ€“120B020406080100120
jobarrivalruntimeturnaroundresponseCPU share (target)
A06097350% (75%)
B060120050% (25%)
average108.501.50

Each tick is one lottery over the ready jobs' tickets. Watch the CPU-share column against its (target): on short stretches the observed share can be well off 75/25 โ€” the book's own 20-draw example gave B only 20% instead of 25%. Re-roll a few times, then lengthen both runtimes: the longer they compete, the closer to target.

Randomness gives probabilistic correctness โ€” no guarantee on any short window (the bookโ€™s 20-draw example handed B 20% instead of 25%), but the law of large numbers pulls long runs toward target.

Tip: Use randomness

Random decisions have three lovely properties. They dodge adversarial corner cases (LRU replacement โ€” coming in the memory chapters โ€” has a cyclic worst-case workload; random has no worst case). They need almost no state โ€” no per-process accounting of CPU received, just ticket counts. And theyโ€™re fast: one random number and youโ€™ve decided. The faster you need it, the more โ€œrandomโ€ tends toward pseudo-random.

Tip: Use tickets to represent shares

The ticket is the mechanism to remember. Waldspurger later used tickets to represent a guest OSโ€™s share of memory in a hypervisor. Whenever you need to represent proportions of ownership, the answer might beโ€ฆ (wait for it) โ€ฆ the ticket.

9.2 Ticket Mechanisms

Tickets come with a small toolkit:

User A100 global ticketsUser B100 global ticketsA1: 500 in Aโ€™s currencyA2: 500 in Aโ€™s currencyB1: 10 in Bโ€™s currencyA1 = 50 globalA2 = 50 globalB1 = 100 globalsystem converts currencies โ†’ lottery held over 200 global tickets

Ticket currency : each user subdivides their allocation however they like; conversion keeps the global lottery fair. Bโ€™s single job ends up with twice the global tickets of either of Aโ€™s two.

  • Ticket transfer : temporarily hand your tickets to another process โ€” the classic case is a client passing tickets to a server so the server runs fast while doing the clientโ€™s work, then hands them back.
  • Ticket inflation : a process boosts (or trims) its own ticket count. Nonsense among competitors โ€” a greedy process could grab the machine โ€” but in a group of mutually trusting processes, itโ€™s a lightweight way to say โ€œI need more CPU right nowโ€ without any communication.

9.3 Implementation

The most amazing part: the whole scheduler needs a random number generator, a list of processes, and the total ticket count. To decide, draw winner in [0, total), then walk the list accumulating tickets until the counter exceeds the winner:

// counter: used to track if we've found the winner yet
int counter = 0;

// winner: use some call to a random number generator to
//         get a value, between 0 and the total # of tickets
int winner = getrandom(0, totaltickets);

// current: use this to walk through the list of jobs
node_t *current = head;
while (current) {
    counter = counter + current->tickets;
    if (counter > winner)
        break; // found the winner
    current = current->next;
}
// 'current' is the winner: schedule it...

Figure 9.1: Lottery Scheduling Decision Code

With the list A(100) โ†’ B(50) โ†’ C(250) and winning ticket 300: counter hits 100 (A โ€” keep going), 150 (B โ€” keep going), 400 (> 300 โ€” stop): C wins. One micro-optimization: keep the list sorted by descending tickets โ€” correctness is unaffected (any order works), but the biggest holders are found in the fewest iterations. Hereโ€™s that exact workload, live:

Three jobs, A:100 B:50 C:250 tickets โ€” the Figure 9.1 workload, running
AarrruntixBarrruntixCarrruntix
CPUC: 0โ€“1A: 1โ€“2C: 2โ€“3A: 3โ€“5C: 5โ€“7B: 7โ€“8A: 8โ€“9C: 9โ€“10A: 10โ€“11C: 11โ€“12A: 12โ€“13B: 13โ€“14C: 14โ€“17A: 17โ€“18C: 18โ€“19A: 19โ€“20C: 20โ€“22A: 22โ€“25C: 25โ€“26A: 26โ€“27B: 27โ€“28C: 28โ€“29B: 29โ€“31C: 31โ€“35CA: 35โ€“36C: 36โ€“37B: 37โ€“39C: 39โ€“41B: 41โ€“42C: 42โ€“47CA: 47โ€“48C: 48โ€“49A: 49โ€“50C: 50โ€“54CB: 54โ€“56C: 56โ€“57B: 57โ€“58A: 58โ€“59C: 59โ€“60A: 60โ€“62C: 62โ€“64B: 64โ€“66C: 66โ€“69B: 69โ€“70C: 70โ€“71B: 71โ€“73C: 73โ€“74A: 74โ€“75C: 75โ€“81CB: 81โ€“82C: 82โ€“83A: 83โ€“85C: 85โ€“88B: 88โ€“89A: 89โ€“101AB: 101โ€“102A: 102โ€“106AB: 106โ€“107A: 107โ€“108B: 108โ€“112BA: 112โ€“119AB: 119โ€“120A: 120โ€“121B: 121โ€“122A: 122โ€“123B: 123โ€“125A: 125โ€“128B: 128โ€“150B0306090120150
jobarrivalruntimeturnaroundresponseCPU share (target)
A050128133% (25%)
B050150733% (13%)
C05088033% (63%)
average122.002.67

Targets: 25% / 12.5% / 62.5%. C wins most drawings and finishes first; once it completes, the remaining tickets (A's 100 + B's 50) re-divide the machine 2:1. Lottery needs no global bookkeeping for any of this โ€” dead jobs simply stop holding tickets.

Simple mechanism, three knobs on top of it, and an implementation that fits on an index card. Next: what randomness costs (short-run unfairness), and the deterministic alternatives โ€” stride scheduling and Linuxโ€™s CFS.

Check yourself

1.A holds 75 tickets, B holds 25. What exactly does lottery scheduling guarantee about their CPU shares?

2.User A gives 500 A-currency tickets to each of two jobs; User B gives 10 B-currency tickets to one job. Both users hold 100 global tickets. Who has more global tickets โ€” A1 or B1?

3.Why does ticket inflation only make sense among mutually trusting processes?

4.In Figure 9.1's list walk (A:100 โ†’ B:50 โ†’ C:250) with winning ticket 300, how is the winner found?

5.Why does keeping the job list sorted by descending ticket count help โ€” and what does it NOT affect?

5 questions