add ostep homeworks
This commit is contained in:
parent
dab71e324d
commit
5ec5376f5b
33
related_info/ostep/README.md
Normal file
33
related_info/ostep/README.md
Normal file
@ -0,0 +1,33 @@
|
||||
# README
|
||||
These resources are from [Operating Systems: Three Easy Pieces](http://pages.cs.wisc.edu/~remzi/OSTEP/) by Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau
|
||||
|
||||
We use&modify them as some homeworks of our OS course.
|
||||
|
||||
## Memory
|
||||
- ostep1-relocation.md
|
||||
- ostep2-segmentation.md
|
||||
- ostep3-malloc.md
|
||||
- ostep4-paging-linear-translate.md
|
||||
- ostep5-paging-multilevel-translate.md
|
||||
- ostep6-paging-policy.md
|
||||
|
||||
## process
|
||||
- ostep7-process-run.md
|
||||
|
||||
## scheduling
|
||||
- ostep8-scheduler.md
|
||||
- ostep9-mlfq.md
|
||||
- ostep10-lottery.md
|
||||
|
||||
## sync/mutex
|
||||
- ostep11-threadintro/race.md
|
||||
- ostep12-threadlock/locks.md
|
||||
|
||||
## file system
|
||||
- ostep13-vsfs.md
|
||||
- ostep14-afs.md
|
||||
|
||||
## disk
|
||||
- ostep15-disk/disk.md
|
||||
- ostep16-raid.md
|
||||
|
98
related_info/ostep/ostep1-relocation.md
Normal file
98
related_info/ostep/ostep1-relocation.md
Normal file
@ -0,0 +1,98 @@
|
||||
|
||||
This program allows you to see how address translations are performed in a
|
||||
system with base and bounds registers. As before, there are two steps to
|
||||
running the program to test out your understanding of base and bounds. First,
|
||||
run without the -c flag to generate a set of translations and see if you can
|
||||
correctly perform the address translations yourself. Then, when done, run with
|
||||
the -c flag to check your answers.
|
||||
|
||||
In this homework, we will assume a slightly different address space than our
|
||||
canonical one with a heap and stack at opposite ends of the space. Rather, we
|
||||
will assume that the address space has a code section, then a fixed-sized
|
||||
(small) stack, and a heap that grows downward right after, looking something
|
||||
like you see in the Figure below. In this configuration, there is only one
|
||||
direction of growth, towards higher regions of the address space.
|
||||
|
||||
-------------- 0KB
|
||||
| Code |
|
||||
-------------- 2KB
|
||||
| Stack |
|
||||
-------------- 4KB
|
||||
| Heap |
|
||||
| | |
|
||||
| v |
|
||||
-------------- 7KB
|
||||
| (free) |
|
||||
| ... |
|
||||
|
||||
In the figure, the bounds register would be set to 7~KB, as that represents
|
||||
the end of the address space. References to any address within the bounds
|
||||
would be considered legal; references above this value are out of bounds and
|
||||
thus the hardware would raise an exception.
|
||||
|
||||
To run with the default flags, type relocation.py at the command line. The
|
||||
result should be something like this:
|
||||
|
||||
prompt> ./relocation.py
|
||||
...
|
||||
Base-and-Bounds register information:
|
||||
|
||||
Base : 0x00003082 (decimal 12418)
|
||||
Limit : 472
|
||||
|
||||
Virtual Address Trace
|
||||
VA 0: 0x01ae (decimal:430) -> PA or violation?
|
||||
VA 1: 0x0109 (decimal:265) -> PA or violation?
|
||||
VA 2: 0x020b (decimal:523) -> PA or violation?
|
||||
VA 3: 0x019e (decimal:414) -> PA or violation?
|
||||
VA 4: 0x0322 (decimal:802) -> PA or violation?
|
||||
|
||||
For each virtual address, either write down the physical address it
|
||||
translates to OR write down that it is an out-of-bounds address
|
||||
(a segmentation violation). For this problem, you should assume a
|
||||
simple virtual address space of a given size.
|
||||
|
||||
As you can see, the homework simply generates randomized virtual
|
||||
addresses. For each, you should determine whether it is in bounds, and if so,
|
||||
determine to which physical address it translates. Running with -c (the
|
||||
"compute this for me" flag) gives us the results of these translations, i.e.,
|
||||
whether they are valid or not, and if valid, the resulting physical
|
||||
addresses. For convenience, all numbers are given both in hex and decimal.
|
||||
|
||||
prompt> ./relocation.py -c
|
||||
...
|
||||
Virtual Address Trace
|
||||
VA 0: 0x01ae (decimal:430) -> VALID: 0x00003230 (dec:12848)
|
||||
VA 1: 0x0109 (decimal:265) -> VALID: 0x0000318b (dec:12683)
|
||||
VA 2: 0x020b (decimal:523) -> SEGMENTATION VIOLATION
|
||||
VA 3: 0x019e (decimal:414) -> VALID: 0x00003220 (dec:12832)
|
||||
VA 4: 0x0322 (decimal:802) -> SEGMENTATION VIOLATION
|
||||
]
|
||||
|
||||
With a base address of 12418 (decimal), address 430 is within bounds (i.e., it
|
||||
is less than the limit register of 472) and thus translates to 430 added to
|
||||
12418 or 12848. A few of the addresses shown above are out of bounds (523,
|
||||
802), as they are in excess of the bounds. Pretty simple, no? Indeed, that is
|
||||
one of the beauties of base and bounds: it's so darn simple!
|
||||
|
||||
There are a few flags you can use to control what's going on better:
|
||||
|
||||
prompt> ./relocation.py -h
|
||||
Usage: relocation.py [options]
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-s SEED, --seed=SEED the random seed
|
||||
-a ASIZE, --asize=ASIZE address space size (e.g., 16, 64k, 32m)
|
||||
-p PSIZE, --physmem=PSIZE physical memory size (e.g., 16, 64k)
|
||||
-n NUM, --addresses=NUM # of virtual addresses to generate
|
||||
-b BASE, --b=BASE value of base register
|
||||
-l LIMIT, --l=LIMIT value of limit register
|
||||
-c, --compute compute answers for me
|
||||
]
|
||||
|
||||
In particular, you can control the virtual address-space size (-a), the size
|
||||
of physical memory (-p), the number of virtual addresses to generate (-n), and
|
||||
the values of the base and bounds registers for this process (-b and -l,
|
||||
respectively).
|
||||
|
117
related_info/ostep/ostep1-relocation.py
Normal file
117
related_info/ostep/ostep1-relocation.py
Normal file
@ -0,0 +1,117 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
import random
|
||||
import math
|
||||
|
||||
def convert(size):
|
||||
length = len(size)
|
||||
lastchar = size[length-1]
|
||||
if (lastchar == 'k') or (lastchar == 'K'):
|
||||
m = 1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
elif (lastchar == 'm') or (lastchar == 'M'):
|
||||
m = 1024*1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
elif (lastchar == 'g') or (lastchar == 'G'):
|
||||
m = 1024*1024*1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
else:
|
||||
nsize = int(size)
|
||||
return nsize
|
||||
|
||||
|
||||
#
|
||||
# main program
|
||||
#
|
||||
parser = OptionParser()
|
||||
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
|
||||
parser.add_option('-a', '--asize', default='1k', help='address space size (e.g., 16, 64k, 32m, 1g)', action='store', type='string', dest='asize')
|
||||
parser.add_option('-p', '--physmem', default='16k', help='physical memory size (e.g., 16, 64k, 32m, 1g)', action='store', type='string', dest='psize')
|
||||
parser.add_option('-n', '--addresses', default=5, help='number of virtual addresses to generate', action='store', type='int', dest='num')
|
||||
parser.add_option('-b', '--b', default='-1', help='value of base register', action='store', type='string', dest='base')
|
||||
parser.add_option('-l', '--l', default='-1', help='value of limit register', action='store', type='string', dest='limit')
|
||||
parser.add_option('-c', '--compute', default=False, help='compute answers for me', action='store_true', dest='solve')
|
||||
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
print ''
|
||||
print 'ARG seed', options.seed
|
||||
print 'ARG address space size', options.asize
|
||||
print 'ARG phys mem size', options.psize
|
||||
print ''
|
||||
|
||||
random.seed(options.seed)
|
||||
asize = convert(options.asize)
|
||||
psize = convert(options.psize)
|
||||
|
||||
if psize <= 1:
|
||||
print 'Error: must specify a non-zero physical memory size.'
|
||||
exit(1)
|
||||
|
||||
if asize == 0:
|
||||
print 'Error: must specify a non-zero address-space size.'
|
||||
exit(1)
|
||||
|
||||
if psize <= asize:
|
||||
print 'Error: physical memory size must be GREATER than address space size (for this simulation)'
|
||||
exit(1)
|
||||
|
||||
#
|
||||
# need to generate base, bounds for segment registers
|
||||
#
|
||||
limit = convert(options.limit)
|
||||
base = convert(options.base)
|
||||
|
||||
if limit == -1:
|
||||
limit = int(asize/4.0 + (asize/4.0 * random.random()))
|
||||
|
||||
# now have to find room for them
|
||||
if base == -1:
|
||||
done = 0
|
||||
while done == 0:
|
||||
base = int(psize * random.random())
|
||||
if (base + limit) < psize:
|
||||
done = 1
|
||||
|
||||
print 'Base-and-Bounds register information:'
|
||||
print ''
|
||||
print ' Base : 0x%08x (decimal %d)' % (base, base)
|
||||
print ' Limit : %d' % (limit)
|
||||
print ''
|
||||
|
||||
if base + limit > psize:
|
||||
print 'Error: address space does not fit into physical memory with those base/bounds values.'
|
||||
print 'Base + Limit:', base + limit, ' Psize:', psize
|
||||
exit(1)
|
||||
|
||||
#
|
||||
# now, need to generate virtual address trace
|
||||
#
|
||||
print 'Virtual Address Trace'
|
||||
for i in range(0,options.num):
|
||||
vaddr = int(asize * random.random())
|
||||
if options.solve == False:
|
||||
print ' VA %2d: 0x%08x (decimal: %4d) --> PA or segmentation violation?' % (i, vaddr, vaddr)
|
||||
else:
|
||||
paddr = 0
|
||||
if (vaddr >= limit):
|
||||
print ' VA %2d: 0x%08x (decimal: %4d) --> SEGMENTATION VIOLATION' % (i, vaddr, vaddr)
|
||||
else:
|
||||
paddr = vaddr + base
|
||||
print ' VA %2d: 0x%08x (decimal: %4d) --> VALID: 0x%08x (decimal: %4d)' % (i, vaddr, vaddr, paddr, paddr)
|
||||
|
||||
|
||||
print ''
|
||||
|
||||
if options.solve == False:
|
||||
print 'For each virtual address, either write down the physical address it translates to'
|
||||
print 'OR write down that it is an out-of-bounds address (a segmentation violation). For'
|
||||
print 'this problem, you should assume a simple virtual address space of a given size.'
|
||||
print ''
|
||||
|
||||
|
||||
|
||||
|
124
related_info/ostep/ostep10-lottery.md
Normal file
124
related_info/ostep/ostep10-lottery.md
Normal file
@ -0,0 +1,124 @@
|
||||
|
||||
This program, lottery.py, allows you to see how a lottery scheduler
|
||||
works. As always, there are two steps to running the program. First, run
|
||||
without the -c flag: this shows you what problem to solve without
|
||||
revealing the answers.
|
||||
|
||||
prompt> ./lottery.py -j 2 -s 0
|
||||
...
|
||||
Here is the job list, with the run time of each job:
|
||||
Job 0 ( length = 8, tickets = 75 )
|
||||
Job 1 ( length = 4, tickets = 25 )
|
||||
|
||||
Here is the set of random numbers you will need (at most):
|
||||
Random 511275
|
||||
Random 404934
|
||||
Random 783799
|
||||
Random 303313
|
||||
Random 476597
|
||||
Random 583382
|
||||
Random 908113
|
||||
Random 504687
|
||||
Random 281838
|
||||
Random 755804
|
||||
Random 618369
|
||||
Random 250506
|
||||
]
|
||||
|
||||
When you run the simulator in this manner, it first assigns you some random
|
||||
jobs (here of lengths 8, and 4), each with some number of tickets (here 75 and
|
||||
25, respectively). The simulator also gives you a list of random numbers,
|
||||
which you will need to determine what the lottery scheduler will do. The
|
||||
random numbers are chosen to be between 0 and a large number; thus, you'll
|
||||
have to use the modulo operator to compute the lottery winner (i.e., winner
|
||||
should equal this random number modulo the total number of tickets in the
|
||||
system).
|
||||
|
||||
Running with -c shows exactly what you are supposed to calculate:
|
||||
|
||||
prompt> ./lottery.py -j 2 -s 0 -c
|
||||
...
|
||||
** Solutions **
|
||||
Random 511275 -> Winning ticket 75 (of 100) -> Run 1
|
||||
Jobs: ( job:0 timeleft:8 tix:75 ) (* job:1 timeleft:4 tix:25 )
|
||||
Random 404934 -> Winning ticket 34 (of 100) -> Run 0
|
||||
Jobs: (* job:0 timeleft:8 tix:75 ) ( job:1 timeleft:3 tix:25 )
|
||||
Random 783799 -> Winning ticket 99 (of 100) -> Run 1
|
||||
Jobs: ( job:0 timeleft:7 tix:75 ) (* job:1 timeleft:3 tix:25 )
|
||||
Random 303313 -> Winning ticket 13 (of 100) -> Run 0
|
||||
Jobs: (* job:0 timeleft:7 tix:75 ) ( job:1 timeleft:2 tix:25 )
|
||||
Random 476597 -> Winning ticket 97 (of 100) -> Run 1
|
||||
Jobs: ( job:0 timeleft:6 tix:75 ) (* job:1 timeleft:2 tix:25 )
|
||||
Random 583382 -> Winning ticket 82 (of 100) -> Run 1
|
||||
Jobs: ( job:0 timeleft:6 tix:75 ) (* job:1 timeleft:1 tix:25 )
|
||||
--> JOB 1 DONE at time 6
|
||||
Random 908113 -> Winning ticket 13 (of 75) -> Run 0
|
||||
Jobs: (* job:0 timeleft:6 tix:75 ) ( job:1 timeleft:0 tix:--- )
|
||||
Random 504687 -> Winning ticket 12 (of 75) -> Run 0
|
||||
Jobs: (* job:0 timeleft:5 tix:75 ) ( job:1 timeleft:0 tix:--- )
|
||||
Random 281838 -> Winning ticket 63 (of 75) -> Run 0
|
||||
Jobs: (* job:0 timeleft:4 tix:75 ) ( job:1 timeleft:0 tix:--- )
|
||||
Random 755804 -> Winning ticket 29 (of 75) -> Run 0
|
||||
Jobs: (* job:0 timeleft:3 tix:75 ) ( job:1 timeleft:0 tix:--- )
|
||||
Random 618369 -> Winning ticket 69 (of 75) -> Run 0
|
||||
Jobs: (* job:0 timeleft:2 tix:75 ) ( job:1 timeleft:0 tix:--- )
|
||||
Random 250506 -> Winning ticket 6 (of 75) -> Run 0
|
||||
Jobs: (* job:0 timeleft:1 tix:75 ) ( job:1 timeleft:0 tix:--- )
|
||||
--> JOB 0 DONE at time 12
|
||||
]
|
||||
|
||||
As you can see from this trace, what you are supposed to do is use the random
|
||||
number to figure out which ticket is the winner. Then, given the winning
|
||||
ticket, figure out which job should run. Repeat this until all of the jobs are
|
||||
finished running. It's as simple as that -- you are just emulating what the
|
||||
lottery scheduler does, but by hand!
|
||||
|
||||
Just to make this absolutely clear, let's look at the first decision made in
|
||||
the example above. At this point, we have two jobs (job 0 which has a runtime
|
||||
of 8 and 75 tickets, and job 1 which has a runtime of 4 and 25 tickets). The
|
||||
first random number we are given is 511275. As there are 100 tickets in the
|
||||
system, 511275 \% 100 is 75, and thus 75 is our winning ticket.
|
||||
|
||||
If ticket 75 is the winner, we simply search through the job list until we
|
||||
find it. The first entry, for job 0, has 75 tickets (0 through 74), and thus
|
||||
does not win; the next entry is for job 1, and thus we have found our winner,
|
||||
so we run job 1 for the quantum length (1 in this example). All of this is
|
||||
shown in the print out as follows:
|
||||
|
||||
Random 511275 -> Winning ticket 75 (of 100) -> Run 1
|
||||
Jobs: ( job:0 timeleft:8 tix:75 ) (* job:1 timeleft:4 tix:25 )
|
||||
]
|
||||
|
||||
As you can see, the first line summarizes what happens, and the second simply
|
||||
shows the entire job queue, with an * denoting which job was chosen.
|
||||
|
||||
The simulator has a few other options, most of which should be
|
||||
self-explanatory. Most notably, the -l/--jlist flag can be used to specify an
|
||||
exact set of jobs and their ticket values, instead of always using
|
||||
randomly-generated job lists.
|
||||
|
||||
prompt> ./lottery.py -h
|
||||
Usage: lottery.py [options]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
show this help message and exit
|
||||
-s SEED, --seed=SEED
|
||||
the random seed
|
||||
-j JOBS, --jobs=JOBS
|
||||
number of jobs in the system
|
||||
-l JLIST, --jlist=JLIST
|
||||
instead of random jobs, provide a comma-separated list
|
||||
of run times and ticket values (e.g., 10:100,20:100
|
||||
would have two jobs with run-times of 10 and 20, each
|
||||
with 100 tickets)
|
||||
-m MAXLEN, --maxlen=MAXLEN
|
||||
max length of job
|
||||
-T MAXTICKET, --maxtick=MAXTICKET
|
||||
maximum ticket value, if randomly assigned
|
||||
-q QUANTUM, --quantum=QUANTUM
|
||||
length of time slice
|
||||
-c, --compute
|
||||
compute answers for me
|
||||
|
||||
|
119
related_info/ostep/ostep10-lottery.py
Executable file
119
related_info/ostep/ostep10-lottery.py
Executable file
@ -0,0 +1,119 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
import random
|
||||
|
||||
parser = OptionParser()
|
||||
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
|
||||
parser.add_option('-j', '--jobs', default=3, help='number of jobs in the system', action='store', type='int', dest='jobs')
|
||||
parser.add_option('-l', '--jlist', default='', help='instead of random jobs, provide a comma-separated list of run times and ticket values (e.g., 10:100,20:100 would have two jobs with run-times of 10 and 20, each with 100 tickets)', action='store', type='string', dest='jlist')
|
||||
parser.add_option('-m', '--maxlen', default=10, help='max length of job', action='store', type='int', dest='maxlen')
|
||||
parser.add_option('-T', '--maxticket', default=100, help='maximum ticket value, if randomly assigned', action='store', type='int', dest='maxticket')
|
||||
parser.add_option('-q', '--quantum', default=1, help='length of time slice', action='store', type='int', dest='quantum')
|
||||
parser.add_option('-c', '--compute', help='compute answers for me', action='store_true', default=False, dest='solve')
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
random.seed(options.seed)
|
||||
|
||||
print 'ARG jlist', options.jlist
|
||||
print 'ARG jobs', options.jobs
|
||||
print 'ARG maxlen', options.maxlen
|
||||
print 'ARG maxticket', options.maxticket
|
||||
print 'ARG quantum', options.quantum
|
||||
print 'ARG seed', options.seed
|
||||
print ''
|
||||
|
||||
print 'Here is the job list, with the run time of each job: '
|
||||
|
||||
import operator
|
||||
|
||||
|
||||
tickTotal = 0
|
||||
runTotal = 0
|
||||
joblist = []
|
||||
if options.jlist == '':
|
||||
for jobnum in range(0,options.jobs):
|
||||
runtime = int(options.maxlen * random.random())
|
||||
tickets = int(options.maxticket * random.random())
|
||||
runTotal += runtime
|
||||
tickTotal += tickets
|
||||
joblist.append([jobnum, runtime, tickets])
|
||||
print ' Job %d ( length = %d, tickets = %d )' % (jobnum, runtime, tickets)
|
||||
else:
|
||||
jobnum = 0
|
||||
for entry in options.jlist.split(','):
|
||||
(runtime, tickets) = entry.split(':')
|
||||
joblist.append([jobnum, int(runtime), int(tickets)])
|
||||
runTotal += int(runtime)
|
||||
tickTotal += int(tickets)
|
||||
jobnum += 1
|
||||
for job in joblist:
|
||||
print ' Job %d ( length = %d, tickets = %d )' % (job[0], job[1], job[2])
|
||||
print '\n'
|
||||
|
||||
if options.solve == False:
|
||||
print 'Here is the set of random numbers you will need (at most):'
|
||||
for i in range(runTotal):
|
||||
r = int(random.random() * 1000001)
|
||||
print 'Random', r
|
||||
|
||||
if options.solve == True:
|
||||
print '** Solutions **\n'
|
||||
|
||||
jobs = len(joblist)
|
||||
clock = 0
|
||||
for i in range(runTotal):
|
||||
r = int(random.random() * 1000001)
|
||||
winner = int(r % tickTotal)
|
||||
|
||||
current = 0
|
||||
for (job, runtime, tickets) in joblist:
|
||||
current += tickets
|
||||
if current > winner:
|
||||
(wjob, wrun, wtix) = (job, runtime, tickets)
|
||||
break
|
||||
|
||||
print 'Random', r, '-> Winning ticket %d (of %d) -> Run %d' % (winner, tickTotal, wjob)
|
||||
# print 'Winning ticket %d (of %d) -> Run %d' % (winner, tickTotal, wjob)
|
||||
|
||||
print ' Jobs:',
|
||||
for (job, runtime, tickets) in joblist:
|
||||
if wjob == job:
|
||||
wstr = '*'
|
||||
else:
|
||||
wstr = ' '
|
||||
|
||||
if runtime > 0:
|
||||
tstr = tickets
|
||||
else:
|
||||
tstr = '---'
|
||||
print ' (%s job:%d timeleft:%d tix:%s ) ' % (wstr, job, runtime, tstr),
|
||||
print ''
|
||||
|
||||
# now do the accounting
|
||||
if wrun >= options.quantum:
|
||||
wrun -= options.quantum
|
||||
else:
|
||||
wrun = 0
|
||||
|
||||
clock += options.quantum
|
||||
|
||||
# job completed!
|
||||
if wrun == 0:
|
||||
print '--> JOB %d DONE at time %d' % (wjob, clock)
|
||||
tickTotal -= wtix
|
||||
wtix = 0
|
||||
jobs -= 1
|
||||
|
||||
# update job list
|
||||
joblist[wjob] = (wjob, wrun, wtix)
|
||||
|
||||
if jobs == 0:
|
||||
print ''
|
||||
break
|
||||
|
||||
|
||||
|
||||
|
6
related_info/ostep/ostep11-threadintro/loop.s
Normal file
6
related_info/ostep/ostep11-threadintro/loop.s
Normal file
@ -0,0 +1,6 @@
|
||||
.main
|
||||
.top
|
||||
sub $1,%dx
|
||||
test $0,%dx
|
||||
jgte .top
|
||||
halt
|
15
related_info/ostep/ostep11-threadintro/looping-race-nolock.s
Normal file
15
related_info/ostep/ostep11-threadintro/looping-race-nolock.s
Normal file
@ -0,0 +1,15 @@
|
||||
# assumes %bx has loop count in it
|
||||
|
||||
.main
|
||||
.top
|
||||
# critical section
|
||||
mov 2000, %ax # get the value at the address
|
||||
add $1, %ax # increment it
|
||||
mov %ax, 2000 # store it back
|
||||
|
||||
# see if we're still looping
|
||||
sub $1, %bx
|
||||
test $0, %bx
|
||||
jgt .top
|
||||
|
||||
halt
|
329
related_info/ostep/ostep11-threadintro/race.md
Normal file
329
related_info/ostep/ostep11-threadintro/race.md
Normal file
@ -0,0 +1,329 @@
|
||||
|
||||
Welcome to this simulator. The idea is to gain familiarity with threads by
|
||||
seeing how they interleave; the simulator, x86.py, will help you in
|
||||
gaining this understanding.
|
||||
|
||||
The simulator mimicks the execution of short assembly sequences by multiple
|
||||
threads. Note that the OS code that would run (for example, to perform a
|
||||
context switch) is *not* shown; thus, all you see is the interleaving of the
|
||||
user code.
|
||||
|
||||
The assembly code that is run is based on x86, but somewhat simplified.
|
||||
In this instruction set, there are four general-purpose registers
|
||||
(%ax, %bx, %cx, %dx), a program counter (PC), and a small set of instructions
|
||||
which will be enough for our purposes.
|
||||
|
||||
Here is an example code snippet that we will be able to run:
|
||||
|
||||
.main
|
||||
mov 2000, %ax # get the value at the address
|
||||
add $1, %ax # increment it
|
||||
mov %ax, 2000 # store it back
|
||||
halt
|
||||
|
||||
The code is easy to understand. The first instruction, an x86 "mov", simply
|
||||
loads a value from the address specified by 2000 into the register %ax.
|
||||
Addresses, in this subset of x86, can take some of the following forms:
|
||||
|
||||
2000 -> the number (2000) is the address
|
||||
(%cx) -> contents of register (in parentheses) forms the address
|
||||
1000(%dx) -> the number + contents of the register form the address
|
||||
10(%ax,%bx) -> the number + reg1 + reg2 forms the address
|
||||
|
||||
To store a value, the same "mov" instruction is used, but this time with the
|
||||
arguments reversed, e.g.:
|
||||
|
||||
mov %ax, 2000
|
||||
|
||||
The "add" instruction, from the sequence above, should be clear: it adds an
|
||||
immediate value (specified by $1) to the register specified in the second
|
||||
argument (i.e., %ax = %ax + 1).
|
||||
|
||||
Thus, we now can understand the code sequence above: it loads the value at
|
||||
address 2000, adds 1 to it, and then stores the value back into address 2000.
|
||||
|
||||
The fake-ish "halt" instruction just stops running this thread.
|
||||
|
||||
Let's run the simulator and see how this all works! Assume the above code
|
||||
sequence is in the file "simple-race.s".
|
||||
|
||||
prompt> ./x86.py -p simple-race.s -t 1
|
||||
|
||||
Thread 0
|
||||
1000 mov 2000, %ax
|
||||
1001 add $1, %ax
|
||||
1002 mov %ax, 2000
|
||||
1003 halt
|
||||
|
||||
prompt>
|
||||
|
||||
The arguments used here specify the program (-p), the number of threads (-t
|
||||
1), and the interrupt interval, which is how often a scheduler will be woken
|
||||
and run to switch to a different task. Because there is only one thread in
|
||||
this example, this interval does not matter.
|
||||
|
||||
The output is easy to read: the simulator prints the program counter (here
|
||||
shown from 1000 to 1003) and the instruction that gets executed. Note that we
|
||||
assume (unrealistically) that all instructions just take up a single byte in
|
||||
memory; in x86, instructions are variable-sized and would take up from one to
|
||||
a small number of bytes.
|
||||
|
||||
We can use more detailed tracing to get a better sense of how machine state
|
||||
changes during the execution:
|
||||
|
||||
prompt> ./x86.py -p simple-race.s -t 1 -M 2000 -R ax,bx
|
||||
|
||||
2000 ax bx Thread 0
|
||||
? ? ?
|
||||
? ? ? 1000 mov 2000, %ax
|
||||
? ? ? 1001 add $1, %ax
|
||||
? ? ? 1002 mov %ax, 2000
|
||||
? ? ? 1003 halt
|
||||
|
||||
Oops! Forgot the -c flag (which actually computes the answers for you).
|
||||
|
||||
prompt> ./x86.py -p simple-race.s -t 1 -M 2000 -R ax,bx -c
|
||||
|
||||
2000 ax bx Thread 0
|
||||
0 0 0
|
||||
0 0 0 1000 mov 2000, %ax
|
||||
0 1 0 1001 add $1, %ax
|
||||
1 1 0 1002 mov %ax, 2000
|
||||
1 1 0 1003 halt
|
||||
|
||||
By using the -M flag, we can trace memory locations (a comma-separated list
|
||||
lets you trace more than one, e.g., 2000,3000); by using the -R flag we can
|
||||
track the values inside specific registers.
|
||||
|
||||
The values on the left show the memory/register contents AFTER the instruction
|
||||
on the right has executed. For example, after the "add" instruction, you can
|
||||
see that %ax has been incremented to the value 1; after the second "mov"
|
||||
instruction (at PC=1002), you can see that the memory contents at 2000 are
|
||||
now also incremented.
|
||||
|
||||
There are a few more instructions you'll need to know, so let's get to them
|
||||
now. Here is a code snippet of a loop:
|
||||
|
||||
.main
|
||||
.top
|
||||
sub $1,%dx
|
||||
test $0,%dx
|
||||
jgte .top
|
||||
halt
|
||||
|
||||
A few things have been introduced here. First is the "test" instruction.
|
||||
This instruction takes two arguments and compares them; it then sets implicit
|
||||
"condition codes" (kind of like 1-bit registers) which subsequent instructions
|
||||
can act upon.
|
||||
|
||||
In this case, the other new instruction is the "jump" instruction (in this
|
||||
case, "jgte" which stands for "jump if greater than or equal to"). This
|
||||
instruction jumps if the first value is greater than or equal to the second
|
||||
in the test.
|
||||
|
||||
One last point: to really make this code work, dx must be initialized to 1 or
|
||||
greater.
|
||||
|
||||
Thus, we run the program like this:
|
||||
|
||||
prompt> ./x86.py -p loop.s -t 1 -a dx=3 -R dx -C -c
|
||||
|
||||
dx >= > <= < != == Thread 0
|
||||
3 0 0 0 0 0 0
|
||||
2 0 0 0 0 0 0 1000 sub $1,%dx
|
||||
2 1 1 0 0 1 0 1001 test $0,%dx
|
||||
2 1 1 0 0 1 0 1002 jgte .top
|
||||
1 1 1 0 0 1 0 1000 sub $1,%dx
|
||||
1 1 1 0 0 1 0 1001 test $0,%dx
|
||||
1 1 1 0 0 1 0 1002 jgte .top
|
||||
0 1 1 0 0 1 0 1000 sub $1,%dx
|
||||
0 1 0 1 0 0 1 1001 test $0,%dx
|
||||
0 1 0 1 0 0 1 1002 jgte .top
|
||||
0 1 0 1 0 0 1 1003 halt
|
||||
|
||||
The "-R dx" flag traces the value of %dx; the "-C" flag traces the values of
|
||||
the condition codes that get set by a test instruction. Finally, the "-a dx=3"
|
||||
flag sets the %dx register to the value 3 to start with.
|
||||
|
||||
As you can see from the trace, the "sub" instruction slowly lowers the value
|
||||
of %dx. The first few times "test" is called, only the ">=", ">", and "!="
|
||||
conditions get set. However, the last "test" in the trace finds %dx and 0 to
|
||||
be equal, and thus the subsequent jump does NOT take place, and the program
|
||||
finally halts.
|
||||
|
||||
Now, finally, we get to a more interesting case, i.e., a race condition with
|
||||
multiple threads. Let's look at the code first:
|
||||
|
||||
.main
|
||||
.top
|
||||
# critical section
|
||||
mov 2000, %ax # get the value at the address
|
||||
add $1, %ax # increment it
|
||||
mov %ax, 2000 # store it back
|
||||
|
||||
# see if we're still looping
|
||||
sub $1, %bx
|
||||
test $0, %bx
|
||||
jgt .top
|
||||
|
||||
halt
|
||||
|
||||
The code has a critical section which loads the value of a variable
|
||||
(at address 2000), then adds 1 to the value, then stores it back.
|
||||
|
||||
The code after just decrements a loop counter (in %bx), tests if it
|
||||
is greater than or equal to zero, and if so, jumps back to the top
|
||||
to the critical section again.
|
||||
|
||||
prompt> ./x86.py -p looping-race-nolock.s -t 2 -a bx=1 -M 2000 -c
|
||||
|
||||
2000 bx Thread 0 Thread 1
|
||||
0 1
|
||||
0 1 1000 mov 2000, %ax
|
||||
0 1 1001 add $1, %ax
|
||||
1 1 1002 mov %ax, 2000
|
||||
1 0 1003 sub $1, %bx
|
||||
1 0 1004 test $0, %bx
|
||||
1 0 1005 jgt .top
|
||||
1 0 1006 halt
|
||||
1 1 ----- Halt;Switch ----- ----- Halt;Switch -----
|
||||
1 1 1000 mov 2000, %ax
|
||||
1 1 1001 add $1, %ax
|
||||
2 1 1002 mov %ax, 2000
|
||||
2 0 1003 sub $1, %bx
|
||||
2 0 1004 test $0, %bx
|
||||
2 0 1005 jgt .top
|
||||
2 0 1006 halt
|
||||
|
||||
Here you can see each thread ran once, and each updated the shared
|
||||
variable at address 2000 once, thus resulting in a count of two there.
|
||||
|
||||
The "Halt;Switch" line is inserted whenever a thread halts and another
|
||||
thread must be run.
|
||||
|
||||
One last example: run the same thing above, but with a smaller interrupt
|
||||
frequency. Here is what that will look like:
|
||||
|
||||
[mac Race-Analyze] ./x86.py -p looping-race-nolock.s -t 2 -a bx=1 -M 2000 -i 2
|
||||
|
||||
2000 Thread 0 Thread 1
|
||||
?
|
||||
? 1000 mov 2000, %ax
|
||||
? 1001 add $1, %ax
|
||||
? ------ Interrupt ------ ------ Interrupt ------
|
||||
? 1000 mov 2000, %ax
|
||||
? 1001 add $1, %ax
|
||||
? ------ Interrupt ------ ------ Interrupt ------
|
||||
? 1002 mov %ax, 2000
|
||||
? 1003 sub $1, %bx
|
||||
? ------ Interrupt ------ ------ Interrupt ------
|
||||
? 1002 mov %ax, 2000
|
||||
? 1003 sub $1, %bx
|
||||
? ------ Interrupt ------ ------ Interrupt ------
|
||||
? 1004 test $0, %bx
|
||||
? 1005 jgt .top
|
||||
? ------ Interrupt ------ ------ Interrupt ------
|
||||
? 1004 test $0, %bx
|
||||
? 1005 jgt .top
|
||||
? ------ Interrupt ------ ------ Interrupt ------
|
||||
? 1006 halt
|
||||
? ----- Halt;Switch ----- ----- Halt;Switch -----
|
||||
? 1006 halt
|
||||
|
||||
As you can see, each thread is interrupt every 2 instructions, as we specify
|
||||
via the "-i 2" flag. What is the value of memory[2000] throughout this run?
|
||||
What should it have been?
|
||||
|
||||
Now let's give a little more information on what can be simulated
|
||||
with this program. The full set of registers: %ax, %bx, %cx, %dx, and the PC.
|
||||
In this version, there is no support for a "stack", nor are there call
|
||||
and return instructions.
|
||||
|
||||
The full set of instructions simulated are:
|
||||
|
||||
mov immediate, register # moves immediate value to register
|
||||
mov memory, register # loads from memory into register
|
||||
mov register, register # moves value from one register to other
|
||||
mov register, memory # stores register contents in memory
|
||||
mov immediate, memory # stores immediate value in memory
|
||||
|
||||
add immediate, register # register = register + immediate
|
||||
add register1, register2 # register2 = register2 + register1
|
||||
sub immediate, register # register = register - immediate
|
||||
sub register1, register2 # register2 = register2 - register1
|
||||
|
||||
test immediate, register # compare immediate and register (set condition codes)
|
||||
test register, immediate # same but register and immediate
|
||||
test register, register # same but register and register
|
||||
|
||||
jne # jump if test'd values are not equal
|
||||
je # ... equal
|
||||
jlt # ... second is less than first
|
||||
jlte # ... less than or equal
|
||||
jgt # ... is greater than
|
||||
jgte # ... greater than or equal
|
||||
|
||||
xchg register, memory # atomic exchange:
|
||||
# put value of register into memory
|
||||
# return old contents of memory into reg
|
||||
# do both things atomically
|
||||
|
||||
nop # no op
|
||||
|
||||
Notes:
|
||||
- 'immediate' is something of the form $number
|
||||
- 'memory' is of the form 'number' or '(reg)' or 'number(reg)' or
|
||||
'number(reg,reg)' (as described above)
|
||||
- 'register' is one of %ax, %bx, %cx, %dx
|
||||
|
||||
Finally, here are the full set of options to the simulator are available with
|
||||
the -h flag:
|
||||
|
||||
Usage: x86.py [options]
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-s SEED, --seed=SEED the random seed
|
||||
-t NUMTHREADS, --threads=NUMTHREADS
|
||||
number of threads
|
||||
-p PROGFILE, --program=PROGFILE
|
||||
source program (in .s)
|
||||
-i INTFREQ, --interrupt=INTFREQ
|
||||
interrupt frequency
|
||||
-r, --randints if interrupts are random
|
||||
-a ARGV, --argv=ARGV comma-separated per-thread args (e.g., ax=1,ax=2 sets
|
||||
thread 0 ax reg to 1 and thread 1 ax reg to 2);
|
||||
specify multiple regs per thread via colon-separated
|
||||
list (e.g., ax=1:bx=2,cx=3 sets thread 0 ax and bx and
|
||||
just cx for thread 1)
|
||||
-L LOADADDR, --loadaddr=LOADADDR
|
||||
address where to load code
|
||||
-m MEMSIZE, --memsize=MEMSIZE
|
||||
size of address space (KB)
|
||||
-M MEMTRACE, --memtrace=MEMTRACE
|
||||
comma-separated list of addrs to trace (e.g.,
|
||||
20000,20001)
|
||||
-R REGTRACE, --regtrace=REGTRACE
|
||||
comma-separated list of regs to trace (e.g.,
|
||||
ax,bx,cx,dx)
|
||||
-C, --cctrace should we trace condition codes
|
||||
-S, --printstats print some extra stats
|
||||
-c, --compute compute answers for me
|
||||
|
||||
|
||||
Most are obvious. Usage of -r turns on a random interrupter (from 1 to intfreq
|
||||
as specified by -i), which can make for more fun during homework problems.
|
||||
|
||||
-L specifies where in the address space to load the code.
|
||||
|
||||
-m specified the size of the address space (in KB).
|
||||
|
||||
-S prints some extra stats
|
||||
|
||||
-c is not really used (unlike most simulators in the book); use the tracing
|
||||
|
||||
or condition codes.
|
||||
|
||||
Now you have the basics in place; read the questions at the end of the chapter
|
||||
to study this race condition and related issues in more depth.
|
||||
|
6
related_info/ostep/ostep11-threadintro/simple-race.s
Normal file
6
related_info/ostep/ostep11-threadintro/simple-race.s
Normal file
@ -0,0 +1,6 @@
|
||||
.main
|
||||
# this is a critical section
|
||||
mov 2000(%bx), %ax # get the value at the address
|
||||
add $1, %ax # increment it
|
||||
mov %ax, 2000(%bx) # store it back
|
||||
halt
|
13
related_info/ostep/ostep11-threadintro/wait-for-me.s
Normal file
13
related_info/ostep/ostep11-threadintro/wait-for-me.s
Normal file
@ -0,0 +1,13 @@
|
||||
.main
|
||||
test $1, %ax # ax should be 1 (signaller) or 0 (waiter)
|
||||
je .signaller
|
||||
|
||||
.waiter
|
||||
mov 2000, %cx
|
||||
test $1, %cx
|
||||
jne .waiter
|
||||
halt
|
||||
|
||||
.signaller
|
||||
mov $1, 2000
|
||||
halt
|
989
related_info/ostep/ostep11-threadintro/x86.py
Executable file
989
related_info/ostep/ostep11-threadintro/x86.py
Executable file
@ -0,0 +1,989 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import sys
|
||||
import time
|
||||
import random
|
||||
from optparse import OptionParser
|
||||
|
||||
#
|
||||
# HELPER
|
||||
#
|
||||
def dospace(howmuch):
|
||||
for i in range(howmuch):
|
||||
print '%24s' % ' ',
|
||||
|
||||
# useful instead of assert
|
||||
def zassert(cond, str):
|
||||
if cond == False:
|
||||
print 'ABORT::', str
|
||||
exit(1)
|
||||
return
|
||||
|
||||
class cpu:
|
||||
#
|
||||
# INIT: how much memory?
|
||||
#
|
||||
def __init__(self, memory, memtrace, regtrace, cctrace, compute, verbose):
|
||||
#
|
||||
# CONSTANTS
|
||||
#
|
||||
|
||||
# conditions
|
||||
self.COND_GT = 0
|
||||
self.COND_GTE = 1
|
||||
self.COND_LT = 2
|
||||
self.COND_LTE = 3
|
||||
self.COND_EQ = 4
|
||||
self.COND_NEQ = 5
|
||||
|
||||
# registers in system
|
||||
self.REG_ZERO = 0
|
||||
self.REG_AX = 1
|
||||
self.REG_BX = 2
|
||||
self.REG_CX = 3
|
||||
self.REG_DX = 4
|
||||
self.REG_SP = 5
|
||||
self.REG_BP = 6
|
||||
|
||||
# system memory: in KB
|
||||
self.max_memory = memory * 1024
|
||||
|
||||
# which memory addrs and registers to trace?
|
||||
self.memtrace = memtrace
|
||||
self.regtrace = regtrace
|
||||
self.cctrace = cctrace
|
||||
self.compute = compute
|
||||
self.verbose = verbose
|
||||
|
||||
self.PC = 0
|
||||
self.registers = {}
|
||||
self.conditions = {}
|
||||
self.labels = {}
|
||||
self.vars = {}
|
||||
self.memory = {}
|
||||
self.pmemory = {} # for printable version of what's in memory (instructions)
|
||||
|
||||
self.condlist = [self.COND_GTE, self.COND_GT, self.COND_LTE, self.COND_LT, self.COND_NEQ, self.COND_EQ]
|
||||
self.regnums = [self.REG_ZERO, self.REG_AX, self.REG_BX, self.REG_CX, self.REG_DX, self.REG_SP, self.REG_BP]
|
||||
|
||||
self.regnames = {}
|
||||
self.regnames['zero'] = self.REG_ZERO # hidden zero-valued register
|
||||
self.regnames['ax'] = self.REG_AX
|
||||
self.regnames['bx'] = self.REG_BX
|
||||
self.regnames['cx'] = self.REG_CX
|
||||
self.regnames['dx'] = self.REG_DX
|
||||
self.regnames['sp'] = self.REG_SP
|
||||
self.regnames['bp'] = self.REG_BP
|
||||
|
||||
tmplist = []
|
||||
for r in self.regtrace:
|
||||
zassert(r in self.regnames, 'Register %s cannot be traced because it does not exist' % r)
|
||||
tmplist.append(self.regnames[r])
|
||||
self.regtrace = tmplist
|
||||
|
||||
self.init_memory()
|
||||
self.init_registers()
|
||||
self.init_condition_codes()
|
||||
|
||||
#
|
||||
# BEFORE MACHINE RUNS
|
||||
#
|
||||
def init_condition_codes(self):
|
||||
for c in self.condlist:
|
||||
self.conditions[c] = False
|
||||
|
||||
def init_memory(self):
|
||||
for i in range(self.max_memory):
|
||||
self.memory[i] = 0
|
||||
|
||||
def init_registers(self):
|
||||
for i in self.regnums:
|
||||
self.registers[i] = 0
|
||||
|
||||
def dump_memory(self):
|
||||
print 'MEMORY DUMP'
|
||||
for i in range(self.max_memory):
|
||||
if i not in self.pmemory and i in self.memory and self.memory[i] != 0:
|
||||
print ' m[%d]' % i, self.memory[i]
|
||||
|
||||
#
|
||||
# INFORMING ABOUT THE HARDWARE
|
||||
#
|
||||
def get_regnum(self, name):
|
||||
assert(name in self.regnames)
|
||||
return self.regnames[name]
|
||||
|
||||
def get_regname(self, num):
|
||||
assert(num in self.regnums)
|
||||
for rname in self.regnames:
|
||||
if self.regnames[rname] == num:
|
||||
return rname
|
||||
return ''
|
||||
|
||||
def get_regnums(self):
|
||||
return self.regnums
|
||||
|
||||
def get_condlist(self):
|
||||
return self.condlist
|
||||
|
||||
def get_reg(self, reg):
|
||||
assert(reg in self.regnums)
|
||||
return self.registers[reg]
|
||||
|
||||
def get_cond(self, cond):
|
||||
assert(cond in self.condlist)
|
||||
return self.conditions[cond]
|
||||
|
||||
def get_pc(self):
|
||||
return self.PC
|
||||
|
||||
def set_reg(self, reg, value):
|
||||
assert(reg in self.regnums)
|
||||
self.registers[reg] = value
|
||||
|
||||
def set_cond(self, cond, value):
|
||||
assert(cond in self.condlist)
|
||||
self.conditions[cond] = value
|
||||
|
||||
def set_pc(self, pc):
|
||||
self.PC = pc
|
||||
|
||||
#
|
||||
# INSTRUCTIONS
|
||||
#
|
||||
def halt(self):
|
||||
return -1
|
||||
|
||||
def iyield(self):
|
||||
return -2
|
||||
|
||||
def nop(self):
|
||||
return 0
|
||||
|
||||
def rdump(self):
|
||||
print 'REGISTERS::',
|
||||
print 'ax:', self.registers[self.REG_AX],
|
||||
print 'bx:', self.registers[self.REG_BX],
|
||||
print 'cx:', self.registers[self.REG_CX],
|
||||
print 'dx:', self.registers[self.REG_DX],
|
||||
|
||||
def mdump(self, index):
|
||||
print 'm[%d] ' % index, self.memory[index]
|
||||
|
||||
def move_i_to_r(self, src, dst):
|
||||
self.registers[dst] = src
|
||||
return 0
|
||||
|
||||
# memory: value, register, register
|
||||
def move_i_to_m(self, src, value, reg1, reg2):
|
||||
tmp = value + self.registers[reg1] + self.registers[reg2]
|
||||
self.memory[tmp] = src
|
||||
return 0
|
||||
|
||||
def move_m_to_r(self, value, reg1, reg2, dst):
|
||||
tmp = value + self.registers[reg1] + self.registers[reg2]
|
||||
# print 'doing mov', 'val:', value, 'r1:', self.get_regname(reg1), self.registers[reg1], 'r2:', self.get_regname(reg2), self.registers[reg2], 'dst', self.get_regname(dst), 'tmp', tmp, 'reg[dst]', self.registers[dst], 'mem', self.memory[tmp]
|
||||
self.registers[dst] = self.memory[tmp]
|
||||
|
||||
def move_r_to_m(self, src, value, reg1, reg2):
|
||||
tmp = value + self.registers[reg1] + self.registers[reg2]
|
||||
self.memory[tmp] = self.registers[src]
|
||||
return 0
|
||||
|
||||
def move_r_to_r(self, src, dst):
|
||||
self.registers[dst] = self.registers[src]
|
||||
return 0
|
||||
|
||||
def add_i_to_r(self, src, dst):
|
||||
self.registers[dst] += src
|
||||
return 0
|
||||
|
||||
def add_r_to_r(self, src, dst):
|
||||
self.registers[dst] += self.registers[src]
|
||||
return 0
|
||||
|
||||
def sub_i_to_r(self, src, dst):
|
||||
self.registers[dst] -= src
|
||||
return 0
|
||||
|
||||
def sub_r_to_r(self, src, dst):
|
||||
self.registers[dst] -= self.registers[src]
|
||||
return 0
|
||||
|
||||
|
||||
#
|
||||
# SUPPORT FOR LOCKS
|
||||
#
|
||||
def atomic_exchange(self, src, value, reg1, reg2):
|
||||
tmp = value + self.registers[reg1] + self.registers[reg2]
|
||||
old = self.memory[tmp]
|
||||
self.memory[tmp] = self.registers[src]
|
||||
self.registers[src] = old
|
||||
return 0
|
||||
|
||||
def fetchadd(self, src, value, reg1, reg2):
|
||||
tmp = value + self.registers[reg1] + self.registers[reg2]
|
||||
old = self.memory[tmp]
|
||||
self.memory[tmp] = self.memory[tmp] + self.registers[src]
|
||||
self.registers[src] = old
|
||||
|
||||
#
|
||||
# TEST for conditions
|
||||
#
|
||||
def test_all(self, src, dst):
|
||||
self.init_condition_codes()
|
||||
if dst > src:
|
||||
self.conditions[self.COND_GT] = True
|
||||
if dst >= src:
|
||||
self.conditions[self.COND_GTE] = True
|
||||
if dst < src:
|
||||
self.conditions[self.COND_LT] = True
|
||||
if dst <= src:
|
||||
self.conditions[self.COND_LTE] = True
|
||||
if dst == src:
|
||||
self.conditions[self.COND_EQ] = True
|
||||
if dst != src:
|
||||
self.conditions[self.COND_NEQ] = True
|
||||
return 0
|
||||
|
||||
def test_i_r(self, src, dst):
|
||||
self.init_condition_codes()
|
||||
return self.test_all(src, self.registers[dst])
|
||||
|
||||
def test_r_i(self, src, dst):
|
||||
self.init_condition_codes()
|
||||
return self.test_all(self.registers[src], dst)
|
||||
|
||||
def test_r_r(self, src, dst):
|
||||
self.init_condition_codes()
|
||||
return self.test_all(self.registers[src], self.registers[dst])
|
||||
|
||||
#
|
||||
# JUMPS
|
||||
#
|
||||
def jump(self, targ):
|
||||
self.PC = targ
|
||||
return 0
|
||||
|
||||
def jump_notequal(self, targ):
|
||||
if self.conditions[self.COND_NEQ] == True:
|
||||
self.PC = targ
|
||||
return 0
|
||||
|
||||
def jump_equal(self, targ):
|
||||
if self.conditions[self.COND_EQ] == True:
|
||||
self.PC = targ
|
||||
return 0
|
||||
|
||||
def jump_lessthan(self, targ):
|
||||
if self.conditions[self.COND_LT] == True:
|
||||
self.PC = targ
|
||||
return 0
|
||||
|
||||
def jump_lessthanorequal(self, targ):
|
||||
if self.conditions[self.COND_LTE] == True:
|
||||
self.PC = targ
|
||||
return 0
|
||||
|
||||
def jump_greaterthan(self, targ):
|
||||
if self.conditions[self.COND_GT] == True:
|
||||
self.PC = targ
|
||||
return 0
|
||||
|
||||
def jump_greaterthanorequal(self, targ):
|
||||
if self.conditions[self.COND_GTE] == True:
|
||||
self.PC = targ
|
||||
return 0
|
||||
|
||||
#
|
||||
# CALL and RETURN
|
||||
#
|
||||
def call(self, targ):
|
||||
self.registers[self.REG_SP] -= 4
|
||||
self.memory[self.registers[self.REG_SP]] = self.PC
|
||||
self.PC = targ
|
||||
|
||||
def ret(self):
|
||||
self.PC = self.memory[self.registers[self.REG_SP]]
|
||||
self.registers[self.REG_SP] += 4
|
||||
|
||||
#
|
||||
# STACK and related
|
||||
#
|
||||
def push_r(self, reg):
|
||||
self.registers[self.REG_SP] -= 4
|
||||
self.memory[self.registers[self.REG_SP]] = self.registers[reg]
|
||||
return 0
|
||||
|
||||
def push_m(self, value, reg1, reg2):
|
||||
# print 'push_m', value, reg1, reg2
|
||||
self.registers[self.REG_SP] -= 4
|
||||
tmp = value + self.registers[reg1] + self.registers[reg2]
|
||||
# push address onto stack, not memory value itself
|
||||
self.memory[self.registers[self.REG_SP]] = tmp
|
||||
return 0
|
||||
|
||||
def pop(self):
|
||||
self.registers[self.REG_SP] += 4
|
||||
|
||||
def pop_r(self, dst):
|
||||
self.registers[dst] = self.registers[self.REG_SP]
|
||||
self.registers[self.REG_SP] += 4
|
||||
|
||||
#
|
||||
# HELPER func for getarg
|
||||
#
|
||||
def register_translate(self, r):
|
||||
if r in self.regnames:
|
||||
return self.regnames[r]
|
||||
zassert(False, 'Register %s is not a valid register' % r)
|
||||
return
|
||||
|
||||
#
|
||||
# HELPER in parsing mov (quite primitive) and other ops
|
||||
# returns: (value, type)
|
||||
# where type is (TYPE_REGISTER, TYPE_IMMEDIATE, TYPE_MEMORY)
|
||||
#
|
||||
# FORMATS
|
||||
# %ax - register
|
||||
# $10 - immediate
|
||||
# 10 - direct memory
|
||||
# 10(%ax) - memory + reg indirect
|
||||
# 10(%ax,%bx) - memory + 2 reg indirect
|
||||
# 10(%ax,%bx,4) - XXX (not handled)
|
||||
#
|
||||
def getarg(self, arg):
|
||||
tmp1 = arg.replace(',', '')
|
||||
tmp = tmp1.replace(' \t', '')
|
||||
|
||||
if tmp[0] == '$':
|
||||
zassert(len(tmp) == 2, 'correct form is $number (not %s)' % tmp)
|
||||
value = tmp.split('$')[1]
|
||||
zassert(value.isdigit(), 'value [%s] must be a digit' % value)
|
||||
return int(value), 'TYPE_IMMEDIATE'
|
||||
elif tmp[0] == '%':
|
||||
register = tmp.split('%')[1]
|
||||
return self.register_translate(register), 'TYPE_REGISTER'
|
||||
elif tmp[0] == '(':
|
||||
register = tmp.split('(')[1].split(')')[0].split('%')[1]
|
||||
return '%d,%d,%d' % (0, self.register_translate(register), self.register_translate('zero')), 'TYPE_MEMORY'
|
||||
elif tmp[0] == '.':
|
||||
targ = tmp
|
||||
return targ, 'TYPE_LABEL'
|
||||
elif tmp[0].isalpha() and not tmp[0].isdigit():
|
||||
zassert(tmp in self.vars, 'Variable %s is not declared' % tmp)
|
||||
# print '%d,%d,%d' % (self.vars[tmp], self.register_translate('zero'), self.register_translate('zero')), 'TYPE_MEMORY'
|
||||
return '%d,%d,%d' % (self.vars[tmp], self.register_translate('zero'), self.register_translate('zero')), 'TYPE_MEMORY'
|
||||
elif tmp[0].isdigit() or tmp[0] == '-':
|
||||
# MOST GENERAL CASE: number(reg,reg) or number(reg)
|
||||
# we ignore the common x86 number(reg,reg,constant) for now
|
||||
neg = 1
|
||||
if tmp[0] == '-':
|
||||
tmp = tmp[1:]
|
||||
neg = -1
|
||||
s = tmp.split('(')
|
||||
if len(s) == 1:
|
||||
value = neg * int(tmp)
|
||||
# print '%d,%d,%d' % (int(value), self.register_translate('zero'), self.register_translate('zero')), 'TYPE_MEMORY'
|
||||
return '%d,%d,%d' % (int(value), self.register_translate('zero'), self.register_translate('zero')), 'TYPE_MEMORY'
|
||||
elif len(s) == 2:
|
||||
value = neg * int(s[0])
|
||||
t = s[1].split(')')[0].split(',')
|
||||
if len(t) == 1:
|
||||
register = t[0].split('%')[1]
|
||||
# print '%d,%d,%d' % (int(value), self.register_translate(register), self.register_translate('zero')), 'TYPE_MEMORY'
|
||||
return '%d,%d,%d' % (int(value), self.register_translate(register), self.register_translate('zero')), 'TYPE_MEMORY'
|
||||
elif len(t) == 2:
|
||||
register1 = t[0].split('%')[1]
|
||||
register2 = t[1].split('%')[1]
|
||||
# print '%d,%d,%d' % (int(value), self.register_translate(register1), self.register_translate(register2)), 'TYPE_MEMORY'
|
||||
return '%d,%d,%d' % (int(value), self.register_translate(register1), self.register_translate(register2)), 'TYPE_MEMORY'
|
||||
else:
|
||||
print 'mov: bad argument [%s]' % tmp
|
||||
exit(1)
|
||||
return
|
||||
zassert(True, 'mov: bad argument [%s]' % arg)
|
||||
return
|
||||
|
||||
#
|
||||
# LOAD a program into memory
|
||||
# make it ready to execute
|
||||
#
|
||||
def load(self, infile, loadaddr):
|
||||
pc = int(loadaddr)
|
||||
fd = open(infile)
|
||||
|
||||
bpc = loadaddr
|
||||
data = 100
|
||||
|
||||
for line in fd:
|
||||
cline = line.rstrip()
|
||||
# print 'PASS 1', cline
|
||||
|
||||
# remove everything after the comment marker
|
||||
ctmp = cline.split('#')
|
||||
assert(len(ctmp) == 1 or len(ctmp) == 2)
|
||||
if len(ctmp) == 2:
|
||||
cline = ctmp[0]
|
||||
|
||||
# remove empty lines, and split line by spaces
|
||||
tmp = cline.split()
|
||||
if len(tmp) == 0:
|
||||
continue
|
||||
|
||||
# only pay attention to labels and variables
|
||||
if tmp[0] == '.var':
|
||||
assert(len(tmp) == 2)
|
||||
assert(tmp[0] not in self.vars)
|
||||
self.vars[tmp[1]] = data
|
||||
data += 4
|
||||
zassert(data < bpc, 'Load address overrun by static data')
|
||||
if self.verbose: print 'ASSIGN VAR', tmp[0], "-->", tmp[1], self.vars[tmp[1]]
|
||||
elif tmp[0][0] == '.':
|
||||
assert(len(tmp) == 1)
|
||||
self.labels[tmp[0]] = int(pc)
|
||||
if self.verbose: print 'ASSIGN LABEL', tmp[0], "-->", pc
|
||||
else:
|
||||
pc += 1
|
||||
fd.close()
|
||||
|
||||
if self.verbose: print ''
|
||||
|
||||
# second pass: do everything else
|
||||
pc = int(loadaddr)
|
||||
fd = open(infile)
|
||||
for line in fd:
|
||||
cline = line.rstrip()
|
||||
# print 'PASS 2', cline
|
||||
|
||||
# remove everything after the comment marker
|
||||
ctmp = cline.split('#')
|
||||
assert(len(ctmp) == 1 or len(ctmp) == 2)
|
||||
if len(ctmp) == 2:
|
||||
cline = ctmp[0]
|
||||
|
||||
# remove empty lines, and split line by spaces
|
||||
tmp = cline.split()
|
||||
if len(tmp) == 0:
|
||||
continue
|
||||
|
||||
# skip labels: all else must be instructions
|
||||
if cline[0] != '.':
|
||||
tmp = cline.split(None, 1)
|
||||
opcode = tmp[0]
|
||||
self.pmemory[pc] = cline.strip()
|
||||
|
||||
# MAIN OPCODE LOOP
|
||||
if opcode == 'mov':
|
||||
rtmp = tmp[1].split(',', 1)
|
||||
zassert(len(tmp) == 2 and len(rtmp) == 2, 'mov: needs two args, separated by commas [%s]' % cline)
|
||||
arg1 = rtmp[0].strip()
|
||||
arg2 = rtmp[1].strip()
|
||||
(src, stype) = self.getarg(arg1)
|
||||
(dst, dtype) = self.getarg(arg2)
|
||||
# print 'MOV', src, stype, dst, dtype
|
||||
if stype == 'TYPE_MEMORY' and dtype == 'TYPE_MEMORY':
|
||||
print 'bad mov: two memory arguments'
|
||||
exit(1)
|
||||
elif stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_IMMEDIATE':
|
||||
print 'bad mov: two immediate arguments'
|
||||
exit(1)
|
||||
elif stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_REGISTER':
|
||||
self.memory[pc] = 'self.move_i_to_r(%d, %d)' % (int(src), dst)
|
||||
elif stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_REGISTER':
|
||||
self.memory[pc] = 'self.move_i_to_r(%d, %d)' % (int(src), dst)
|
||||
elif stype == 'TYPE_MEMORY' and dtype == 'TYPE_REGISTER':
|
||||
tmp = src.split(',')
|
||||
assert(len(tmp) == 3)
|
||||
self.memory[pc] = 'self.move_m_to_r(%d, %d, %d, %d)' % (int(tmp[0]), int(tmp[1]), int(tmp[2]), dst)
|
||||
elif stype == 'TYPE_REGISTER' and dtype == 'TYPE_MEMORY':
|
||||
tmp = dst.split(',')
|
||||
assert(len(tmp) == 3)
|
||||
self.memory[pc] = 'self.move_r_to_m(%d, %d, %d, %d)' % (src, int(tmp[0]), int(tmp[1]), int(tmp[2]))
|
||||
elif stype == 'TYPE_REGISTER' and dtype == 'TYPE_REGISTER':
|
||||
self.memory[pc] = 'self.move_r_to_r(%d, %d)' % (src, dst)
|
||||
elif stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_MEMORY':
|
||||
tmp = dst.split(',')
|
||||
assert(len(tmp) == 3)
|
||||
self.memory[pc] = 'self.move_i_to_m(%d, %d, %d, %d)' % (src, int(tmp[0]), int(tmp[1]), int(tmp[2]))
|
||||
else:
|
||||
zassert(False, 'malformed mov instruction')
|
||||
elif opcode == 'pop':
|
||||
if len(tmp) == 1:
|
||||
self.memory[pc] = 'self.pop()'
|
||||
elif len(tmp) == 2:
|
||||
arg = tmp[1].strip()
|
||||
(dst, dtype) = self.getarg(arg)
|
||||
zassert(dtype == 'TYPE_REGISTER', 'Can only pop into a register')
|
||||
self.memory[pc] = 'self.pop_r(%d)' % dst
|
||||
else:
|
||||
zassert(False, 'pop instruction must take zero/one args')
|
||||
elif opcode == 'push':
|
||||
(src, stype) = self.getarg(tmp[1].strip())
|
||||
if stype == 'TYPE_REGISTER':
|
||||
self.memory[pc] = 'self.push_r(%d)' % (int(src))
|
||||
elif stype == 'TYPE_MEMORY':
|
||||
tmp = src.split(',')
|
||||
assert(len(tmp) == 3)
|
||||
self.memory[pc] = 'self.push_m(%d,%d,%d)' % (int(tmp[0]), int(tmp[1]), int(tmp[2]))
|
||||
else:
|
||||
zassert(False, 'Cannot push anything but registers')
|
||||
elif opcode == 'call':
|
||||
(targ, ttype) = self.getarg(tmp[1].strip())
|
||||
if ttype == 'TYPE_LABEL':
|
||||
self.memory[pc] = 'self.call(%d)' % (int(self.labels[targ]))
|
||||
else:
|
||||
zassert(False, 'Cannot call anything but a label')
|
||||
elif opcode == 'ret':
|
||||
assert(len(tmp) == 1)
|
||||
self.memory[pc] = 'self.ret()'
|
||||
elif opcode == 'add':
|
||||
rtmp = tmp[1].split(',', 1)
|
||||
zassert(len(tmp) == 2 and len(rtmp) == 2, 'add: needs two args, separated by commas [%s]' % cline)
|
||||
arg1 = rtmp[0].strip()
|
||||
arg2 = rtmp[1].strip()
|
||||
(src, stype) = self.getarg(arg1)
|
||||
(dst, dtype) = self.getarg(arg2)
|
||||
if stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_REGISTER':
|
||||
self.memory[pc] = 'self.add_i_to_r(%d, %d)' % (int(src), dst)
|
||||
elif stype == 'TYPE_REGISTER' and dtype == 'TYPE_REGISTER':
|
||||
self.memory[pc] = 'self.add_r_to_r(%d, %d)' % (int(src), dst)
|
||||
else:
|
||||
zassert(False, 'malformed usage of add instruction')
|
||||
elif opcode == 'sub':
|
||||
rtmp = tmp[1].split(',', 1)
|
||||
zassert(len(tmp) == 2 and len(rtmp) == 2, 'sub: needs two args, separated by commas [%s]' % cline)
|
||||
arg1 = rtmp[0].strip()
|
||||
arg2 = rtmp[1].strip()
|
||||
(src, stype) = self.getarg(arg1)
|
||||
(dst, dtype) = self.getarg(arg2)
|
||||
if stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_REGISTER':
|
||||
self.memory[pc] = 'self.sub_i_to_r(%d, %d)' % (int(src), dst)
|
||||
elif stype == 'TYPE_REGISTER' and dtype == 'TYPE_REGISTER':
|
||||
self.memory[pc] = 'self.sub_r_to_r(%d, %d)' % (int(src), dst)
|
||||
else:
|
||||
zassert(False, 'malformed usage of sub instruction')
|
||||
elif opcode == 'fetchadd':
|
||||
rtmp = tmp[1].split(',', 1)
|
||||
zassert(len(tmp) == 2 and len(rtmp) == 2, 'fetchadd: needs two args, separated by commas [%s]' % cline)
|
||||
arg1 = rtmp[0].strip()
|
||||
arg2 = rtmp[1].strip()
|
||||
(src, stype) = self.getarg(arg1)
|
||||
(dst, dtype) = self.getarg(arg2)
|
||||
tmp = dst.split(',')
|
||||
assert(len(tmp) == 3)
|
||||
if stype == 'TYPE_REGISTER' and dtype == 'TYPE_MEMORY':
|
||||
self.memory[pc] = 'self.fetchadd(%d, %d, %d, %d)' % (src, int(tmp[0]), int(tmp[1]), int(tmp[2]))
|
||||
else:
|
||||
zassert(False, 'poorly specified fetch and add')
|
||||
elif opcode == 'xchg':
|
||||
rtmp = tmp[1].split(',', 1)
|
||||
zassert(len(tmp) == 2 and len(rtmp) == 2, 'xchg: needs two args, separated by commas [%s]' % cline)
|
||||
arg1 = rtmp[0].strip()
|
||||
arg2 = rtmp[1].strip()
|
||||
(src, stype) = self.getarg(arg1)
|
||||
(dst, dtype) = self.getarg(arg2)
|
||||
tmp = dst.split(',')
|
||||
assert(len(tmp) == 3)
|
||||
if stype == 'TYPE_REGISTER' and dtype == 'TYPE_MEMORY':
|
||||
self.memory[pc] = 'self.atomic_exchange(%d, %d, %d, %d)' % (src, int(tmp[0]), int(tmp[1]), int(tmp[2]))
|
||||
else:
|
||||
zassert(False, 'poorly specified atomic exchange')
|
||||
elif opcode == 'test':
|
||||
rtmp = tmp[1].split(',', 1)
|
||||
zassert(len(tmp) == 2 and len(rtmp) == 2, 'test: needs two args, separated by commas [%s]' % cline)
|
||||
arg1 = rtmp[0].strip()
|
||||
arg2 = rtmp[1].strip()
|
||||
(src, stype) = self.getarg(arg1)
|
||||
(dst, dtype) = self.getarg(arg2)
|
||||
if stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_REGISTER':
|
||||
self.memory[pc] = 'self.test_i_r(%d, %d)' % (int(src), dst)
|
||||
elif stype == 'TYPE_REGISTER' and dtype == 'TYPE_REGISTER':
|
||||
self.memory[pc] = 'self.test_r_r(%d, %d)' % (int(src), dst)
|
||||
elif stype == 'TYPE_REGISTER' and dtype == 'TYPE_IMMEDIATE':
|
||||
self.memory[pc] = 'self.test_r_i(%d, %d)' % (int(src), dst)
|
||||
else:
|
||||
zassert(False, 'malformed usage of test instruction')
|
||||
elif opcode == 'j':
|
||||
(targ, ttype) = self.getarg(tmp[1].strip())
|
||||
zassert(ttype == 'TYPE_LABEL', 'bad jump target [%s]' % tmp[1].strip())
|
||||
self.memory[pc] = 'self.jump(%d)' % int(self.labels[targ])
|
||||
elif opcode == 'jne':
|
||||
(targ, ttype) = self.getarg(tmp[1].strip())
|
||||
zassert(ttype == 'TYPE_LABEL', 'bad jump target [%s]' % tmp[1].strip())
|
||||
self.memory[pc] = 'self.jump_notequal(%d)' % int(self.labels[targ])
|
||||
elif opcode == 'je':
|
||||
(targ, ttype) = self.getarg(tmp[1].strip())
|
||||
zassert(ttype == 'TYPE_LABEL', 'bad jump target [%s]' % tmp[1].strip())
|
||||
self.memory[pc] = 'self.jump_equal(%d)' % self.labels[targ]
|
||||
elif opcode == 'jlt':
|
||||
(targ, ttype) = self.getarg(tmp[1].strip())
|
||||
zassert(ttype == 'TYPE_LABEL', 'bad jump target [%s]' % tmp[1].strip())
|
||||
self.memory[pc] = 'self.jump_lessthan(%d)' % int(self.labels[targ])
|
||||
elif opcode == 'jlte':
|
||||
(targ, ttype) = self.getarg(tmp[1].strip())
|
||||
zassert(ttype == 'TYPE_LABEL', 'bad jump target [%s]' % tmp[1].strip())
|
||||
self.memory[pc] = 'self.jump_lessthanorequal(%s)' % self.labels[targ]
|
||||
elif opcode == 'jgt':
|
||||
(targ, ttype) = self.getarg(tmp[1].strip())
|
||||
zassert(ttype == 'TYPE_LABEL', 'bad jump target [%s]' % tmp[1].strip())
|
||||
self.memory[pc] = 'self.jump_greaterthan(%d)' % int(self.labels[targ])
|
||||
elif opcode == 'jgte':
|
||||
(targ, ttype) = self.getarg(tmp[1].strip())
|
||||
zassert(ttype == 'TYPE_LABEL', 'bad jump target [%s]' % tmp[1].strip())
|
||||
self.memory[pc] = 'self.jump_greaterthanorequal(%s)' % self.labels[targ]
|
||||
elif opcode == 'nop':
|
||||
self.memory[pc] = 'self.nop()'
|
||||
elif opcode == 'halt':
|
||||
self.memory[pc] = 'self.halt()'
|
||||
elif opcode == 'yield':
|
||||
self.memory[pc] = 'self.iyield()'
|
||||
elif opcode == 'rdump':
|
||||
self.memory[pc] = 'self.rdump()'
|
||||
elif opcode == 'mdump':
|
||||
self.memory[pc] = 'self.mdump(%s)' % tmp[1]
|
||||
else:
|
||||
print 'illegal opcode: ', opcode
|
||||
exit(1)
|
||||
|
||||
if self.verbose: print 'pc:%d LOADING %20s --> %s' % (pc, self.pmemory[pc], self.memory[pc])
|
||||
|
||||
# INCREMENT PC for loader
|
||||
pc += 1
|
||||
# END: loop over file
|
||||
fd.close()
|
||||
if self.verbose: print ''
|
||||
return
|
||||
# END: load
|
||||
|
||||
def print_headers(self, procs):
|
||||
# print some headers
|
||||
if len(self.memtrace) > 0:
|
||||
for m in self.memtrace:
|
||||
if m[0].isdigit():
|
||||
print '%5d' % int(m),
|
||||
else:
|
||||
zassert(m in self.vars, 'Traced variable %s not declared' % m)
|
||||
print '%5s' % m,
|
||||
print ' ',
|
||||
if len(self.regtrace) > 0:
|
||||
for r in self.regtrace:
|
||||
print '%5s' % self.get_regname(r),
|
||||
print ' ',
|
||||
if cctrace == True:
|
||||
print '>= > <= < != ==',
|
||||
|
||||
# and per thread
|
||||
for i in range(procs.getnum()):
|
||||
print ' Thread %d ' % i,
|
||||
print ''
|
||||
return
|
||||
|
||||
def print_trace(self, newline):
|
||||
if len(self.memtrace) > 0:
|
||||
for m in self.memtrace:
|
||||
if self.compute:
|
||||
if m[0].isdigit():
|
||||
print '%5d' % self.memory[int(m)],
|
||||
else:
|
||||
zassert(m in self.vars, 'Traced variable %s not declared' % m)
|
||||
print '%5d' % self.memory[self.vars[m]],
|
||||
else:
|
||||
print '%5s' % '?',
|
||||
print ' ',
|
||||
if len(self.regtrace) > 0:
|
||||
for r in self.regtrace:
|
||||
if self.compute:
|
||||
print '%5d' % self.registers[r],
|
||||
else:
|
||||
print '%5s' % '?',
|
||||
print ' ',
|
||||
if cctrace == True:
|
||||
for c in self.condlist:
|
||||
if self.compute:
|
||||
if self.conditions[c]:
|
||||
print '1 ',
|
||||
else:
|
||||
print '0 ',
|
||||
else:
|
||||
print '? ',
|
||||
if (len(self.memtrace) > 0 or len(self.regtrace) > 0 or cctrace == True) and newline == True:
|
||||
print ''
|
||||
return
|
||||
|
||||
def setint(self, intfreq, intrand):
|
||||
if intrand == False:
|
||||
return intfreq
|
||||
return int(random.random() * intfreq) + 1
|
||||
|
||||
def run(self, procs, intfreq, intrand):
|
||||
# hw init: cc's, interrupt frequency, etc.
|
||||
interrupt = self.setint(intfreq, intrand)
|
||||
icount = 0
|
||||
|
||||
self.print_headers(procs)
|
||||
self.print_trace(True)
|
||||
|
||||
while True:
|
||||
# need thread ID of current process
|
||||
tid = procs.getcurr().gettid()
|
||||
|
||||
# FETCH
|
||||
prevPC = self.PC
|
||||
instruction = self.memory[self.PC]
|
||||
self.PC += 1
|
||||
|
||||
# DECODE and EXECUTE
|
||||
# key: self.PC may be changed during eval; thus MUST be incremented BEFORE eval
|
||||
rc = eval(instruction)
|
||||
|
||||
# tracing details: ALWAYS AFTER EXECUTION OF INSTRUCTION
|
||||
self.print_trace(False)
|
||||
|
||||
# output: thread-proportional spacing followed by PC and instruction
|
||||
dospace(tid)
|
||||
print prevPC, self.pmemory[prevPC]
|
||||
icount += 1
|
||||
|
||||
# halt instruction issued
|
||||
if rc == -1:
|
||||
procs.done()
|
||||
if procs.numdone() == procs.getnum():
|
||||
return icount
|
||||
procs.next()
|
||||
procs.restore()
|
||||
|
||||
self.print_trace(False)
|
||||
for i in range(procs.getnum()):
|
||||
print '----- Halt;Switch ----- ',
|
||||
print ''
|
||||
|
||||
# do interrupt processing
|
||||
interrupt -= 1
|
||||
if interrupt == 0 or rc == -2:
|
||||
interrupt = self.setint(intfreq, intrand)
|
||||
procs.save()
|
||||
procs.next()
|
||||
procs.restore()
|
||||
|
||||
self.print_trace(False)
|
||||
for i in range(procs.getnum()):
|
||||
print '------ Interrupt ------ ',
|
||||
print ''
|
||||
# END: while
|
||||
return
|
||||
|
||||
#
|
||||
# END: class cpu
|
||||
#
|
||||
|
||||
|
||||
#
|
||||
# PROCESS LIST class
|
||||
#
|
||||
class proclist:
|
||||
def __init__(self):
|
||||
self.plist = []
|
||||
self.curr = 0
|
||||
self.active = 0
|
||||
|
||||
def done(self):
|
||||
self.plist[self.curr].setdone()
|
||||
self.active -= 1
|
||||
|
||||
def numdone(self):
|
||||
return len(self.plist) - self.active
|
||||
|
||||
def getnum(self):
|
||||
return len(self.plist)
|
||||
|
||||
def add(self, p):
|
||||
self.active += 1
|
||||
self.plist.append(p)
|
||||
|
||||
def getcurr(self):
|
||||
return self.plist[self.curr]
|
||||
|
||||
def save(self):
|
||||
self.plist[self.curr].save()
|
||||
|
||||
def restore(self):
|
||||
self.plist[self.curr].restore()
|
||||
|
||||
def next(self):
|
||||
for i in range(self.curr+1, len(self.plist)):
|
||||
if self.plist[i].isdone() == False:
|
||||
self.curr = i
|
||||
return
|
||||
for i in range(0, self.curr+1):
|
||||
if self.plist[i].isdone() == False:
|
||||
self.curr = i
|
||||
return
|
||||
|
||||
#
|
||||
# PROCESS class
|
||||
#
|
||||
class process:
|
||||
def __init__(self, cpu, tid, pc, stackbottom, reginit):
|
||||
self.cpu = cpu # object reference
|
||||
self.tid = tid
|
||||
self.pc = pc
|
||||
self.regs = {}
|
||||
self.cc = {}
|
||||
self.done = False
|
||||
self.stack = stackbottom
|
||||
|
||||
# init regs: all 0 or specially set to something
|
||||
for r in self.cpu.get_regnums():
|
||||
self.regs[r] = 0
|
||||
if reginit != '':
|
||||
# form: ax=1,bx=2 (for some subset of registers)
|
||||
for r in reginit.split(':'):
|
||||
tmp = r.split('=')
|
||||
assert(len(tmp) == 2)
|
||||
self.regs[self.cpu.get_regnum(tmp[0])] = int(tmp[1])
|
||||
|
||||
# init CCs
|
||||
for c in self.cpu.get_condlist():
|
||||
self.cc[c] = False
|
||||
|
||||
# stack
|
||||
self.regs[self.cpu.get_regnum('sp')] = stackbottom
|
||||
# print 'REG', self.cpu.get_regnum('sp'), self.regs[self.cpu.get_regnum('sp')]
|
||||
|
||||
return
|
||||
|
||||
def gettid(self):
|
||||
return self.tid
|
||||
|
||||
def save(self):
|
||||
self.pc = self.cpu.get_pc()
|
||||
for c in self.cpu.get_condlist():
|
||||
self.cc[c] = self.cpu.get_cond(c)
|
||||
for r in self.cpu.get_regnums():
|
||||
self.regs[r] = self.cpu.get_reg(r)
|
||||
|
||||
def restore(self):
|
||||
self.cpu.set_pc(self.pc)
|
||||
for c in self.cpu.get_condlist():
|
||||
self.cpu.set_cond(c, self.cc[c])
|
||||
for r in self.cpu.get_regnums():
|
||||
self.cpu.set_reg(r, self.regs[r])
|
||||
|
||||
def setdone(self):
|
||||
self.done = True
|
||||
|
||||
def isdone(self):
|
||||
return self.done == True
|
||||
|
||||
#
|
||||
# main program
|
||||
#
|
||||
parser = OptionParser()
|
||||
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
|
||||
parser.add_option('-t', '--threads', default=2, help='number of threads', action='store', type='int', dest='numthreads')
|
||||
parser.add_option('-p', '--program', default='', help='source program (in .s)', action='store', type='string', dest='progfile')
|
||||
parser.add_option('-i', '--interrupt', default=50, help='interrupt frequency', action='store', type='int', dest='intfreq')
|
||||
parser.add_option('-r', '--randints', default=False, help='if interrupts are random', action='store_true', dest='intrand')
|
||||
parser.add_option('-a', '--argv', default='',
|
||||
help='comma-separated per-thread args (e.g., ax=1,ax=2 sets thread 0 ax reg to 1 and thread 1 ax reg to 2); specify multiple regs per thread via colon-separated list (e.g., ax=1:bx=2,cx=3 sets thread 0 ax and bx and just cx for thread 1)',
|
||||
action='store', type='string', dest='argv')
|
||||
parser.add_option('-L', '--loadaddr', default=1000, help='address where to load code', action='store', type='int', dest='loadaddr')
|
||||
parser.add_option('-m', '--memsize', default=128, help='size of address space (KB)', action='store', type='int', dest='memsize')
|
||||
parser.add_option('-M', '--memtrace', default='', help='comma-separated list of addrs to trace (e.g., 20000,20001)', action='store',
|
||||
type='string', dest='memtrace')
|
||||
parser.add_option('-R', '--regtrace', default='', help='comma-separated list of regs to trace (e.g., ax,bx,cx,dx)', action='store',
|
||||
type='string', dest='regtrace')
|
||||
parser.add_option('-C', '--cctrace', default=False, help='should we trace condition codes', action='store_true', dest='cctrace')
|
||||
parser.add_option('-S', '--printstats',default=False, help='print some extra stats', action='store_true', dest='printstats')
|
||||
parser.add_option('-v', '--verbose', default=False, help='print some extra info', action='store_true', dest='verbose')
|
||||
parser.add_option('-c', '--compute', default=False, help='compute answers for me', action='store_true', dest='solve')
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
print 'ARG seed', options.seed
|
||||
print 'ARG numthreads', options.numthreads
|
||||
print 'ARG program', options.progfile
|
||||
print 'ARG interrupt frequency', options.intfreq
|
||||
print 'ARG interrupt randomness',options.intrand
|
||||
print 'ARG argv', options.argv
|
||||
print 'ARG load address', options.loadaddr
|
||||
print 'ARG memsize', options.memsize
|
||||
print 'ARG memtrace', options.memtrace
|
||||
print 'ARG regtrace', options.regtrace
|
||||
print 'ARG cctrace', options.cctrace
|
||||
print 'ARG printstats', options.printstats
|
||||
print 'ARG verbose', options.verbose
|
||||
print ''
|
||||
|
||||
seed = int(options.seed)
|
||||
numthreads = int(options.numthreads)
|
||||
intfreq = int(options.intfreq)
|
||||
zassert(intfreq > 0, 'Interrupt frequency must be greater than 0')
|
||||
intrand = int(options.intrand)
|
||||
progfile = options.progfile
|
||||
zassert(progfile != '', 'Program file must be specified')
|
||||
argv = options.argv.split(',')
|
||||
zassert(len(argv) == numthreads or len(argv) == 1, 'argv: must be one per-thread or just one set of values for all threads')
|
||||
|
||||
loadaddr = options.loadaddr
|
||||
memsize = options.memsize
|
||||
|
||||
memtrace = []
|
||||
if options.memtrace != '':
|
||||
for m in options.memtrace.split(','):
|
||||
memtrace.append(m)
|
||||
|
||||
regtrace = []
|
||||
if options.regtrace != '':
|
||||
for r in options.regtrace.split(','):
|
||||
regtrace.append(r)
|
||||
|
||||
cctrace = options.cctrace
|
||||
|
||||
printstats = options.printstats
|
||||
verbose = options.verbose
|
||||
|
||||
#
|
||||
# MAIN program
|
||||
#
|
||||
debug = False
|
||||
debug = False
|
||||
|
||||
cpu = cpu(memsize, memtrace, regtrace, cctrace, options.solve, verbose)
|
||||
|
||||
# load a program
|
||||
cpu.load(progfile, loadaddr)
|
||||
|
||||
# process list
|
||||
procs = proclist()
|
||||
pid = 0
|
||||
stack = memsize * 1000
|
||||
for t in range(numthreads):
|
||||
if len(argv) > 1:
|
||||
arg = argv[pid]
|
||||
else:
|
||||
arg = argv[0]
|
||||
procs.add(process(cpu, pid, loadaddr, stack, arg))
|
||||
stack -= 1000
|
||||
pid += 1
|
||||
|
||||
# get first one ready!
|
||||
procs.restore()
|
||||
|
||||
# run it
|
||||
t1 = time.clock()
|
||||
ic = cpu.run(procs, intfreq, intrand)
|
||||
t2 = time.clock()
|
||||
|
||||
if printstats:
|
||||
print ''
|
||||
print 'STATS:: Instructions %d' % ic
|
||||
print 'STATS:: Emulation Rate %.2f kinst/sec' % (float(ic) / float(t2 - t1) / 1000.0)
|
||||
|
||||
# use this for profiling
|
||||
# import cProfile
|
||||
# cProfile.run('run()')
|
||||
|
||||
|
||||
|
||||
|
27
related_info/ostep/ostep12-threadlock/flag.s
Normal file
27
related_info/ostep/ostep12-threadlock/flag.s
Normal file
@ -0,0 +1,27 @@
|
||||
.var flag
|
||||
.var count
|
||||
|
||||
.main
|
||||
.top
|
||||
|
||||
.acquire
|
||||
mov flag, %ax # get flag
|
||||
test $0, %ax # if we get 0 back: lock is free!
|
||||
jne .acquire # if not, try again
|
||||
mov $1, flag # store 1 into flag
|
||||
|
||||
# critical section
|
||||
mov count, %ax # get the value at the address
|
||||
add $1, %ax # increment it
|
||||
mov %ax, count # store it back
|
||||
|
||||
# release lock
|
||||
mov $0, flag # clear the flag now
|
||||
|
||||
# see if we're still looping
|
||||
sub $1, %bx
|
||||
test $0, %bx
|
||||
jgt .top
|
||||
|
||||
halt
|
||||
|
351
related_info/ostep/ostep12-threadlock/locks.md
Normal file
351
related_info/ostep/ostep12-threadlock/locks.md
Normal file
@ -0,0 +1,351 @@
|
||||
|
||||
Welcome to this simulator. The idea is to gain familiarity with threads by
|
||||
seeing how they interleave; the simulator, x86.py, will help you in
|
||||
gaining this understanding.
|
||||
|
||||
The simulator mimicks the execution of short assembly sequences by multiple
|
||||
threads. Note that the OS code that would run (for example, to perform a
|
||||
context switch) is *not* shown; thus, all you see is the interleaving of the
|
||||
user code.
|
||||
|
||||
The assembly code that is run is based on x86, but somewhat simplified.
|
||||
In this instruction set, there are four general-purpose registers
|
||||
(%ax, %bx, %cx, %dx), a program counter (PC), and a small set of instructions
|
||||
which will be enough for our purposes. We've also added a few extra GP
|
||||
registers (%ex, %fx) which don't quite match anything in x86 land
|
||||
(but that is OK).
|
||||
|
||||
Here is an example code snippet that we will be able to run:
|
||||
|
||||
.main
|
||||
mov 2000, %ax # get the value at the address
|
||||
add $1, %ax # increment it
|
||||
mov %ax, 2000 # store it back
|
||||
halt
|
||||
|
||||
The code is easy to understand. The first instruction, an x86 "mov", simply
|
||||
loads a value from the address specified by 2000 into the register %ax.
|
||||
Addresses, in this subset of x86, can take some of the following forms:
|
||||
|
||||
2000 -> the number (2000) is the address
|
||||
(%cx) -> contents of register (in parentheses) forms the address
|
||||
1000(%dx) -> the number + contents of the register form the address
|
||||
10(%ax,%bx) -> the number + reg1 + reg2 forms the address
|
||||
10(%ax,%bx,4) -> the number + reg1 + (reg2*scaling) forms the address
|
||||
|
||||
To store a value, the same "mov" instruction is used, but this time with the
|
||||
arguments reversed, e.g.:
|
||||
|
||||
mov %ax, 2000
|
||||
|
||||
The "add" instruction, from the sequence above, should be clear: it adds an
|
||||
immediate value (specified by $1) to the register specified in the second
|
||||
argument (i.e., %ax = %ax + 1).
|
||||
|
||||
Thus, we now can understand the code sequence above: it loads the value at
|
||||
address 2000, adds 1 to it, and then stores the value back into address 2000.
|
||||
|
||||
The fake-ish "halt" instruction just stops running this thread.
|
||||
|
||||
Let's run the simulator and see how this all works! Assume the above code
|
||||
sequence is in the file "simple-race.s".
|
||||
|
||||
prompt> ./x86.py -p simple-race.s -t 1
|
||||
|
||||
Thread 0
|
||||
1000 mov 2000, %ax
|
||||
1001 add $1, %ax
|
||||
1002 mov %ax, 2000
|
||||
1003 halt
|
||||
|
||||
prompt>
|
||||
|
||||
The arguments used here specify the program (-p), the number of threads (-t
|
||||
1), and the interrupt interval, which is how often a scheduler will be woken
|
||||
and run to switch to a different task. Because there is only one thread in
|
||||
this example, this interval does not matter.
|
||||
|
||||
The output is easy to read: the simulator prints the program counter (here
|
||||
shown from 1000 to 1003) and the instruction that gets executed. Note that we
|
||||
assume (unrealistically) that all instructions just take up a single byte in
|
||||
memory; in x86, instructions are variable-sized and would take up from one to
|
||||
a small number of bytes.
|
||||
|
||||
We can use more detailed tracing to get a better sense of how machine state
|
||||
changes during the execution:
|
||||
|
||||
prompt> ./x86.py -p simple-race.s -t 1 -M 2000 -R ax,bx
|
||||
|
||||
2000 ax bx Thread 0
|
||||
? ? ?
|
||||
? ? ? 1000 mov 2000, %ax
|
||||
? ? ? 1001 add $1, %ax
|
||||
? ? ? 1002 mov %ax, 2000
|
||||
? ? ? 1003 halt
|
||||
|
||||
Oops! Forgot the -c flag (which actually computes the answers for you).
|
||||
|
||||
prompt> ./x86.py -p simple-race.s -t 1 -M 2000 -R ax,bx -c
|
||||
|
||||
2000 ax bx Thread 0
|
||||
0 0 0
|
||||
0 0 0 1000 mov 2000, %ax
|
||||
0 1 0 1001 add $1, %ax
|
||||
1 1 0 1002 mov %ax, 2000
|
||||
1 1 0 1003 halt
|
||||
|
||||
By using the -M flag, we can trace memory locations (a comma-separated list
|
||||
lets you trace more than one, e.g., 2000,3000); by using the -R flag we can
|
||||
track the values inside specific registers.
|
||||
|
||||
The values on the left show the memory/register contents AFTER the instruction
|
||||
on the right has executed. For example, after the "add" instruction, you can
|
||||
see that %ax has been incremented to the value 1; after the second "mov"
|
||||
instruction (at PC=1002), you can see that the memory contents at 2000 are
|
||||
now also incremented.
|
||||
|
||||
There are a few more instructions you'll need to know, so let's get to them
|
||||
now. Here is a code snippet of a loop:
|
||||
|
||||
.main
|
||||
.top
|
||||
sub $1,%dx
|
||||
test $0,%dx
|
||||
jgte .top
|
||||
halt
|
||||
|
||||
A few things have been introduced here. First is the "test" instruction.
|
||||
This instruction takes two arguments and compares them; it then sets implicit
|
||||
"condition codes" (kind of like 1-bit registers) which subsequent instructions
|
||||
can act upon.
|
||||
|
||||
In this case, the other new instruction is the "jump" instruction (in this
|
||||
case, "jgte" which stands for "jump if greater than or equal to"). This
|
||||
instruction jumps if the first value is greater than or equal to the second
|
||||
in the test.
|
||||
|
||||
One last point: to really make this code work, dx must be initialized to 1 or
|
||||
greater.
|
||||
|
||||
Thus, we run the program like this:
|
||||
|
||||
prompt> ./x86.py -p loop.s -t 1 -a dx=3 -R dx -C -c
|
||||
|
||||
dx >= > <= < != == Thread 0
|
||||
3 0 0 0 0 0 0
|
||||
2 0 0 0 0 0 0 1000 sub $1,%dx
|
||||
2 1 1 0 0 1 0 1001 test $0,%dx
|
||||
2 1 1 0 0 1 0 1002 jgte .top
|
||||
1 1 1 0 0 1 0 1000 sub $1,%dx
|
||||
1 1 1 0 0 1 0 1001 test $0,%dx
|
||||
1 1 1 0 0 1 0 1002 jgte .top
|
||||
0 1 1 0 0 1 0 1000 sub $1,%dx
|
||||
0 1 0 1 0 0 1 1001 test $0,%dx
|
||||
0 1 0 1 0 0 1 1002 jgte .top
|
||||
0 1 0 1 0 0 1 1003 halt
|
||||
|
||||
The "-R dx" flag traces the value of %dx; the "-C" flag traces the values of
|
||||
the condition codes that get set by a test instruction. Finally, the "-a dx=3"
|
||||
flag sets the %dx register to the value 3 to start with.
|
||||
|
||||
As you can see from the trace, the "sub" instruction slowly lowers the value
|
||||
of %dx. The first few times "test" is called, only the ">=", ">", and "!="
|
||||
conditions get set. However, the last "test" in the trace finds %dx and 0 to
|
||||
be equal, and thus the subsequent jump does NOT take place, and the program
|
||||
finally halts.
|
||||
|
||||
Now, finally, we get to a more interesting case, i.e., a race condition with
|
||||
multiple threads. Let's look at the code first:
|
||||
|
||||
.main
|
||||
.top
|
||||
# critical section
|
||||
mov 2000, %ax # get the value at the address
|
||||
add $1, %ax # increment it
|
||||
mov %ax, 2000 # store it back
|
||||
|
||||
# see if we're still looping
|
||||
sub $1, %bx
|
||||
test $0, %bx
|
||||
jgt .top
|
||||
|
||||
halt
|
||||
|
||||
The code has a critical section which loads the value of a variable
|
||||
(at address 2000), then adds 1 to the value, then stores it back.
|
||||
|
||||
The code after just decrements a loop counter (in %bx), tests if it
|
||||
is greater than or equal to zero, and if so, jumps back to the top
|
||||
to the critical section again.
|
||||
|
||||
prompt> ./x86.py -p looping-race-nolock.s -t 2 -a bx=1 -M 2000 -c
|
||||
|
||||
2000 bx Thread 0 Thread 1
|
||||
0 1
|
||||
0 1 1000 mov 2000, %ax
|
||||
0 1 1001 add $1, %ax
|
||||
1 1 1002 mov %ax, 2000
|
||||
1 0 1003 sub $1, %bx
|
||||
1 0 1004 test $0, %bx
|
||||
1 0 1005 jgt .top
|
||||
1 0 1006 halt
|
||||
1 1 ----- Halt;Switch ----- ----- Halt;Switch -----
|
||||
1 1 1000 mov 2000, %ax
|
||||
1 1 1001 add $1, %ax
|
||||
2 1 1002 mov %ax, 2000
|
||||
2 0 1003 sub $1, %bx
|
||||
2 0 1004 test $0, %bx
|
||||
2 0 1005 jgt .top
|
||||
2 0 1006 halt
|
||||
|
||||
Here you can see each thread ran once, and each updated the shared
|
||||
variable at address 2000 once, thus resulting in a count of two there.
|
||||
|
||||
The "Halt;Switch" line is inserted whenever a thread halts and another
|
||||
thread must be run.
|
||||
|
||||
One last example: run the same thing above, but with a smaller interrupt
|
||||
frequency. Here is what that will look like:
|
||||
|
||||
[mac Race-Analyze] ./x86.py -p looping-race-nolock.s -t 2 -a bx=1 -M 2000 -i 2
|
||||
|
||||
2000 Thread 0 Thread 1
|
||||
?
|
||||
? 1000 mov 2000, %ax
|
||||
? 1001 add $1, %ax
|
||||
? ------ Interrupt ------ ------ Interrupt ------
|
||||
? 1000 mov 2000, %ax
|
||||
? 1001 add $1, %ax
|
||||
? ------ Interrupt ------ ------ Interrupt ------
|
||||
? 1002 mov %ax, 2000
|
||||
? 1003 sub $1, %bx
|
||||
? ------ Interrupt ------ ------ Interrupt ------
|
||||
? 1002 mov %ax, 2000
|
||||
? 1003 sub $1, %bx
|
||||
? ------ Interrupt ------ ------ Interrupt ------
|
||||
? 1004 test $0, %bx
|
||||
? 1005 jgt .top
|
||||
? ------ Interrupt ------ ------ Interrupt ------
|
||||
? 1004 test $0, %bx
|
||||
? 1005 jgt .top
|
||||
? ------ Interrupt ------ ------ Interrupt ------
|
||||
? 1006 halt
|
||||
? ----- Halt;Switch ----- ----- Halt;Switch -----
|
||||
? 1006 halt
|
||||
|
||||
As you can see, each thread is interrupt every 2 instructions, as we specify
|
||||
via the "-i 2" flag. What is the value of memory[2000] throughout this run?
|
||||
What should it have been?
|
||||
|
||||
Now let's give a little more information on what can be simulated
|
||||
with this program. The full set of registers: %ax, %bx, %cx, %dx, and the PC.
|
||||
In this version, there is no support for a "stack", nor are there call
|
||||
and return instructions.
|
||||
|
||||
The full set of instructions simulated are:
|
||||
|
||||
mov immediate, register # moves immediate value to register
|
||||
mov memory, register # loads from memory into register
|
||||
mov register, register # moves value from one register to other
|
||||
mov register, memory # stores register contents in memory
|
||||
mov immediate, memory # stores immediate value in memory
|
||||
|
||||
add immediate, register # register = register + immediate
|
||||
add register1, register2 # register2 = register2 + register1
|
||||
sub immediate, register # register = register - immediate
|
||||
sub register1, register2 # register2 = register2 - register1
|
||||
|
||||
neg register # negates contents of register
|
||||
|
||||
test immediate, register # compare immediate and register (set condition codes)
|
||||
test register, immediate # same but register and immediate
|
||||
test register, register # same but register and register
|
||||
|
||||
jne # jump if test'd values are not equal
|
||||
je # ... equal
|
||||
jlt # ... second is less than first
|
||||
jlte # ... less than or equal
|
||||
jgt # ... is greater than
|
||||
jgte # ... greater than or equal
|
||||
|
||||
push memory or register # push value in memory or from reg onto stack
|
||||
# stack is defined by sp register
|
||||
pop [register] # pop value off stack (into optional register)
|
||||
call label # call function at label
|
||||
|
||||
xchg register, memory # atomic exchange:
|
||||
# put value of register into memory
|
||||
# return old contents of memory into reg
|
||||
# do both things atomically
|
||||
|
||||
yield # switch to the next thread in the runqueue
|
||||
|
||||
nop # no op
|
||||
|
||||
|
||||
Notes:
|
||||
- 'immediate' is something of the form $number
|
||||
- 'memory' is of the form 'number' or '(reg)' or 'number(reg)' or
|
||||
'number(reg,reg)' or 'number(reg,reg,scale)' (as described above)
|
||||
- 'register' is one of %ax, %bx, %cx, %dx
|
||||
|
||||
Finally, here are the full set of options to the simulator are available with
|
||||
the -h flag:
|
||||
|
||||
Usage: x86.py [options]
|
||||
|
||||
Options:
|
||||
-s SEED, --seed=SEED the random seed
|
||||
-t NUMTHREADS, --threads=NUMTHREADS
|
||||
number of threads
|
||||
-p PROGFILE, --program=PROGFILE
|
||||
source program (in .s)
|
||||
-i INTFREQ, --interrupt=INTFREQ
|
||||
interrupt frequency
|
||||
-P PROCSCHED, --procsched=PROCSCHED
|
||||
control exactly which thread runs when
|
||||
-r, --randints if interrupts are random
|
||||
-a ARGV, --argv=ARGV comma-separated per-thread args (e.g., ax=1,ax=2 sets
|
||||
thread 0 ax reg to 1 and thread 1 ax reg to 2);
|
||||
specify multiple regs per thread via colon-separated
|
||||
list (e.g., ax=1:bx=2,cx=3 sets thread 0 ax and bx and
|
||||
just cx for thread 1)
|
||||
-L LOADADDR, --loadaddr=LOADADDR
|
||||
address where to load code
|
||||
-m MEMSIZE, --memsize=MEMSIZE
|
||||
size of address space (KB)
|
||||
-M MEMTRACE, --memtrace=MEMTRACE
|
||||
comma-separated list of addrs to trace (e.g.,
|
||||
20000,20001)
|
||||
-R REGTRACE, --regtrace=REGTRACE
|
||||
comma-separated list of regs to trace (e.g.,
|
||||
ax,bx,cx,dx)
|
||||
-C, --cctrace should we trace condition codes
|
||||
-S, --printstats print some extra stats
|
||||
-v, --verbose print some extra info
|
||||
-H HEADERCOUNT, --headercount=HEADERCOUNT
|
||||
how often to print a row header
|
||||
-c, --compute compute answers for me
|
||||
|
||||
|
||||
Most are obvious. Usage of -r turns on a random interrupter (from 1 to intfreq
|
||||
as specified by -i), which can make for more fun during homework problems.
|
||||
|
||||
-P lets you specify exactly which threads run when;
|
||||
e.g., 11000 would run thread 1 for 2 instructions, then thread 0 for 3,
|
||||
then repeat
|
||||
|
||||
-L specifies where in the address space to load the code.
|
||||
|
||||
-m specified the size of the address space (in KB).
|
||||
|
||||
-S prints some extra stats
|
||||
|
||||
-c lets you see the values of the traced registers or memory values
|
||||
(otherwise they show up as question marks)
|
||||
|
||||
-H lets you specify how often to print a row header (useful for long traces)
|
||||
|
||||
Now you have the basics in place; read the questions at the end of the chapter
|
||||
to study this race condition and related issues in more depth.
|
||||
|
53
related_info/ostep/ostep12-threadlock/peterson.s
Normal file
53
related_info/ostep/ostep12-threadlock/peterson.s
Normal file
@ -0,0 +1,53 @@
|
||||
# array of 2 integers (each size 4 bytes)
|
||||
# load address of flag into fx register
|
||||
# access flag[] with 0(%fx,%index,4)
|
||||
# where %index is a register holding 0 or 1
|
||||
# index reg contains 0 -> flag[0], if 1->flag[1]
|
||||
.var flag 2
|
||||
|
||||
# global turn variable
|
||||
.var turn
|
||||
|
||||
# global count
|
||||
.var count
|
||||
|
||||
.main
|
||||
|
||||
# put address of flag into fx
|
||||
lea flag, %fx
|
||||
|
||||
# assume thread ID is in bx (0 or 1, scale by 4 to get proper flag address)
|
||||
mov %bx, %cx # bx: self, now copies to cx
|
||||
neg %cx # cx: - self
|
||||
add $1, %cx # cx: 1 - self
|
||||
|
||||
.acquire
|
||||
mov $1, 0(%fx,%bx,4) # flag[self] = 1
|
||||
mov %cx, turn # turn = 1 - self
|
||||
|
||||
.spin1
|
||||
mov 0(%fx,%cx,4), %ax # flag[1-self]
|
||||
test $1, %ax
|
||||
jne .fini # if flag[1-self] != 1, skip past loop to .fini
|
||||
|
||||
.spin2 # just labeled for fun, not needed
|
||||
mov turn, %ax
|
||||
test %cx, %ax # compare 'turn' and '1 - self'
|
||||
je .spin1 # if turn==1-self, go back and start spin again
|
||||
|
||||
# fall out of spin
|
||||
.fini
|
||||
|
||||
# do critical section now
|
||||
mov count, %ax
|
||||
add $1, %ax
|
||||
mov %ax, count
|
||||
|
||||
.release
|
||||
mov $0, 0(%fx,%bx,4) # flag[self] = 0
|
||||
|
||||
|
||||
# end case: make sure it's other's turn
|
||||
mov %cx, turn # turn = 1 - self
|
||||
halt
|
||||
|
26
related_info/ostep/ostep12-threadlock/test-and-set.s
Normal file
26
related_info/ostep/ostep12-threadlock/test-and-set.s
Normal file
@ -0,0 +1,26 @@
|
||||
.var mutex
|
||||
.var count
|
||||
|
||||
.main
|
||||
.top
|
||||
|
||||
.acquire
|
||||
mov $1, %ax
|
||||
xchg %ax, mutex # atomic swap of 1 and mutex
|
||||
test $0, %ax # if we get 0 back: lock is free!
|
||||
jne .acquire # if not, try again
|
||||
|
||||
# critical section
|
||||
mov count, %ax # get the value at the address
|
||||
add $1, %ax # increment it
|
||||
mov %ax, count # store it back
|
||||
|
||||
# release lock
|
||||
mov $0, mutex
|
||||
|
||||
# see if we're still looping
|
||||
sub $1, %bx
|
||||
test $0, %bx
|
||||
jgt .top
|
||||
|
||||
halt
|
@ -0,0 +1,29 @@
|
||||
.var mutex
|
||||
.var count
|
||||
|
||||
.main
|
||||
.top
|
||||
|
||||
.acquire
|
||||
mov mutex, %ax
|
||||
test $0, %ax
|
||||
jne .acquire
|
||||
mov $1, %ax
|
||||
xchg %ax, mutex # atomic swap of 1 and mutex
|
||||
test $0, %ax # if we get 0 back: lock is free!
|
||||
jne .acquire # if not, try again
|
||||
|
||||
# critical section
|
||||
mov count, %ax # get the value at the address
|
||||
add $1, %ax # increment it
|
||||
mov %ax, count # store it back
|
||||
|
||||
# release lock
|
||||
mov $0, mutex
|
||||
|
||||
# see if we're still looping
|
||||
sub $1, %bx
|
||||
test $0, %bx
|
||||
jgt .top
|
||||
|
||||
halt
|
30
related_info/ostep/ostep12-threadlock/ticket.s
Normal file
30
related_info/ostep/ostep12-threadlock/ticket.s
Normal file
@ -0,0 +1,30 @@
|
||||
.var ticket
|
||||
.var turn
|
||||
.var count
|
||||
|
||||
.main
|
||||
.top
|
||||
|
||||
.acquire
|
||||
mov $1, %ax
|
||||
fetchadd %ax, ticket # grab a ticket (keep it in dx)
|
||||
.tryagain
|
||||
mov turn, %cx # check if it's your turn
|
||||
test %cx, %ax
|
||||
jne .tryagain
|
||||
|
||||
# critical section
|
||||
mov count, %ax # get the value at the address
|
||||
add $1, %ax # increment it
|
||||
mov %ax, count # store it back
|
||||
|
||||
# release lock
|
||||
mov $1, %ax
|
||||
fetchadd %ax, turn
|
||||
|
||||
# see if we're still looping
|
||||
sub $1, %bx
|
||||
test $0, %bx
|
||||
jgt .top
|
||||
|
||||
halt
|
1186
related_info/ostep/ostep12-threadlock/x86.py
Executable file
1186
related_info/ostep/ostep12-threadlock/x86.py
Executable file
File diff suppressed because it is too large
Load Diff
29
related_info/ostep/ostep12-threadlock/yield.s
Normal file
29
related_info/ostep/ostep12-threadlock/yield.s
Normal file
@ -0,0 +1,29 @@
|
||||
.var mutex
|
||||
.var count
|
||||
|
||||
.main
|
||||
.top
|
||||
|
||||
.acquire
|
||||
mov $1, %ax
|
||||
xchg %ax, mutex # atomic swap of 1 and mutex
|
||||
test $0, %ax # if we get 0 back: lock is free!
|
||||
je .acquire_done
|
||||
yield # if not, yield and try again
|
||||
j .acquire
|
||||
.acquire_done
|
||||
|
||||
# critical section
|
||||
mov count, %ax # get the value at the address
|
||||
add $1, %ax # increment it
|
||||
mov %ax, count # store it back
|
||||
|
||||
# release lock
|
||||
mov $0, mutex
|
||||
|
||||
# see if we're still looping
|
||||
sub $1, %bx
|
||||
test $0, %bx
|
||||
jgt .top
|
||||
|
||||
halt
|
256
related_info/ostep/ostep13-vsfs.md
Normal file
256
related_info/ostep/ostep13-vsfs.md
Normal file
@ -0,0 +1,256 @@
|
||||
|
||||
Use this tool, vsfs.py, to study how file system state changes as various
|
||||
operations take place. The file system begins in an empty state, with just a
|
||||
root directory. As the simulation takes place, various operations are
|
||||
performed, thus slowly changing the on-disk state of the file system.
|
||||
|
||||
The possible operations are:
|
||||
|
||||
- mkdir() - creates a new directory
|
||||
- creat() - creates a new (empty) file
|
||||
- open(), write(), close() - appends a block to a file
|
||||
- link() - creates a hard link to a file
|
||||
- unlink() - unlinks a file (removing it if linkcnt==0)
|
||||
|
||||
To understand how this homework functions, you must first understand how the
|
||||
on-disk state of this file system is represented. The state of the file
|
||||
system is shown by printing the contents of four different data structures:
|
||||
|
||||
inode bitmap - indicates which inodes are allocated
|
||||
inodes - table of inodes and their contents
|
||||
data bitmap - indicates which data blocks are allocated
|
||||
data - indicates contents of data blocks
|
||||
|
||||
The bitmaps should be fairly straightforward to understand, with a 1
|
||||
indicating that the corresponding inode or data block is allocated, and a 0
|
||||
indicating said inode or data block is free.
|
||||
|
||||
The inodes each have three fields: the first field indicates the type of file
|
||||
(e.g., f for a regular file, d for a directory); the second indicates which
|
||||
data block belongs to a file (here, files can only be empty, which would have
|
||||
the address of the data block set to -1, or one block in size, which would
|
||||
have a non-negative address); the third shows the reference count for the file
|
||||
or directory. For example, the following inode is a regular file, which is
|
||||
empty (address field set to -1), and has just one link in the file system:
|
||||
|
||||
[f a:-1 r:1]
|
||||
|
||||
If the same file had a block allocated to it (say block 10), it would be shown
|
||||
as follows:
|
||||
|
||||
[f a:10 r:1]
|
||||
|
||||
If someone then created a hard link to this inode, it would then become:
|
||||
|
||||
[f a:10 r:2]
|
||||
|
||||
Finally, data blocks can either retain user data or directory data. If filled
|
||||
with directory data, each entry within the block is of the form (name,
|
||||
inumber), where "name" is the name of the file or directory, and "inumber" is
|
||||
the inode number of the file. Thus, an empty root directory looks like this,
|
||||
assuming the root inode is 0:
|
||||
|
||||
[(.,0) (..,0)]
|
||||
|
||||
If we add a single file "f" to the root directory, which has been allocated
|
||||
inode number 1, the root directory contents would then become:
|
||||
|
||||
[(.,0) (..,0) (f,1)]
|
||||
|
||||
If a data block contains user data, it is shown as just a single character
|
||||
within the block, e.g., "h". If it is empty and unallocated, just a pair of
|
||||
empty brackets ([]) are shown.
|
||||
|
||||
An entire file system is thus depicted as follows:
|
||||
|
||||
inode bitmap 11110000
|
||||
inodes [d a:0 r:6] [f a:1 r:1] [f a:-1 r:1] [d a:2 r:2] [] ...
|
||||
data bitmap 11100000
|
||||
data [(.,0) (..,0) (y,1) (z,2) (f,3)] [u] [(.,3) (..,0)] [] ...
|
||||
|
||||
This file system has eight inodes and eight data blocks. The root directory
|
||||
contains three entries (other than "." and ".."), to "y", "z", and "f". By
|
||||
looking up inode 1, we can see that "y" is a regular file (type f), with a
|
||||
single data block allocated to it (address 1). In that data block 1 are the
|
||||
contents of the file "y": namely, "u". We can also see that "z" is an empty
|
||||
regular file (address field set to -1), and that "f" (inode number 3) is a
|
||||
directory, also empty. You can also see from the bitmaps that the first four
|
||||
inode bitmap entries are marked as allocated, as well as the first three data
|
||||
bitmap entries.
|
||||
|
||||
The simulator can be run with the following flags:
|
||||
|
||||
prompt> vsfs.py -h
|
||||
Usage: vsfs.py [options]
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-s SEED, --seed=SEED the random seed
|
||||
-i NUMINODES, --numInodes=NUMINODES
|
||||
number of inodes in file system
|
||||
-d NUMDATA, --numData=NUMDATA
|
||||
number of data blocks in file system
|
||||
-n NUMREQUESTS, --numRequests=NUMREQUESTS
|
||||
number of requests to simulate
|
||||
-r, --reverse instead of printing state, print ops
|
||||
-p, --printFinal print the final set of files/dirs
|
||||
-c, --compute compute answers for me
|
||||
]
|
||||
|
||||
A typical usage would simply specify a random seed (to generate a different
|
||||
problem), and the number of requests to simulate. In this default mode, the
|
||||
simulator prints out the state of the file system at each step, and asks you
|
||||
which operation must have taken place to take the file system from one state
|
||||
to another. For example:
|
||||
|
||||
prompt> vsfs.py -n 6 -s 16
|
||||
...
|
||||
Initial state
|
||||
|
||||
inode bitmap 10000000
|
||||
inodes [d a:0 r:2] [] [] [] [] [] [] []
|
||||
data bitmap 10000000
|
||||
data [(.,0) (..,0)] [] [] [] [] [] [] []
|
||||
|
||||
Which operation took place?
|
||||
|
||||
inode bitmap 11000000
|
||||
inodes [d a:0 r:3] [f a:-1 r:1] [] [] [] [] [] []
|
||||
data bitmap 10000000
|
||||
data [(.,0) (..,0) (y,1)] [] [] [] [] [] [] []
|
||||
|
||||
Which operation took place?
|
||||
|
||||
inode bitmap 11000000
|
||||
inodes [d a:0 r:3] [f a:1 r:1] [] [] [] [] [] []
|
||||
data bitmap 11000000
|
||||
data [(.,0) (..,0) (y,1)] [u] [] [] [] [] [] []
|
||||
|
||||
Which operation took place?
|
||||
|
||||
inode bitmap 11000000
|
||||
inodes [d a:0 r:4] [f a:1 r:2] [] [] [] [] [] []
|
||||
data bitmap 11000000
|
||||
data [(.,0) (..,0) (y,1) (m,1)] [u] [] [] [] [] [] []
|
||||
|
||||
Which operation took place?
|
||||
|
||||
inode bitmap 11000000
|
||||
inodes [d a:0 r:4] [f a:1 r:1] [] [] [] [] [] []
|
||||
data bitmap 11000000
|
||||
data [(.,0) (..,0) (y,1)] [u] [] [] [] [] [] []
|
||||
|
||||
Which operation took place?
|
||||
|
||||
inode bitmap 11100000
|
||||
inodes [d a:0 r:5] [f a:1 r:1] [f a:-1 r:1] [] [] [] [] []
|
||||
data bitmap 11000000
|
||||
data [(.,0) (..,0) (y,1) (z,2)] [u] [] [] [] [] [] []
|
||||
|
||||
Which operation took place?
|
||||
|
||||
inode bitmap 11110000
|
||||
inodes [d a:0 r:6] [f a:1 r:1] [f a:-1 r:1] [d a:2 r:2] [] ...
|
||||
data bitmap 11100000
|
||||
data [(.,0) (..,0) (y,1) (z,2) (f,3)] [u] [(.,3) (..,0)] [] ...
|
||||
]
|
||||
|
||||
When run in this mode, the simulator just shows a series of states, and asks
|
||||
what operations caused these transitions to occur. Running with the "-c" flag
|
||||
shows us the answers. Specifically, file "/y" was created, a single block
|
||||
appended to it, a hard link from "/m" to "/y" created, "/m" removed via a call
|
||||
to unlink, the file "/z" created, and the directory "/f" created:
|
||||
|
||||
prompt> vsfs.py -n 6 -s 16 -c
|
||||
...
|
||||
Initial state
|
||||
|
||||
inode bitmap 10000000
|
||||
inodes [d a:0 r:2] [] [] [] [] [] [] []
|
||||
data bitmap 10000000
|
||||
data [(.,0) (..,0)] [] [] [] [] [] [] []
|
||||
|
||||
creat("/y");
|
||||
|
||||
inode bitmap 11000000
|
||||
inodes [d a:0 r:3] [f a:-1 r:1] [] [] [] [] [] []
|
||||
data bitmap 10000000
|
||||
data [(.,0) (..,0) (y,1)] [] [] [] [] [] [] []
|
||||
|
||||
fd=open("/y", O_WRONLY|O_APPEND); write(fd, buf, BLOCKSIZE); close(fd);
|
||||
|
||||
inode bitmap 11000000
|
||||
inodes [d a:0 r:3] [f a:1 r:1] [] [] [] [] [] []
|
||||
data bitmap 11000000
|
||||
data [(.,0) (..,0) (y,1)] [u] [] [] [] [] [] []
|
||||
|
||||
link("/y", "/m");
|
||||
|
||||
inode bitmap 11000000
|
||||
inodes [d a:0 r:4] [f a:1 r:2] [] [] [] [] [] []
|
||||
data bitmap 11000000
|
||||
data [(.,0) (..,0) (y,1) (m,1)] [u] [] [] [] [] [] []
|
||||
|
||||
unlink("/m")
|
||||
|
||||
inode bitmap 11000000
|
||||
inodes [d a:0 r:4] [f a:1 r:1] [] [] [] [] [] []
|
||||
data bitmap 11000000
|
||||
data [(.,0) (..,0) (y,1)] [u] [] [] [] [] [] []
|
||||
|
||||
creat("/z");
|
||||
|
||||
inode bitmap 11100000
|
||||
inodes [d a:0 r:5] [f a:1 r:1] [f a:-1 r:1] [] [] [] [] []
|
||||
data bitmap 11000000
|
||||
data [(.,0) (..,0) (y,1) (z,2)] [u] [] [] [] [] [] []
|
||||
|
||||
mkdir("/f");
|
||||
|
||||
inode bitmap 11110000
|
||||
inodes [d a:0 r:6] [f a:1 r:1] [f a:-1 r:1] [d a:2 r:2] [] ...
|
||||
data bitmap 11100000
|
||||
data [(.,0) (..,0) (y,1) (z,2) (f,3)] [u] [(.,3) (..,0)] [] ...
|
||||
]
|
||||
|
||||
You can also run the simulator in "reverse" mode (with the "-r" flag),
|
||||
printing the operations instead of the states to see if you can predict the
|
||||
state changes from the given operations:
|
||||
|
||||
prompt> ./vsfs.py -n 6 -s 16 -r
|
||||
Initial state
|
||||
|
||||
inode bitmap 10000000
|
||||
inodes [d a:0 r:2] [] [] [] [] [] [] []
|
||||
data bitmap 10000000
|
||||
data [(.,0) (..,0)] [] [] [] [] [] [] []
|
||||
|
||||
creat("/y");
|
||||
|
||||
State of file system (inode bitmap, inodes, data bitmap, data)?
|
||||
|
||||
fd=open("/y", O_WRONLY|O_APPEND); write(fd, buf, BLOCKSIZE); close(fd);
|
||||
|
||||
State of file system (inode bitmap, inodes, data bitmap, data)?
|
||||
|
||||
link("/y", "/m");
|
||||
|
||||
State of file system (inode bitmap, inodes, data bitmap, data)?
|
||||
|
||||
unlink("/m")
|
||||
|
||||
State of file system (inode bitmap, inodes, data bitmap, data)?
|
||||
|
||||
creat("/z");
|
||||
|
||||
State of file system (inode bitmap, inodes, data bitmap, data)?
|
||||
|
||||
mkdir("/f");
|
||||
|
||||
State of file system (inode bitmap, inodes, data bitmap, data)?
|
||||
]
|
||||
|
||||
A few other flags control various aspects of the simulation, including the
|
||||
number of inodes ("-i"), the number of data blocks ("-d"), and whether to
|
||||
print the final list of all directories and files in the file system ("-p").
|
||||
|
551
related_info/ostep/ostep13-vsfs.py
Executable file
551
related_info/ostep/ostep13-vsfs.py
Executable file
@ -0,0 +1,551 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import random
|
||||
from optparse import OptionParser
|
||||
|
||||
DEBUG = False
|
||||
|
||||
def dprint(str):
|
||||
if DEBUG:
|
||||
print str
|
||||
|
||||
printOps = True
|
||||
printState = True
|
||||
printFinal = True
|
||||
|
||||
class bitmap:
|
||||
def __init__(self, size):
|
||||
self.size = size
|
||||
self.bmap = []
|
||||
for num in range(size):
|
||||
self.bmap.append(0)
|
||||
|
||||
def alloc(self):
|
||||
for num in range(len(self.bmap)):
|
||||
if self.bmap[num] == 0:
|
||||
self.bmap[num] = 1
|
||||
return num
|
||||
return -1
|
||||
|
||||
def free(self, num):
|
||||
assert(self.bmap[num] == 1)
|
||||
self.bmap[num] = 0
|
||||
|
||||
def markAllocated(self, num):
|
||||
assert(self.bmap[num] == 0)
|
||||
self.bmap[num] = 1
|
||||
|
||||
def dump(self):
|
||||
s = ''
|
||||
for i in range(len(self.bmap)):
|
||||
s += str(self.bmap[i])
|
||||
return s
|
||||
|
||||
class block:
|
||||
def __init__(self, ftype):
|
||||
assert(ftype == 'd' or ftype == 'f' or ftype == 'free')
|
||||
self.ftype = ftype
|
||||
# only for directories, properly a subclass but who cares
|
||||
self.dirUsed = 0
|
||||
self.maxUsed = 32
|
||||
self.dirList = []
|
||||
self.data = ''
|
||||
|
||||
def dump(self):
|
||||
if self.ftype == 'free':
|
||||
return '[]'
|
||||
elif self.ftype == 'd':
|
||||
rc = ''
|
||||
for d in self.dirList:
|
||||
# d is of the form ('name', inum)
|
||||
short = '(%s,%s)' % (d[0], d[1])
|
||||
if rc == '':
|
||||
rc = short
|
||||
else:
|
||||
rc += ' ' + short
|
||||
return '['+rc+']'
|
||||
# return '%s' % self.dirList
|
||||
else:
|
||||
return '[%s]' % self.data
|
||||
|
||||
def setType(self, ftype):
|
||||
assert(self.ftype == 'free')
|
||||
self.ftype = ftype
|
||||
|
||||
def addData(self, data):
|
||||
assert(self.ftype == 'f')
|
||||
self.data = data
|
||||
|
||||
def getNumEntries(self):
|
||||
assert(self.ftype == 'd')
|
||||
return self.dirUsed
|
||||
|
||||
def getFreeEntries(self):
|
||||
assert(self.ftype == 'd')
|
||||
return self.maxUsed - self.dirUsed
|
||||
|
||||
def getEntry(self, num):
|
||||
assert(self.ftype == 'd')
|
||||
assert(num < self.dirUsed)
|
||||
return self.dirList[num]
|
||||
|
||||
def addDirEntry(self, name, inum):
|
||||
assert(self.ftype == 'd')
|
||||
self.dirList.append((name, inum))
|
||||
self.dirUsed += 1
|
||||
assert(self.dirUsed <= self.maxUsed)
|
||||
|
||||
def delDirEntry(self, name):
|
||||
assert(self.ftype == 'd')
|
||||
tname = name.split('/')
|
||||
dname = tname[len(tname) - 1]
|
||||
for i in range(len(self.dirList)):
|
||||
if self.dirList[i][0] == dname:
|
||||
self.dirList.pop(i)
|
||||
self.dirUsed -= 1
|
||||
return
|
||||
assert(1 == 0)
|
||||
|
||||
def dirEntryExists(self, name):
|
||||
assert(self.ftype == 'd')
|
||||
for d in self.dirList:
|
||||
if name == d[0]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def free(self):
|
||||
assert(self.ftype != 'free')
|
||||
if self.ftype == 'd':
|
||||
# check for only dot, dotdot here
|
||||
assert(self.dirUsed == 2)
|
||||
self.dirUsed = 0
|
||||
self.data = ''
|
||||
self.ftype = 'free'
|
||||
|
||||
class inode:
|
||||
def __init__(self, ftype='free', addr=-1, refCnt=1):
|
||||
self.setAll(ftype, addr, refCnt)
|
||||
|
||||
def setAll(self, ftype, addr, refCnt):
|
||||
assert(ftype == 'd' or ftype == 'f' or ftype == 'free')
|
||||
self.ftype = ftype
|
||||
self.addr = addr
|
||||
self.refCnt = refCnt
|
||||
|
||||
def incRefCnt(self):
|
||||
self.refCnt += 1
|
||||
|
||||
def decRefCnt(self):
|
||||
self.refCnt -= 1
|
||||
|
||||
def getRefCnt(self):
|
||||
return self.refCnt
|
||||
|
||||
def setType(self, ftype):
|
||||
assert(ftype == 'd' or ftype == 'f' or ftype == 'free')
|
||||
self.ftype = ftype
|
||||
|
||||
def setAddr(self, block):
|
||||
self.addr = block
|
||||
|
||||
def getSize(self):
|
||||
if self.addr == -1:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
def getAddr(self):
|
||||
return self.addr
|
||||
|
||||
def getType(self):
|
||||
return self.ftype
|
||||
|
||||
def free(self):
|
||||
self.ftype = 'free'
|
||||
self.addr = -1
|
||||
|
||||
|
||||
class fs:
|
||||
def __init__(self, numInodes, numData):
|
||||
self.numInodes = numInodes
|
||||
self.numData = numData
|
||||
|
||||
self.ibitmap = bitmap(self.numInodes)
|
||||
self.inodes = []
|
||||
for i in range(self.numInodes):
|
||||
self.inodes.append(inode())
|
||||
|
||||
self.dbitmap = bitmap(self.numData)
|
||||
self.data = []
|
||||
for i in range(self.numData):
|
||||
self.data.append(block('free'))
|
||||
|
||||
# root inode
|
||||
self.ROOT = 0
|
||||
|
||||
# create root directory
|
||||
self.ibitmap.markAllocated(self.ROOT)
|
||||
self.inodes[self.ROOT].setAll('d', 0, 2)
|
||||
self.dbitmap.markAllocated(self.ROOT)
|
||||
self.data[0].setType('d')
|
||||
self.data[0].addDirEntry('.', self.ROOT)
|
||||
self.data[0].addDirEntry('..', self.ROOT)
|
||||
|
||||
# these is just for the fake workload generator
|
||||
self.files = []
|
||||
self.dirs = ['/']
|
||||
self.nameToInum = {'/':self.ROOT}
|
||||
|
||||
def dump(self):
|
||||
print 'inode bitmap ', self.ibitmap.dump()
|
||||
print 'inodes ',
|
||||
for i in range(0,self.numInodes):
|
||||
ftype = self.inodes[i].getType()
|
||||
if ftype == 'free':
|
||||
print '[]',
|
||||
else:
|
||||
print '[%s a:%s r:%d]' % (ftype, self.inodes[i].getAddr(), self.inodes[i].getRefCnt()),
|
||||
print ''
|
||||
print 'data bitmap ', self.dbitmap.dump()
|
||||
print 'data ',
|
||||
for i in range(self.numData):
|
||||
print self.data[i].dump(),
|
||||
print ''
|
||||
|
||||
def makeName(self):
|
||||
p = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
|
||||
return p[int(random.random() * len(p))]
|
||||
p = ['b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 's', 't', 'v', 'w', 'x', 'y', 'z']
|
||||
f = p[int(random.random() * len(p))]
|
||||
p = ['a', 'e', 'i', 'o', 'u']
|
||||
s = p[int(random.random() * len(p))]
|
||||
p = ['b', 'c', 'd', 'f', 'g', 'j', 'k', 'l', 'm', 'n', 'p', 's', 't', 'v', 'w', 'x', 'y', 'z']
|
||||
l = p[int(random.random() * len(p))]
|
||||
return '%c%c%c' % (f, s, l)
|
||||
|
||||
def inodeAlloc(self):
|
||||
return self.ibitmap.alloc()
|
||||
|
||||
def inodeFree(self, inum):
|
||||
self.ibitmap.free(inum)
|
||||
self.inodes[inum].free()
|
||||
|
||||
def dataAlloc(self):
|
||||
return self.dbitmap.alloc()
|
||||
|
||||
def dataFree(self, bnum):
|
||||
self.dbitmap.free(bnum)
|
||||
self.data[bnum].free()
|
||||
|
||||
def getParent(self, name):
|
||||
tmp = name.split('/')
|
||||
if len(tmp) == 2:
|
||||
return '/'
|
||||
pname = ''
|
||||
for i in range(1, len(tmp)-1):
|
||||
pname = pname + '/' + tmp[i]
|
||||
return pname
|
||||
|
||||
def deleteFile(self, tfile):
|
||||
if printOps:
|
||||
print 'unlink("%s");' % tfile
|
||||
|
||||
inum = self.nameToInum[tfile]
|
||||
|
||||
if self.inodes[inum].getRefCnt() == 1:
|
||||
# free data blocks first
|
||||
dblock = self.inodes[inum].getAddr()
|
||||
if dblock != -1:
|
||||
self.dataFree(dblock)
|
||||
# then free inode
|
||||
self.inodeFree(inum)
|
||||
else:
|
||||
self.inodes[inum].decRefCnt()
|
||||
|
||||
# remove from parent directory
|
||||
parent = self.getParent(tfile)
|
||||
# print '--> delete from parent', parent
|
||||
pinum = self.nameToInum[parent]
|
||||
# print '--> delete from parent inum', pinum
|
||||
pblock = self.inodes[pinum].getAddr()
|
||||
# FIXED BUG: DECREASE PARENT INODE REF COUNT! (thanks to Srinivasan Thirunarayanan)
|
||||
self.inodes[pinum].decRefCnt()
|
||||
# print '--> delete from parent addr', pblock
|
||||
self.data[pblock].delDirEntry(tfile)
|
||||
|
||||
# finally, remove from files list
|
||||
self.files.remove(tfile)
|
||||
return 0
|
||||
|
||||
def createLink(self, target, newfile, parent):
|
||||
# find info about parent
|
||||
parentInum = self.nameToInum[parent]
|
||||
|
||||
# is there room in the parent directory?
|
||||
pblock = self.inodes[parentInum].getAddr()
|
||||
if self.data[pblock].getFreeEntries() <= 0:
|
||||
dprint('*** createLink failed: no room in parent directory ***')
|
||||
return -1
|
||||
|
||||
# print 'is %s in directory %d' % (newfile, pblock)
|
||||
if self.data[pblock].dirEntryExists(newfile):
|
||||
dprint('*** createLink failed: not a unique name ***')
|
||||
return -1
|
||||
|
||||
# now, find inumber of target
|
||||
tinum = self.nameToInum[target]
|
||||
self.inodes[tinum].incRefCnt()
|
||||
|
||||
# inc parent ref count
|
||||
self.inodes[parentInum].incRefCnt()
|
||||
|
||||
# now add to directory
|
||||
tmp = newfile.split('/')
|
||||
ename = tmp[len(tmp)-1]
|
||||
self.data[pblock].addDirEntry(ename, tinum)
|
||||
return tinum
|
||||
|
||||
def createFile(self, parent, newfile, ftype):
|
||||
# find info about parent
|
||||
parentInum = self.nameToInum[parent]
|
||||
|
||||
# is there room in the parent directory?
|
||||
pblock = self.inodes[parentInum].getAddr()
|
||||
if self.data[pblock].getFreeEntries() <= 0:
|
||||
dprint('*** createFile failed: no room in parent directory ***')
|
||||
return -1
|
||||
|
||||
# have to make sure file name is unique
|
||||
block = self.inodes[parentInum].getAddr()
|
||||
# print 'is %s in directory %d' % (newfile, block)
|
||||
if self.data[block].dirEntryExists(newfile):
|
||||
dprint('*** createFile failed: not a unique name ***')
|
||||
return -1
|
||||
|
||||
# find free inode
|
||||
inum = self.inodeAlloc()
|
||||
if inum == -1:
|
||||
dprint('*** createFile failed: no inodes left ***')
|
||||
return -1
|
||||
|
||||
# if a directory, have to allocate directory block for basic (., ..) info
|
||||
fblock = -1
|
||||
if ftype == 'd':
|
||||
refCnt = 2
|
||||
fblock = self.dataAlloc()
|
||||
if fblock == -1:
|
||||
dprint('*** createFile failed: no data blocks left ***')
|
||||
self.inodeFree(inum)
|
||||
return -1
|
||||
else:
|
||||
self.data[fblock].setType('d')
|
||||
self.data[fblock].addDirEntry('.', inum)
|
||||
self.data[fblock].addDirEntry('..', parentInum)
|
||||
else:
|
||||
refCnt = 1
|
||||
|
||||
# now ok to init inode properly
|
||||
self.inodes[inum].setAll(ftype, fblock, refCnt)
|
||||
|
||||
# inc parent ref count
|
||||
self.inodes[parentInum].incRefCnt()
|
||||
|
||||
# and add to directory of parent
|
||||
self.data[pblock].addDirEntry(newfile, inum)
|
||||
return inum
|
||||
|
||||
def writeFile(self, tfile, data):
|
||||
inum = self.nameToInum[tfile]
|
||||
curSize = self.inodes[inum].getSize()
|
||||
dprint('writeFile: inum:%d cursize:%d refcnt:%d' % (inum, curSize, self.inodes[inum].getRefCnt()))
|
||||
if curSize == 1:
|
||||
dprint('*** writeFile failed: file is full ***')
|
||||
return -1
|
||||
fblock = self.dataAlloc()
|
||||
if fblock == -1:
|
||||
dprint('*** writeFile failed: no data blocks left ***')
|
||||
return -1
|
||||
else:
|
||||
self.data[fblock].setType('f')
|
||||
self.data[fblock].addData(data)
|
||||
self.inodes[inum].setAddr(fblock)
|
||||
if printOps:
|
||||
print 'fd=open("%s", O_WRONLY|O_APPEND); write(fd, buf, BLOCKSIZE); close(fd);' % tfile
|
||||
return 0
|
||||
|
||||
def doDelete(self):
|
||||
dprint('doDelete')
|
||||
if len(self.files) == 0:
|
||||
return -1
|
||||
dfile = self.files[int(random.random() * len(self.files))]
|
||||
dprint('try delete(%s)' % dfile)
|
||||
return self.deleteFile(dfile)
|
||||
|
||||
def doLink(self):
|
||||
dprint('doLink')
|
||||
if len(self.files) == 0:
|
||||
return -1
|
||||
parent = self.dirs[int(random.random() * len(self.dirs))]
|
||||
nfile = self.makeName()
|
||||
|
||||
# pick random target
|
||||
target = self.files[int(random.random() * len(self.files))]
|
||||
|
||||
# get full name of newfile
|
||||
if parent == '/':
|
||||
fullName = parent + nfile
|
||||
else:
|
||||
fullName = parent + '/' + nfile
|
||||
|
||||
dprint('try createLink(%s %s %s)' % (target, nfile, parent))
|
||||
inum = self.createLink(target, nfile, parent)
|
||||
if inum >= 0:
|
||||
self.files.append(fullName)
|
||||
self.nameToInum[fullName] = inum
|
||||
if printOps:
|
||||
print 'link("%s", "%s");' % (target, fullName)
|
||||
return 0
|
||||
return -1
|
||||
|
||||
def doCreate(self, ftype):
|
||||
dprint('doCreate')
|
||||
parent = self.dirs[int(random.random() * len(self.dirs))]
|
||||
nfile = self.makeName()
|
||||
if ftype == 'd':
|
||||
tlist = self.dirs
|
||||
else:
|
||||
tlist = self.files
|
||||
|
||||
if parent == '/':
|
||||
fullName = parent + nfile
|
||||
else:
|
||||
fullName = parent + '/' + nfile
|
||||
|
||||
dprint('try createFile(%s %s %s)' % (parent, nfile, ftype))
|
||||
inum = self.createFile(parent, nfile, ftype)
|
||||
if inum >= 0:
|
||||
tlist.append(fullName)
|
||||
self.nameToInum[fullName] = inum
|
||||
if parent == '/':
|
||||
parent = ''
|
||||
if ftype == 'd':
|
||||
if printOps:
|
||||
print 'mkdir("%s/%s");' % (parent, nfile)
|
||||
else:
|
||||
if printOps:
|
||||
print 'creat("%s/%s");' % (parent, nfile)
|
||||
return 0
|
||||
return -1
|
||||
|
||||
def doAppend(self):
|
||||
dprint('doAppend')
|
||||
if len(self.files) == 0:
|
||||
return -1
|
||||
afile = self.files[int(random.random() * len(self.files))]
|
||||
dprint('try writeFile(%s)' % afile)
|
||||
data = chr(ord('a') + int(random.random() * 26))
|
||||
rc = self.writeFile(afile, data)
|
||||
return rc
|
||||
|
||||
def run(self, numRequests):
|
||||
self.percentMkdir = 0.40
|
||||
self.percentWrite = 0.40
|
||||
self.percentDelete = 0.20
|
||||
self.numRequests = 20
|
||||
|
||||
print 'Initial state'
|
||||
print ''
|
||||
self.dump()
|
||||
print ''
|
||||
|
||||
for i in range(numRequests):
|
||||
if printOps == False:
|
||||
print 'Which operation took place?'
|
||||
rc = -1
|
||||
while rc == -1:
|
||||
r = random.random()
|
||||
if r < 0.3:
|
||||
rc = self.doAppend()
|
||||
dprint('doAppend rc:%d' % rc)
|
||||
elif r < 0.5:
|
||||
rc = self.doDelete()
|
||||
dprint('doDelete rc:%d' % rc)
|
||||
elif r < 0.7:
|
||||
rc = self.doLink()
|
||||
dprint('doLink rc:%d' % rc)
|
||||
else:
|
||||
if random.random() < 0.75:
|
||||
rc = self.doCreate('f')
|
||||
dprint('doCreate(f) rc:%d' % rc)
|
||||
else:
|
||||
rc = self.doCreate('d')
|
||||
dprint('doCreate(d) rc:%d' % rc)
|
||||
if printState == True:
|
||||
print ''
|
||||
self.dump()
|
||||
print ''
|
||||
else:
|
||||
print ''
|
||||
print ' State of file system (inode bitmap, inodes, data bitmap, data)?'
|
||||
print ''
|
||||
|
||||
if printFinal:
|
||||
print ''
|
||||
print 'Summary of files, directories::'
|
||||
print ''
|
||||
print ' Files: ', self.files
|
||||
print ' Directories:', self.dirs
|
||||
print ''
|
||||
|
||||
#
|
||||
# main program
|
||||
#
|
||||
parser = OptionParser()
|
||||
|
||||
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
|
||||
parser.add_option('-i', '--numInodes', default=8, help='number of inodes in file system', action='store', type='int', dest='numInodes')
|
||||
parser.add_option('-d', '--numData', default=8, help='number of data blocks in file system', action='store', type='int', dest='numData')
|
||||
parser.add_option('-n', '--numRequests', default=10, help='number of requests to simulate', action='store', type='int', dest='numRequests')
|
||||
parser.add_option('-r', '--reverse', default=False, help='instead of printing state, print ops', action='store_true', dest='reverse')
|
||||
parser.add_option('-p', '--printFinal', default=False, help='print the final set of files/dirs', action='store_true', dest='printFinal')
|
||||
parser.add_option('-c', '--compute', default=False, help='compute answers for me', action='store_true', dest='solve')
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
print 'ARG seed', options.seed
|
||||
print 'ARG numInodes', options.numInodes
|
||||
print 'ARG numData', options.numData
|
||||
print 'ARG numRequests', options.numRequests
|
||||
print 'ARG reverse', options.reverse
|
||||
print 'ARG printFinal', options.printFinal
|
||||
print ''
|
||||
|
||||
random.seed(options.seed)
|
||||
|
||||
if options.reverse:
|
||||
printState = False
|
||||
printOps = True
|
||||
else:
|
||||
printState = True
|
||||
printOps = False
|
||||
|
||||
if options.solve:
|
||||
printOps = True
|
||||
printState = True
|
||||
|
||||
printFinal = options.printFinal
|
||||
|
||||
#
|
||||
# have to generate RANDOM requests to the file system
|
||||
# that are VALID!
|
||||
#
|
||||
|
||||
f = fs(options.numInodes, options.numData)
|
||||
|
||||
#
|
||||
# ops: mkdir rmdir : create delete : append write
|
||||
#
|
||||
|
||||
f.run(options.numRequests)
|
||||
|
||||
|
252
related_info/ostep/ostep14-afs.md
Normal file
252
related_info/ostep/ostep14-afs.md
Normal file
@ -0,0 +1,252 @@
|
||||
|
||||
This program, afs.py, allows you to experiment with the cache consistency
|
||||
behavior of the Andrew File System (AFS). The program generates random client
|
||||
traces (of file opens, reads, writes, and closes), enabling the user to see if
|
||||
they can predict what values end up in various files.
|
||||
|
||||
Here is an example run:
|
||||
|
||||
prompt> ./afs.py -C 2 -n 1 -s 12
|
||||
|
||||
Server c0 c1
|
||||
file:a contains:0
|
||||
open:a [fd:0]
|
||||
write:0 value? -> 1
|
||||
close:0
|
||||
open:a [fd:0]
|
||||
read:0 -> value?
|
||||
close:0
|
||||
file:a contains:?
|
||||
prompt>
|
||||
|
||||
The trace is fairly simple to read. On the left is the server, and each column
|
||||
shows actions being taken on each of two clients (use -C <clients> to specify
|
||||
a different number). Each client generates one random action (-n 1), which is
|
||||
either the open/read/close of a file or the open/write/close of a file. The
|
||||
contents of a file, for simplicity, is always just a single number.
|
||||
|
||||
To generate different traces, use '-s' (for a random seed), as always. Here we
|
||||
set it to 12 to get this specific trace.
|
||||
|
||||
In the trace, the server shows the initial contents of all the files in the
|
||||
system:
|
||||
|
||||
file:a contains:0
|
||||
|
||||
As you can see in this trace, there is just one file (a) and it contains the
|
||||
value 0.
|
||||
|
||||
Time increases downwards, and what is next is client 0 (c0) opening the file
|
||||
'a' (which returns a file descriptor, 0 in this case), writing to that
|
||||
descriptor, and then closing the file.
|
||||
|
||||
Immediately you see the first question posed to you:
|
||||
|
||||
write:0 value? -> 1
|
||||
|
||||
When writing to descriptor 0, you are overwriting an existing value with the
|
||||
new value of 1. What was that old value? (pretty easy in this case: 0).
|
||||
|
||||
Then client 1 begins doing some work (c1). It opens the file, reads it, and
|
||||
closes it. Again, we have a question to answer:
|
||||
|
||||
read:0 -> value?
|
||||
|
||||
When reading from this file, what value should client 1 see? Again, given AFS
|
||||
consistency, the answer is straightforward: 1 (the value placed in the file
|
||||
when c0 closed the file and updated the server).
|
||||
|
||||
The final question in the trace is the final value of the file on the server:
|
||||
|
||||
file:a contains:?
|
||||
|
||||
Again, the answer here is easy: 1 (as generated by c0).
|
||||
|
||||
To see if you have answered these questions correctly, run with the '-c' flag
|
||||
(or '--compute'), as follows:
|
||||
|
||||
prompt> ./afs.py -C 2 -n 1 -s 12 -c
|
||||
|
||||
Server c0 c1
|
||||
file:a contains:0
|
||||
open:a [fd:0]
|
||||
write:0 0 -> 1
|
||||
close:0
|
||||
open:a [fd:0]
|
||||
read:0 -> 1
|
||||
close:0
|
||||
file:a contains:1
|
||||
prompt>
|
||||
|
||||
From this trace, you can see that all the question marks have been filled in
|
||||
with answers.
|
||||
|
||||
More detail is available on what has happened too, with the '-d' ('--detail')
|
||||
flag. Here is an example that shows when each client issued a get or put of a
|
||||
file to the server:
|
||||
|
||||
prompt> ./afs.py -C 2 -n 1 -s 12 -c -d 1
|
||||
|
||||
Server c0 c1
|
||||
file:a contains:0
|
||||
open:a [fd:0]
|
||||
getfile:a c:c0 [0]
|
||||
|
||||
write:0 0 -> 1
|
||||
|
||||
close:0
|
||||
putfile:a c:c0 [1]
|
||||
|
||||
open:a [fd:0]
|
||||
getfile:a c:c1 [1]
|
||||
|
||||
read:0 -> 1
|
||||
|
||||
close:0
|
||||
|
||||
file:a contains:1
|
||||
prompt>
|
||||
|
||||
You can show more with higher levels of detail, including cache invalidations,
|
||||
the exact client cache state after each step, and extra diagnostic
|
||||
information. We'll show these in one more example below.
|
||||
|
||||
Random client actions are useful to generate new problems and try to solve
|
||||
them; however, in some cases it is useful to control exactly what each client
|
||||
does in order to see specific AFS behaviors. To do this, you can use the '-A'
|
||||
and '-S' flags (either together or in tandem).
|
||||
|
||||
The '-S' flag lets you control the exact schedule of client actions. Assume our
|
||||
example above. Let's say we wish to run client 1 in entirety first; to achieve
|
||||
this end, we simply run the following:
|
||||
|
||||
prompt> ./afs.py -C 2 -n 1 -s 12 -S 111000
|
||||
|
||||
Server c0 c1
|
||||
file:a contains:0
|
||||
open:a [fd:0]
|
||||
read:0 -> value?
|
||||
close:0
|
||||
open:a [fd:0]
|
||||
write:0 value? -> 1
|
||||
close:0
|
||||
file:a contains:?
|
||||
prompt>
|
||||
|
||||
The -S flag here is passed "111000" which means "run client 1, then client 1,
|
||||
then 1 again, then 0, 0, 0, and then repeat (if need be)". The result in this
|
||||
case is client 1 reading file a before client 1 writes it.
|
||||
|
||||
The '-A' flag gives exact control over which actions the clients take. Here is
|
||||
an example:
|
||||
|
||||
prompt> ./afs.py -s 12 -S 011100 -A oa1:r1:c1,oa1:w1:c1
|
||||
|
||||
Server c0 c1
|
||||
file:a contains:0
|
||||
open:a [fd:1]
|
||||
open:a [fd:1]
|
||||
write:1 value? -> 1
|
||||
close:1
|
||||
read:1 -> value?
|
||||
close:1
|
||||
file:a contains:?
|
||||
prompt>
|
||||
|
||||
In this example, we have specified the following via -A "oa1:r1:c1,oa1:w1:c1".
|
||||
The list splits each clients actions by a comma; thus, client 0 should do
|
||||
whatever "oa1:r1:c1" indicates, whereas client 1 should do whatever the string
|
||||
"oa1:w1:c1" indicates. Parsing each command string is straightforward: "oa1"
|
||||
means open file 'a' and assign it file descriptor 1; "r1" or "w1" means read
|
||||
or write file descriptor 1; "c1" means close file descriptor 1.
|
||||
|
||||
So what value will the read on client 0 return?
|
||||
|
||||
We can also see the cache state, callbacks, and invalidations with a few extra
|
||||
flags (-d 7):
|
||||
|
||||
prompt> ./afs.py -s 12 -S 011100 -A oa1:r1:c1,oa1:w1:c1 -c -d 7
|
||||
|
||||
Server c0 c1
|
||||
file:a contains:0
|
||||
open:a [fd:1]
|
||||
getfile:a c:c0 [0]
|
||||
[a: 0 (v=1,d=0,r=1)]
|
||||
|
||||
open:a [fd:1]
|
||||
getfile:a c:c1 [0]
|
||||
[a: 0 (v=1,d=0,r=1)]
|
||||
|
||||
write:1 0 -> 1
|
||||
[a: 1 (v=1,d=1,r=1)]
|
||||
|
||||
close:1
|
||||
putfile:a c:c1 [1]
|
||||
callback: c:c0 file:a
|
||||
invalidate a
|
||||
[a: 0 (v=0,d=0,r=1)]
|
||||
[a: 1 (v=1,d=0,r=0)]
|
||||
|
||||
read:1 -> 0
|
||||
[a: 0 (v=0,d=0,r=1)]
|
||||
|
||||
close:1
|
||||
|
||||
file:a contains:1
|
||||
prompt>
|
||||
|
||||
From this trace, we can see what happens when client 1 closes the (modified)
|
||||
file. At that point, c1 puts the file to the server. The server knows that c0
|
||||
has the file cached, and thus sends an invalidation to c0. However, c0 already
|
||||
has the file open; as a result, the cache keeps the old contents until the
|
||||
file is closed.
|
||||
|
||||
You can see this in tracking the cache contents throughout the trace
|
||||
(available with the correct -d flag, in particular any value which sets the
|
||||
3rd least significant bit to 1, such as -d 4, -d 5, -d 6, -d 7, etc.). When
|
||||
client 0 opens the file, you see the following cache state after the open is
|
||||
finished:
|
||||
|
||||
[a: 0 (v=1,d=0,r=1)]
|
||||
|
||||
This means file 'a' is in the cache with value '0', and has three bits of
|
||||
state associated with it: v (valid), d (dirty), and r (reference count). The
|
||||
valid bit tracks whether the contents are valid; it is now, because the cache
|
||||
has not been invalidated by a callback (yet). The dirty bit changes when the
|
||||
file has been written to and must be flushed back to the server when
|
||||
closed. Finally, the reference count tracks how many times the file has been
|
||||
opened (but not yet closed); this is used to ensure the client gets the old
|
||||
value of the file until it's been closed by all readers and then re-opened.
|
||||
|
||||
The full list of options is available here:
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-s SEED, --seed=SEED the random seed
|
||||
-C NUMCLIENTS, --clients=NUMCLIENTS
|
||||
number of clients
|
||||
-n NUMSTEPS, --numsteps=NUMSTEPS
|
||||
ops each client will do
|
||||
-f NUMFILES, --numfiles=NUMFILES
|
||||
number of files in server
|
||||
-r READRATIO, --readratio=READRATIO
|
||||
ratio of reads/writes
|
||||
-A ACTIONS, --actions=ACTIONS
|
||||
client actions exactly specified, e.g.,
|
||||
oa1:r1:c1,oa1:w1:c1 specifies two clients; each opens
|
||||
the file a, client 0 reads it whereas client 1 writes
|
||||
it, and then each closes it
|
||||
-S SCHEDULE, --schedule=SCHEDULE
|
||||
exact schedule to run; 01 alternates round robin
|
||||
between clients 0 and 1. Left unspecified leads to
|
||||
random scheduling
|
||||
-p, --printstats print extra stats
|
||||
-c, --compute compute answers for me
|
||||
-d DETAIL, --detail=DETAIL
|
||||
detail level when giving answers (1:server
|
||||
actions,2:invalidations,4:client cache,8:extra
|
||||
labels); OR together for multiple
|
||||
|
||||
Read the AFS chapter, and answer the questions at the back, or just explore
|
||||
this simulator more on your own to increase your understanding of AFS.
|
||||
|
600
related_info/ostep/ostep14-afs.py
Normal file
600
related_info/ostep/ostep14-afs.py
Normal file
@ -0,0 +1,600 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import random
|
||||
from optparse import OptionParser
|
||||
import string
|
||||
|
||||
def tprint(str):
|
||||
print str
|
||||
|
||||
def dprint(str):
|
||||
return
|
||||
|
||||
def dospace(howmuch):
|
||||
for i in range(howmuch + 1):
|
||||
print '%28s' % ' ',
|
||||
|
||||
# given list, pick random element and return it
|
||||
def pickrand(tlist):
|
||||
n = int(random.random() * len(tlist))
|
||||
p = tlist[n]
|
||||
return p
|
||||
|
||||
# given number, conclude if nth bit is set
|
||||
def isset(num, index):
|
||||
mask = 1 << index
|
||||
return (num & mask) > 0
|
||||
|
||||
# useful instead of assert
|
||||
def zassert(cond, str):
|
||||
if cond == False:
|
||||
print 'ABORT::', str
|
||||
exit(1)
|
||||
|
||||
#
|
||||
# Which files are used in the simulation
|
||||
#
|
||||
# Not representing a realistic piece of anything
|
||||
# but rather just for convenience when generating
|
||||
# random traces ...
|
||||
#
|
||||
# Files are named 'a', 'b', etc. for ease of use
|
||||
# Could probably add a numeric aspect to allow
|
||||
# for more than 26 files but who cares
|
||||
#
|
||||
|
||||
class files:
|
||||
def __init__(self, numfiles):
|
||||
self.numfiles = numfiles
|
||||
self.value = 0
|
||||
self.filelist = list(string.ascii_lowercase)[0:numfiles]
|
||||
|
||||
def getfiles(self):
|
||||
return self.filelist
|
||||
|
||||
def getvalue(self):
|
||||
rc = self.value
|
||||
self.value += 1
|
||||
return rc
|
||||
|
||||
#
|
||||
# Models the actions of the AFS server
|
||||
#
|
||||
# The only real interactions are get/put
|
||||
# get() causes the server to track which files cache what;
|
||||
# put() may cause callbacks to invalidate client caches
|
||||
#
|
||||
class server:
|
||||
def __init__(self, files, solve, detail):
|
||||
self.files = files
|
||||
self.solve = solve
|
||||
self.detail = detail
|
||||
|
||||
flist = self.files.getfiles()
|
||||
self.contents = {}
|
||||
for f in flist:
|
||||
v = self.files.getvalue()
|
||||
self.contents[f] = v
|
||||
self.getcnt, self.putcnt = 0, 0
|
||||
|
||||
def stats(self):
|
||||
print 'Server -- Gets:%d Puts:%d' % (self.getcnt, self.putcnt)
|
||||
|
||||
def filestats(self, printcontents):
|
||||
for fname in self.contents:
|
||||
if printcontents:
|
||||
print('file:%s contains:%d' % (fname, self.contents[fname]))
|
||||
else:
|
||||
print('file:%s contains:?' % fname)
|
||||
|
||||
|
||||
def setclients(self, clients):
|
||||
# need list of clients
|
||||
self.clients = clients
|
||||
|
||||
# per client callback list
|
||||
self.cache = {}
|
||||
for c in self.clients:
|
||||
self.cache[c.getname()] = []
|
||||
|
||||
def get(self, client, fname):
|
||||
zassert(fname in self.contents, 'server:get() -- file:%s not found on server' % fname)
|
||||
self.getcnt += 1
|
||||
if self.solve and isset(self.detail, 0):
|
||||
print('getfile:%s c:%s [%d]' % (fname, client, self.contents[fname]))
|
||||
if fname not in self.cache[client]:
|
||||
self.cache[client].append(fname)
|
||||
# dprint(' -> List for client %s' % client, ' is ', self.cache[client])
|
||||
return self.contents[fname]
|
||||
|
||||
def put(self, client, fname, value):
|
||||
zassert(fname in self.contents, 'server:put() -- file:%s not found on server' % fname)
|
||||
self.putcnt += 1
|
||||
self.contents[fname] = value
|
||||
if self.solve and isset(self.detail, 0):
|
||||
print('putfile:%s c:%s [%s]' % (fname, client, self.contents[fname]))
|
||||
# scan others for callback
|
||||
for c in self.clients:
|
||||
cname = c.getname()
|
||||
if fname in self.cache[cname] and cname != client:
|
||||
if self.solve and isset(self.detail, 1):
|
||||
print 'callback: c:%s file:%s' % (cname, fname)
|
||||
c.invalidate(fname)
|
||||
self.cache[cname].remove(fname)
|
||||
|
||||
#
|
||||
# Per-client file descriptors
|
||||
#
|
||||
# Would be useful if the simulation allowed more
|
||||
# than one active file open() at a time; it kind
|
||||
# of does but this isn't really utilized
|
||||
#
|
||||
class filedesc:
|
||||
def __init__(self, max=1024):
|
||||
self.max = max
|
||||
self.fd = {}
|
||||
for i in range(self.max):
|
||||
self.fd[i] = ''
|
||||
|
||||
def alloc(self, fname, sfd=-1):
|
||||
if sfd != -1:
|
||||
zassert(self.fd[sfd] == '', 'filedesc:alloc() -- fd:%d already in use, cannot allocate' % sfd)
|
||||
self.fd[sfd] = fname
|
||||
return sfd
|
||||
else:
|
||||
for i in range(self.max):
|
||||
if self.fd[i] == '':
|
||||
self.fd[i] = fname
|
||||
return i
|
||||
return -1
|
||||
|
||||
def lookup(self, sfd):
|
||||
zassert(i >= 0 and i < self.max, 'filedesc:lookup() -- file descriptor out of valid range (%d not between 0 and %d)' % (sfd, self.max))
|
||||
zassert(self.fd[sfd] != '', 'filedesc:lookup() -- fd:%d not in use, cannot lookup' % sfd)
|
||||
return self.fd[sfd]
|
||||
|
||||
def free(self, i):
|
||||
zassert(i >= 0 and i < self.max, 'filedesc:free() -- file descriptor out of valid range (%d not between 0 and %d)' % (sfd, self.max))
|
||||
zassert(self.fd[sfd] != '', 'filedesc:free() -- fd:%d not in use, cannot free' % sfd)
|
||||
self.fd[i] = ''
|
||||
|
||||
#
|
||||
# The client cache
|
||||
#
|
||||
# Just models what files are cached.
|
||||
# When a file is opened, its contents are fetched
|
||||
# from the server and put in the cache. At that point,
|
||||
# the cache contents are VALID, DIRTY/NOT (depending
|
||||
# on whether this is for reading or writing), and the
|
||||
# REFERENCE COUNT is set to 1. If multiple open's take
|
||||
# place on this file, REFERENCE COUNT will be updated
|
||||
# accordingly. VALID gets set to 0 if the cache is
|
||||
# invalidated by a callback; however, the contents
|
||||
# still might be used by a given client if the file
|
||||
# is already open. Note that a callback does NOT
|
||||
# prevent a client from overwriting an already opened file.
|
||||
#
|
||||
class cache:
|
||||
def __init__(self, name, num, solve, detail):
|
||||
self.name = name
|
||||
self.num = num
|
||||
self.solve = solve
|
||||
self.detail = detail
|
||||
|
||||
self.cache = {}
|
||||
|
||||
self.hitcnt = 0
|
||||
self.misscnt = 0
|
||||
self.invalidcnt = 0
|
||||
|
||||
def stats(self):
|
||||
print ' Cache -- Hits:%d Misses:%d Invalidates:%d' % (self.hitcnt, self.misscnt, self.invalidcnt)
|
||||
|
||||
def put(self, fname, data, dirty, refcnt):
|
||||
self.cache[fname] = dict(data=data, dirty=dirty, refcnt=refcnt, valid=True)
|
||||
|
||||
def update(self, fname, data):
|
||||
self.cache[fname] = dict(data=data, dirty=True, refcnt=self.cache[fname]['refcnt'], valid=self.cache[fname]['valid'])
|
||||
|
||||
def invalidate(self, fname):
|
||||
zassert(fname in self.cache, 'cache:invalidate() -- cannot invalidate file not in cache (%s)' % fname)
|
||||
self.invalidcnt += 1
|
||||
self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
|
||||
refcnt=self.cache[fname]['refcnt'], valid=False)
|
||||
if self.solve and isset(self.detail, 1):
|
||||
dospace(self.num)
|
||||
if isset(self.detail,3):
|
||||
print '%2s invalidate %s' % (self.name, fname)
|
||||
else:
|
||||
print 'invalidate %s' % (fname)
|
||||
self.printstate(self.num)
|
||||
|
||||
def checkvalid(self, fname):
|
||||
zassert(fname in self.cache, 'cache:checkvalid() -- cannot checkvalid on file not in cache (%s)' % fname)
|
||||
if self.cache[fname]['valid'] == False and self.cache[fname]['refcnt'] == 0:
|
||||
del self.cache[fname]
|
||||
|
||||
def printstate(self, fname):
|
||||
for fname in self.cache:
|
||||
data = self.cache[fname]['data']
|
||||
dirty = self.cache[fname]['dirty']
|
||||
refcnt = self.cache[fname]['refcnt']
|
||||
valid = self.cache[fname]['valid']
|
||||
if valid == True:
|
||||
validPrint = 1
|
||||
else:
|
||||
validPrint = 0
|
||||
if dirty == True:
|
||||
dirtyPrint = 1
|
||||
else:
|
||||
dirtyPrint = 0
|
||||
|
||||
if self.solve and isset(self.detail, 2):
|
||||
dospace(self.num)
|
||||
if isset(self.detail, 3):
|
||||
print '%s [%s:%2d (v=%d,d=%d,r=%d)]' % (self.name, fname, data, validPrint, dirtyPrint, refcnt)
|
||||
else:
|
||||
print '[%s:%2d (v=%d,d=%d,r=%d)]' % (fname, data, validPrint, dirtyPrint, refcnt)
|
||||
|
||||
def checkget(self, fname):
|
||||
if fname in self.cache:
|
||||
self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
|
||||
refcnt=self.cache[fname]['refcnt'], valid=self.cache[fname]['valid'])
|
||||
self.hitcnt += 1
|
||||
return (True, self.cache[fname])
|
||||
self.misscnt += 1
|
||||
return (False, -1)
|
||||
|
||||
def get(self, fname):
|
||||
assert(fname in self.cache)
|
||||
return (True, self.cache[fname])
|
||||
|
||||
def incref(self, fname):
|
||||
assert(fname in self.cache)
|
||||
self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
|
||||
refcnt=self.cache[fname]['refcnt'] + 1, valid=self.cache[fname]['valid'])
|
||||
|
||||
def decref(self, fname):
|
||||
assert(fname in self.cache)
|
||||
self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
|
||||
refcnt=self.cache[fname]['refcnt'] - 1, valid=self.cache[fname]['valid'])
|
||||
|
||||
def setdirty(self, fname, dirty):
|
||||
assert(fname in self.cache)
|
||||
self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=dirty,
|
||||
refcnt=self.cache[fname]['refcnt'], valid=self.cache[fname]['valid'])
|
||||
|
||||
def setclean(self, fname):
|
||||
assert(fname in self.cache)
|
||||
self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=False,
|
||||
refcnt=self.cache[fname]['refcnt'], valid=self.cache[fname]['valid'])
|
||||
|
||||
def isdirty(self, fname):
|
||||
assert(fname in self.cache)
|
||||
return (self.cache[fname]['dirty'] == True)
|
||||
|
||||
def setvalid(self, fname):
|
||||
assert(fname in self.cache)
|
||||
self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
|
||||
refcnt=self.cache[fname]['refcnt'], valid=True)
|
||||
|
||||
|
||||
# actions
|
||||
MICRO_OPEN = 1
|
||||
MICRO_READ = 2
|
||||
MICRO_WRITE = 3
|
||||
MICRO_CLOSE = 4
|
||||
|
||||
def op2name(op):
|
||||
if op == MICRO_OPEN:
|
||||
return 'MICRO_OPEN'
|
||||
elif op == MICRO_READ:
|
||||
return 'MICRO_READ'
|
||||
elif op == MICRO_WRITE:
|
||||
return 'MICRO_WRITE'
|
||||
elif op == MICRO_CLOSE:
|
||||
return 'MICRO_CLOSE'
|
||||
else:
|
||||
abort('error: bad op -> ' + op)
|
||||
|
||||
#
|
||||
# Client class
|
||||
#
|
||||
# Models the behavior of each client in the system.
|
||||
#
|
||||
#
|
||||
#
|
||||
class client:
|
||||
def __init__(self, name, cid, server, files, bias, numsteps, actions, solve, detail):
|
||||
self.name = name # readable name of client
|
||||
self.cid = cid # client ID
|
||||
self.server = server # server object
|
||||
self.files = files # files object
|
||||
self.bias = bias # bias
|
||||
self.actions = actions # schedule exactly?
|
||||
self.solve = solve # show answers?
|
||||
self.detail = detail # how much of an answer to show
|
||||
|
||||
# cache
|
||||
self.cache = cache(self.name, self.cid, self.solve, self.detail)
|
||||
|
||||
# file desc
|
||||
self.fd = filedesc()
|
||||
|
||||
# stats
|
||||
self.readcnt = 0
|
||||
self.writecnt = 0
|
||||
|
||||
# init actions
|
||||
self.done = False # track state
|
||||
self.acnt = 0 # this is used when running
|
||||
self.acts = [] # this just tracks the opcodes
|
||||
|
||||
if self.actions == '':
|
||||
# in case with no specific actions, generate one...
|
||||
for i in range(numsteps):
|
||||
fname = pickrand(self.files.getfiles())
|
||||
r = random.random()
|
||||
fd = self.fd.alloc(fname)
|
||||
zassert(fd >= 0, 'client:init() -- ran out of file descriptors, sorry!')
|
||||
if r < self.bias[0]:
|
||||
# FILE_READ
|
||||
self.acts.append((MICRO_OPEN, fname, fd))
|
||||
self.acts.append((MICRO_READ, fd))
|
||||
self.acts.append((MICRO_CLOSE, fd))
|
||||
else:
|
||||
# FILE_WRITE
|
||||
self.acts.append((MICRO_OPEN, fname, fd))
|
||||
self.acts.append((MICRO_WRITE, fd))
|
||||
self.acts.append((MICRO_CLOSE, fd))
|
||||
else:
|
||||
# in this case, unpack actions and make it happen
|
||||
# should look like this: "oa1:ra1:ca1" (open 'a' for reading with file desc 1, read from file a (fd:1), etc.)
|
||||
# yes the file descriptor and file name are redundant for read/write and close
|
||||
for a in self.actions.split(':'):
|
||||
act = a[0]
|
||||
if act == 'o':
|
||||
zassert(len(a) == 3, 'client:init() -- malformed open action (%s) should be oa1 or something like that' % a)
|
||||
fname, fd = a[1], int(a[2])
|
||||
self.fd.alloc(fname, fd)
|
||||
assert(fd >= 0)
|
||||
self.acts.append((MICRO_OPEN, fname, fd))
|
||||
elif act == 'r':
|
||||
zassert(len(a) == 2, 'client:init() -- malformed read action (%s) should be r1 or something like that' % a)
|
||||
fd = int(a[1])
|
||||
self.acts.append((MICRO_READ, fd))
|
||||
elif act == 'w':
|
||||
zassert(len(a) == 2, 'client:init() -- malformed write action (%s) should be w1 or something like that' % a)
|
||||
fd = int(a[1])
|
||||
self.acts.append((MICRO_WRITE, fd))
|
||||
elif act == 'c':
|
||||
zassert(len(a) == 2, 'client:init() -- malformed close action (%s) should be c1 or something like that' % a)
|
||||
fd = int(a[1])
|
||||
self.acts.append((MICRO_CLOSE, fd))
|
||||
else:
|
||||
print 'Unrecognized command: %s (from %s)' % (act, a)
|
||||
exit(1)
|
||||
# debug ACTS
|
||||
# print self.acts
|
||||
|
||||
def getname(self):
|
||||
return self.name
|
||||
|
||||
def stats(self):
|
||||
print '%s -- Reads:%d Writes:%d' % (self.name, self.readcnt, self.writecnt)
|
||||
self.cache.stats()
|
||||
|
||||
def getfile(self, fname, dirty):
|
||||
(incache, item) = self.cache.checkget(fname)
|
||||
if incache == True and item['valid'] == 1:
|
||||
dprint(' -> CLIENT %s:: HAS LOCAL COPY of %s' % (self.name, fname))
|
||||
self.cache.setdirty(fname, dirty)
|
||||
else:
|
||||
data = self.server.get(self.name, fname)
|
||||
self.cache.put(fname, data, dirty, 0)
|
||||
self.cache.incref(fname)
|
||||
|
||||
def putfile(self, fname, value):
|
||||
self.server.put(self.name, fname, value)
|
||||
self.cache.setclean(fname)
|
||||
self.cache.setvalid(fname)
|
||||
|
||||
def invalidate(self, fname):
|
||||
self.cache.invalidate(fname)
|
||||
|
||||
def step(self, space):
|
||||
if self.done == True:
|
||||
return -1
|
||||
if self.acnt == len(self.acts):
|
||||
self.done = True
|
||||
return 0
|
||||
|
||||
# now figure out what to do and do it
|
||||
# action, fname, fd = self.acts[self.acnt]
|
||||
action = self.acts[self.acnt][0]
|
||||
|
||||
# print ''
|
||||
# print '*************************'
|
||||
# print '%s ACTION -> %s' % (self.name, op2name(action))
|
||||
# print '*************************'
|
||||
|
||||
# first, do spacing for command (below)
|
||||
dospace(space)
|
||||
|
||||
if isset(self.detail, 3) == True:
|
||||
print self.name,
|
||||
|
||||
# now handle the action
|
||||
if action == MICRO_OPEN:
|
||||
fname, fd = self.acts[self.acnt][1], self.acts[self.acnt][2]
|
||||
tprint('open:%s [fd:%d]' % (fname, fd))
|
||||
self.getfile(fname, dirty=False)
|
||||
elif action == MICRO_READ:
|
||||
fd = self.acts[self.acnt][1]
|
||||
fname = self.fd.lookup(fd)
|
||||
self.readcnt += 1
|
||||
incache, contents = self.cache.get(fname)
|
||||
assert(incache == True)
|
||||
if self.solve:
|
||||
tprint('read:%d -> %d' % (fd, contents['data']))
|
||||
else:
|
||||
tprint('read:%d -> value?' % (fd))
|
||||
elif action == MICRO_WRITE:
|
||||
fd = self.acts[self.acnt][1]
|
||||
fname = self.fd.lookup(fd)
|
||||
self.writecnt += 1
|
||||
incache, contents = self.cache.get(fname)
|
||||
assert(incache == True)
|
||||
v = self.files.getvalue()
|
||||
self.cache.update(fname, v)
|
||||
if self.solve:
|
||||
tprint('write:%d %d -> %d' % (fd, contents['data'], v))
|
||||
else:
|
||||
tprint('write:%d value? -> %d' % (fd, v))
|
||||
elif action == MICRO_CLOSE:
|
||||
fd = self.acts[self.acnt][1]
|
||||
fname = self.fd.lookup(fd)
|
||||
incache, contents = self.cache.get(fname)
|
||||
assert(incache == True)
|
||||
tprint('close:%d' % (fd))
|
||||
if self.cache.isdirty(fname):
|
||||
self.putfile(fname, contents['data'])
|
||||
self.cache.decref(fname)
|
||||
self.cache.checkvalid(fname)
|
||||
|
||||
# useful to see
|
||||
self.cache.printstate(self.name)
|
||||
|
||||
if self.solve and self.detail > 0:
|
||||
print ''
|
||||
|
||||
# return that there is more left to do
|
||||
self.acnt += 1
|
||||
return 1
|
||||
|
||||
|
||||
#
|
||||
# main program
|
||||
#
|
||||
parser = OptionParser()
|
||||
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
|
||||
parser.add_option('-C', '--clients', default=2, help='number of clients', action='store', type='int', dest='numclients')
|
||||
parser.add_option('-n', '--numsteps', default=2, help='ops each client will do', action='store', type='int', dest='numsteps')
|
||||
parser.add_option('-f', '--numfiles', default=1, help='number of files in server', action='store', type='int', dest='numfiles')
|
||||
parser.add_option('-r', '--readratio', default=0.5, help='ratio of reads/writes', action='store', type='float', dest='readratio')
|
||||
parser.add_option('-A', '--actions', default='', help='client actions exactly specified, e.g., oa1:r1:c1,oa1:w1:c1 specifies two clients; each opens the file a, client 0 reads it whereas client 1 writes it, and then each closes it',
|
||||
action='store', type='string', dest='actions')
|
||||
parser.add_option('-S', '--schedule', default='', help='exact schedule to run; 01 alternates round robin between clients 0 and 1. Left unspecified leads to random scheduling',
|
||||
action='store', type='string', dest='schedule')
|
||||
parser.add_option('-p', '--printstats', default=False, help='print extra stats', action='store_true', dest='printstats')
|
||||
parser.add_option('-c', '--compute', default=False, help='compute answers for me', action='store_true', dest='solve')
|
||||
parser.add_option('-d', '--detail', default=0, help='detail level when giving answers (1:server actions,2:invalidations,4:client cache,8:extra labels); OR together for multiple', action='store', type='int', dest='detail')
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
print 'ARG seed', options.seed
|
||||
print 'ARG numclients', options.numclients
|
||||
print 'ARG numsteps', options.numsteps
|
||||
print 'ARG numfiles', options.numfiles
|
||||
print 'ARG readratio', options.readratio
|
||||
print 'ARG actions', options.actions
|
||||
print 'ARG schedule', options.schedule
|
||||
print 'ARG detail', options.detail
|
||||
print ''
|
||||
|
||||
seed = int(options.seed)
|
||||
numclients = int(options.numclients)
|
||||
numsteps = int(options.numsteps)
|
||||
numfiles = int(options.numfiles)
|
||||
readratio = float(options.readratio)
|
||||
actions = options.actions
|
||||
schedule = options.schedule
|
||||
printstats = options.printstats
|
||||
solve = options.solve
|
||||
detail = options.detail
|
||||
|
||||
# with specific schedule, files are all specified by a single letter in specific actions list
|
||||
# but we ignore this for now...
|
||||
|
||||
zassert(numfiles > 0 and numfiles <= 26, 'main: can only simulate 26 or fewer files, sorry')
|
||||
zassert(readratio >= 0.0 and readratio <= 1.0, 'main: read ratio must be between 0 and 1 inclusive')
|
||||
|
||||
# start it
|
||||
random.seed(seed)
|
||||
|
||||
# files in server to begin with
|
||||
f = files(numfiles)
|
||||
|
||||
# make server
|
||||
s = server(f, solve, detail)
|
||||
|
||||
clients = []
|
||||
|
||||
if actions != '':
|
||||
# if specific actions are specified, figure some stuff out now
|
||||
# e.g., oa1:ra1:ca1,oa1:ra1:ca1 which is list of 0's actions, then 1's, then...
|
||||
cactions = actions.split(',')
|
||||
if numclients != len(cactions):
|
||||
numclients = len(cactions)
|
||||
i = 0
|
||||
for clist in cactions:
|
||||
clients.append(client('c%d' % i, i, s, f, [], len(clist), clist, solve, detail))
|
||||
i += 1
|
||||
else:
|
||||
# else, make random clients
|
||||
for i in range(numclients):
|
||||
clients.append(client('c%d' % i, i, s, f, [readratio, 1.0], numsteps, '', solve, detail))
|
||||
|
||||
# tell server about these clients
|
||||
s.setclients(clients)
|
||||
|
||||
# init print out for clients
|
||||
print '%12s' % 'Server', '%12s' % ' ',
|
||||
for c in clients:
|
||||
print '%13s' % c.getname(), '%13s' % ' ',
|
||||
print ''
|
||||
|
||||
# main loop
|
||||
#
|
||||
# over time, pick a random client
|
||||
# have it do one thing, show what happens
|
||||
# move on to next and so forth
|
||||
|
||||
s.filestats(True)
|
||||
|
||||
# for use with specific schedule
|
||||
schedcurr = 0
|
||||
|
||||
# check for legal schedule (must include all clients)
|
||||
if schedule != '':
|
||||
for i in range(len(clients)):
|
||||
cnt = 0
|
||||
for j in range(len(schedule)):
|
||||
curr = schedule[j]
|
||||
if int(curr) == i:
|
||||
cnt += 1
|
||||
zassert(cnt != 0, 'main: client %d not in schedule:%s, which would never terminate' % (i, schedule))
|
||||
|
||||
# RUN the schedule (either random or specified by user)
|
||||
numrunning = len(clients)
|
||||
while numrunning > 0:
|
||||
if schedule == '':
|
||||
c = pickrand(clients)
|
||||
else:
|
||||
idx = int(schedule[schedcurr])
|
||||
# print 'SCHEDULE DEBUG:: schedule:', schedule, 'schedcurr', schedcurr, 'index', idx
|
||||
c = clients[idx]
|
||||
schedcurr += 1
|
||||
if schedcurr == len(schedule):
|
||||
schedcurr = 0
|
||||
rc = c.step(clients.index(c))
|
||||
if rc == 0:
|
||||
numrunning -= 1
|
||||
|
||||
|
||||
s.filestats(solve)
|
||||
|
||||
if printstats:
|
||||
s.stats()
|
||||
for c in clients:
|
||||
c.stats()
|
||||
|
719
related_info/ostep/ostep15-disk/disk-precise.py
Executable file
719
related_info/ostep/ostep15-disk/disk-precise.py
Executable file
@ -0,0 +1,719 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
from Tkinter import *
|
||||
from types import *
|
||||
import math, random, time, sys, os
|
||||
from optparse import OptionParser
|
||||
from decimal import *
|
||||
|
||||
MAXTRACKS = 1000
|
||||
|
||||
# states that a request/disk go through
|
||||
STATE_NULL = 0
|
||||
STATE_SEEK = 1
|
||||
STATE_ROTATE = 2
|
||||
STATE_XFER = 3
|
||||
STATE_DONE = 4
|
||||
|
||||
#
|
||||
# TODO
|
||||
# XXX transfer time
|
||||
# XXX satf
|
||||
# XXX skew
|
||||
# XXX scheduling window
|
||||
# XXX sstf
|
||||
# XXX specify requests vs. random requests in range
|
||||
# XXX add new requests as old ones complete (starvation)
|
||||
# XXX run in non-graphical mode
|
||||
# XXX better graphical display (show key, long lists of requests, more timings on screen)
|
||||
# XXX be able to do "pure" sequential
|
||||
# XXX add more blocks around outer tracks (zoning)
|
||||
# XXX simple flag to make scheduling window a fairness window (-F)
|
||||
# new algs to scan and c-scan the disk?
|
||||
#
|
||||
|
||||
class Disk:
|
||||
def __init__(self, addr, addrDesc, lateAddr, lateAddrDesc,
|
||||
policy, seekSpeed, rotateSpeed, skew, window, compute,
|
||||
graphics, zoning):
|
||||
self.addr = addr
|
||||
self.addrDesc = addrDesc
|
||||
self.lateAddr = lateAddr
|
||||
self.lateAddrDesc = lateAddrDesc
|
||||
self.policy = policy
|
||||
self.seekSpeed = Decimal(seekSpeed)
|
||||
self.rotateSpeed = Decimal(rotateSpeed)
|
||||
self.skew = skew
|
||||
self.window = window
|
||||
self.compute = compute
|
||||
self.graphics = graphics
|
||||
self.zoning = zoning
|
||||
|
||||
# figure out zones first, to figure out the max possible request
|
||||
self.InitBlockLayout()
|
||||
|
||||
# figure out requests
|
||||
random.seed(options.seed)
|
||||
self.requests = self.MakeRequests(self.addr, self.addrDesc)
|
||||
self.lateRequests = self.MakeRequests(self.lateAddr, self.lateAddrDesc)
|
||||
|
||||
# graphical startup
|
||||
self.width = 500
|
||||
if self.graphics:
|
||||
self.root = Tk()
|
||||
tmpLen = len(self.requests)
|
||||
if len(self.lateRequests) > 0:
|
||||
tmpLen += len(self.lateRequests)
|
||||
self.canvas = Canvas(self.root, width=410, height=460 + ((tmpLen / 20) * 20))
|
||||
self.canvas.pack()
|
||||
|
||||
# fairness stuff
|
||||
if self.policy == 'BSATF' and self.window != -1:
|
||||
self.fairWindow = self.window
|
||||
else:
|
||||
self.fairWindow = -1
|
||||
|
||||
print 'REQUESTS', self.requests
|
||||
print ''
|
||||
|
||||
# for late requests
|
||||
self.lateCount = 0
|
||||
if len(self.lateRequests) > 0:
|
||||
print 'LATE REQUESTS', self.lateRequests
|
||||
print ''
|
||||
|
||||
if self.compute == False:
|
||||
print ''
|
||||
print 'For the requests above, compute the seek, rotate, and transfer times.'
|
||||
print 'Use -c or the graphical mode (-G) to see the answers.'
|
||||
print ''
|
||||
|
||||
# BINDINGS
|
||||
if self.graphics:
|
||||
self.root.bind('s', self.Start)
|
||||
self.root.bind('p', self.Pause)
|
||||
self.root.bind('q', self.Exit)
|
||||
|
||||
# TRACK INFO
|
||||
self.tracks = {}
|
||||
self.trackWidth = 40
|
||||
self.tracks[0] = 140
|
||||
self.tracks[1] = self.tracks[0] - self.trackWidth
|
||||
self.tracks[2] = self.tracks[1] - self.trackWidth
|
||||
|
||||
if (self.seekSpeed > 1 and self.trackWidth % self.seekSpeed != 0):
|
||||
print 'Seek speed (%d) must divide evenly into track width (%d)' % (self.seekSpeed, self.trackWidth)
|
||||
sys.exit(1)
|
||||
if self.seekSpeed < 1:
|
||||
x = (self.trackWidth / self.seekSpeed)
|
||||
y = int(float(self.trackWidth) / float(self.seekSpeed))
|
||||
if float(x) != float(y):
|
||||
print 'Seek speed (%d) must divide evenly into track width (%d)' % (self.seekSpeed, self.trackWidth)
|
||||
sys.exit(1)
|
||||
|
||||
# DISK SURFACE
|
||||
self.cx = self.width/2.0
|
||||
self.cy = self.width/2.0
|
||||
if self.graphics:
|
||||
self.canvas.create_rectangle(self.cx-175, 30, self.cx - 20, 80, fill='gray', outline='black')
|
||||
self.platterSize = 320
|
||||
ps2 = self.platterSize / 2.0
|
||||
if self.graphics:
|
||||
self.canvas.create_oval(self.cx-ps2, self.cy-ps2, self.cx+ps2, self.cy + ps2, fill='darkgray', outline='black')
|
||||
for i in range(len(self.tracks)):
|
||||
t = self.tracks[i] - (self.trackWidth / 2.0)
|
||||
if self.graphics:
|
||||
self.canvas.create_oval(self.cx - t, self.cy - t, self.cx + t, self.cy + t, fill='', outline='black', width=1.0)
|
||||
|
||||
# SPINDLE
|
||||
self.spindleX = self.cx
|
||||
self.spindleY = self.cy
|
||||
if self.graphics:
|
||||
self.spindleID = self.canvas.create_oval(self.spindleX-3, self.spindleY-3, self.spindleX+3, self.spindleY+3, fill='orange', outline='black')
|
||||
|
||||
# DISK ARM
|
||||
self.armTrack = 0
|
||||
self.armSpeedBase = float(seekSpeed)
|
||||
self.armSpeed = float(seekSpeed)
|
||||
|
||||
distFromSpindle = self.tracks[self.armTrack]
|
||||
self.armWidth = 20
|
||||
self.headWidth = 10
|
||||
|
||||
self.armX = self.spindleX - (distFromSpindle * math.cos(math.radians(0)))
|
||||
self.armX1 = self.armX - self.armWidth
|
||||
self.armX2 = self.armX + self.armWidth
|
||||
self.armY1 = 50.0
|
||||
self.armY2 = self.width / 2.0
|
||||
|
||||
self.headX1 = self.armX - self.headWidth
|
||||
self.headX2 = self.armX + self.headWidth
|
||||
self.headY1 = (self.width/2.0) - self.headWidth
|
||||
self.headY2 = (self.width/2.0) + self.headWidth
|
||||
|
||||
if self.graphics:
|
||||
self.armID = self.canvas.create_rectangle(self.armX1, self.armY1, self.armX2, self.armY2, fill='gray', outline='black')
|
||||
self.headID = self.canvas.create_rectangle(self.headX1, self.headY1, self.headX2, self.headY2, fill='gray', outline='black')
|
||||
|
||||
self.targetSize = 10.0
|
||||
if self.graphics:
|
||||
sz = self.targetSize
|
||||
self.targetID = self.canvas.create_oval(self.armX1-sz, self.armY1-sz, self.armX1+sz, self.armY1+sz, fill='orange', outline='')
|
||||
|
||||
# IO QUEUE
|
||||
self.queueX = 20
|
||||
self.queueY = 450
|
||||
|
||||
self.requestCount = 0
|
||||
self.requestQueue = []
|
||||
self.requestState = []
|
||||
self.queueBoxSize = 20
|
||||
self.queueBoxID = {}
|
||||
self.queueTxtID = {}
|
||||
|
||||
# draw each box
|
||||
for index in range(len(self.requests)):
|
||||
self.AddQueueEntry(int(self.requests[index]), index)
|
||||
if self.graphics:
|
||||
self.canvas.create_text(self.queueX - 5, self.queueY - 20, anchor='w', text='Queue:')
|
||||
|
||||
# scheduling window
|
||||
self.currWindow = self.window
|
||||
|
||||
# draw current limits of queue
|
||||
if self.graphics:
|
||||
self.windowID = -1
|
||||
self.DrawWindow()
|
||||
|
||||
# initial scheduling info
|
||||
self.currentIndex = -1
|
||||
self.currentBlock = -1
|
||||
|
||||
# initial state of disk (vs seeking, rotating, transferring)
|
||||
self.state = STATE_NULL
|
||||
|
||||
# DRAW BLOCKS on the TRACKS
|
||||
for bid in range(len(self.blockInfoList)):
|
||||
(track, angle, name) = self.blockInfoList[bid]
|
||||
if self.graphics:
|
||||
distFromSpindle = self.tracks[track]
|
||||
xc = self.spindleX + (distFromSpindle * math.cos(math.radians(angle)))
|
||||
yc = self.spindleY + (distFromSpindle * math.sin(math.radians(angle)))
|
||||
cid = self.canvas.create_text(xc, yc, text=name, anchor='center')
|
||||
else:
|
||||
cid = -1
|
||||
self.blockInfoList[bid] = (track, angle, name, cid)
|
||||
|
||||
# angle of rotation
|
||||
self.angle = Decimal(0.0)
|
||||
|
||||
# TIME INFO
|
||||
if self.graphics:
|
||||
self.timeID = self.canvas.create_text(10, 10, text='Time: 0.00', anchor='w')
|
||||
self.canvas.create_rectangle(95,0,200,18, fill='orange', outline='orange')
|
||||
self.seekID = self.canvas.create_text(100, 10, text='Seek: 0.00', anchor='w')
|
||||
self.canvas.create_rectangle(195,0,300,18, fill='lightblue', outline='lightblue')
|
||||
self.rotID = self.canvas.create_text(200, 10, text='Rotate: 0.00', anchor='w')
|
||||
self.canvas.create_rectangle(295,0,400,18, fill='green', outline='green')
|
||||
self.xferID = self.canvas.create_text(300, 10, text='Transfer: 0.00', anchor='w')
|
||||
self.canvas.create_text(320, 40, text='"s" to start', anchor='w')
|
||||
self.canvas.create_text(320, 60, text='"p" to pause', anchor='w')
|
||||
self.canvas.create_text(320, 80, text='"q" to quit', anchor='w')
|
||||
self.timer = 0
|
||||
|
||||
# STATS
|
||||
self.seekTotal = 0.0
|
||||
self.rotTotal = 0.0
|
||||
self.xferTotal = 0.0
|
||||
|
||||
# set up animation loop
|
||||
if self.graphics:
|
||||
self.doAnimate = True
|
||||
else:
|
||||
self.doAnimate = False
|
||||
self.isDone = False
|
||||
|
||||
# call this to start simulation
|
||||
def Go(self):
|
||||
if options.graphics:
|
||||
self.root.mainloop()
|
||||
else:
|
||||
self.GetNextIO()
|
||||
while self.isDone == False:
|
||||
self.Animate()
|
||||
|
||||
# crappy error message
|
||||
def PrintAddrDescMessage(self, value):
|
||||
print 'Bad address description (%s)' % value
|
||||
print 'The address description must be a comma-separated list of length three, without spaces.'
|
||||
print 'For example, "10,100,0" would indicate that 10 addresses should be generated, with'
|
||||
print '100 as the maximum value, and 0 as the minumum. A max of -1 means just use the highest'
|
||||
print 'possible value as the max address to generate.'
|
||||
sys.exit(1)
|
||||
|
||||
#
|
||||
# ZONES AND BLOCK LAYOUT
|
||||
#
|
||||
def InitBlockLayout(self):
|
||||
self.blockInfoList = []
|
||||
self.blockToTrackMap = {}
|
||||
self.blockToAngleMap = {}
|
||||
self.tracksBeginEnd = {}
|
||||
self.blockAngleOffset = []
|
||||
|
||||
zones = self.zoning.split(',')
|
||||
assert(len(zones) == 3)
|
||||
for i in range(len(zones)):
|
||||
self.blockAngleOffset.append(int(zones[i]) / 2)
|
||||
|
||||
track = 0 # outer track
|
||||
angleOffset = 2 * self.blockAngleOffset[track]
|
||||
for angle in range(0, 360, angleOffset):
|
||||
block = angle / angleOffset
|
||||
self.blockToTrackMap[block] = track
|
||||
self.blockToAngleMap[block] = angle
|
||||
self.blockInfoList.append((track, angle, block))
|
||||
self.tracksBeginEnd[track] = (0, block)
|
||||
pblock = block + 1
|
||||
|
||||
track = 1 # middle track
|
||||
skew = self.skew
|
||||
angleOffset = 2 * self.blockAngleOffset[track]
|
||||
for angle in range(0, 360, angleOffset):
|
||||
block = (angle / angleOffset) + pblock
|
||||
self.blockToTrackMap[block] = track
|
||||
self.blockToAngleMap[block] = angle + (angleOffset * skew)
|
||||
self.blockInfoList.append((track, angle + (angleOffset * skew), block))
|
||||
self.tracksBeginEnd[track] = (pblock, block)
|
||||
pblock = block + 1
|
||||
|
||||
track = 2 # inner track
|
||||
skew = 2 * self.skew
|
||||
angleOffset = 2 * self.blockAngleOffset[track]
|
||||
for angle in range(0, 360, angleOffset):
|
||||
block = (angle / angleOffset) + pblock
|
||||
self.blockToTrackMap[block] = track
|
||||
self.blockToAngleMap[block] = angle + (angleOffset * skew)
|
||||
self.blockInfoList.append((track, angle + (angleOffset * skew), block))
|
||||
self.tracksBeginEnd[track] = (pblock, block)
|
||||
self.maxBlock = pblock
|
||||
# print 'MAX BLOCK:', self.maxBlock
|
||||
|
||||
# adjust angle to starting position relative
|
||||
for i in self.blockToAngleMap:
|
||||
self.blockToAngleMap[i] = (self.blockToAngleMap[i] + 180) % 360
|
||||
|
||||
# print 'btoa map', self.blockToAngleMap
|
||||
# print 'btot map', self.blockToTrackMap
|
||||
# print 'bao', self.blockAngleOffset
|
||||
|
||||
def MakeRequests(self, addr, addrDesc):
|
||||
(numRequests, maxRequest, minRequest) = (0, 0, 0)
|
||||
if addr == '-1':
|
||||
# first extract values from descriptor
|
||||
desc = addrDesc.split(',')
|
||||
if len(desc) != 3:
|
||||
self.PrintAddrDescMessage(addrDesc)
|
||||
(numRequests, maxRequest, minRequest) = (int(desc[0]), int(desc[1]), int(desc[2]))
|
||||
if maxRequest == -1:
|
||||
maxRequest = self.maxBlock
|
||||
# now make list
|
||||
tmpList = []
|
||||
for i in range(numRequests):
|
||||
tmpList.append(int(random.random() * maxRequest) + minRequest)
|
||||
return tmpList
|
||||
else:
|
||||
return addr.split(',')
|
||||
|
||||
#
|
||||
# BUTTONS
|
||||
#
|
||||
def Start(self, event):
|
||||
self.GetNextIO()
|
||||
self.doAnimate = True
|
||||
self.Animate()
|
||||
|
||||
def Pause(self, event):
|
||||
if self.doAnimate == False:
|
||||
self.doAnimate = True
|
||||
else:
|
||||
self.doAnimate = False
|
||||
|
||||
def Exit(self, event):
|
||||
sys.exit(0)
|
||||
|
||||
#
|
||||
# CORE SIMULATION and ANIMATION
|
||||
#
|
||||
def UpdateTime(self):
|
||||
if self.graphics:
|
||||
self.canvas.itemconfig(self.timeID, text='Time: ' + str(self.timer))
|
||||
self.canvas.itemconfig(self.seekID, text='Seek: ' + str(self.seekTotal))
|
||||
self.canvas.itemconfig(self.rotID, text='Rotate: ' + str(self.rotTotal))
|
||||
self.canvas.itemconfig(self.xferID, text='Transfer: ' + str(self.xferTotal))
|
||||
|
||||
def AddRequest(self, block):
|
||||
self.AddQueueEntry(block, len(self.requestQueue))
|
||||
|
||||
def QueueMap(self, index):
|
||||
numPerRow = 400 / self.queueBoxSize
|
||||
return (index % numPerRow, index / numPerRow)
|
||||
|
||||
def DrawWindow(self):
|
||||
if self.window == -1:
|
||||
return
|
||||
(col, row) = self.QueueMap(self.currWindow)
|
||||
if col == 0:
|
||||
(col, row) = (20, row - 1)
|
||||
if self.windowID != -1:
|
||||
self.canvas.delete(self.windowID)
|
||||
self.windowID = self.canvas.create_line(self.queueX + (col * 20) - 10, self.queueY - 13 + (row * 20),
|
||||
self.queueX + (col * 20) - 10, self.queueY + 13 + (row * 20), width=2)
|
||||
|
||||
def AddQueueEntry(self, block, index):
|
||||
self.requestQueue.append((block, index))
|
||||
self.requestState.append(STATE_NULL)
|
||||
if self.graphics:
|
||||
(col, row) = self.QueueMap(index)
|
||||
sizeHalf = self.queueBoxSize / 2.0
|
||||
(cx, cy) = (self.queueX + (col * self.queueBoxSize), self.queueY + (row * self.queueBoxSize))
|
||||
self.queueBoxID[index] = self.canvas.create_rectangle(cx - sizeHalf, cy - sizeHalf, cx + sizeHalf, cy + sizeHalf, fill='white')
|
||||
self.queueTxtID[index] = self.canvas.create_text(cx, cy, anchor='center', text=str(block))
|
||||
|
||||
def SwitchColors(self, c):
|
||||
if self.graphics:
|
||||
self.canvas.itemconfig(self.queueBoxID[self.currentIndex], fill=c)
|
||||
self.canvas.itemconfig(self.targetID, fill=c)
|
||||
|
||||
def SwitchState(self, newState):
|
||||
self.state = newState
|
||||
self.requestState[self.currentIndex] = newState
|
||||
|
||||
def RadiallyCloseTo(self, a1, a2):
|
||||
if a1 > a2:
|
||||
v = a1 - a2
|
||||
else:
|
||||
v = a2 - a1
|
||||
if v < self.rotateSpeed:
|
||||
return True
|
||||
return False
|
||||
|
||||
def DoneWithTransfer(self):
|
||||
angleOffset = self.blockAngleOffset[self.armTrack]
|
||||
# if int(self.angle) == (self.blockToAngleMap[self.currentBlock] + angleOffset) % 360:
|
||||
if self.RadiallyCloseTo(self.angle, Decimal((self.blockToAngleMap[self.currentBlock] + angleOffset) % 360)):
|
||||
# print 'END TRANSFER', self.angle, self.timer
|
||||
self.SwitchState(STATE_DONE)
|
||||
self.requestCount += 1
|
||||
return True
|
||||
return False
|
||||
|
||||
def DoneWithRotation(self):
|
||||
angleOffset = self.blockAngleOffset[self.armTrack]
|
||||
# XXX there is a weird bug in here
|
||||
# print self.timer, 'ROTATE:: ', self.currentBlock, 'currangle: ', self.angle, ' - mapangle: ', self.blockToAngleMap[self.currentBlock]
|
||||
# print ' angleOffset ', angleOffset
|
||||
# print ' blockMap ', (self.blockToAngleMap[self.currentBlock] - angleOffset) % 360
|
||||
# print ' self.angle ', self.angle, int(self.angle)
|
||||
# if int(self.angle) == (self.blockToAngleMap[self.currentBlock] - angleOffset) % 360:
|
||||
if self.RadiallyCloseTo(self.angle, Decimal((self.blockToAngleMap[self.currentBlock] - angleOffset) % 360)):
|
||||
self.SwitchState(STATE_XFER)
|
||||
# print ' --> DONE WITH ROTATION!', self.timer
|
||||
return True
|
||||
return False
|
||||
|
||||
def PlanSeek(self, track):
|
||||
self.seekBegin = self.timer
|
||||
self.SwitchColors('orange')
|
||||
self.SwitchState(STATE_SEEK)
|
||||
if track == self.armTrack:
|
||||
self.rotBegin = self.timer
|
||||
self.SwitchColors('lightblue')
|
||||
self.SwitchState(STATE_ROTATE)
|
||||
return
|
||||
self.armTarget = track
|
||||
self.armTargetX1 = self.spindleX - self.tracks[track] - (self.trackWidth / 2.0)
|
||||
if track >= self.armTrack:
|
||||
self.armSpeed = self.armSpeedBase
|
||||
else:
|
||||
self.armSpeed = - self.armSpeedBase
|
||||
|
||||
def DoneWithSeek(self):
|
||||
# move the disk arm
|
||||
self.armX1 += self.armSpeed
|
||||
self.armX2 += self.armSpeed
|
||||
self.headX1 += self.armSpeed
|
||||
self.headX2 += self.armSpeed
|
||||
# update it on screen
|
||||
if self.graphics:
|
||||
self.canvas.coords(self.armID, self.armX1, self.armY1, self.armX2, self.armY2)
|
||||
self.canvas.coords(self.headID, self.headX1, self.headY1, self.headX2, self.headY2)
|
||||
# check if done
|
||||
if (self.armSpeed > 0.0 and self.armX1 >= self.armTargetX1) or (self.armSpeed < 0.0 and self.armX1 <= self.armTargetX1):
|
||||
self.armTrack = self.armTarget
|
||||
return True
|
||||
return False
|
||||
|
||||
def DoSATF(self, rList):
|
||||
minBlock = -1
|
||||
minIndex = -1
|
||||
minEst = -1
|
||||
|
||||
# print '**** DoSATF ****', rList
|
||||
for (block, index) in rList:
|
||||
if self.requestState[index] == STATE_DONE:
|
||||
continue
|
||||
track = self.blockToTrackMap[block]
|
||||
angle = self.blockToAngleMap[block]
|
||||
# print 'track', track, 'angle', angle
|
||||
|
||||
# estimate seek time
|
||||
dist = int(math.fabs(self.armTrack - track))
|
||||
seekEst = Decimal(self.trackWidth / self.armSpeedBase) * dist
|
||||
|
||||
# estimate rotate time
|
||||
angleOffset = self.blockAngleOffset[track]
|
||||
angleAtArrival = (Decimal(self.angle) + (seekEst * self.rotateSpeed))
|
||||
while angleAtArrival > 360.0:
|
||||
angleAtArrival -= 360.0
|
||||
rotDist = Decimal((angle - angleOffset) - angleAtArrival)
|
||||
while rotDist > 360.0:
|
||||
rotDist -= Decimal(360.0)
|
||||
while rotDist < 0.0:
|
||||
rotDist += Decimal(360.0)
|
||||
rotEst = rotDist / self.rotateSpeed
|
||||
|
||||
# finally, transfer
|
||||
xferEst = (Decimal(angleOffset) * Decimal(2.0)) / self.rotateSpeed
|
||||
|
||||
totalEst = seekEst + rotEst + xferEst
|
||||
|
||||
# should probably pick one on same track in case of a TIE
|
||||
if minEst == -1 or totalEst < minEst:
|
||||
minEst = totalEst
|
||||
minBlock = block
|
||||
minIndex = index
|
||||
# END loop
|
||||
|
||||
# when done
|
||||
self.totalEst = minEst
|
||||
assert(minBlock != -1)
|
||||
assert(minIndex != -1)
|
||||
return (minBlock, minIndex)
|
||||
|
||||
#
|
||||
# actually doesn't quite do SSTF
|
||||
# just finds all the blocks on the nearest track
|
||||
# (whatever that may be) and returns it as a list
|
||||
#
|
||||
def DoSSTF(self, rList):
|
||||
minDist = MAXTRACKS
|
||||
minBlock = -1
|
||||
trackList = [] # all the blocks on a track
|
||||
|
||||
for (block, index) in rList:
|
||||
if self.requestState[index] == STATE_DONE:
|
||||
continue
|
||||
track = self.blockToTrackMap[block]
|
||||
dist = int(math.fabs(self.armTrack - track))
|
||||
if dist < minDist:
|
||||
trackList = []
|
||||
trackList.append((block, index))
|
||||
minDist = dist
|
||||
elif dist == minDist:
|
||||
trackList.append((block, index))
|
||||
assert(trackList != [])
|
||||
return trackList
|
||||
|
||||
def UpdateWindow(self):
|
||||
if self.fairWindow == -1 and self.currWindow > 0 and self.currWindow < len(self.requestQueue):
|
||||
self.currWindow += 1
|
||||
if self.graphics:
|
||||
self.DrawWindow()
|
||||
|
||||
def GetWindow(self):
|
||||
if self.currWindow <= -1:
|
||||
return len(self.requestQueue)
|
||||
else:
|
||||
if self.fairWindow != -1:
|
||||
if self.requestCount > 0 and (self.requestCount % self.fairWindow == 0):
|
||||
self.currWindow = self.currWindow + self.fairWindow
|
||||
if self.currWindow > len(self.requestQueue):
|
||||
self.currWindow = len(self.requestQueue)
|
||||
if self.graphics:
|
||||
self.DrawWindow()
|
||||
return self.currWindow
|
||||
else:
|
||||
return self.currWindow
|
||||
|
||||
def GetNextIO(self):
|
||||
# check if done: if so, print stats and end animation
|
||||
if self.requestCount == len(self.requestQueue):
|
||||
self.UpdateTime()
|
||||
self.PrintStats()
|
||||
self.doAnimate = False
|
||||
self.isDone = True
|
||||
return
|
||||
|
||||
# do policy: should set currentBlock,
|
||||
if self.policy == 'FIFO':
|
||||
(self.currentBlock, self.currentIndex) = self.requestQueue[self.requestCount]
|
||||
self.DoSATF(self.requestQueue[self.requestCount:self.requestCount+1])
|
||||
elif self.policy == 'SATF' or self.policy == 'BSATF':
|
||||
(self.currentBlock, self.currentIndex) = self.DoSATF(self.requestQueue[0:self.GetWindow()])
|
||||
elif self.policy == 'SSTF':
|
||||
# first, find all the blocks on a given track (given window constraints)
|
||||
trackList = self.DoSSTF(self.requestQueue[0:self.GetWindow()])
|
||||
# then, do SATF on those blocks (otherwise, will not do them in obvious order)
|
||||
(self.currentBlock, self.currentIndex) = self.DoSATF(trackList)
|
||||
else:
|
||||
print 'policy (%s) not implemented' % self.policy
|
||||
sys.exit(1)
|
||||
|
||||
# once best block is decided, go ahead and do the seek
|
||||
self.PlanSeek(self.blockToTrackMap[self.currentBlock])
|
||||
|
||||
# add another block?
|
||||
if len(self.lateRequests) > 0 and self.lateCount < len(self.lateRequests):
|
||||
self.AddRequest(self.lateRequests[self.lateCount])
|
||||
self.lateCount += 1
|
||||
|
||||
def Animate(self):
|
||||
if self.graphics == True and self.doAnimate == False:
|
||||
self.root.after(20, self.Animate)
|
||||
return
|
||||
|
||||
# timer
|
||||
self.timer += 1
|
||||
self.UpdateTime()
|
||||
|
||||
# see which blocks are rotating on the disk
|
||||
# print 'SELF ANGLE', self.angle
|
||||
self.angle = Decimal(self.angle + self.rotateSpeed)
|
||||
if self.angle >= 360.0:
|
||||
self.angle = Decimal(0.0)
|
||||
|
||||
# move the blocks
|
||||
if self.graphics:
|
||||
for (track, angle, name, cid) in self.blockInfoList:
|
||||
distFromSpindle = self.tracks[track]
|
||||
na = angle - self.angle
|
||||
xc = self.spindleX + (distFromSpindle * math.cos(math.radians(na)))
|
||||
yc = self.spindleY + (distFromSpindle * math.sin(math.radians(na)))
|
||||
if self.graphics:
|
||||
self.canvas.coords(cid, xc, yc)
|
||||
if self.currentBlock == name:
|
||||
sz = self.targetSize
|
||||
self.canvas.coords(self.targetID, xc-sz, yc-sz, xc+sz, yc+sz)
|
||||
|
||||
# move the arm OR wait for a rotational delay
|
||||
if self.state == STATE_SEEK:
|
||||
if self.DoneWithSeek():
|
||||
self.rotBegin = self.timer
|
||||
self.SwitchState(STATE_ROTATE)
|
||||
self.SwitchColors('lightblue')
|
||||
if self.state == STATE_ROTATE:
|
||||
# check for read (disk arm must be settled)
|
||||
if self.DoneWithRotation():
|
||||
self.xferBegin = self.timer
|
||||
self.SwitchState(STATE_XFER)
|
||||
self.SwitchColors('green')
|
||||
if self.state == STATE_XFER:
|
||||
if self.DoneWithTransfer():
|
||||
self.DoRequestStats()
|
||||
self.SwitchState(STATE_DONE)
|
||||
self.SwitchColors('red')
|
||||
self.UpdateWindow()
|
||||
currentBlock = self.currentBlock
|
||||
self.GetNextIO()
|
||||
nextBlock = self.currentBlock
|
||||
if self.blockToTrackMap[currentBlock] == self.blockToTrackMap[nextBlock]:
|
||||
if (currentBlock == self.tracksBeginEnd[self.armTrack][1] and nextBlock == self.tracksBeginEnd[self.armTrack][0]) or (currentBlock + 1 == nextBlock):
|
||||
# need a special case here: to handle when we stay in transfer mode
|
||||
(self.rotBegin, self.seekBegin, self.xferBegin) = (self.timer, self.timer, self.timer)
|
||||
self.SwitchState(STATE_XFER)
|
||||
self.SwitchColors('green')
|
||||
|
||||
|
||||
|
||||
# make sure to keep the animation going!
|
||||
if self.graphics:
|
||||
self.root.after(20, self.Animate)
|
||||
|
||||
def DoRequestStats(self):
|
||||
seekTime = self.rotBegin - self.seekBegin
|
||||
rotTime = self.xferBegin - self.rotBegin
|
||||
xferTime = self.timer - self.xferBegin
|
||||
totalTime = self.timer - self.seekBegin
|
||||
|
||||
if self.compute == True:
|
||||
print 'Block: %3d Seek:%3d Rotate:%3d Transfer:%3d Total:%4d' % (self.currentBlock, seekTime, rotTime, xferTime, totalTime)
|
||||
|
||||
# if int(totalTime) != int(self.totalEst):
|
||||
# print 'INTERNAL ERROR: estimate was', self.totalEst, 'whereas actual time to access block was', totalTime
|
||||
# print 'Please report this bug and as much information as possible so as to make it easy to recreate. Thanks!'
|
||||
|
||||
# update stats
|
||||
self.seekTotal += seekTime
|
||||
self.rotTotal += rotTime
|
||||
self.xferTotal += xferTime
|
||||
|
||||
|
||||
|
||||
def PrintStats(self):
|
||||
if self.compute == True:
|
||||
print '\nTOTALS Seek:%3d Rotate:%3d Transfer:%3d Total:%4d\n' % (self.seekTotal, self.rotTotal, self.xferTotal, self.timer)
|
||||
|
||||
# END: class Disk
|
||||
|
||||
|
||||
|
||||
#
|
||||
# MAIN SIMULATOR
|
||||
#
|
||||
parser = OptionParser()
|
||||
parser.add_option('-s', '--seed', default='0', help='Random seed', action='store', type='int', dest='seed')
|
||||
parser.add_option('-a', '--addr', default='-1', help='Request list (comma-separated) [-1 -> use addrDesc]', action='store', type='string', dest='addr')
|
||||
parser.add_option('-A', '--addrDesc', default='5,-1,0', help='Num requests, max request (-1->all), min request', action='store', type='string', dest='addrDesc')
|
||||
parser.add_option('-S', '--seekSpeed', default='1', help='Speed of seek', action='store', type='string', dest='seekSpeed')
|
||||
parser.add_option('-R', '--rotSpeed', default='1', help='Speed of rotation', action='store', type='string', dest='rotateSpeed')
|
||||
parser.add_option('-p', '--policy', default='FIFO', help='Scheduling policy (FIFO, SSTF, SATF, BSATF)', action='store', type='string', dest='policy')
|
||||
parser.add_option('-w', '--schedWindow', default=-1, help='Size of scheduling window (-1 -> all)', action='store', type='int', dest='window')
|
||||
parser.add_option('-o', '--skewOffset', default=0, help='Amount of skew (in blocks)', action='store', type='int', dest='skew')
|
||||
parser.add_option('-z', '--zoning', default='30,30,30', help='Angles between blocks on outer,middle,inner tracks', action='store', type='string', dest='zoning')
|
||||
parser.add_option('-G', '--graphics', default=False, help='Turn on graphics', action='store_true', dest='graphics')
|
||||
parser.add_option('-l', '--lateAddr', default='-1', help='Late: request list (comma-separated) [-1 -> random]', action='store', type='string', dest='lateAddr')
|
||||
parser.add_option('-L', '--lateAddrDesc', default='0,-1,0', help='Num requests, max request (-1->all), min request', action='store', type='string', dest='lateAddrDesc')
|
||||
parser.add_option('-c', '--compute', default=False, help='Compute the answers', action='store_true', dest='compute')
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
print 'OPTIONS seed', options.seed
|
||||
print 'OPTIONS addr', options.addr
|
||||
print 'OPTIONS addrDesc', options.addrDesc
|
||||
print 'OPTIONS seekSpeed', options.seekSpeed
|
||||
print 'OPTIONS rotateSpeed', options.rotateSpeed
|
||||
print 'OPTIONS skew', options.skew
|
||||
print 'OPTIONS window', options.window
|
||||
print 'OPTIONS policy', options.policy
|
||||
print 'OPTIONS compute', options.compute
|
||||
print 'OPTIONS graphics', options.graphics
|
||||
print 'OPTIONS zoning', options.zoning
|
||||
print 'OPTIONS lateAddr', options.lateAddr
|
||||
print 'OPTIONS lateAddrDesc', options.lateAddrDesc
|
||||
print ''
|
||||
|
||||
if options.window == 0:
|
||||
print 'Scheduling window (%d) must be positive or -1 (which means a full window)' % options.window
|
||||
sys.exit(1)
|
||||
|
||||
if options.graphics and options.compute == False:
|
||||
print '\nWARNING: Setting compute flag to True, as graphics are on\n'
|
||||
options.compute = True
|
||||
|
||||
# set up simulator info
|
||||
d = Disk(addr=options.addr, addrDesc=options.addrDesc, lateAddr=options.lateAddr, lateAddrDesc=options.lateAddrDesc,
|
||||
policy=options.policy, seekSpeed=Decimal(options.seekSpeed), rotateSpeed=Decimal(options.rotateSpeed),
|
||||
skew=options.skew, window=options.window, compute=options.compute, graphics=options.graphics, zoning=options.zoning)
|
||||
|
||||
# run simulation
|
||||
d.Go()
|
133
related_info/ostep/ostep15-disk/disk.md
Normal file
133
related_info/ostep/ostep15-disk/disk.md
Normal file
@ -0,0 +1,133 @@
|
||||
|
||||
This homework uses disk.py to familiarize you with how a modern hard
|
||||
drive works. It has a lot of different options, and unlike most of the other
|
||||
simulations, has a graphical animator to show you exactly what happens when
|
||||
the disk is in action.
|
||||
|
||||
[Note: there is also an experimental program, 'disk-precise.py', included
|
||||
in the download. This version of the simulator uses the python Decimal
|
||||
package for precise floating point computation, thus giving slightly
|
||||
better answers in some corner cases than 'disk.py'. However, it has
|
||||
not been very carefully tested, so use at your own caution.]
|
||||
|
||||
Let's do a simple example first. To run the simulator and compute some basic
|
||||
seek, rotation, and transfer times, you first have to give a list of requests
|
||||
to the simulator. This can either be done by specifying the exact requests, or
|
||||
by having the simulator generate some randomly.
|
||||
|
||||
We'll start by specifying a list of requests ourselves. Let's do a single
|
||||
request first:
|
||||
|
||||
prompt> disk.py -a 10
|
||||
|
||||
At this point you'll see:
|
||||
|
||||
...
|
||||
REQUESTS [br '10']
|
||||
|
||||
For the requests above, compute the seek, rotate, and transfer times.
|
||||
Use -c or the graphical mode (-G) to see the answers.
|
||||
|
||||
|
||||
To be able to compute the seek, rotation, and transfer times for this request,
|
||||
you'll have to know a little more information about the layout of sectors, the
|
||||
starting position of the disk head, and so forth. To see much of this
|
||||
information, run the simulator in graphical mode (-G):
|
||||
|
||||
prompt> disk.py -a 10 -G
|
||||
|
||||
|
||||
At this point, a window should appear with our simple disk on it.
|
||||
The disk head is positioned on the outside track, halfway through sector 6.
|
||||
As you can see, sector 10 (our example sector) is on the same track, about a
|
||||
third of the way around. The direction of rotation is counter-clockwise.
|
||||
To run the simulation, press the "s" key while the simulator window is
|
||||
highlighted.
|
||||
|
||||
When the simulation completes, you should be able to see that the disk spent
|
||||
105 time units in rotation and 30 in transfer in order to access sector 10,
|
||||
with no seek time. Press "q" to close the simulator window.
|
||||
|
||||
To calculate this (instead of just running the simulation), you would need to
|
||||
know a few details about the disk. First, the rotational speed is by default
|
||||
set to 1 degree per time unit. Thus, to make a complete revolution, it takes
|
||||
360 time units. Second, transfer begins and ends at the halfway point between
|
||||
sectors. Thus, to read sector 10, the transfer begins halfway between 9 and 10,
|
||||
and ends halfway between 10 and 11. Finally, in the default disk, there are
|
||||
12 sectors per track, meaning that each sector takes up 30 degrees of the
|
||||
rotational space. Thus, to read a sector, it takes 30 time units (given our
|
||||
default speed of rotation).
|
||||
|
||||
With this information in hand, you now should be able to compute the seek,
|
||||
rotation, and transfer times for accessing sector 10. Because the head starts
|
||||
on the same track as 10, there is no seek time. Because the disk rotates at
|
||||
1 degree / time unit, it takes 105 time units to get to the beginning of sector
|
||||
10, halfway between 9 and 10 (note that it is exactly 90 degrees to the middle
|
||||
of sector 9, and another 15 to the halfway point). Finally, to transfer the
|
||||
sector takes 30 time units.
|
||||
|
||||
Now let's do a slightly more complex example:
|
||||
|
||||
prompt> disk.py -a 10,11 -G
|
||||
|
||||
|
||||
In this case, we're transferring two sectors, 10 and 11. How long will it take?
|
||||
Try guessing before running the simulation!
|
||||
|
||||
As you probably guessed, this simulation takes just 30 time units longer, to
|
||||
transfer the next sector 11. Thus, the seek and rotate times remain the same,
|
||||
but the transfer time for the requests is doubled. You can in fact see these
|
||||
sums across the top of the simulator window; they also get printed out to the
|
||||
console as follows:
|
||||
|
||||
...
|
||||
Sector: 10 Seek: 0 Rotate:105 Transfer: 30 Total: 135
|
||||
Sector: 11 Seek: 0 Rotate: 0 Transfer: 30 Total: 30
|
||||
TOTALS Seek: 0 Rotate:105 Transfer: 60 Total: 165
|
||||
|
||||
|
||||
Now let's do an example with a seek. Try the following set of requests:
|
||||
|
||||
prompt> disk.py -a 10,18 -G
|
||||
|
||||
|
||||
To compute how long this will take, you need to know how long a seek will
|
||||
take. The distance between each track is by default 40 distance units, and the
|
||||
default rate of seeking is 1 distance unit per unit time. Thus, a seek from
|
||||
the outer track to the middle track takes 40 time units.
|
||||
|
||||
You'd also have to know the scheduling policy. The default is FIFO, though, so
|
||||
for now you can just compute the request times assuming the processing order
|
||||
matches the list specified via the "-a" flag.
|
||||
|
||||
To compute how long it will take the disk to service these requests, we first
|
||||
compute how long it takes to access sector 10, which we know from above to be
|
||||
135 time units (105 rotating, 30 transferring). Once this request is complete,
|
||||
the disk begins to seek to the middle track where sector 18 lies, taking 40
|
||||
time units. Then the disk rotates to sector 18, and transfers it for 30 time
|
||||
units, thus completing the simulation. But how long does this final rotation
|
||||
take?
|
||||
|
||||
To compute the rotational delay for 18, first figure out how long the disk
|
||||
would take to rotate from the end of the access to sector 10 to the beginning
|
||||
of the access to sector 18, assuming a zero-cost seek. As you can see from the
|
||||
simulator, sector 10 on the outer track is lined up with sector 22 on the middle
|
||||
track, and there are 7 sectors separating 22 from 18 (23, 12, 13, 14, 15, 16,
|
||||
and 17, as the disk spins counter-clockwise). Rotating through 7 sectors takes
|
||||
210 time units (30 per sector). However, the first part of this rotation is
|
||||
actually spent seeking to the middle track, for 40 time units. Thus, the
|
||||
actual rotational delay for accessing sector 18 is 210 minus 40, or 170 time
|
||||
units. Run the simulator to see this for yourself; note that you can run
|
||||
without graphics and with the "-c" flag to just see the results without
|
||||
seeing the graphics.
|
||||
|
||||
prompt> ./disk.py -a 10,18 -c
|
||||
...
|
||||
Sector: 10 Seek: 0 Rotate:105 Transfer: 30 Total: 135
|
||||
Sector: 18 Seek: 40 Rotate:170 Transfer: 30 Total: 240
|
||||
TOTALS Seek: 40 Rotate:275 Transfer: 60 Total: 375
|
||||
|
||||
You should now have a basic idea of how the simulator works. The questions
|
||||
below will explore some of the different options, to better help you build a
|
||||
model of how a disk really works.
|
||||
|
730
related_info/ostep/ostep15-disk/disk.py
Executable file
730
related_info/ostep/ostep15-disk/disk.py
Executable file
@ -0,0 +1,730 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
from Tkinter import *
|
||||
from types import *
|
||||
import math, random, time, sys, os
|
||||
from optparse import OptionParser
|
||||
|
||||
MAXTRACKS = 1000
|
||||
|
||||
# states that a request/disk go through
|
||||
STATE_NULL = 0
|
||||
STATE_SEEK = 1
|
||||
STATE_ROTATE = 2
|
||||
STATE_XFER = 3
|
||||
STATE_DONE = 4
|
||||
|
||||
#
|
||||
# TODO
|
||||
# XXX transfer time
|
||||
# XXX satf
|
||||
# XXX skew
|
||||
# XXX scheduling window
|
||||
# XXX sstf
|
||||
# XXX specify requests vs. random requests in range
|
||||
# XXX add new requests as old ones complete (starvation)
|
||||
# XXX run in non-graphical mode
|
||||
# XXX better graphical display (show key, long lists of requests, more timings on screen)
|
||||
# XXX be able to do "pure" sequential
|
||||
# XXX add more blocks around outer tracks (zoning)
|
||||
# XXX simple flag to make scheduling window a fairness window (-F)
|
||||
# new algs to scan and c-scan the disk?
|
||||
#
|
||||
|
||||
class Disk:
|
||||
def __init__(self, addr, addrDesc, lateAddr, lateAddrDesc,
|
||||
policy, seekSpeed, rotateSpeed, skew, window, compute,
|
||||
graphics, zoning):
|
||||
self.addr = addr
|
||||
self.addrDesc = addrDesc
|
||||
self.lateAddr = lateAddr
|
||||
self.lateAddrDesc = lateAddrDesc
|
||||
self.policy = policy
|
||||
self.seekSpeed = seekSpeed
|
||||
self.rotateSpeed = rotateSpeed
|
||||
self.skew = skew
|
||||
self.window = window
|
||||
self.compute = compute
|
||||
self.graphics = graphics
|
||||
self.zoning = zoning
|
||||
|
||||
# figure out zones first, to figure out the max possible request
|
||||
self.InitBlockLayout()
|
||||
|
||||
# figure out requests
|
||||
random.seed(options.seed)
|
||||
self.requests = self.MakeRequests(self.addr, self.addrDesc)
|
||||
self.lateRequests = self.MakeRequests(self.lateAddr, self.lateAddrDesc)
|
||||
|
||||
# graphical startup
|
||||
self.width = 500
|
||||
if self.graphics:
|
||||
self.root = Tk()
|
||||
tmpLen = len(self.requests)
|
||||
if len(self.lateRequests) > 0:
|
||||
tmpLen += len(self.lateRequests)
|
||||
self.canvas = Canvas(self.root, width=410, height=460 + ((tmpLen / 20) * 20))
|
||||
self.canvas.pack()
|
||||
|
||||
# fairness stuff
|
||||
if self.policy == 'BSATF' and self.window != -1:
|
||||
self.fairWindow = self.window
|
||||
else:
|
||||
self.fairWindow = -1
|
||||
|
||||
print 'REQUESTS', self.requests
|
||||
print ''
|
||||
|
||||
# for late requests
|
||||
self.lateCount = 0
|
||||
if len(self.lateRequests) > 0:
|
||||
print 'LATE REQUESTS', self.lateRequests
|
||||
print ''
|
||||
|
||||
if self.compute == False:
|
||||
print ''
|
||||
print 'For the requests above, compute the seek, rotate, and transfer times.'
|
||||
print 'Use -c or the graphical mode (-G) to see the answers.'
|
||||
print ''
|
||||
|
||||
# BINDINGS
|
||||
if self.graphics:
|
||||
self.root.bind('s', self.Start)
|
||||
self.root.bind('p', self.Pause)
|
||||
self.root.bind('q', self.Exit)
|
||||
|
||||
# TRACK INFO
|
||||
self.tracks = {}
|
||||
self.trackWidth = 40
|
||||
self.tracks[0] = 140
|
||||
self.tracks[1] = self.tracks[0] - self.trackWidth
|
||||
self.tracks[2] = self.tracks[1] - self.trackWidth
|
||||
|
||||
if (self.seekSpeed > 1 and self.trackWidth % self.seekSpeed != 0):
|
||||
print 'Seek speed (%d) must divide evenly into track width (%d)' % (self.seekSpeed, self.trackWidth)
|
||||
sys.exit(1)
|
||||
if self.seekSpeed < 1:
|
||||
x = (self.trackWidth / self.seekSpeed)
|
||||
y = int(float(self.trackWidth) / float(self.seekSpeed))
|
||||
if float(x) != float(y):
|
||||
print 'Seek speed (%d) must divide evenly into track width (%d)' % (self.seekSpeed, self.trackWidth)
|
||||
sys.exit(1)
|
||||
|
||||
# DISK SURFACE
|
||||
self.cx = self.width/2.0
|
||||
self.cy = self.width/2.0
|
||||
if self.graphics:
|
||||
self.canvas.create_rectangle(self.cx-175, 30, self.cx - 20, 80, fill='gray', outline='black')
|
||||
self.platterSize = 320
|
||||
ps2 = self.platterSize / 2.0
|
||||
if self.graphics:
|
||||
self.canvas.create_oval(self.cx-ps2, self.cy-ps2, self.cx+ps2, self.cy + ps2, fill='darkgray', outline='black')
|
||||
for i in range(len(self.tracks)):
|
||||
t = self.tracks[i] - (self.trackWidth / 2.0)
|
||||
if self.graphics:
|
||||
self.canvas.create_oval(self.cx - t, self.cy - t, self.cx + t, self.cy + t, fill='', outline='black', width=1.0)
|
||||
|
||||
# SPINDLE
|
||||
self.spindleX = self.cx
|
||||
self.spindleY = self.cy
|
||||
if self.graphics:
|
||||
self.spindleID = self.canvas.create_oval(self.spindleX-3, self.spindleY-3, self.spindleX+3, self.spindleY+3, fill='orange', outline='black')
|
||||
|
||||
# DISK ARM
|
||||
self.armTrack = 0
|
||||
self.armSpeedBase = float(seekSpeed)
|
||||
self.armSpeed = float(seekSpeed)
|
||||
|
||||
distFromSpindle = self.tracks[self.armTrack]
|
||||
self.armWidth = 20
|
||||
self.headWidth = 10
|
||||
|
||||
self.armX = self.spindleX - (distFromSpindle * math.cos(math.radians(0)))
|
||||
self.armX1 = self.armX - self.armWidth
|
||||
self.armX2 = self.armX + self.armWidth
|
||||
self.armY1 = 50.0
|
||||
self.armY2 = self.width / 2.0
|
||||
|
||||
self.headX1 = self.armX - self.headWidth
|
||||
self.headX2 = self.armX + self.headWidth
|
||||
self.headY1 = (self.width/2.0) - self.headWidth
|
||||
self.headY2 = (self.width/2.0) + self.headWidth
|
||||
|
||||
if self.graphics:
|
||||
self.armID = self.canvas.create_rectangle(self.armX1, self.armY1, self.armX2, self.armY2, fill='gray', outline='black')
|
||||
self.headID = self.canvas.create_rectangle(self.headX1, self.headY1, self.headX2, self.headY2, fill='gray', outline='black')
|
||||
|
||||
self.targetSize = 10.0
|
||||
if self.graphics:
|
||||
sz = self.targetSize
|
||||
self.targetID = self.canvas.create_oval(self.armX1-sz, self.armY1-sz, self.armX1+sz, self.armY1+sz, fill='orange', outline='')
|
||||
|
||||
# IO QUEUE
|
||||
self.queueX = 20
|
||||
self.queueY = 450
|
||||
|
||||
self.requestCount = 0
|
||||
self.requestQueue = []
|
||||
self.requestState = []
|
||||
self.queueBoxSize = 20
|
||||
self.queueBoxID = {}
|
||||
self.queueTxtID = {}
|
||||
|
||||
# draw each box
|
||||
for index in range(len(self.requests)):
|
||||
self.AddQueueEntry(int(self.requests[index]), index)
|
||||
if self.graphics:
|
||||
self.canvas.create_text(self.queueX - 5, self.queueY - 20, anchor='w', text='Queue:')
|
||||
|
||||
# scheduling window
|
||||
self.currWindow = self.window
|
||||
|
||||
# draw current limits of queue
|
||||
if self.graphics:
|
||||
self.windowID = -1
|
||||
self.DrawWindow()
|
||||
|
||||
# initial scheduling info
|
||||
self.currentIndex = -1
|
||||
self.currentBlock = -1
|
||||
|
||||
# initial state of disk (vs seeking, rotating, transferring)
|
||||
self.state = STATE_NULL
|
||||
|
||||
# DRAW BLOCKS on the TRACKS
|
||||
for bid in range(len(self.blockInfoList)):
|
||||
(track, angle, name) = self.blockInfoList[bid]
|
||||
if self.graphics:
|
||||
distFromSpindle = self.tracks[track]
|
||||
xc = self.spindleX + (distFromSpindle * math.cos(math.radians(angle)))
|
||||
yc = self.spindleY + (distFromSpindle * math.sin(math.radians(angle)))
|
||||
cid = self.canvas.create_text(xc, yc, text=name, anchor='center')
|
||||
else:
|
||||
cid = -1
|
||||
self.blockInfoList[bid] = (track, angle, name, cid)
|
||||
|
||||
# angle of rotation
|
||||
self.angle = 0.0
|
||||
|
||||
# TIME INFO
|
||||
if self.graphics:
|
||||
self.timeID = self.canvas.create_text(10, 10, text='Time: 0.00', anchor='w')
|
||||
self.canvas.create_rectangle(95,0,200,18, fill='orange', outline='orange')
|
||||
self.seekID = self.canvas.create_text(100, 10, text='Seek: 0.00', anchor='w')
|
||||
self.canvas.create_rectangle(195,0,300,18, fill='lightblue', outline='lightblue')
|
||||
self.rotID = self.canvas.create_text(200, 10, text='Rotate: 0.00', anchor='w')
|
||||
self.canvas.create_rectangle(295,0,400,18, fill='green', outline='green')
|
||||
self.xferID = self.canvas.create_text(300, 10, text='Transfer: 0.00', anchor='w')
|
||||
self.canvas.create_text(320, 40, text='"s" to start', anchor='w')
|
||||
self.canvas.create_text(320, 60, text='"p" to pause', anchor='w')
|
||||
self.canvas.create_text(320, 80, text='"q" to quit', anchor='w')
|
||||
self.timer = 0
|
||||
|
||||
# STATS
|
||||
self.seekTotal = 0.0
|
||||
self.rotTotal = 0.0
|
||||
self.xferTotal = 0.0
|
||||
|
||||
# set up animation loop
|
||||
if self.graphics:
|
||||
self.doAnimate = True
|
||||
else:
|
||||
self.doAnimate = False
|
||||
self.isDone = False
|
||||
|
||||
# call this to start simulation
|
||||
def Go(self):
|
||||
if options.graphics:
|
||||
self.root.mainloop()
|
||||
else:
|
||||
self.GetNextIO()
|
||||
while self.isDone == False:
|
||||
self.Animate()
|
||||
|
||||
# crappy error message
|
||||
def PrintAddrDescMessage(self, value):
|
||||
print 'Bad address description (%s)' % value
|
||||
print 'The address description must be a comma-separated list of length three, without spaces.'
|
||||
print 'For example, "10,100,0" would indicate that 10 addresses should be generated, with'
|
||||
print '100 as the maximum value, and 0 as the minumum. A max of -1 means just use the highest'
|
||||
print 'possible value as the max address to generate.'
|
||||
sys.exit(1)
|
||||
|
||||
#
|
||||
# ZONES AND BLOCK LAYOUT
|
||||
#
|
||||
def InitBlockLayout(self):
|
||||
self.blockInfoList = []
|
||||
self.blockToTrackMap = {}
|
||||
self.blockToAngleMap = {}
|
||||
self.tracksBeginEnd = {}
|
||||
self.blockAngleOffset = []
|
||||
|
||||
zones = self.zoning.split(',')
|
||||
assert(len(zones) == 3)
|
||||
for i in range(len(zones)):
|
||||
self.blockAngleOffset.append(int(zones[i]) / 2)
|
||||
|
||||
track = 0 # outer track
|
||||
angleOffset = 2 * self.blockAngleOffset[track]
|
||||
for angle in range(0, 360, angleOffset):
|
||||
block = angle / angleOffset
|
||||
self.blockToTrackMap[block] = track
|
||||
self.blockToAngleMap[block] = angle
|
||||
self.blockInfoList.append((track, angle, block))
|
||||
self.tracksBeginEnd[track] = (0, block)
|
||||
pblock = block + 1
|
||||
|
||||
track = 1 # middle track
|
||||
skew = self.skew
|
||||
angleOffset = 2 * self.blockAngleOffset[track]
|
||||
for angle in range(0, 360, angleOffset):
|
||||
block = (angle / angleOffset) + pblock
|
||||
self.blockToTrackMap[block] = track
|
||||
self.blockToAngleMap[block] = angle + (angleOffset * skew)
|
||||
self.blockInfoList.append((track, angle + (angleOffset * skew), block))
|
||||
self.tracksBeginEnd[track] = (pblock, block)
|
||||
pblock = block + 1
|
||||
|
||||
track = 2 # inner track
|
||||
skew = 2 * self.skew
|
||||
angleOffset = 2 * self.blockAngleOffset[track]
|
||||
for angle in range(0, 360, angleOffset):
|
||||
block = (angle / angleOffset) + pblock
|
||||
self.blockToTrackMap[block] = track
|
||||
self.blockToAngleMap[block] = angle + (angleOffset * skew)
|
||||
self.blockInfoList.append((track, angle + (angleOffset * skew), block))
|
||||
self.tracksBeginEnd[track] = (pblock, block)
|
||||
self.maxBlock = pblock
|
||||
# print 'MAX BLOCK:', self.maxBlock
|
||||
|
||||
# adjust angle to starting position relative
|
||||
for i in self.blockToAngleMap:
|
||||
self.blockToAngleMap[i] = (self.blockToAngleMap[i] + 180) % 360
|
||||
|
||||
# print 'btoa map', self.blockToAngleMap
|
||||
# print 'btot map', self.blockToTrackMap
|
||||
# print 'bao', self.blockAngleOffset
|
||||
|
||||
def MakeRequests(self, addr, addrDesc):
|
||||
(numRequests, maxRequest, minRequest) = (0, 0, 0)
|
||||
if addr == '-1':
|
||||
# first extract values from descriptor
|
||||
desc = addrDesc.split(',')
|
||||
if len(desc) != 3:
|
||||
self.PrintAddrDescMessage(addrDesc)
|
||||
(numRequests, maxRequest, minRequest) = (int(desc[0]), int(desc[1]), int(desc[2]))
|
||||
if maxRequest == -1:
|
||||
maxRequest = self.maxBlock
|
||||
# now make list
|
||||
tmpList = []
|
||||
for i in range(numRequests):
|
||||
tmpList.append(int(random.random() * maxRequest) + minRequest)
|
||||
return tmpList
|
||||
else:
|
||||
return addr.split(',')
|
||||
|
||||
#
|
||||
# BUTTONS
|
||||
#
|
||||
def Start(self, event):
|
||||
self.GetNextIO()
|
||||
self.doAnimate = True
|
||||
self.Animate()
|
||||
|
||||
def Pause(self, event):
|
||||
if self.doAnimate == False:
|
||||
self.doAnimate = True
|
||||
else:
|
||||
self.doAnimate = False
|
||||
|
||||
def Exit(self, event):
|
||||
sys.exit(0)
|
||||
|
||||
#
|
||||
# CORE SIMULATION and ANIMATION
|
||||
#
|
||||
def UpdateTime(self):
|
||||
if self.graphics:
|
||||
self.canvas.itemconfig(self.timeID, text='Time: ' + str(self.timer))
|
||||
self.canvas.itemconfig(self.seekID, text='Seek: ' + str(self.seekTotal))
|
||||
self.canvas.itemconfig(self.rotID, text='Rotate: ' + str(self.rotTotal))
|
||||
self.canvas.itemconfig(self.xferID, text='Transfer: ' + str(self.xferTotal))
|
||||
|
||||
def AddRequest(self, block):
|
||||
self.AddQueueEntry(block, len(self.requestQueue))
|
||||
|
||||
def QueueMap(self, index):
|
||||
numPerRow = 400 / self.queueBoxSize
|
||||
return (index % numPerRow, index / numPerRow)
|
||||
|
||||
def DrawWindow(self):
|
||||
if self.window == -1:
|
||||
return
|
||||
(col, row) = self.QueueMap(self.currWindow)
|
||||
if col == 0:
|
||||
(col, row) = (20, row - 1)
|
||||
if self.windowID != -1:
|
||||
self.canvas.delete(self.windowID)
|
||||
self.windowID = self.canvas.create_line(self.queueX + (col * 20) - 10, self.queueY - 13 + (row * 20),
|
||||
self.queueX + (col * 20) - 10, self.queueY + 13 + (row * 20), width=2)
|
||||
|
||||
def AddQueueEntry(self, block, index):
|
||||
self.requestQueue.append((block, index))
|
||||
self.requestState.append(STATE_NULL)
|
||||
if self.graphics:
|
||||
(col, row) = self.QueueMap(index)
|
||||
sizeHalf = self.queueBoxSize / 2.0
|
||||
(cx, cy) = (self.queueX + (col * self.queueBoxSize), self.queueY + (row * self.queueBoxSize))
|
||||
self.queueBoxID[index] = self.canvas.create_rectangle(cx - sizeHalf, cy - sizeHalf, cx + sizeHalf, cy + sizeHalf, fill='white')
|
||||
self.queueTxtID[index] = self.canvas.create_text(cx, cy, anchor='center', text=str(block))
|
||||
|
||||
def SwitchColors(self, c):
|
||||
if self.graphics:
|
||||
self.canvas.itemconfig(self.queueBoxID[self.currentIndex], fill=c)
|
||||
self.canvas.itemconfig(self.targetID, fill=c)
|
||||
|
||||
def SwitchState(self, newState):
|
||||
self.state = newState
|
||||
self.requestState[self.currentIndex] = newState
|
||||
|
||||
def RadiallyCloseTo(self, a1, a2):
|
||||
if a1 > a2:
|
||||
v = a1 - a2
|
||||
else:
|
||||
v = a2 - a1
|
||||
if v < self.rotateSpeed:
|
||||
return True
|
||||
return False
|
||||
|
||||
def DoneWithTransfer(self):
|
||||
angleOffset = self.blockAngleOffset[self.armTrack]
|
||||
# if int(self.angle) == (self.blockToAngleMap[self.currentBlock] + angleOffset) % 360:
|
||||
if self.RadiallyCloseTo(self.angle, float((self.blockToAngleMap[self.currentBlock] + angleOffset) % 360)):
|
||||
# print 'END TRANSFER', self.angle, self.timer
|
||||
self.SwitchState(STATE_DONE)
|
||||
self.requestCount += 1
|
||||
return True
|
||||
return False
|
||||
|
||||
def DoneWithRotation(self):
|
||||
angleOffset = self.blockAngleOffset[self.armTrack]
|
||||
# XXX there is a weird bug in here
|
||||
# print self.timer, 'ROTATE:: ', self.currentBlock, 'currangle: ', self.angle, ' - mapangle: ', self.blockToAngleMap[self.currentBlock]
|
||||
# print ' angleOffset ', angleOffset
|
||||
# print ' blockMap ', (self.blockToAngleMap[self.currentBlock] - angleOffset) % 360
|
||||
# print ' self.angle ', self.angle, int(self.angle)
|
||||
# if int(self.angle) == (self.blockToAngleMap[self.currentBlock] - angleOffset) % 360:
|
||||
if self.RadiallyCloseTo(self.angle, float((self.blockToAngleMap[self.currentBlock] - angleOffset) % 360)):
|
||||
self.SwitchState(STATE_XFER)
|
||||
# print ' --> DONE WITH ROTATION!', self.timer
|
||||
return True
|
||||
return False
|
||||
|
||||
def PlanSeek(self, track):
|
||||
self.seekBegin = self.timer
|
||||
self.SwitchColors('orange')
|
||||
self.SwitchState(STATE_SEEK)
|
||||
if track == self.armTrack:
|
||||
self.rotBegin = self.timer
|
||||
self.SwitchColors('lightblue')
|
||||
self.SwitchState(STATE_ROTATE)
|
||||
return
|
||||
self.armTarget = track
|
||||
self.armTargetX1 = self.spindleX - self.tracks[track] - (self.trackWidth / 2.0)
|
||||
if track >= self.armTrack:
|
||||
self.armSpeed = self.armSpeedBase
|
||||
else:
|
||||
self.armSpeed = - self.armSpeedBase
|
||||
|
||||
def DoneWithSeek(self):
|
||||
# move the disk arm
|
||||
self.armX1 += self.armSpeed
|
||||
self.armX2 += self.armSpeed
|
||||
self.headX1 += self.armSpeed
|
||||
self.headX2 += self.armSpeed
|
||||
# update it on screen
|
||||
if self.graphics:
|
||||
self.canvas.coords(self.armID, self.armX1, self.armY1, self.armX2, self.armY2)
|
||||
self.canvas.coords(self.headID, self.headX1, self.headY1, self.headX2, self.headY2)
|
||||
# check if done
|
||||
if (self.armSpeed > 0.0 and self.armX1 >= self.armTargetX1) or (self.armSpeed < 0.0 and self.armX1 <= self.armTargetX1):
|
||||
self.armTrack = self.armTarget
|
||||
return True
|
||||
return False
|
||||
|
||||
def DoSATF(self, rList):
|
||||
minBlock = -1
|
||||
minIndex = -1
|
||||
minEst = -1
|
||||
|
||||
# print '**** DoSATF ****', rList
|
||||
for (block, index) in rList:
|
||||
if self.requestState[index] == STATE_DONE:
|
||||
continue
|
||||
track = self.blockToTrackMap[block]
|
||||
angle = self.blockToAngleMap[block]
|
||||
# print 'track', track, 'angle', angle
|
||||
|
||||
# estimate seek time
|
||||
dist = int(math.fabs(self.armTrack - track))
|
||||
seekEst = (self.trackWidth / self.armSpeedBase) * dist
|
||||
# print 'dist', dist
|
||||
# print 'seekEst', seekEst
|
||||
|
||||
# estimate rotate time
|
||||
angleOffset = self.blockAngleOffset[track]
|
||||
# print 'angleOffset', angleOffset
|
||||
# print 'self.angle', self.angle
|
||||
angleAtArrival = (self.angle + (seekEst * self.rotateSpeed))
|
||||
while angleAtArrival > 360.0:
|
||||
angleAtArrival -= 360.0
|
||||
# print 'self.rotateSpeed', self.rotateSpeed
|
||||
# print 'angleAtArrival', angleAtArrival
|
||||
rotDist = ((angle - angleOffset) - angleAtArrival)
|
||||
while rotDist > 360.0:
|
||||
rotDist -= 360.0
|
||||
while rotDist < 0.0:
|
||||
rotDist += 360.0
|
||||
rotEst = rotDist / self.rotateSpeed
|
||||
# print 'rotEst', rotDist, self.rotateSpeed, ' -> ', rotEst
|
||||
|
||||
# finally, transfer
|
||||
xferEst = (angleOffset * 2.0) / self.rotateSpeed
|
||||
|
||||
# print 'xferEst', xferEst
|
||||
|
||||
totalEst = seekEst + rotEst + xferEst
|
||||
# print 'totalEst', seekEst, rotEst, xferEst, ' -> ', totalEst
|
||||
|
||||
# print '--> block:%d seek:%d rotate:%d xfer:%d est:%d' % (block, seekEst, rotEst, xferEst, totalEst)
|
||||
|
||||
# should probably pick one on same track in case of a TIE
|
||||
if minEst == -1 or totalEst < minEst:
|
||||
minEst = totalEst
|
||||
minBlock = block
|
||||
minIndex = index
|
||||
# END loop
|
||||
|
||||
# when done
|
||||
self.totalEst = minEst
|
||||
assert(minBlock != -1)
|
||||
assert(minIndex != -1)
|
||||
return (minBlock, minIndex)
|
||||
|
||||
#
|
||||
# actually doesn't quite do SSTF
|
||||
# just finds all the blocks on the nearest track
|
||||
# (whatever that may be) and returns it as a list
|
||||
#
|
||||
def DoSSTF(self, rList):
|
||||
minDist = MAXTRACKS
|
||||
minBlock = -1
|
||||
trackList = [] # all the blocks on a track
|
||||
|
||||
for (block, index) in rList:
|
||||
if self.requestState[index] == STATE_DONE:
|
||||
continue
|
||||
track = self.blockToTrackMap[block]
|
||||
dist = int(math.fabs(self.armTrack - track))
|
||||
if dist < minDist:
|
||||
trackList = []
|
||||
trackList.append((block, index))
|
||||
minDist = dist
|
||||
elif dist == minDist:
|
||||
trackList.append((block, index))
|
||||
assert(trackList != [])
|
||||
return trackList
|
||||
|
||||
def UpdateWindow(self):
|
||||
if self.fairWindow == -1 and self.currWindow > 0 and self.currWindow < len(self.requestQueue):
|
||||
self.currWindow += 1
|
||||
if self.graphics:
|
||||
self.DrawWindow()
|
||||
|
||||
def GetWindow(self):
|
||||
if self.currWindow <= -1:
|
||||
return len(self.requestQueue)
|
||||
else:
|
||||
if self.fairWindow != -1:
|
||||
if self.requestCount > 0 and (self.requestCount % self.fairWindow == 0):
|
||||
self.currWindow = self.currWindow + self.fairWindow
|
||||
if self.currWindow > len(self.requestQueue):
|
||||
self.currWindow = len(self.requestQueue)
|
||||
if self.graphics:
|
||||
self.DrawWindow()
|
||||
return self.currWindow
|
||||
else:
|
||||
return self.currWindow
|
||||
|
||||
def GetNextIO(self):
|
||||
# check if done: if so, print stats and end animation
|
||||
if self.requestCount == len(self.requestQueue):
|
||||
self.UpdateTime()
|
||||
self.PrintStats()
|
||||
self.doAnimate = False
|
||||
self.isDone = True
|
||||
return
|
||||
|
||||
# do policy: should set currentBlock,
|
||||
if self.policy == 'FIFO':
|
||||
(self.currentBlock, self.currentIndex) = self.requestQueue[self.requestCount]
|
||||
self.DoSATF(self.requestQueue[self.requestCount:self.requestCount+1])
|
||||
elif self.policy == 'SATF' or self.policy == 'BSATF':
|
||||
(self.currentBlock, self.currentIndex) = self.DoSATF(self.requestQueue[0:self.GetWindow()])
|
||||
elif self.policy == 'SSTF':
|
||||
# first, find all the blocks on a given track (given window constraints)
|
||||
trackList = self.DoSSTF(self.requestQueue[0:self.GetWindow()])
|
||||
# then, do SATF on those blocks (otherwise, will not do them in obvious order)
|
||||
(self.currentBlock, self.currentIndex) = self.DoSATF(trackList)
|
||||
else:
|
||||
print 'policy (%s) not implemented' % self.policy
|
||||
sys.exit(1)
|
||||
|
||||
# once best block is decided, go ahead and do the seek
|
||||
self.PlanSeek(self.blockToTrackMap[self.currentBlock])
|
||||
|
||||
# add another block?
|
||||
if len(self.lateRequests) > 0 and self.lateCount < len(self.lateRequests):
|
||||
self.AddRequest(self.lateRequests[self.lateCount])
|
||||
self.lateCount += 1
|
||||
|
||||
def Animate(self):
|
||||
if self.graphics == True and self.doAnimate == False:
|
||||
self.root.after(20, self.Animate)
|
||||
return
|
||||
|
||||
# timer
|
||||
self.timer += 1
|
||||
self.UpdateTime()
|
||||
|
||||
# see which blocks are rotating on the disk
|
||||
# print 'SELF ANGLE', self.angle
|
||||
self.angle = self.angle + self.rotateSpeed
|
||||
if self.angle >= 360.0:
|
||||
self.angle = 0.0
|
||||
|
||||
# move the blocks
|
||||
if self.graphics:
|
||||
for (track, angle, name, cid) in self.blockInfoList:
|
||||
distFromSpindle = self.tracks[track]
|
||||
na = angle - self.angle
|
||||
xc = self.spindleX + (distFromSpindle * math.cos(math.radians(na)))
|
||||
yc = self.spindleY + (distFromSpindle * math.sin(math.radians(na)))
|
||||
if self.graphics:
|
||||
self.canvas.coords(cid, xc, yc)
|
||||
if self.currentBlock == name:
|
||||
sz = self.targetSize
|
||||
self.canvas.coords(self.targetID, xc-sz, yc-sz, xc+sz, yc+sz)
|
||||
|
||||
# move the arm OR wait for a rotational delay
|
||||
if self.state == STATE_SEEK:
|
||||
if self.DoneWithSeek():
|
||||
self.rotBegin = self.timer
|
||||
self.SwitchState(STATE_ROTATE)
|
||||
self.SwitchColors('lightblue')
|
||||
if self.state == STATE_ROTATE:
|
||||
# check for read (disk arm must be settled)
|
||||
if self.DoneWithRotation():
|
||||
self.xferBegin = self.timer
|
||||
self.SwitchState(STATE_XFER)
|
||||
self.SwitchColors('green')
|
||||
if self.state == STATE_XFER:
|
||||
if self.DoneWithTransfer():
|
||||
self.DoRequestStats()
|
||||
self.SwitchState(STATE_DONE)
|
||||
self.SwitchColors('red')
|
||||
self.UpdateWindow()
|
||||
currentBlock = self.currentBlock
|
||||
self.GetNextIO()
|
||||
nextBlock = self.currentBlock
|
||||
if self.blockToTrackMap[currentBlock] == self.blockToTrackMap[nextBlock]:
|
||||
if (currentBlock == self.tracksBeginEnd[self.armTrack][1] and nextBlock == self.tracksBeginEnd[self.armTrack][0]) or (currentBlock + 1 == nextBlock):
|
||||
# need a special case here: to handle when we stay in transfer mode
|
||||
(self.rotBegin, self.seekBegin, self.xferBegin) = (self.timer, self.timer, self.timer)
|
||||
self.SwitchState(STATE_XFER)
|
||||
self.SwitchColors('green')
|
||||
|
||||
|
||||
|
||||
# make sure to keep the animation going!
|
||||
if self.graphics:
|
||||
self.root.after(20, self.Animate)
|
||||
|
||||
def DoRequestStats(self):
|
||||
seekTime = self.rotBegin - self.seekBegin
|
||||
rotTime = self.xferBegin - self.rotBegin
|
||||
xferTime = self.timer - self.xferBegin
|
||||
totalTime = self.timer - self.seekBegin
|
||||
|
||||
if self.compute == True:
|
||||
print 'Block: %3d Seek:%3d Rotate:%3d Transfer:%3d Total:%4d' % (self.currentBlock, seekTime, rotTime, xferTime, totalTime)
|
||||
|
||||
# if int(totalTime) != int(self.totalEst):
|
||||
# print 'INTERNAL ERROR: estimate was', self.totalEst, 'whereas actual time to access block was', totalTime
|
||||
# print 'Please report this bug and as much information as possible so as to make it easy to recreate. Thanks!'
|
||||
|
||||
# update stats
|
||||
self.seekTotal += seekTime
|
||||
self.rotTotal += rotTime
|
||||
self.xferTotal += xferTime
|
||||
|
||||
|
||||
|
||||
def PrintStats(self):
|
||||
if self.compute == True:
|
||||
print '\nTOTALS Seek:%3d Rotate:%3d Transfer:%3d Total:%4d\n' % (self.seekTotal, self.rotTotal, self.xferTotal, self.timer)
|
||||
|
||||
# END: class Disk
|
||||
|
||||
|
||||
|
||||
#
|
||||
# MAIN SIMULATOR
|
||||
#
|
||||
parser = OptionParser()
|
||||
parser.add_option('-s', '--seed', default='0', help='Random seed', action='store', type='int', dest='seed')
|
||||
parser.add_option('-a', '--addr', default='-1', help='Request list (comma-separated) [-1 -> use addrDesc]', action='store', type='string', dest='addr')
|
||||
parser.add_option('-A', '--addrDesc', default='5,-1,0', help='Num requests, max request (-1->all), min request', action='store', type='string', dest='addrDesc')
|
||||
parser.add_option('-S', '--seekSpeed', default='1', help='Speed of seek', action='store', type='string', dest='seekSpeed')
|
||||
parser.add_option('-R', '--rotSpeed', default='1', help='Speed of rotation', action='store', type='string', dest='rotateSpeed')
|
||||
parser.add_option('-p', '--policy', default='FIFO', help='Scheduling policy (FIFO, SSTF, SATF, BSATF)', action='store', type='string', dest='policy')
|
||||
parser.add_option('-w', '--schedWindow', default=-1, help='Size of scheduling window (-1 -> all)', action='store', type='int', dest='window')
|
||||
parser.add_option('-o', '--skewOffset', default=0, help='Amount of skew (in blocks)', action='store', type='int', dest='skew')
|
||||
parser.add_option('-z', '--zoning', default='30,30,30', help='Angles between blocks on outer,middle,inner tracks', action='store', type='string', dest='zoning')
|
||||
parser.add_option('-G', '--graphics', default=False, help='Turn on graphics', action='store_true', dest='graphics')
|
||||
parser.add_option('-l', '--lateAddr', default='-1', help='Late: request list (comma-separated) [-1 -> random]', action='store', type='string', dest='lateAddr')
|
||||
parser.add_option('-L', '--lateAddrDesc', default='0,-1,0', help='Num requests, max request (-1->all), min request', action='store', type='string', dest='lateAddrDesc')
|
||||
parser.add_option('-c', '--compute', default=False, help='Compute the answers', action='store_true', dest='compute')
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
print 'OPTIONS seed', options.seed
|
||||
print 'OPTIONS addr', options.addr
|
||||
print 'OPTIONS addrDesc', options.addrDesc
|
||||
print 'OPTIONS seekSpeed', options.seekSpeed
|
||||
print 'OPTIONS rotateSpeed', options.rotateSpeed
|
||||
print 'OPTIONS skew', options.skew
|
||||
print 'OPTIONS window', options.window
|
||||
print 'OPTIONS policy', options.policy
|
||||
print 'OPTIONS compute', options.compute
|
||||
print 'OPTIONS graphics', options.graphics
|
||||
print 'OPTIONS zoning', options.zoning
|
||||
print 'OPTIONS lateAddr', options.lateAddr
|
||||
print 'OPTIONS lateAddrDesc', options.lateAddrDesc
|
||||
print ''
|
||||
|
||||
if options.window == 0:
|
||||
print 'Scheduling window (%d) must be positive or -1 (which means a full window)' % options.window
|
||||
sys.exit(1)
|
||||
|
||||
if options.graphics and options.compute == False:
|
||||
print '\nWARNING: Setting compute flag to True, as graphics are on\n'
|
||||
options.compute = True
|
||||
|
||||
# set up simulator info
|
||||
d = Disk(addr=options.addr, addrDesc=options.addrDesc, lateAddr=options.lateAddr, lateAddrDesc=options.lateAddrDesc,
|
||||
policy=options.policy, seekSpeed=float(options.seekSpeed), rotateSpeed=float(options.rotateSpeed),
|
||||
skew=options.skew, window=options.window, compute=options.compute, graphics=options.graphics, zoning=options.zoning)
|
||||
|
||||
# run simulation
|
||||
d.Go()
|
223
related_info/ostep/ostep16-raid.md
Normal file
223
related_info/ostep/ostep16-raid.md
Normal file
@ -0,0 +1,223 @@
|
||||
|
||||
This section introduces raid.py, a simple RAID simulator you can use to shore
|
||||
up your knowledge of how RAID systems work. It has a number of options, as we
|
||||
see below:
|
||||
|
||||
Usage: raid.py [options]
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-s SEED, --seed=SEED the random seed
|
||||
-D NUMDISKS, --numDisks=NUMDISKS
|
||||
number of disks in RAID
|
||||
-C CHUNKSIZE, --chunkSize=CHUNKSIZE
|
||||
chunk size of the RAID
|
||||
-n NUMREQUESTS, --numRequests=NUMREQUESTS
|
||||
number of requests to simulate
|
||||
-S SIZE, --reqSize=SIZE
|
||||
size of requests
|
||||
-W WORKLOAD, --workload=WORKLOAD
|
||||
either "rand" or "seq" workloads
|
||||
-w WRITEFRAC, --writeFrac=WRITEFRAC
|
||||
write fraction (100->all writes, 0->all reads)
|
||||
-R RANGE, --randRange=RANGE
|
||||
range of requests (when using "rand" workload)
|
||||
-L LEVEL, --level=LEVEL
|
||||
RAID level (0, 1, 4, 5)
|
||||
-5 RAID5TYPE, --raid5=RAID5TYPE
|
||||
RAID-5 left-symmetric "LS" or left-asym "LA"
|
||||
-r, --reverse instead of showing logical ops, show physical
|
||||
-t, --timing use timing mode, instead of mapping mode
|
||||
-c, --compute compute answers for me
|
||||
|
||||
In its basic mode, you can use it to understand how the different RAID levels
|
||||
map logical blocks to underlying disks and offsets. For example, let's say we
|
||||
wish to see how a simple striping RAID (RAID-0) with four disks does this
|
||||
mapping.
|
||||
|
||||
prompt> ./raid.py -n 5 -L 0 -R 20
|
||||
...
|
||||
LOGICAL READ from addr:16 size:4096
|
||||
Physical reads/writes?
|
||||
|
||||
LOGICAL READ from addr:8 size:4096
|
||||
Physical reads/writes?
|
||||
|
||||
LOGICAL READ from addr:10 size:4096
|
||||
Physical reads/writes?
|
||||
|
||||
LOGICAL READ from addr:15 size:4096
|
||||
Physical reads/writes?
|
||||
|
||||
LOGICAL READ from addr:9 size:4096
|
||||
Physical reads/writes?
|
||||
|
||||
In this example, we simulate five requests (-n 5), specifying RAID level zero
|
||||
(-L 0), and restrict the range of random requests to just the first twenty
|
||||
blocks of the RAID (-R 20). The result is a series of random reads to the
|
||||
first twenty blocks of the RAID; the simulator then asks you to guess which
|
||||
underlying disks/offsets were accessed to service the request, for each
|
||||
logical read.
|
||||
|
||||
In this case, calculating the answers is easy: in RAID-0, recall that the
|
||||
underlying disk and offset that services a request is calculated via modulo
|
||||
arithmetic:
|
||||
|
||||
disk = address % number_of_disks
|
||||
offset = address / number_of_disks
|
||||
|
||||
Thus, the first request to 16 should be serviced by disk 0, at offset 4. And
|
||||
so forth. You can, as usual see the answers (once you've computed them!), by
|
||||
using the handy "-c" flag to compute the results.
|
||||
|
||||
prompt> ./raid.py -R 20 -n 5 -L 0 -c
|
||||
...
|
||||
LOGICAL READ from addr:16 size:4096
|
||||
read [disk 0, offset 4]
|
||||
|
||||
LOGICAL READ from addr:8 size:4096
|
||||
read [disk 0, offset 2]
|
||||
|
||||
LOGICAL READ from addr:10 size:4096
|
||||
read [disk 2, offset 2]
|
||||
|
||||
LOGICAL READ from addr:15 size:4096
|
||||
read [disk 3, offset 3]
|
||||
|
||||
LOGICAL READ from addr:9 size:4096
|
||||
read [disk 1, offset 2]
|
||||
|
||||
|
||||
Because we like to have fun, you can also do this problem in reverse, with the
|
||||
"-r" flag. Running the simulator this way shows you the low-level disk reads
|
||||
and writes, and asks you to reverse engineer which logical request must have
|
||||
been given to the RAID:
|
||||
|
||||
prompt> ./raid.py -R 20 -n 5 -L 0 -r
|
||||
...
|
||||
LOGICAL OPERATION is ?
|
||||
read [disk 0, offset 4]
|
||||
|
||||
LOGICAL OPERATION is ?
|
||||
read [disk 0, offset 2]
|
||||
|
||||
LOGICAL OPERATION is ?
|
||||
read [disk 2, offset 2]
|
||||
|
||||
LOGICAL OPERATION is ?
|
||||
read [disk 3, offset 3]
|
||||
|
||||
LOGICAL OPERATION is ?
|
||||
read [disk 1, offset 2]
|
||||
|
||||
You can again use -c to show the answers. To get more variety, a
|
||||
different random seed (-s) can be given.
|
||||
|
||||
Even further variety is available by examining different RAID levels.
|
||||
In the simulator, RAID-0 (block striping), RAID-1 (mirroring), RAID-4
|
||||
(block-striping plus a single parity disk), and RAID-5 (block-striping with
|
||||
rotating parity) are supported.
|
||||
|
||||
In this next example, we show how to run the simulator in mirrored mode. We
|
||||
show the answers to save space:
|
||||
|
||||
prompt> ./raid.py -R 20 -n 5 -L 1 -c
|
||||
...
|
||||
LOGICAL READ from addr:16 size:4096
|
||||
read [disk 0, offset 8]
|
||||
|
||||
LOGICAL READ from addr:8 size:4096
|
||||
read [disk 0, offset 4]
|
||||
|
||||
LOGICAL READ from addr:10 size:4096
|
||||
read [disk 1, offset 5]
|
||||
|
||||
LOGICAL READ from addr:15 size:4096
|
||||
read [disk 3, offset 7]
|
||||
|
||||
LOGICAL READ from addr:9 size:4096
|
||||
read [disk 2, offset 4]
|
||||
|
||||
You might notice a few things about this example. First, the mirrored RAID-1
|
||||
assumes a striped layout (which some might call RAID-01), where logical block
|
||||
0 is mapped to the 0th block of disks 0 and 1, logical block 1 is mapped to
|
||||
the 0th blocks of disks 2 and 3, and so forth (in this four-disk example).
|
||||
Second, when reading a single block from a mirrored RAID system, the RAID has
|
||||
a choice of which of two blocks to read. In this simulator, we use a
|
||||
relatively silly way: for even-numbered logical blocks, the RAID chooses the
|
||||
even-numbered disk in the pair; the odd disk is used for odd-numbered logical
|
||||
blocks. This is done to make the results of each run easy to guess for you
|
||||
(instead of, for example, a random choice).
|
||||
|
||||
We can also explore how writes behave (instead of just reads) with the -w
|
||||
flag, which specifies the "write fraction" of a workload, i.e., the fraction
|
||||
of requests that are writes. By default, it is set to zero, and thus the
|
||||
examples so far were 100\% reads. Let's see what happens to our mirrored RAID
|
||||
when some writes are introduced:
|
||||
|
||||
prompt> ./raid.py -R 20 -n 5 -L 1 -w 100 -c
|
||||
...
|
||||
LOGICAL WRITE to addr:16 size:4096
|
||||
write [disk 0, offset 8] write [disk 1, offset 8]
|
||||
|
||||
LOGICAL WRITE to addr:8 size:4096
|
||||
write [disk 0, offset 4] write [disk 1, offset 4]
|
||||
|
||||
LOGICAL WRITE to addr:10 size:4096
|
||||
write [disk 0, offset 5] write [disk 1, offset 5]
|
||||
|
||||
LOGICAL WRITE to addr:15 size:4096
|
||||
write [disk 2, offset 7] write [disk 3, offset 7]
|
||||
|
||||
LOGICAL WRITE to addr:9 size:4096
|
||||
write [disk 2, offset 4] write [disk 3, offset 4]
|
||||
|
||||
With writes, instead of generating just a single low-level disk operation, the
|
||||
RAID must of course update both disks, and hence two writes are issued.
|
||||
Even more interesting things happen with RAID-4 and RAID-5, as you might
|
||||
guess; we'll leave the exploration of such things to you in the questions
|
||||
below.
|
||||
|
||||
The remaining options are discovered via the help flag. They are:
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-s SEED, --seed=SEED the random seed
|
||||
-D NUMDISKS, --numDisks=NUMDISKS
|
||||
number of disks in RAID
|
||||
-C CHUNKSIZE, --chunkSize=CHUNKSIZE
|
||||
chunk size of the RAID
|
||||
-n NUMREQUESTS, --numRequests=NUMREQUESTS
|
||||
number of requests to simulate
|
||||
-S SIZE, --reqSize=SIZE
|
||||
size of requests
|
||||
-W WORKLOAD, --workload=WORKLOAD
|
||||
either "rand" or "seq" workloads
|
||||
-w WRITEFRAC, --writeFrac=WRITEFRAC
|
||||
write fraction (100->all writes, 0->all reads)
|
||||
-R RANGE, --randRange=RANGE
|
||||
range of requests (when using "rand" workload)
|
||||
-L LEVEL, --level=LEVEL
|
||||
RAID level (0, 1, 4, 5)
|
||||
-5 RAID5TYPE, --raid5=RAID5TYPE
|
||||
RAID-5 left-symmetric "LS" or left-asym "LA"
|
||||
-r, --reverse instead of showing logical ops, show physical
|
||||
-t, --timing use timing mode, instead of mapping mode
|
||||
-c, --compute compute answers for me
|
||||
|
||||
|
||||
The -C flag allows you to set the chunk size of the RAID, instead of using the
|
||||
default size of one 4-KB block per chunk. The size of each request can be
|
||||
similarly adjusted with the -S flag. The default workload accesses random
|
||||
blocks; use -W sequential to explore the behavior of sequential accesses. With
|
||||
RAID-5, two different layout schemes are available, left-symmetric and
|
||||
left-asymmetric; use -5 LS or -5 LA to try those out with RAID-5 (-L 5).
|
||||
|
||||
Finally, in timing mode (-t), the simulator uses an incredibly simple disk
|
||||
model to estimate how long a set of requests takes, instead of just focusing
|
||||
on mappings. In this mode, a random request takes 10 milliseconds, whereas a
|
||||
sequential request takes 0.1 milliseconds. The disk is assumed to have a tiny
|
||||
number of blocks per track (100), and a similarly small number of tracks
|
||||
(100). You can thus use the simulator to estimate RAID performance under some
|
||||
different workloads.
|
||||
|
447
related_info/ostep/ostep16-raid.py
Executable file
447
related_info/ostep/ostep16-raid.py
Executable file
@ -0,0 +1,447 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import math
|
||||
import random
|
||||
from optparse import OptionParser
|
||||
|
||||
# minimum unit of transfer to RAID
|
||||
BLOCKSIZE = 4096
|
||||
|
||||
def convert(size):
|
||||
length = len(size)
|
||||
lastchar = size[length-1]
|
||||
if (lastchar == 'k') or (lastchar == 'K'):
|
||||
m = 1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
elif (lastchar == 'm') or (lastchar == 'M'):
|
||||
m = 1024*1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
elif (lastchar == 'g') or (lastchar == 'G'):
|
||||
m = 1024*1024*1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
else:
|
||||
nsize = int(size)
|
||||
return nsize
|
||||
|
||||
class disk:
|
||||
def __init__(self, seekTime=10, xferTime=0.1, queueLen=8):
|
||||
# these are both in milliseconds
|
||||
# seek is the time to seek (simple constant amount)
|
||||
# transfer is the time to read one block
|
||||
self.seekTime = seekTime
|
||||
self.xferTime = xferTime
|
||||
|
||||
# length of scheduling queue
|
||||
self.queueLen = queueLen
|
||||
|
||||
# current location: make it negative so that whatever
|
||||
# the first read is, it causes a seek
|
||||
self.currAddr = -10000
|
||||
|
||||
# queue
|
||||
self.queue = []
|
||||
|
||||
# disk geometry
|
||||
self.numTracks = 100
|
||||
self.blocksPerTrack = 100
|
||||
self.blocksPerDisk = self.numTracks * self.blocksPerTrack
|
||||
|
||||
# stats
|
||||
self.countIO = 0
|
||||
self.countSeq = 0
|
||||
self.countNseq = 0
|
||||
self.countRand = 0
|
||||
self.utilTime = 0
|
||||
|
||||
def stats(self):
|
||||
return (self.countIO, self.countSeq, self.countNseq, self.countRand, self.utilTime)
|
||||
|
||||
def enqueue(self, addr):
|
||||
assert(addr < self.blocksPerDisk)
|
||||
self.countIO += 1
|
||||
|
||||
# check if this is on the same track, or a different one
|
||||
currTrack = self.currAddr / self.numTracks
|
||||
newTrack = addr / self.numTracks
|
||||
|
||||
# absolute diff
|
||||
diff = addr - self.currAddr
|
||||
|
||||
# if on the same track...
|
||||
if currTrack == newTrack or diff < self.blocksPerTrack:
|
||||
if diff == 1:
|
||||
self.countSeq += 1
|
||||
else:
|
||||
self.countNseq += 1
|
||||
self.utilTime += (diff * self.xferTime)
|
||||
else:
|
||||
self.countRand += 1
|
||||
self.utilTime += (self.seekTime + self.xferTime)
|
||||
self.currAddr = addr
|
||||
|
||||
def go(self):
|
||||
return self.utilTime
|
||||
|
||||
class raid:
|
||||
def __init__(self, chunkSize='4k', numDisks=4, level=0, timing=False, reverse=False, solve=False, raid5type='LS'):
|
||||
chunkSize = int(convert(chunkSize))
|
||||
self.chunkSize = chunkSize / BLOCKSIZE
|
||||
self.numDisks = numDisks
|
||||
self.raidLevel = level
|
||||
self.timing = timing
|
||||
self.reverse = reverse
|
||||
self.solve = solve
|
||||
self.raid5type = raid5type
|
||||
|
||||
if (chunkSize % BLOCKSIZE) != 0:
|
||||
print 'chunksize (%d) must be multiple of blocksize (%d): %d' % (chunkSize, BLOCKSIZE, self.chunkSize % BLOCKSIZE)
|
||||
exit(1)
|
||||
if self.raidLevel == 1 and numDisks % 2 != 0:
|
||||
print 'raid1: disks (%d) must be a multiple of two' % numDisks
|
||||
exit(1)
|
||||
|
||||
if self.raidLevel == 4:
|
||||
self.blocksInStripe = (self.numDisks - 1) * self.chunkSize
|
||||
self.pdisk = self.numDisks - 1
|
||||
if self.raidLevel == 5:
|
||||
self.blocksInStripe = (self.numDisks - 1) * self.chunkSize
|
||||
self.pdisk = -1
|
||||
|
||||
self.disks = []
|
||||
for i in range(self.numDisks):
|
||||
self.disks.append(disk())
|
||||
|
||||
# print per-disk stats
|
||||
def stats(self, totalTime):
|
||||
for d in range(self.numDisks):
|
||||
s = self.disks[d].stats()
|
||||
if s[4] == totalTime:
|
||||
print 'disk:%d busy: %.2f I/Os: %5d (sequential:%d nearly:%d random:%d)' % (d, (100.0*float(s[4])/totalTime), s[0], s[1], s[2], s[3])
|
||||
elif s[4] == 0:
|
||||
print 'disk:%d busy: %.2f I/Os: %5d (sequential:%d nearly:%d random:%d)' % (d, (100.0*float(s[4])/totalTime), s[0], s[1], s[2], s[3])
|
||||
else:
|
||||
print 'disk:%d busy: %.2f I/Os: %5d (sequential:%d nearly:%d random:%d)' % (d, (100.0*float(s[4])/totalTime), s[0], s[1], s[2], s[3])
|
||||
|
||||
# global enqueue function
|
||||
def enqueue(self, addr, size, isWrite):
|
||||
# should we print out the logical operation?
|
||||
if self.timing == False:
|
||||
if self.solve or self.reverse==False:
|
||||
if isWrite:
|
||||
print 'LOGICAL WRITE to addr:%d size:%d' % (addr, size * BLOCKSIZE)
|
||||
else:
|
||||
print 'LOGICAL READ from addr:%d size:%d' % (addr, size * BLOCKSIZE)
|
||||
if self.solve == False:
|
||||
print ' Physical reads/writes?\n'
|
||||
else:
|
||||
print 'LOGICAL OPERATION is ?'
|
||||
|
||||
# should we print out the physical operations?
|
||||
if self.timing == False and (self.solve or self.reverse==True):
|
||||
self.printPhysical = True
|
||||
else:
|
||||
self.printPhysical = False
|
||||
|
||||
if self.raidLevel == 0:
|
||||
self.enqueue0(addr, size, isWrite)
|
||||
elif self.raidLevel == 1:
|
||||
self.enqueue1(addr, size, isWrite)
|
||||
elif self.raidLevel == 4 or self.raidLevel == 5:
|
||||
self.enqueue45(addr, size, isWrite)
|
||||
|
||||
# process disk workloads one at a time, returning final completion time
|
||||
def go(self):
|
||||
tmax = 0
|
||||
for d in range(self.numDisks):
|
||||
# print '**** disk ****', d
|
||||
t = self.disks[d].go()
|
||||
if t > tmax:
|
||||
tmax = t
|
||||
return tmax
|
||||
|
||||
# helper functions
|
||||
def doSingleRead(self, disk, off, doNewline=False):
|
||||
if self.printPhysical:
|
||||
print ' read [disk %d, offset %d] ' % (disk, off),
|
||||
if doNewline:
|
||||
print ''
|
||||
self.disks[disk].enqueue(off)
|
||||
|
||||
def doSingleWrite(self, disk, off, doNewline=False):
|
||||
if self.printPhysical:
|
||||
print ' write [disk %d, offset %d] ' % (disk, off),
|
||||
if doNewline:
|
||||
print ''
|
||||
self.disks[disk].enqueue(off)
|
||||
|
||||
#
|
||||
# mapping for RAID 0 (striping)
|
||||
#
|
||||
def bmap0(self, bnum):
|
||||
cnum = bnum / self.chunkSize
|
||||
coff = bnum % self.chunkSize
|
||||
return (cnum % self.numDisks, (cnum / self.numDisks) * self.chunkSize + coff)
|
||||
|
||||
def enqueue0(self, addr, size, isWrite):
|
||||
# can ignore isWrite, as I/O pattern is the same for striping
|
||||
for b in range(addr, addr+size):
|
||||
(disk, off) = self.bmap0(b)
|
||||
if isWrite:
|
||||
self.doSingleWrite(disk, off, True)
|
||||
else:
|
||||
self.doSingleRead(disk, off, True)
|
||||
if self.timing == False and self.printPhysical:
|
||||
print ''
|
||||
|
||||
#
|
||||
# mapping for RAID 1 (mirroring)
|
||||
#
|
||||
def bmap1(self, bnum):
|
||||
cnum = bnum / self.chunkSize
|
||||
coff = bnum % self.chunkSize
|
||||
disk = 2 * (cnum % (self.numDisks / 2))
|
||||
return (disk, disk + 1, (cnum / (self.numDisks / 2)) * self.chunkSize + coff)
|
||||
|
||||
def enqueue1(self, addr, size, isWrite):
|
||||
for b in range(addr, addr+size):
|
||||
(disk1, disk2, off) = self.bmap1(b)
|
||||
# print 'enqueue:', addr, size, '-->', m
|
||||
if isWrite:
|
||||
self.doSingleWrite(disk1, off, False)
|
||||
self.doSingleWrite(disk2, off, True)
|
||||
else:
|
||||
# the raid-1 read balancing algorithm is here;
|
||||
# could be something more intelligent --
|
||||
# instead, it is just based on the disk offset
|
||||
# to produce something easily reproducible
|
||||
if off % 2 == 0:
|
||||
self.doSingleRead(disk1, off, True)
|
||||
else:
|
||||
self.doSingleRead(disk2, off, True)
|
||||
if self.timing == False and self.printPhysical:
|
||||
print ''
|
||||
|
||||
#
|
||||
# mapping for RAID 4 (parity disk)
|
||||
#
|
||||
# assumes (for now) that there is just one parity disk
|
||||
#
|
||||
def bmap4(self, bnum):
|
||||
cnum = bnum / self.chunkSize
|
||||
coff = bnum % self.chunkSize
|
||||
return (cnum % (self.numDisks - 1), (cnum / (self.numDisks - 1)) * self.chunkSize + coff)
|
||||
|
||||
def pmap4(self, snum):
|
||||
return self.pdisk
|
||||
|
||||
#
|
||||
# mapping for RAID 5 (rotated parity)
|
||||
#
|
||||
def __bmap5(self, bnum):
|
||||
cnum = bnum / self.chunkSize
|
||||
coff = bnum % self.chunkSize
|
||||
ddsk = cnum / (self.numDisks - 1)
|
||||
doff = (ddsk * self.chunkSize) + coff
|
||||
disk = cnum % (self.numDisks - 1)
|
||||
col = (ddsk % self.numDisks)
|
||||
pdsk = (self.numDisks - 1) - col
|
||||
|
||||
# supports left-asymmetric and left-symmetric layouts
|
||||
if self.raid5type == 'LA':
|
||||
if disk >= pdisk:
|
||||
disk += 1
|
||||
elif self.raid5type == 'LS':
|
||||
disk = (disk - col) % (self.numDisks)
|
||||
else:
|
||||
print 'error: no such RAID scheme'
|
||||
exit(1)
|
||||
assert(disk != pdsk)
|
||||
return (disk, pdsk, doff)
|
||||
|
||||
# yes this is lame (redundant call to __bmap5 is serious programmer laziness)
|
||||
def bmap5(self, bnum):
|
||||
(disk, pdisk, off) = self.__bmap5(bnum)
|
||||
return (disk, off)
|
||||
|
||||
# this too is lame (redundant call to __bmap5 is serious programmer laziness)
|
||||
def pmap5(self, snum):
|
||||
(disk, pdisk, off) = self.__bmap5(snum * self.blocksInStripe)
|
||||
return pdisk
|
||||
|
||||
# RAID 4/5 helper routine to write out some blocks in a stripe
|
||||
def doPartialWrite(self, stripe, begin, end, bmap, pmap):
|
||||
numWrites = end - begin
|
||||
pdisk = pmap(stripe)
|
||||
if (numWrites + 1) <= (self.blocksInStripe - numWrites):
|
||||
# SUBTRACTIVE PARITY
|
||||
# print 'SUBTRACTIVE'
|
||||
offList = []
|
||||
for voff in range(begin, end):
|
||||
(disk, off) = bmap(voff)
|
||||
self.doSingleRead(disk, off)
|
||||
if off not in offList:
|
||||
offList.append(off)
|
||||
for i in range(len(offList)):
|
||||
self.doSingleRead(pdisk, offList[i], i == (len(offList) - 1))
|
||||
else:
|
||||
# ADDITIVE PARITY
|
||||
# print 'ADDITIVE'
|
||||
stripeBegin = stripe * self.blocksInStripe
|
||||
stripeEnd = stripeBegin + self.blocksInStripe
|
||||
for voff in range(stripeBegin, begin):
|
||||
(disk, off) = bmap(voff)
|
||||
self.doSingleRead(disk, off, (voff == (begin - 1)) and (end == stripeEnd))
|
||||
for voff in range(end, stripeEnd):
|
||||
(disk, off) = bmap(voff)
|
||||
self.doSingleRead(disk, off, voff == (stripeEnd - 1))
|
||||
|
||||
# WRITES: same for additive or subtractive parity
|
||||
offList = []
|
||||
for voff in range(begin, end):
|
||||
(disk, off) = bmap(voff)
|
||||
self.doSingleWrite(disk, off)
|
||||
if off not in offList:
|
||||
offList.append(off)
|
||||
for i in range(len(offList)):
|
||||
self.doSingleWrite(pdisk, offList[i], i == (len(offList) - 1))
|
||||
|
||||
# RAID 4/5 enqueue routine
|
||||
def enqueue45(self, addr, size, isWrite):
|
||||
if self.raidLevel == 4:
|
||||
(bmap, pmap) = (self.bmap4, self.pmap4)
|
||||
elif self.raidLevel == 5:
|
||||
(bmap, pmap) = (self.bmap5, self.pmap5)
|
||||
|
||||
if isWrite == False:
|
||||
for b in range(addr, addr+size):
|
||||
(disk, off) = bmap(b)
|
||||
self.doSingleRead(disk, off)
|
||||
else:
|
||||
# process the write request, one stripe at a time
|
||||
initStripe = (addr) / self.blocksInStripe
|
||||
finalStripe = (addr + size - 1) / self.blocksInStripe
|
||||
|
||||
left = size
|
||||
begin = addr
|
||||
for stripe in range(initStripe, finalStripe + 1):
|
||||
endOfStripe = (stripe * self.blocksInStripe) + self.blocksInStripe
|
||||
|
||||
if left >= self.blocksInStripe:
|
||||
end = begin + self.blocksInStripe
|
||||
else:
|
||||
end = begin + left
|
||||
|
||||
if end >= endOfStripe:
|
||||
end = endOfStripe
|
||||
|
||||
self.doPartialWrite(stripe, begin, end, bmap, pmap)
|
||||
|
||||
left -= (end - begin)
|
||||
begin = end
|
||||
|
||||
# for all cases, print this for pretty-ness in mapping mode
|
||||
if self.timing == False and self.printPhysical:
|
||||
print ''
|
||||
|
||||
#
|
||||
# main program
|
||||
#
|
||||
parser = OptionParser()
|
||||
|
||||
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
|
||||
parser.add_option('-D', '--numDisks', default=4, help='number of disks in RAID', action='store', type='int', dest='numDisks')
|
||||
parser.add_option('-C', '--chunkSize', default='4k', help='chunk size of the RAID', action='store', type='string', dest='chunkSize')
|
||||
parser.add_option('-n', '--numRequests', default=10, help='number of requests to simulate', action='store', type='int', dest='numRequests')
|
||||
parser.add_option('-S', '--reqSize', default='4k', help='size of requests', action='store', type='string', dest='size')
|
||||
parser.add_option('-W', '--workload', default='rand', help='either "rand" or "seq" workloads', action='store', type='string', dest='workload')
|
||||
parser.add_option('-w', '--writeFrac', default=0, help='write fraction (100->all writes, 0->all reads)', action='store', type='int', dest='writeFrac')
|
||||
parser.add_option('-R', '--randRange', default=10000, help='range of requests (when using "rand" workload)', action='store', type='int', dest='range')
|
||||
parser.add_option('-L', '--level', default=0, help='RAID level (0, 1, 4, 5)', action='store', type='int', dest='level')
|
||||
parser.add_option('-5', '--raid5', default='LS', help='RAID-5 left-symmetric "LS" or left-asym "LA"', action='store', type='string', dest='raid5type')
|
||||
parser.add_option('-r', '--reverse', default=False, help='instead of showing logical ops, show physical', action='store_true', dest='reverse')
|
||||
parser.add_option('-t', '--timing', default=False, help='use timing mode, instead of mapping mode', action='store_true', dest='timing')
|
||||
parser.add_option('-c', '--compute', default=False, help='compute answers for me', action='store_true', dest='solve')
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
print 'ARG blockSize', BLOCKSIZE
|
||||
print 'ARG seed', options.seed
|
||||
print 'ARG numDisks', options.numDisks
|
||||
print 'ARG chunkSize', options.chunkSize
|
||||
print 'ARG numRequests', options.numRequests
|
||||
print 'ARG reqSize', options.size
|
||||
print 'ARG workload', options.workload
|
||||
print 'ARG writeFrac', options.writeFrac
|
||||
print 'ARG randRange', options.range
|
||||
print 'ARG level', options.level
|
||||
print 'ARG raid5', options.raid5type
|
||||
print 'ARG reverse', options.reverse
|
||||
print 'ARG timing', options.timing
|
||||
|
||||
print ''
|
||||
|
||||
writeFrac = float(options.writeFrac) / 100.0
|
||||
assert(writeFrac >= 0.0 and writeFrac <= 1.0)
|
||||
|
||||
random.seed(options.seed)
|
||||
|
||||
size = convert(options.size)
|
||||
if size % BLOCKSIZE != 0:
|
||||
print 'error: request size (%d) must be a multiple of BLOCKSIZE (%d)' % (size, BLOCKSIZE)
|
||||
exit(1)
|
||||
size = size / BLOCKSIZE
|
||||
|
||||
if options.workload == 'seq' or options.workload == 's' or options.workload == 'sequential':
|
||||
workloadIsSequential = True
|
||||
elif options.workload == 'rand' or options.workload == 'r' or options.workload == 'random':
|
||||
workloadIsSequential = False
|
||||
else:
|
||||
print 'error: workload must be either r/rand/random or s/seq/sequential'
|
||||
exit(1)
|
||||
|
||||
assert(options.level == 0 or options.level == 1 or options.level == 4 or options.level == 5)
|
||||
if options.level != 0 and options.numDisks < 2:
|
||||
print 'RAID-4 and RAID-5 need more than 1 disk'
|
||||
exit(1)
|
||||
|
||||
if options.level == 5 and options.raid5type != 'LA' and options.raid5type != 'LS':
|
||||
print 'Only two types of RAID-5 supported: left-asymmetric (LA) and left-symmetric (LS) (%s is not)' % options.raid5type
|
||||
exit(1)
|
||||
|
||||
# instantiate RAID
|
||||
r = raid(chunkSize=options.chunkSize, numDisks=options.numDisks, level=options.level, timing=options.timing,
|
||||
reverse=options.reverse, solve=options.solve, raid5type=options.raid5type)
|
||||
|
||||
# generate requests
|
||||
off = 0
|
||||
for i in range(options.numRequests):
|
||||
if workloadIsSequential == True:
|
||||
blk = off
|
||||
off += size
|
||||
else:
|
||||
blk = int(random.random() * options.range)
|
||||
if random.random() < writeFrac:
|
||||
r.enqueue(blk, size, True)
|
||||
else:
|
||||
r.enqueue(blk, size, False)
|
||||
|
||||
# process requests
|
||||
t = r.go()
|
||||
|
||||
# print out some final info, if needed
|
||||
if options.timing == False:
|
||||
print ''
|
||||
exit(0)
|
||||
|
||||
if options.solve:
|
||||
print ''
|
||||
r.stats(t)
|
||||
print ''
|
||||
print 'STAT totalTime', t
|
||||
print ''
|
||||
else:
|
||||
print ''
|
||||
print 'Estimate how long the workload should take to complete.'
|
||||
print '- Roughly how many requests should each disk receive?'
|
||||
print '- How many requests are random, how many sequential?'
|
||||
print ''
|
166
related_info/ostep/ostep2-segmentation.md
Normal file
166
related_info/ostep/ostep2-segmentation.md
Normal file
@ -0,0 +1,166 @@
|
||||
|
||||
This program allows you to see how address translations are performed in a
|
||||
system with segmentation. The segmentation that this system uses is pretty
|
||||
simple: an address space has just *two* segments; further, the top bit of the
|
||||
virtual address generated by the process determines which segment the address
|
||||
is in: 0 for segment 0 (where, say, code and the heap would reside) and 1 for
|
||||
segment 1 (where the stack lives). Segment 0 grows in a positive direction
|
||||
(towards higher addresses), whereas segment 1 grows in the negative direction.
|
||||
|
||||
Visually, the address space looks like this:
|
||||
|
||||
--------------- virtual address 0
|
||||
| seg0 |
|
||||
| |
|
||||
| |
|
||||
|-------------|
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
|(unallocated)|
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
|-------------|
|
||||
| |
|
||||
| seg1 |
|
||||
|-------------| virtual address max (size of address space)
|
||||
|
||||
With segmentation, as you might recall, there is a base/limit pair of
|
||||
registers per segment. Thus, in this problem, there are two base/limit
|
||||
pairs. The segment-0 base tells which physical address the *top* of segment 0
|
||||
has been placed in physical memory and the limit tells how big the segment is;
|
||||
the segment-1 base tells where the *bottom* of segment 1 has been placed in
|
||||
physical memory and the corresponding limit also tells us how big the segment
|
||||
is (or how far it grows in the negative direction).
|
||||
|
||||
As before, there are two steps to running the program to test out your
|
||||
understanding of segmentation. First, run without the "-c" flag to generate a
|
||||
set of translations and see if you can correctly perform the address
|
||||
translations yourself. Then, when done, run with the "-c" flag to check your
|
||||
answers.
|
||||
|
||||
For example, to run with the default flags, type:
|
||||
|
||||
prompt> ./segmentation.py
|
||||
|
||||
(or
|
||||
prompt> python ./segmentation.py
|
||||
if that doesn't work)
|
||||
|
||||
You should see this:
|
||||
ARG seed 0
|
||||
ARG address space size 1k
|
||||
ARG phys mem size 16k
|
||||
|
||||
Segment register information:
|
||||
|
||||
Segment 0 base (grows positive) : 0x00001aea (decimal 6890)
|
||||
Segment 0 limit : 472
|
||||
|
||||
Segment 1 base (grows negative) : 0x00001254 (decimal 4692)
|
||||
Segment 1 limit : 450
|
||||
|
||||
Virtual Address Trace
|
||||
VA 0: 0x0000020b (decimal: 523) --> PA or segmentation violation?
|
||||
VA 1: 0x0000019e (decimal: 414) --> PA or segmentation violation?
|
||||
VA 2: 0x00000322 (decimal: 802) --> PA or segmentation violation?
|
||||
VA 3: 0x00000136 (decimal: 310) --> PA or segmentation violation?
|
||||
VA 4: 0x000001e8 (decimal: 488) --> PA or segmentation violation?
|
||||
|
||||
For each virtual address, either write down the physical address it translates
|
||||
to OR write down that it is an out-of-bounds address (a segmentation
|
||||
violation). For this problem, you should assume a simple address space with
|
||||
two segments: the top bit of the virtual address can thus be used to check
|
||||
whether the virtual address is in segment 0 (topbit=0) or segment 1
|
||||
(topbit=1). Note that the base/limit pairs given to you grow in different
|
||||
directions, depending on the segment, i.e., segment 0 grows in the positive
|
||||
direction, whereas segment 1 in the negative.
|
||||
|
||||
Then, after you have computed the translations in the virtual address trace,
|
||||
run the program again with the "-c" flag. You will see the following (not
|
||||
including the redundant information):
|
||||
|
||||
Virtual Address Trace
|
||||
VA 0: 0x0000020b (decimal: 523) --> SEGMENTATION VIOLATION (SEG1)
|
||||
VA 1: 0x0000019e (decimal: 414) --> VALID in SEG0: 0x00001c88 (decimal: 7304)
|
||||
VA 2: 0x00000322 (decimal: 802) --> VALID in SEG1: 0x00001176 (decimal: 4470)
|
||||
VA 3: 0x00000136 (decimal: 310) --> VALID in SEG0: 0x00001c20 (decimal: 7200)
|
||||
VA 4: 0x000001e8 (decimal: 488) --> SEGMENTATION VIOLATION (SEG0)
|
||||
|
||||
As you can see, with -c, the program translates the addresses for you, and
|
||||
hence you can check if you understand how a system using segmentation
|
||||
translates addresses.
|
||||
|
||||
Of course, there are some parameters you can use to give yourself different
|
||||
problems. One particularly important parameter is the -s or -seed parameter,
|
||||
which lets you generate different problems by passing in a different random
|
||||
seed. Of course, make sure to use the same random seed when you are generating
|
||||
a problem and then solving it.
|
||||
|
||||
There are also some parameters you can use to play with different-sized
|
||||
address spaces and physical memories. For example, to experiment with
|
||||
segmentation in a tiny system, you might type:
|
||||
|
||||
prompt> ./segmentation.py -s 100 -a 16 -p 32
|
||||
ARG seed 0
|
||||
ARG address space size 16
|
||||
ARG phys mem size 32
|
||||
|
||||
Segment register information:
|
||||
|
||||
Segment 0 base (grows positive) : 0x00000018 (decimal 24)
|
||||
Segment 0 limit : 4
|
||||
|
||||
Segment 1 base (grows negative) : 0x00000012 (decimal 18)
|
||||
Segment 1 limit : 5
|
||||
|
||||
Virtual Address Trace
|
||||
VA 0: 0x0000000c (decimal: 12) --> PA or segmentation violation?
|
||||
VA 1: 0x00000008 (decimal: 8) --> PA or segmentation violation?
|
||||
VA 2: 0x00000001 (decimal: 1) --> PA or segmentation violation?
|
||||
VA 3: 0x00000007 (decimal: 7) --> PA or segmentation violation?
|
||||
VA 4: 0x00000000 (decimal: 0) --> PA or segmentation violation?
|
||||
|
||||
which tells the program to generate virtual addresses for a 16-byte address
|
||||
space placed somewhere in a 32-byte physical memory. As you can see, the
|
||||
resulting virtual addresses are tiny (12, 8, 1, 7, and 0). As you can also
|
||||
see, the program picks tiny base register and limit values, as
|
||||
appropriate. Run with -c to see the answers.
|
||||
|
||||
This example should also show you exactly what each base pair means. For
|
||||
example, segment 0's base is set to a physical address of 24 (decimal) and is
|
||||
of size 4 bytes. Thus, *virtual* addresses 0, 1, 2, and 3 are in segment 0 and
|
||||
valid, and map to physical addresses 24, 25, 26, and 27, respectively.
|
||||
|
||||
Slightly more tricky is the negative-direction-growing segment 1. In the tiny
|
||||
example above, segment 1's base register is set to physical address 18, with a
|
||||
size of 5 bytes. That means that the *last* five bytes of the virtual address
|
||||
space, in this case 11, 12, 13, 14, and 15, are valid virtual addresses, and
|
||||
that they map to physical addresses 13, 14, 15, 16, and 17, respectively.
|
||||
|
||||
If that doesn't make sense, read it again -- you will have to make sense of
|
||||
how this works in order to do any of these problems.
|
||||
|
||||
Note you can specify bigger values by tacking a "k", "m", or even "g" onto the
|
||||
values you pass in with the -a or -p flags, as in "kilobytes", "megabytes",
|
||||
and "gigabytes". Thus, if you wanted to do some translations with a 1-MB
|
||||
address space set in a 32-MB physical memory, you might type:
|
||||
|
||||
prompt> ./segmentation.py -a 1m -p 32m
|
||||
|
||||
If you want to get even more specific, you can set the base register and limit
|
||||
register values yourself, with the --b0, --l0, --b1, and --l1 registers. Try
|
||||
them and see.
|
||||
|
||||
Finally, you can always run
|
||||
|
||||
prompt> ./segmentation.py -h
|
||||
|
||||
to get a complete list of flags and options.
|
||||
|
||||
Enjoy!
|
||||
|
||||
|
||||
|
193
related_info/ostep/ostep2-segmentation.py
Executable file
193
related_info/ostep/ostep2-segmentation.py
Executable file
@ -0,0 +1,193 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
import random
|
||||
import math
|
||||
|
||||
def convert(size):
|
||||
length = len(size)
|
||||
lastchar = size[length-1]
|
||||
if (lastchar == 'k') or (lastchar == 'K'):
|
||||
m = 1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
elif (lastchar == 'm') or (lastchar == 'M'):
|
||||
m = 1024*1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
elif (lastchar == 'g') or (lastchar == 'G'):
|
||||
m = 1024*1024*1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
else:
|
||||
nsize = int(size)
|
||||
return nsize
|
||||
|
||||
|
||||
#
|
||||
# main program
|
||||
#
|
||||
parser = OptionParser()
|
||||
parser.add_option("-s", "--seed", default=0, help="the random seed",
|
||||
action="store", type="int", dest="seed")
|
||||
parser.add_option("-A", "--addresses", default="-1",
|
||||
help="a set of comma-separated pages to access; -1 means randomly generate",
|
||||
action="store", type="string", dest="addresses")
|
||||
parser.add_option("-a", "--asize", default="1k",
|
||||
help="address space size (e.g., 16, 64k, 32m, 1g)",
|
||||
action="store", type="string", dest="asize")
|
||||
parser.add_option("-p", "--physmem", default="16k",
|
||||
help="physical memory size (e.g., 16, 64k, 32m, 1g)",
|
||||
action="store", type="string", dest="psize")
|
||||
parser.add_option("-n", "--numaddrs", default=5,
|
||||
help="number of virtual addresses to generate",
|
||||
action="store", type="int", dest="num")
|
||||
parser.add_option("-b", "--b0", default="-1",
|
||||
help="value of segment 0 base register",
|
||||
action="store", type="string", dest="base0")
|
||||
parser.add_option("-l", "--l0", default="-1",
|
||||
help="value of segment 0 limit register",
|
||||
action="store", type="string", dest="len0")
|
||||
parser.add_option("-B", "--b1", default="-1",
|
||||
help="value of segment 1 base register",
|
||||
action="store", type="string", dest="base1")
|
||||
parser.add_option("-L", "--l1", default="-1",
|
||||
help="value of segment 1 limit register",
|
||||
action="store", type="string", dest="len1")
|
||||
parser.add_option("-c", help="compute answers for me",
|
||||
action="store_true", default=False, dest="solve")
|
||||
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
print "ARG seed", options.seed
|
||||
print "ARG address space size", options.asize
|
||||
print "ARG phys mem size", options.psize
|
||||
print ""
|
||||
|
||||
random.seed(options.seed)
|
||||
asize = convert(options.asize)
|
||||
psize = convert(options.psize)
|
||||
addresses = str(options.addresses)
|
||||
|
||||
if psize <= 1:
|
||||
print 'Error: must specify a non-zero physical memory size.'
|
||||
exit(1)
|
||||
|
||||
if asize == 0:
|
||||
print 'Error: must specify a non-zero address-space size.'
|
||||
exit(1)
|
||||
|
||||
if psize <= asize:
|
||||
print 'Error: physical memory size must be GREATER than address space size (for this simulation)'
|
||||
exit(1)
|
||||
|
||||
#
|
||||
# need to generate base, bounds for segment registers
|
||||
#
|
||||
len0 = convert(options.len0)
|
||||
len1 = convert(options.len1)
|
||||
base0 = convert(options.base0)
|
||||
base1 = convert(options.base1)
|
||||
|
||||
if len0 == -1:
|
||||
len0 = int(asize/4.0 + (asize/4.0 * random.random()))
|
||||
|
||||
if len1 == -1:
|
||||
len1 = int(asize/4.0 + (asize/4.0 * random.random()))
|
||||
|
||||
# now have to find room for them
|
||||
if base0 == -1:
|
||||
done = 0
|
||||
while done == 0:
|
||||
base0 = int(psize * random.random())
|
||||
if (base0 + len0) < psize:
|
||||
done = 1
|
||||
|
||||
# internally, base1 points to the lower address, and base1+len1 the higher address
|
||||
# (this differs from what the user would pass in, for example)
|
||||
if base1 == -1:
|
||||
done = 0
|
||||
while done == 0:
|
||||
base1 = int(psize * random.random())
|
||||
if (base1 + len1) < psize:
|
||||
if (base1 > (base0 + len0)) or ((base1 + len1) < base0):
|
||||
done = 1
|
||||
else:
|
||||
base1 = base1 - len1
|
||||
|
||||
|
||||
if len0 > asize/2.0 or len1 > asize/2.0:
|
||||
print 'Error: length register is too large for this address space'
|
||||
exit(1)
|
||||
|
||||
|
||||
print 'Segment register information:'
|
||||
print ''
|
||||
print ' Segment 0 base (grows positive) : 0x%08x (decimal %d)' % (base0, base0)
|
||||
print ' Segment 0 limit : %d' % (len0)
|
||||
print ''
|
||||
print ' Segment 1 base (grows negative) : 0x%08x (decimal %d)' % (base1+len1, base1+len1)
|
||||
print ' Segment 1 limit : %d' % (len1)
|
||||
print ''
|
||||
nbase1 = base1 + len1
|
||||
|
||||
if (len0 + base0) > (base1) and (base1 > base0):
|
||||
print 'Error: segments overlap in physical memory'
|
||||
exit(1)
|
||||
|
||||
|
||||
addrList = []
|
||||
if addresses == '-1':
|
||||
# need to generate addresses
|
||||
for i in range(0, options.num):
|
||||
n = int(asize * random.random())
|
||||
addrList.append(n)
|
||||
else:
|
||||
addrList = addresses.split(',')
|
||||
|
||||
#
|
||||
# now, need to generate virtual address trace
|
||||
#
|
||||
print 'Virtual Address Trace'
|
||||
i = 0
|
||||
for vStr in addrList:
|
||||
# vaddr = int(asize * random.random())
|
||||
vaddr = int(vStr)
|
||||
if vaddr < 0 or vaddr >= asize:
|
||||
print 'Error: virtual address %d cannot be generated in an address space of size %d' % (vaddr, asize)
|
||||
exit(1)
|
||||
if options.solve == False:
|
||||
print ' VA %2d: 0x%08x (decimal: %4d) --> PA or segmentation violation?' % (i, vaddr, vaddr)
|
||||
else:
|
||||
paddr = 0
|
||||
if (vaddr >= (asize / 2)):
|
||||
# seg 1
|
||||
paddr = nbase1 + (vaddr - asize)
|
||||
if paddr < base1:
|
||||
print ' VA %2d: 0x%08x (decimal: %4d) --> SEGMENTATION VIOLATION (SEG1)' % (i, vaddr, vaddr)
|
||||
else:
|
||||
print ' VA %2d: 0x%08x (decimal: %4d) --> VALID in SEG1: 0x%08x (decimal: %4d)' % (i, vaddr, vaddr, paddr, paddr)
|
||||
else:
|
||||
# seg 0
|
||||
if (vaddr >= len0):
|
||||
print ' VA %2d: 0x%08x (decimal: %4d) --> SEGMENTATION VIOLATION (SEG0)' % (i, vaddr, vaddr)
|
||||
else:
|
||||
paddr = vaddr + base0
|
||||
print ' VA %2d: 0x%08x (decimal: %4d) --> VALID in SEG0: 0x%08x (decimal: %4d)' % (i, vaddr, vaddr, paddr, paddr)
|
||||
i += 1
|
||||
|
||||
print ''
|
||||
|
||||
if options.solve == False:
|
||||
print 'For each virtual address, either write down the physical address it translates to'
|
||||
print 'OR write down that it is an out-of-bounds address (a segmentation violation). For'
|
||||
print 'this problem, you should assume a simple address space with two segments: the top'
|
||||
print 'bit of the virtual address can thus be used to check whether the virtual address'
|
||||
print 'is in segment 0 (topbit=0) or segment 1 (topbit=1). Note that the base/limit pairs'
|
||||
print 'given to you grow in different directions, depending on the segment, i.e., segment 0'
|
||||
print 'grows in the positive direction, whereas segment 1 in the negative. '
|
||||
print ''
|
||||
|
||||
|
||||
|
||||
|
||||
|
162
related_info/ostep/ostep3-malloc.md
Normal file
162
related_info/ostep/ostep3-malloc.md
Normal file
@ -0,0 +1,162 @@
|
||||
|
||||
This program, malloc.py, allows you to see how a simple memory allocator
|
||||
works. Here are the options that you have at your disposal:
|
||||
|
||||
-h, --help show this help message and exit
|
||||
-s SEED, --seed=SEED the random seed
|
||||
-S HEAPSIZE, --size=HEAPSIZE
|
||||
size of the heap
|
||||
-b BASEADDR, --baseAddr=BASEADDR
|
||||
base address of heap
|
||||
-H HEADERSIZE, --headerSize=HEADERSIZE
|
||||
size of the header
|
||||
-a ALIGNMENT, --alignment=ALIGNMENT
|
||||
align allocated units to size; -1->no align
|
||||
-p POLICY, --policy=POLICY
|
||||
list search (BEST, WORST, FIRST)
|
||||
-l ORDER, --listOrder=ORDER
|
||||
list order (ADDRSORT, SIZESORT+, SIZESORT-, INSERT-FRONT, INSERT-BACK)
|
||||
-C, --coalesce coalesce the free list?
|
||||
-n OPSNUM, --numOps=OPSNUM
|
||||
number of random ops to generate
|
||||
-r OPSRANGE, --range=OPSRANGE
|
||||
max alloc size
|
||||
-P OPSPALLOC, --percentAlloc=OPSPALLOC
|
||||
percent of ops that are allocs
|
||||
-A OPSLIST, --allocList=OPSLIST
|
||||
instead of random, list of ops (+10,-0,etc)
|
||||
-c, --compute compute answers for me
|
||||
|
||||
One way to use it is to have the program generate some random allocation/free
|
||||
operations and for you to see if you can figure out what the free list would
|
||||
look like, as well as the success or failure of each operation.
|
||||
|
||||
Here is a simple example:
|
||||
|
||||
prompt> ./malloc.py -S 100 -b 1000 -H 4 -a 4 -l ADDRSORT -p BEST -n 5
|
||||
|
||||
ptr[0] = Alloc(3) returned ?
|
||||
List?
|
||||
|
||||
Free(ptr[0]) returned ?
|
||||
List?
|
||||
|
||||
ptr[1] = Alloc(5) returned ?
|
||||
List?
|
||||
|
||||
Free(ptr[1]) returned ?
|
||||
List?
|
||||
|
||||
ptr[2] = Alloc(8) returned ?
|
||||
List?
|
||||
|
||||
|
||||
In this example, we specify a heap of size 100 bytes (-S 100), starting at
|
||||
address 1000 (-b 1000). We specify an additional 4 bytes of header per
|
||||
allocated block (-H 4), and make sure each allocated space rounds up to the
|
||||
nearest 4-byte free chunk in size (-a 4). We specify that the free list be
|
||||
kept ordered by address (increasing). Finally, we specify a "best fit"
|
||||
free-list searching policy (-p BEST), and ask for 5 random operations to be
|
||||
generated (-n 5). The results of running this are above; your job is to figure
|
||||
out what each allocation/free operation returns, as well as the state of the
|
||||
free list after each operation.
|
||||
|
||||
Here we look at the results by using the -c option.
|
||||
|
||||
prompt> ./malloc.py -S 100 -b 1000 -H 4 -a 4 -l ADDRSORT -p BEST -n 5 -c
|
||||
|
||||
ptr[0] = Alloc(3) returned 1004 (searched 1 elements)
|
||||
Free List [ Size 1 ]: [ addr:1008 sz:92 ]
|
||||
|
||||
Free(ptr[0]) returned 0
|
||||
Free List [ Size 2 ]: [ addr:1000 sz:8 ] [ addr:1008 sz:92 ]
|
||||
|
||||
ptr[1] = Alloc(5) returned 1012 (searched 2 elements)
|
||||
Free List [ Size 2 ]: [ addr:1000 sz:8 ] [ addr:1020 sz:80 ]
|
||||
|
||||
Free(ptr[1]) returned 0
|
||||
Free List [ Size 3 ]: [ addr:1000 sz:8 ] [ addr:1008 sz:12 ] [ addr:1020 sz:80 ]
|
||||
|
||||
ptr[2] = Alloc(8) returned 1012 (searched 3 elements)
|
||||
Free List [ Size 2 ]: [ addr:1000 sz:8 ] [ addr:1020 sz:80 ]
|
||||
|
||||
As you can see, the first allocation operation (an allocation) returns the
|
||||
following information:
|
||||
|
||||
ptr[0] = Alloc(3) returned 1004 (searched 1 elements)
|
||||
Free List [ Size 1 ]: [ addr:1008 sz:92 ]
|
||||
|
||||
Because the initial state of the free list is just one large element, it is
|
||||
easy to guess that the Alloc(3) request will succeed. Further, it will just
|
||||
return the first chunk of memory and make the remainder into a free list. The
|
||||
pointer returned will be just beyond the header (address:1004), and the
|
||||
allocated space is rounded up to 4 bytes, leaving the free list with 92 bytes
|
||||
starting at 1008.
|
||||
|
||||
The next operation is a Free, of "ptr[0]" which is what stores the results of
|
||||
the previous allocation request. As you can expect, this free will succeed
|
||||
(thus returning "0"), and the free list now looks a little more complicated:
|
||||
|
||||
Free(ptr[0]) returned 0
|
||||
Free List [ Size 2 ]: [ addr:1000 sz:8 ] [ addr:1008 sz:92 ]
|
||||
|
||||
Indeed, because we are NOT coalescing the free list, we now have two elements
|
||||
on it, the first being 8 bytes large and holding the just-returned space, and
|
||||
the second being the 92-byte chunk.
|
||||
|
||||
We can indeed turn on coalescing via the -C flag, and the result is:
|
||||
|
||||
prompt> ./malloc.py -S 100 -b 1000 -H 4 -a 4 -l ADDRSORT -p BEST -n 5 -c -C
|
||||
ptr[0] = Alloc(3) returned 1004 (searched 1 elements)
|
||||
Free List [ Size 1 ]: [ addr:1008 sz:92 ]
|
||||
|
||||
Free(ptr[0]) returned 0
|
||||
Free List [ Size 1 ]: [ addr:1000 sz:100 ]
|
||||
|
||||
ptr[1] = Alloc(5) returned 1004 (searched 1 elements)
|
||||
Free List [ Size 1 ]: [ addr:1012 sz:88 ]
|
||||
|
||||
Free(ptr[1]) returned 0
|
||||
Free List [ Size 1 ]: [ addr:1000 sz:100 ]
|
||||
|
||||
ptr[2] = Alloc(8) returned 1004 (searched 1 elements)
|
||||
Free List [ Size 1 ]: [ addr:1012 sz:88 ]
|
||||
|
||||
You can see that when the Free operations take place, the free list is
|
||||
coalesced as expected.
|
||||
|
||||
There are some other interesting options to explore:
|
||||
|
||||
* -p (BEST, WORST, FIRST)
|
||||
|
||||
This option lets you use these three different strategies to look for a
|
||||
chunk of memory to use during an allocation request
|
||||
|
||||
* -l (ADDRSORT, SIZESORT+, SIZESORT-, INSERT-FRONT, INSERT-BACK)
|
||||
|
||||
This option lets you keep the free list in a particular order,
|
||||
say sorted by address of the free chunk, size of free chunk (either
|
||||
increasing with a + or decreasing with a -), or simply returning free
|
||||
chunks to the front (INSERT-FRONT) or back (INSERT-BACK) of the free list.
|
||||
|
||||
* -A (list of ops)
|
||||
|
||||
This option lets you specify an exact series of requests instead
|
||||
of randomly-generated ones.
|
||||
|
||||
For example, running with the flag "-A +10,+10,+10,-0,-2" will allocate
|
||||
three chunks of size 10 bytes (plus header), and then free the first one
|
||||
("-0") and then free the third one ("-2"). What will the free list look
|
||||
like then?
|
||||
|
||||
Those are the basics. Use the questions from the book chapter to explore more,
|
||||
or create new and interesting questions yourself to better understand how
|
||||
allocators function.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
238
related_info/ostep/ostep3-malloc.py
Executable file
238
related_info/ostep/ostep3-malloc.py
Executable file
@ -0,0 +1,238 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import random
|
||||
from optparse import OptionParser
|
||||
|
||||
class malloc:
|
||||
def __init__(self, size, start, headerSize, policy, order, coalesce, align):
|
||||
# size of space
|
||||
self.size = size
|
||||
|
||||
# info about pretend headers
|
||||
self.headerSize = headerSize
|
||||
|
||||
# init free list
|
||||
self.freelist = []
|
||||
self.freelist.append((start, size))
|
||||
|
||||
# keep track of ptr to size mappings
|
||||
self.sizemap = {}
|
||||
|
||||
# policy
|
||||
self.policy = policy
|
||||
assert(self.policy in ['FIRST', 'BEST', 'WORST'])
|
||||
|
||||
# list ordering
|
||||
self.returnPolicy = order
|
||||
assert(self.returnPolicy in ['ADDRSORT', 'SIZESORT+', 'SIZESORT-', 'INSERT-FRONT', 'INSERT-BACK'])
|
||||
|
||||
# this does a ridiculous full-list coalesce, but that is ok
|
||||
self.coalesce = coalesce
|
||||
|
||||
# alignment (-1 if no alignment)
|
||||
self.align = align
|
||||
assert(self.align == -1 or self.align > 0)
|
||||
|
||||
def addToMap(self, addr, size):
|
||||
assert(addr not in self.sizemap)
|
||||
self.sizemap[addr] = size
|
||||
# print 'adding', addr, 'to map of size', size
|
||||
|
||||
def malloc(self, size):
|
||||
if self.align != -1:
|
||||
left = size % self.align
|
||||
if left != 0:
|
||||
diff = self.align - left
|
||||
else:
|
||||
diff = 0
|
||||
# print 'aligning: adding %d to %d' % (diff, size)
|
||||
size += diff
|
||||
|
||||
size += self.headerSize
|
||||
|
||||
bestIdx = -1
|
||||
if self.policy == 'BEST':
|
||||
bestSize = self.size + 1
|
||||
elif self.policy == 'WORST' or self.policy == 'FIRST':
|
||||
bestSize = -1
|
||||
|
||||
count = 0
|
||||
|
||||
for i in range(len(self.freelist)):
|
||||
eaddr, esize = self.freelist[i][0], self.freelist[i][1]
|
||||
count += 1
|
||||
if esize >= size and ((self.policy == 'BEST' and esize < bestSize) or
|
||||
(self.policy == 'WORST' and esize > bestSize) or
|
||||
(self.policy == 'FIRST')):
|
||||
bestAddr = eaddr
|
||||
bestSize = esize
|
||||
bestIdx = i
|
||||
if self.policy == 'FIRST':
|
||||
break
|
||||
|
||||
if bestIdx != -1:
|
||||
if bestSize > size:
|
||||
# print 'SPLIT', bestAddr, size
|
||||
self.freelist[bestIdx] = (bestAddr + size, bestSize - size)
|
||||
self.addToMap(bestAddr, size)
|
||||
elif bestSize == size:
|
||||
# print 'PERFECT MATCH (no split)', bestAddr, size
|
||||
self.freelist.pop(bestIdx)
|
||||
self.addToMap(bestAddr, size)
|
||||
else:
|
||||
abort('should never get here')
|
||||
return (bestAddr, count)
|
||||
|
||||
# print '*** FAILED TO FIND A SPOT', size
|
||||
return (-1, count)
|
||||
|
||||
def free(self, addr):
|
||||
# simple back on end of list, no coalesce
|
||||
if addr not in self.sizemap:
|
||||
return -1
|
||||
|
||||
size = self.sizemap[addr]
|
||||
if self.returnPolicy == 'INSERT-BACK':
|
||||
self.freelist.append((addr, size))
|
||||
elif self.returnPolicy == 'INSERT-FRONT':
|
||||
self.freelist.insert(0, (addr, size))
|
||||
elif self.returnPolicy == 'ADDRSORT':
|
||||
self.freelist.append((addr, size))
|
||||
self.freelist = sorted(self.freelist, key=lambda e: e[0])
|
||||
elif self.returnPolicy == 'SIZESORT+':
|
||||
self.freelist.append((addr, size))
|
||||
self.freelist = sorted(self.freelist, key=lambda e: e[1], reverse=False)
|
||||
elif self.returnPolicy == 'SIZESORT-':
|
||||
self.freelist.append((addr, size))
|
||||
self.freelist = sorted(self.freelist, key=lambda e: e[1], reverse=True)
|
||||
|
||||
# not meant to be an efficient or realistic coalescing...
|
||||
if self.coalesce == True:
|
||||
self.newlist = []
|
||||
self.curr = self.freelist[0]
|
||||
for i in range(1, len(self.freelist)):
|
||||
eaddr, esize = self.freelist[i]
|
||||
if eaddr == (self.curr[0] + self.curr[1]):
|
||||
self.curr = (self.curr[0], self.curr[1] + esize)
|
||||
else:
|
||||
self.newlist.append(self.curr)
|
||||
self.curr = eaddr, esize
|
||||
self.newlist.append(self.curr)
|
||||
self.freelist = self.newlist
|
||||
|
||||
del self.sizemap[addr]
|
||||
return 0
|
||||
|
||||
def dump(self):
|
||||
print 'Free List [ Size %d ]: ' % len(self.freelist),
|
||||
for e in self.freelist:
|
||||
print '[ addr:%d sz:%d ]' % (e[0], e[1]),
|
||||
print ''
|
||||
|
||||
|
||||
#
|
||||
# main program
|
||||
#
|
||||
parser = OptionParser()
|
||||
|
||||
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
|
||||
parser.add_option('-S', '--size', default=100, help='size of the heap', action='store', type='int', dest='heapSize')
|
||||
parser.add_option('-b', '--baseAddr', default=1000, help='base address of heap', action='store', type='int', dest='baseAddr')
|
||||
parser.add_option('-H', '--headerSize', default=0, help='size of the header', action='store', type='int', dest='headerSize')
|
||||
parser.add_option('-a', '--alignment', default=-1, help='align allocated units to size; -1->no align', action='store', type='int', dest='alignment')
|
||||
parser.add_option('-p', '--policy', default='BEST', help='list search (BEST, WORST, FIRST)', action='store', type='string', dest='policy')
|
||||
parser.add_option('-l', '--listOrder', default='ADDRSORT', help='list order (ADDRSORT, SIZESORT+, SIZESORT-, INSERT-FRONT, INSERT-BACK)', action='store', type='string', dest='order')
|
||||
parser.add_option('-C', '--coalesce', default=False, help='coalesce the free list?', action='store_true', dest='coalesce')
|
||||
parser.add_option('-n', '--numOps', default=10, help='number of random ops to generate', action='store', type='int', dest='opsNum')
|
||||
parser.add_option('-r', '--range', default=10, help='max alloc size', action='store', type='int', dest='opsRange')
|
||||
parser.add_option('-P', '--percentAlloc',default=50, help='percent of ops that are allocs', action='store', type='int', dest='opsPAlloc')
|
||||
parser.add_option('-A', '--allocList', default='', help='instead of random, list of ops (+10,-0,etc)', action='store', type='string', dest='opsList')
|
||||
parser.add_option('-c', '--compute', default=False, help='compute answers for me', action='store_true', dest='solve')
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
m = malloc(int(options.heapSize), int(options.baseAddr), int(options.headerSize),
|
||||
options.policy, options.order, options.coalesce, options.alignment)
|
||||
|
||||
percent = int(options.opsPAlloc) / 100.0
|
||||
|
||||
random.seed(int(options.seed))
|
||||
p = {}
|
||||
L = []
|
||||
assert(percent > 0)
|
||||
|
||||
if options.opsList == '':
|
||||
c = 0
|
||||
j = 0
|
||||
while j < int(options.opsNum):
|
||||
pr = False
|
||||
if random.random() < percent:
|
||||
size = int(random.random() * int(options.opsRange)) + 1
|
||||
ptr, cnt = m.malloc(size)
|
||||
if ptr != -1:
|
||||
p[c] = ptr
|
||||
L.append(c)
|
||||
print 'ptr[%d] = Alloc(%d)' % (c, size),
|
||||
if options.solve == True:
|
||||
print ' returned %d (searched %d elements)' % (ptr + options.headerSize, cnt)
|
||||
else:
|
||||
print ' returned ?'
|
||||
c += 1
|
||||
j += 1
|
||||
pr = True
|
||||
else:
|
||||
if len(p) > 0:
|
||||
# pick random one to delete
|
||||
d = int(random.random() * len(L))
|
||||
rc = m.free(p[L[d]])
|
||||
print 'Free(ptr[%d])' % L[d],
|
||||
if options.solve == True:
|
||||
print 'returned %d' % rc
|
||||
else:
|
||||
print 'returned ?'
|
||||
del p[L[d]]
|
||||
del L[d]
|
||||
# print 'DEBUG p', p
|
||||
# print 'DEBUG L', L
|
||||
pr = True
|
||||
j += 1
|
||||
if pr:
|
||||
if options.solve == True:
|
||||
m.dump()
|
||||
else:
|
||||
print 'List? '
|
||||
print ''
|
||||
else:
|
||||
c = 0
|
||||
for op in options.opsList.split(','):
|
||||
if op[0] == '+':
|
||||
# allocation!
|
||||
size = int(op.split('+')[1])
|
||||
ptr, cnt = m.malloc(size)
|
||||
if ptr != -1:
|
||||
p[c] = ptr
|
||||
print 'ptr[%d] = Alloc(%d)' % (c, size),
|
||||
if options.solve == True:
|
||||
print ' returned %d (searched %d elements)' % (ptr, cnt)
|
||||
else:
|
||||
print ' returned ?'
|
||||
c += 1
|
||||
elif op[0] == '-':
|
||||
# free
|
||||
index = int(op.split('-')[1])
|
||||
if index >= len(p):
|
||||
print 'Invalid Free: Skipping'
|
||||
continue
|
||||
print 'Free(ptr[%d])' % index,
|
||||
rc = m.free(p[index])
|
||||
if options.solve == True:
|
||||
print 'returned %d' % rc
|
||||
else:
|
||||
print 'returned ?'
|
||||
else:
|
||||
abort('badly specified operand: must be +Size or -Index')
|
||||
if options.solve == True:
|
||||
m.dump()
|
||||
else:
|
||||
print 'List?'
|
||||
print ''
|
131
related_info/ostep/ostep4-paging-linear-translate.md
Normal file
131
related_info/ostep/ostep4-paging-linear-translate.md
Normal file
@ -0,0 +1,131 @@
|
||||
|
||||
In this homework, you will use a simple program, which is known as
|
||||
paging-linear-translate.py, to see if you understand how simple
|
||||
virtual-to-physical address translation works with linear page tables. To run
|
||||
the program, remember to either type just the name of the program
|
||||
(./paging-linear-translate.py) or possibly this (python
|
||||
paging-linear-translate.py). When you run it with the -h (help) flag, you
|
||||
see:
|
||||
|
||||
Usage: paging-linear-translate.py [options]
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-s SEED, --seed=SEED the random seed
|
||||
-a ASIZE, --asize=ASIZE
|
||||
address space size (e.g., 16, 64k, ...)
|
||||
-p PSIZE, --physmem=PSIZE
|
||||
physical memory size (e.g., 16, 64k, ...)
|
||||
-P PAGESIZE, --pagesize=PAGESIZE
|
||||
page size (e.g., 4k, 8k, ...)
|
||||
-n NUM, --addresses=NUM number of virtual addresses to generate
|
||||
-u USED, --used=USED percent of address space that is used
|
||||
-v verbose mode
|
||||
-c compute answers for me
|
||||
|
||||
First, run the program without any arguments:
|
||||
|
||||
ARG seed 0
|
||||
ARG address space size 16k
|
||||
ARG phys mem size 64k
|
||||
ARG page size 4k
|
||||
ARG verbose False
|
||||
|
||||
The format of the page table is simple:
|
||||
The high-order (left-most) bit is the VALID bit.
|
||||
If the bit is 1, the rest of the entry is the PFN.
|
||||
If the bit is 0, the page is not valid.
|
||||
Use verbose mode (-v) if you want to print the VPN # by
|
||||
each entry of the page table.
|
||||
|
||||
Page Table (from entry 0 down to the max size)
|
||||
0x8000000c
|
||||
0x00000000
|
||||
0x00000000
|
||||
0x80000006
|
||||
|
||||
Virtual Address Trace
|
||||
VA 0: 0x00003229 (decimal: 12841) --> PA or invalid?
|
||||
VA 1: 0x00001369 (decimal: 4969) --> PA or invalid?
|
||||
VA 2: 0x00001e80 (decimal: 7808) --> PA or invalid?
|
||||
VA 3: 0x00002556 (decimal: 9558) --> PA or invalid?
|
||||
VA 4: 0x00003a1e (decimal: 14878) --> PA or invalid?
|
||||
|
||||
For each virtual address, write down the physical address it
|
||||
translates to OR write down that it is an out-of-bounds
|
||||
address (e.g., a segmentation fault).
|
||||
|
||||
As you can see, what the program provides for you is a page table for a
|
||||
particular process (remember, in a real system with linear page tables, there
|
||||
is one page table per process; here we just focus on one process, its address
|
||||
space, and thus a single page table). The page table tells you, for each
|
||||
virtual page number (VPN) of the address space, that the virtual page is
|
||||
mapped to a particular physical frame number (PFN) and thus valid, or not
|
||||
valid.
|
||||
|
||||
The format of the page-table entry is simple: the left-most (high-order) bit
|
||||
is the valid bit; the remaining bits, if valid is 1, is the PFN.
|
||||
|
||||
In the example above, the page table maps VPN 0 to PFN 0xc (decimal 12), VPN 3
|
||||
to PFN 0x6 (decimal 6), and leaves the other two virtual pages, 1 and 2, as
|
||||
not valid.
|
||||
|
||||
Because the page table is a linear array, what is printed above is a replica
|
||||
of what you would see in memory if you looked at the bits yourself. However,
|
||||
it is sometimes easier to use this simulator if you run with the verbose flag
|
||||
(-v); this flag also prints out the VPN (index) into the page table. From the
|
||||
example above, run with the -v flag:
|
||||
|
||||
Page Table (from entry 0 down to the max size)
|
||||
[ 0] 0x8000000c
|
||||
[ 1] 0x00000000
|
||||
[ 2] 0x00000000
|
||||
[ 3] 0x80000006
|
||||
|
||||
Your job, then, is to use this page table to translate the virtual addresses
|
||||
given to you in the trace to physical addresses. Let's look at the first one:
|
||||
VA 0x3229. To translate this virtual address into a physical address, we first
|
||||
have to break it up into its constituent components: a virtual page number and
|
||||
an offset. We do this by noting down the size of the address space and the
|
||||
page size. In this example, the address space is set to 16KB (a very small
|
||||
address space) and the page size is 4KB. Thus, we know that there are 14 bits
|
||||
in the virtual address, and that the offset is 12 bits, leaving 2 bits for the
|
||||
VPN. Thus, with our address 0x3229, which is binary 11 0010 0010 1001, we know
|
||||
the top two bits specify the VPN. Thus, 0x3229 is on virtual page 3 with an
|
||||
offset of 0x229.
|
||||
|
||||
We next look in the page table to see if VPN 3 is valid and mapped to some
|
||||
physical frame or invalid, and we see that it is indeed valid (the high bit is
|
||||
1) and mapped to physical page 6. Thus, we can form our final physical address
|
||||
by taking the physical page 6 and adding it onto the offset, as follows:
|
||||
0x6000 (the physical page, shifted into the proper spot) OR 0x0229 (the
|
||||
offset), yielding the final physical address: 0x6229. Thus, we can see that
|
||||
virtual address 0x3229 translates to physical address 0x6229 in this example.
|
||||
|
||||
To see the rest of the solutions (after you have computed them yourself!),
|
||||
just run with the -c flag (as always):
|
||||
|
||||
...
|
||||
VA 0: 00003229 (decimal: 12841) --> 00006229 (25129) [VPN 3]
|
||||
VA 1: 00001369 (decimal: 4969) --> Invalid (VPN 1 not valid)
|
||||
VA 2: 00001e80 (decimal: 7808) --> Invalid (VPN 1 not valid)
|
||||
VA 3: 00002556 (decimal: 9558) --> Invalid (VPN 2 not valid)
|
||||
VA 4: 00003a1e (decimal: 14878) --> 00006a1e (27166) [VPN 3]
|
||||
|
||||
Of course, you can change many of these parameters to make more interesting
|
||||
problems. Run the program with the -h flag to see what options there are:
|
||||
|
||||
- The -s flag changes the random seed and thus generates different
|
||||
page table values as well as different virtual addresses to translate.
|
||||
- The -a flag changes the size of the address space.
|
||||
- The -p flag changes the size of physical memory.
|
||||
- The -P flag changes the size of a page.
|
||||
- The -n flag can be used to generate more addresses to translate
|
||||
(instead of the default 5).
|
||||
- The -u flag changes the fraction of mappings that are valid, from
|
||||
0% (-u 0) up to 100% (-u 100). The default is 50, which means
|
||||
that roughly 1/2 of the pages in the virtual address space will be valid.
|
||||
- The -v flag prints out the VPN numbers to make your life easier.
|
||||
|
||||
|
||||
|
192
related_info/ostep/ostep4-paging-linear-translate.py
Executable file
192
related_info/ostep/ostep4-paging-linear-translate.py
Executable file
@ -0,0 +1,192 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
import random
|
||||
import math
|
||||
|
||||
def mustbepowerof2(bits, size, msg):
|
||||
if math.pow(2,bits) != size:
|
||||
print 'Error in argument: %s' % msg
|
||||
sys.exit(1)
|
||||
|
||||
def mustbemultipleof(bignum, num, msg):
|
||||
if (int(float(bignum)/float(num)) != (int(bignum) / int(num))):
|
||||
print 'Error in argument: %s' % msg
|
||||
sys.exit(1)
|
||||
|
||||
def convert(size):
|
||||
length = len(size)
|
||||
lastchar = size[length-1]
|
||||
if (lastchar == 'k') or (lastchar == 'K'):
|
||||
m = 1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
elif (lastchar == 'm') or (lastchar == 'M'):
|
||||
m = 1024*1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
elif (lastchar == 'g') or (lastchar == 'G'):
|
||||
m = 1024*1024*1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
else:
|
||||
nsize = int(size)
|
||||
return nsize
|
||||
|
||||
|
||||
#
|
||||
# main program
|
||||
#
|
||||
parser = OptionParser()
|
||||
parser.add_option('-A', '--addresses', default='-1',
|
||||
help='a set of comma-separated pages to access; -1 means randomly generate',
|
||||
action='store', type='string', dest='addresses')
|
||||
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
|
||||
parser.add_option('-a', '--asize', default='16k', help='address space size (e.g., 16, 64k, 32m, 1g)', action='store', type='string', dest='asize')
|
||||
parser.add_option('-p', '--physmem', default='64k', help='physical memory size (e.g., 16, 64k, 32m, 1g)', action='store', type='string', dest='psize')
|
||||
parser.add_option('-P', '--pagesize', default='4k', help='page size (e.g., 4k, 8k, whatever)', action='store', type='string', dest='pagesize')
|
||||
parser.add_option('-n', '--numaddrs', default=5, help='number of virtual addresses to generate', action='store', type='int', dest='num')
|
||||
parser.add_option('-u', '--used', default=50, help='percent of virtual address space that is used', action='store', type='int', dest='used')
|
||||
parser.add_option('-v', help='verbose mode', action='store_true', default=False, dest='verbose')
|
||||
parser.add_option('-c', help='compute answers for me', action='store_true', default=False, dest='solve')
|
||||
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
print 'ARG seed', options.seed
|
||||
print 'ARG address space size', options.asize
|
||||
print 'ARG phys mem size', options.psize
|
||||
print 'ARG page size', options.pagesize
|
||||
print 'ARG verbose', options.verbose
|
||||
print 'ARG addresses', options.addresses
|
||||
print ''
|
||||
|
||||
random.seed(options.seed)
|
||||
|
||||
asize = convert(options.asize)
|
||||
psize = convert(options.psize)
|
||||
pagesize = convert(options.pagesize)
|
||||
addresses = str(options.addresses)
|
||||
|
||||
if psize <= 1:
|
||||
print 'Error: must specify a non-zero physical memory size.'
|
||||
exit(1)
|
||||
|
||||
if asize < 1:
|
||||
print 'Error: must specify a non-zero address-space size.'
|
||||
exit(1)
|
||||
|
||||
if psize <= asize:
|
||||
print 'Error: physical memory size must be GREATER than address space size (for this simulation)'
|
||||
exit(1)
|
||||
|
||||
if psize >= convert('1g') or asize >= convert('1g'):
|
||||
print 'Error: must use smaller sizes (less than 1 GB) for this simulation.'
|
||||
exit(1)
|
||||
|
||||
mustbemultipleof(asize, pagesize, 'address space must be a multiple of the pagesize')
|
||||
mustbemultipleof(psize, pagesize, 'physical memory must be a multiple of the pagesize')
|
||||
|
||||
# print some useful info, like the darn page table
|
||||
pages = psize / pagesize;
|
||||
import array
|
||||
used = array.array('i')
|
||||
pt = array.array('i')
|
||||
for i in range(0,pages):
|
||||
used.insert(i,0)
|
||||
vpages = asize / pagesize
|
||||
|
||||
# now, assign some pages of the VA
|
||||
vabits = int(math.log(float(asize))/math.log(2.0))
|
||||
mustbepowerof2(vabits, asize, 'address space must be a power of 2')
|
||||
pagebits = int(math.log(float(pagesize))/math.log(2.0))
|
||||
mustbepowerof2(pagebits, pagesize, 'page size must be a power of 2')
|
||||
vpnbits = vabits - pagebits
|
||||
pagemask = (1 << pagebits) - 1
|
||||
|
||||
# import ctypes
|
||||
# vpnmask = ctypes.c_uint32(~pagemask).value
|
||||
vpnmask = 0xFFFFFFFF & ~pagemask
|
||||
#if vpnmask2 != vpnmask:
|
||||
# print 'ERROR'
|
||||
# exit(1)
|
||||
# print 'va:%d page:%d vpn:%d -- %08x %08x' % (vabits, pagebits, vpnbits, vpnmask, pagemask)
|
||||
|
||||
print ''
|
||||
print 'The format of the page table is simple:'
|
||||
print 'The high-order (left-most) bit is the VALID bit.'
|
||||
print ' If the bit is 1, the rest of the entry is the PFN.'
|
||||
print ' If the bit is 0, the page is not valid.'
|
||||
print 'Use verbose mode (-v) if you want to print the VPN # by'
|
||||
print 'each entry of the page table.'
|
||||
print ''
|
||||
|
||||
print 'Page Table (from entry 0 down to the max size)'
|
||||
for v in range(0,vpages):
|
||||
done = 0
|
||||
while done == 0:
|
||||
if ((random.random() * 100.0) > (100.0 - float(options.used))):
|
||||
u = int(pages * random.random())
|
||||
if used[u] == 0:
|
||||
done = 1
|
||||
# print '%8d - %d' % (v, u)
|
||||
if options.verbose == True:
|
||||
print ' [%8d] ' % v,
|
||||
else:
|
||||
print ' ',
|
||||
print '0x%08x' % (0x80000000 | u)
|
||||
pt.insert(v,u)
|
||||
else:
|
||||
# print '%8d - not valid' % v
|
||||
if options.verbose == True:
|
||||
print ' [%8d] ' % v,
|
||||
else:
|
||||
print ' ',
|
||||
print '0x%08x' % 0
|
||||
pt.insert(v,-1)
|
||||
done = 1
|
||||
print ''
|
||||
|
||||
|
||||
#
|
||||
# now, need to generate virtual address trace
|
||||
#
|
||||
|
||||
addrList = []
|
||||
if addresses == '-1':
|
||||
# need to generate addresses
|
||||
for i in range(0, options.num):
|
||||
n = int(asize * random.random())
|
||||
addrList.append(n)
|
||||
else:
|
||||
addrList = addresses.split(',')
|
||||
|
||||
|
||||
print 'Virtual Address Trace'
|
||||
for vStr in addrList:
|
||||
# vaddr = int(asize * random.random())
|
||||
vaddr = int(vStr)
|
||||
if options.solve == False:
|
||||
print ' VA 0x%08x (decimal: %8d) --> PA or invalid address?' % (vaddr, vaddr)
|
||||
else:
|
||||
paddr = 0
|
||||
# split vaddr into VPN | offset
|
||||
vpn = (vaddr & vpnmask) >> pagebits
|
||||
if pt[vpn] < 0:
|
||||
print ' VA 0x%08x (decimal: %8d) --> Invalid (VPN %d not valid)' % (vaddr, vaddr, vpn)
|
||||
else:
|
||||
pfn = pt[vpn]
|
||||
offset = vaddr & pagemask
|
||||
paddr = (pfn << pagebits) | offset
|
||||
print ' VA 0x%08x (decimal: %8d) --> %08x (decimal %8d) [VPN %d]' % (vaddr, vaddr, paddr, paddr, vpn)
|
||||
print ''
|
||||
|
||||
if options.solve == False:
|
||||
print 'For each virtual address, write down the physical address it translates to'
|
||||
print 'OR write down that it is an out-of-bounds address (e.g., segfault).'
|
||||
print ''
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
71
related_info/ostep/ostep5-paging-multilevel-translate.md
Normal file
71
related_info/ostep/ostep5-paging-multilevel-translate.md
Normal file
@ -0,0 +1,71 @@
|
||||
|
||||
This fun little homework tests if you understand how a multi-level page table
|
||||
works. And yes, there is some debate over the use of the term fun in the
|
||||
previous sentence. The program is called:
|
||||
paging-multilevel-translate.py
|
||||
|
||||
Some basic assumptions:
|
||||
|
||||
- The page size is an unrealistically-small 32 bytes
|
||||
- The virtual address space for the process in question (assume there is
|
||||
only one) is 1024 pages, or 32 KB
|
||||
- physical memory consists of 128 pages
|
||||
|
||||
Thus, a virtual address needs 15 bits (5 for the offset, 10 for the VPN).
|
||||
A physical address requires 12 bits (5 offset, 7 for the PFN).
|
||||
|
||||
The system assumes a multi-level page table. Thus, the upper five bits of a virtual
|
||||
address are used to index into a page directory; the page directory entry (PDE), if valid,
|
||||
points to a page of the page table. Each page table page holds 32 page-table entries
|
||||
(PTEs). Each PTE, if valid, holds the desired translation (physical frame number, or PFN)
|
||||
of the virtual page in question.
|
||||
|
||||
The format of a PTE is thus:
|
||||
|
||||
VALID | PFN6 ... PFN0
|
||||
|
||||
and is thus 8 bits or 1 byte.
|
||||
|
||||
The format of a PDE is essentially identical:
|
||||
|
||||
VALID | PT6 ... PT0
|
||||
|
||||
You are given two pieces of information to begin with.
|
||||
|
||||
First, you are given the value of the page directory base register (PDBR),
|
||||
which tells you which page the page directory is located upon.
|
||||
|
||||
Second, you are given a complete dump of each page of memory. A page dump
|
||||
looks like this:
|
||||
|
||||
page 0: 08 00 01 15 11 1d 1d 1c 01 17 15 14 16 1b 13 0b ...
|
||||
page 1: 19 05 1e 13 02 16 1e 0c 15 09 06 16 00 19 10 03 ...
|
||||
page 2: 1d 07 11 1b 12 05 07 1e 09 1a 18 17 16 18 1a 01 ...
|
||||
...
|
||||
|
||||
which shows the 32 bytes found on pages 0, 1, 2, and so forth. The first byte
|
||||
(0th byte) on page 0 has the value 0x08, the second is 0x00, the third 0x01,
|
||||
and so forth.
|
||||
|
||||
You are then given a list of virtual addresses to translate.
|
||||
|
||||
Use the PDBR to find the relevant page table entries for this virtual page.
|
||||
Then find if it is valid. If so, use the translation to form a final physical
|
||||
address. Using this address, you can find the VALUE that the memory reference
|
||||
is looking for.
|
||||
|
||||
Of course, the virtual address may not be valid and thus generate a fault.
|
||||
|
||||
Some useful options:
|
||||
|
||||
-s SEED, --seed=SEED the random seed
|
||||
-n NUM, --addresses=NUM number of virtual addresses to generate
|
||||
-c, --solve compute answers for me
|
||||
|
||||
Change the seed to get different problems, as always.
|
||||
|
||||
Change the number of virtual addresses generated to do more translations
|
||||
for a given memory dump.
|
||||
|
||||
Use -c (or --solve) to show the solutions.
|
||||
|
263
related_info/ostep/ostep5-paging-multilevel-translate.py
Executable file
263
related_info/ostep/ostep5-paging-multilevel-translate.py
Executable file
@ -0,0 +1,263 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
import random
|
||||
import math
|
||||
|
||||
def convert(size):
|
||||
length = len(size)
|
||||
lastchar = size[length-1]
|
||||
if (lastchar == 'k') or (lastchar == 'K'):
|
||||
m = 1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
elif (lastchar == 'm') or (lastchar == 'M'):
|
||||
m = 1024*1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
elif (lastchar == 'g') or (lastchar == 'G'):
|
||||
m = 1024*1024*1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
else:
|
||||
nsize = int(size)
|
||||
return nsize
|
||||
|
||||
def roundup(size):
|
||||
value = 1.0
|
||||
while value < size:
|
||||
value = value * 2.0
|
||||
return value
|
||||
|
||||
|
||||
class OS:
|
||||
def __init__(self):
|
||||
# 4k phys memory (128 pages)
|
||||
self.pageSize = 32
|
||||
self.physPages = 128
|
||||
self.physMem = self.pageSize * self.physPages
|
||||
self.vaPages = 1024
|
||||
self.vaSize = self.pageSize * self.vaPages
|
||||
self.pteSize = 1
|
||||
self.pageBits = 5 # log of page size
|
||||
|
||||
# os tracks
|
||||
self.usedPages = []
|
||||
self.usedPagesCount = 0
|
||||
self.maxPageCount = self.physMem / self.pageSize
|
||||
|
||||
# no pages used (yet)
|
||||
for i in range(0, self.maxPageCount):
|
||||
self.usedPages.append(0)
|
||||
|
||||
# set contents of memory to 0, too
|
||||
self.memory = []
|
||||
for i in range(0, self.physMem):
|
||||
self.memory.append(0)
|
||||
|
||||
# associative array of pdbr's (indexed by PID)
|
||||
self.pdbr = {}
|
||||
|
||||
# mask is 11111 00000 00000 --> 0111 1100 0000 0000
|
||||
self.PDE_MASK = 0x7c00
|
||||
self.PDE_SHIFT = 10
|
||||
|
||||
# 00000 11111 00000 -> 000 0011 1110 0000
|
||||
self.PTE_MASK = 0x03e0
|
||||
self.PTE_SHIFT = 5
|
||||
|
||||
self.VPN_MASK = self.PDE_MASK | self.PTE_MASK
|
||||
self.VPN_SHIFT = self.PTE_SHIFT
|
||||
|
||||
# grabs the last five bits of a virtual address
|
||||
self.OFFSET_MASK = 0x1f
|
||||
|
||||
def findFree(self):
|
||||
assert(self.usedPagesCount < self.maxPageCount)
|
||||
look = int(random.random() * self.maxPageCount)
|
||||
while self.usedPages[look] == 1:
|
||||
look = int(random.random() * self.maxPageCount)
|
||||
self.usedPagesCount = self.usedPagesCount + 1
|
||||
self.usedPages[look] = 1
|
||||
return look
|
||||
|
||||
def initPageDir(self, whichPage):
|
||||
whichByte = whichPage << self.pageBits
|
||||
for i in range(whichByte, whichByte + self.pageSize):
|
||||
self.memory[i] = 0x7f
|
||||
|
||||
def initPageTablePage(self, whichPage):
|
||||
self.initPageDir(whichPage)
|
||||
|
||||
def getPageTableEntry(self, virtualAddr, ptePage, printStuff):
|
||||
pteBits = (virtualAddr & self.PTE_MASK) >> self.PTE_SHIFT
|
||||
pteAddr = (ptePage << self.pageBits) | pteBits
|
||||
pte = self.memory[pteAddr]
|
||||
valid = (pte & 0x80) >> 7
|
||||
pfn = (pte & 0x7f)
|
||||
if printStuff == True:
|
||||
print ' --> pte index:0x%x pte contents:(valid %d, pfn 0x%02x)' % (pteBits, valid, pfn)
|
||||
return (valid, pfn, pteAddr)
|
||||
|
||||
def getPageDirEntry(self, pid, virtualAddr, printStuff):
|
||||
pageDir = self.pdbr[pid]
|
||||
pdeBits = (virtualAddr & self.PDE_MASK) >> self.PDE_SHIFT
|
||||
pdeAddr = (pageDir << self.pageBits) | pdeBits
|
||||
pde = self.memory[pdeAddr]
|
||||
valid = (pde & 0x80) >> 7
|
||||
ptPtr = (pde & 0x7f)
|
||||
if printStuff == True:
|
||||
print ' --> pde index:0x%x pde contents:(valid %d, pfn 0x%02x)' % (pdeBits, valid, ptPtr)
|
||||
return (valid, ptPtr, pdeAddr)
|
||||
|
||||
def setPageTableEntry(self, pteAddr, physicalPage):
|
||||
self.memory[pteAddr] = 0x80 | physicalPage
|
||||
|
||||
def setPageDirEntry(self, pdeAddr, physicalPage):
|
||||
self.memory[pdeAddr] = 0x80 | physicalPage
|
||||
|
||||
def allocVirtualPage(self, pid, virtualPage, physicalPage):
|
||||
# make it into a virtual address, as everything uses this (and not VPN)
|
||||
virtualAddr = virtualPage << self.pageBits
|
||||
(valid, ptPtr, pdeAddr) = self.getPageDirEntry(pid, virtualAddr, False)
|
||||
if valid == 0:
|
||||
# must allocate a page of the page table now, and have the PD point to it
|
||||
assert(ptPtr == 127)
|
||||
ptePage = self.findFree()
|
||||
self.setPageDirEntry(pdeAddr, ptePage)
|
||||
self.initPageTablePage(ptePage)
|
||||
else:
|
||||
# otherwise, just extract page number of page table page
|
||||
ptePage = ptPtr
|
||||
# now, look up page table entry too, and mark it valid and fill in translation
|
||||
(valid, pfn, pteAddr) = self.getPageTableEntry(virtualAddr, ptePage, False)
|
||||
assert(valid == 0)
|
||||
assert(pfn == 127)
|
||||
self.setPageTableEntry(pteAddr, physicalPage)
|
||||
|
||||
# -2 -> PTE fault, -1 means PDE fault
|
||||
def translate(self, pid, virtualAddr):
|
||||
(valid, ptPtr, pdeAddr) = self.getPageDirEntry(pid, virtualAddr, True)
|
||||
if valid == 1:
|
||||
ptePage = ptPtr
|
||||
(valid, pfn, pteAddr) = self.getPageTableEntry(virtualAddr, ptePage, True)
|
||||
if valid == 1:
|
||||
offset = (virtualAddr & self.OFFSET_MASK)
|
||||
paddr = (pfn << self.pageBits) | offset
|
||||
# print ' --> pfn: %02x offset: %x' % (pfn, offset)
|
||||
return paddr
|
||||
else:
|
||||
return -2
|
||||
return -1
|
||||
|
||||
def fillPage(self, whichPage):
|
||||
for j in range(0, self.pageSize):
|
||||
self.memory[(whichPage * self.pageSize) + j] = int(random.random() * 31)
|
||||
|
||||
def procAlloc(self, pid, numPages):
|
||||
# need a PDBR: find one somewhere in memory
|
||||
pageDir = self.findFree()
|
||||
# print '**ALLOCATE** page dir', pageDir
|
||||
self.pdbr[pid] = pageDir
|
||||
self.initPageDir(pageDir)
|
||||
|
||||
used = {}
|
||||
for vp in range(0, self.vaPages):
|
||||
used[vp] = 0
|
||||
allocatedVPs = []
|
||||
|
||||
for vp in range(0, numPages):
|
||||
vp = int(random.random() * self.vaPages)
|
||||
while used[vp] == 1:
|
||||
vp = int(random.random() * self.vaPages)
|
||||
assert(used[vp] == 0)
|
||||
used[vp] = 1
|
||||
allocatedVPs.append(vp)
|
||||
pp = self.findFree()
|
||||
# print '**ALLOCATE** page', pp
|
||||
# print ' trying to map vp:%08x to pp:%08x' % (vp, pp)
|
||||
self.allocVirtualPage(pid, vp, pp)
|
||||
self.fillPage(pp)
|
||||
return allocatedVPs
|
||||
|
||||
def dumpPage(self, whichPage):
|
||||
i = whichPage
|
||||
for j in range(0, self.pageSize):
|
||||
print self.memory[(i * self.pageSize) + j],
|
||||
print ''
|
||||
|
||||
def memoryDump(self):
|
||||
for i in range(0, self.physMem / self.pageSize):
|
||||
print 'page %3d:' % i,
|
||||
for j in range(0, self.pageSize):
|
||||
print '%02x' % self.memory[(i * self.pageSize) + j],
|
||||
print ''
|
||||
|
||||
def getPDBR(self, pid):
|
||||
return self.pdbr[pid]
|
||||
|
||||
def getValue(self, addr):
|
||||
return self.memory[addr]
|
||||
|
||||
# allocate some processes in memory
|
||||
# allocate some multi-level page tables in memory
|
||||
# make a bit of a mystery:
|
||||
# can examine PDBR (PFN of current proc's page directory)
|
||||
# can examine contents of any page
|
||||
# fill pages with values too
|
||||
# ask: when given
|
||||
# LOAD VA, R1
|
||||
# what will final value will be loaded into R1?
|
||||
|
||||
#
|
||||
# main program
|
||||
#
|
||||
parser = OptionParser()
|
||||
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
|
||||
parser.add_option('-a', '--allocated', default=64, help='number of virtual pages allocated',
|
||||
action='store', type='int', dest='allocated')
|
||||
parser.add_option('-n', '--addresses', default=10, help='number of virtual addresses to generate',
|
||||
action='store', type='int', dest='num')
|
||||
parser.add_option('-c', '--solve', help='compute answers for me', action='store_true', default=False, dest='solve')
|
||||
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
print 'ARG seed', options.seed
|
||||
print 'ARG allocated', options.allocated
|
||||
print 'ARG num', options.num
|
||||
print ""
|
||||
|
||||
random.seed(options.seed)
|
||||
|
||||
# do the work now
|
||||
os = OS()
|
||||
used = os.procAlloc(1, options.allocated)
|
||||
|
||||
os.memoryDump()
|
||||
|
||||
print '\nPDBR:', os.getPDBR(1), ' (decimal) [This means the page directory is held in this page]\n'
|
||||
|
||||
for i in range(0, options.num):
|
||||
if (random.random() * 100) > 50.0 or i >= len(used):
|
||||
vaddr = int(random.random() * 1024 * 32)
|
||||
else:
|
||||
vaddr = (used[i] << 5) | int(random.random() * 32)
|
||||
if options.solve == True:
|
||||
print 'Virtual Address %04x:' % vaddr
|
||||
r = os.translate(1, vaddr)
|
||||
if r > -1:
|
||||
print ' --> Translates to Physical Address 0x%03x --> Value: %02x' % (r, os.getValue(r))
|
||||
elif r == -1:
|
||||
print ' --> Fault (page directory entry not valid)'
|
||||
else:
|
||||
print ' --> Fault (page table entry not valid)'
|
||||
else:
|
||||
print 'Virtual Address %04x: Translates To What Physical Address (And Fetches what Value)? Or Fault?' % vaddr
|
||||
|
||||
print ''
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
|
||||
|
||||
|
119
related_info/ostep/ostep6-paging-policy.md
Normal file
119
related_info/ostep/ostep6-paging-policy.md
Normal file
@ -0,0 +1,119 @@
|
||||
|
||||
This simulator, paging-policy.py, allows you to play around with different
|
||||
page-replacement policies. For example, let's examine how LRU performs with a
|
||||
series of page references with a cache of size 3:
|
||||
|
||||
0 1 2 0 1 3 0 3 1 2 1
|
||||
|
||||
To do so, run the simulator as follows:
|
||||
|
||||
prompt> ./paging-policy.py --addresses=0,1,2,0,1,3,0,3,1,2,1
|
||||
--policy=LRU --cachesize=3 -c
|
||||
|
||||
And what you would see is:
|
||||
|
||||
ARG addresses 0,1,2,0,1,3,0,3,1,2,1
|
||||
ARG numaddrs 10
|
||||
ARG policy LRU
|
||||
ARG cachesize 3
|
||||
ARG maxpage 10
|
||||
ARG seed 0
|
||||
|
||||
Solving...
|
||||
|
||||
Access: 0 MISS LRU-> [br 0]<-MRU Replace:- [br Hits:0 Misses:1]
|
||||
Access: 1 MISS LRU-> [br 0, 1]<-MRU Replace:- [br Hits:0 Misses:2]
|
||||
Access: 2 MISS LRU->[br 0, 1, 2]<-MRU Replace:- [br Hits:0 Misses:3]
|
||||
Access: 0 HIT LRU->[br 1, 2, 0]<-MRU Replace:- [br Hits:1 Misses:3]
|
||||
Access: 1 HIT LRU->[br 2, 0, 1]<-MRU Replace:- [br Hits:2 Misses:3]
|
||||
Access: 3 MISS LRU->[br 0, 1, 3]<-MRU Replace:2 [br Hits:2 Misses:4]
|
||||
Access: 0 HIT LRU->[br 1, 3, 0]<-MRU Replace:2 [br Hits:3 Misses:4]
|
||||
Access: 3 HIT LRU->[br 1, 0, 3]<-MRU Replace:2 [br Hits:4 Misses:4]
|
||||
Access: 1 HIT LRU->[br 0, 3, 1]<-MRU Replace:2 [br Hits:5 Misses:4]
|
||||
Access: 2 MISS LRU->[br 3, 1, 2]<-MRU Replace:0 [br Hits:5 Misses:5]
|
||||
Access: 1 HIT LRU->[br 3, 2, 1]<-MRU Replace:0 [br Hits:6 Misses:5]
|
||||
]
|
||||
|
||||
The complete set of possible arguments for paging-policy is listed on the
|
||||
following page, and includes a number of options for varying the policy, how
|
||||
addresses are specified/generated, and other important parameters such as the
|
||||
size of the cache.
|
||||
|
||||
prompt> ./paging-policy.py --help
|
||||
Usage: paging-policy.py [options]
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-a ADDRESSES, --addresses=ADDRESSES
|
||||
a set of comma-separated pages to access;
|
||||
-1 means randomly generate
|
||||
-f ADDRESSFILE, --addressfile=ADDRESSFILE
|
||||
a file with a bunch of addresses in it
|
||||
-n NUMADDRS, --numaddrs=NUMADDRS
|
||||
if -a (--addresses) is -1, this is the
|
||||
number of addrs to generate
|
||||
-p POLICY, --policy=POLICY
|
||||
replacement policy: FIFO, LRU, LFU, OPT,
|
||||
UNOPT, RAND, CLOCK
|
||||
-b CLOCKBITS, --clockbits=CLOCKBITS
|
||||
for CLOCK policy, how many clock bits to use
|
||||
-C CACHESIZE, --cachesize=CACHESIZE
|
||||
size of the page cache, in pages
|
||||
-m MAXPAGE, --maxpage=MAXPAGE
|
||||
if randomly generating page accesses,
|
||||
this is the max page number
|
||||
-s SEED, --seed=SEED random number seed
|
||||
-N, --notrace do not print out a detailed trace
|
||||
-c, --compute compute answers for me
|
||||
]
|
||||
|
||||
As usual, "-c" is used to solve a particular problem, whereas without it, the
|
||||
accesses are just listed (and the program does not tell you whether or not a
|
||||
particular access is a hit or miss).
|
||||
|
||||
To generate a random problem, instead of using "-a/--addresses" to pass in
|
||||
some page references, you can instead pass in "-n/--numaddrs" as the number of
|
||||
addresses the program should randomly generate, with "-s/--seed" used to
|
||||
specify a different random seed. For example:
|
||||
|
||||
prompt> ./paging-policy.py -s 10 -n 3
|
||||
.. .
|
||||
Assuming a replacement policy of FIFO, and a cache of size 3 pages,
|
||||
figure out whether each of the following page references hit or miss
|
||||
in the page cache.
|
||||
|
||||
Access: 5 Hit/Miss? State of Memory?
|
||||
Access: 4 Hit/Miss? State of Memory?
|
||||
Access: 5 Hit/Miss? State of Memory?
|
||||
]
|
||||
|
||||
As you can see, in this example, we specify "-n 3" which means the program
|
||||
should generate 3 random page references, which it does: 5, 7, and 5. The
|
||||
random seed is also specified (10), which is what gets us those particular
|
||||
numbers. After working this out yourself, have the program solve the problem
|
||||
for you by passing in the same arguments but with "-c" (showing just the
|
||||
relevant part here):
|
||||
|
||||
prompt> ./paging-policy.py -s 10 -n 3 -c
|
||||
...
|
||||
Solving...
|
||||
|
||||
Access: 5 MISS FirstIn-> [br 5] <-Lastin Replace:- [br Hits:0 Misses:1]
|
||||
Access: 4 MISS FirstIn->[br 5, 4] <-Lastin Replace:- [br Hits:0 Misses:2]
|
||||
Access: 5 HIT FirstIn->[br 5, 4] <-Lastin Replace:- [br Hits:1 Misses:2]
|
||||
]
|
||||
|
||||
The default policy is FIFO, though others are available, including LRU, MRU,
|
||||
OPT (the optimal replacement policy, which peeks into the future to see what
|
||||
is best to replace), UNOPT (which is the pessimal replacement), RAND (which
|
||||
does random replacement), and CLOCK (which does the clock algorithm). The
|
||||
CLOCK algorithm also takes another argument (-b), which states how many bits
|
||||
should be kept per page; the more clock bits there are, the better the
|
||||
algorithm should be at determining which pages to keep in memory.
|
||||
|
||||
Other options include: "-C/--cachesize" which changes the size of the page
|
||||
cache; "-m/--maxpage" which is the largest page number that will be used if
|
||||
the simulator is generating references for you; and "-f/--addressfile" which
|
||||
lets you specify a file with addresses in them, in case you wish to get traces
|
||||
from a real application or otherwise use a long trace as input.
|
||||
|
275
related_info/ostep/ostep6-paging-policy.py
Executable file
275
related_info/ostep/ostep6-paging-policy.py
Executable file
@ -0,0 +1,275 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
import random
|
||||
import math
|
||||
|
||||
def convert(size):
|
||||
length = len(size)
|
||||
lastchar = size[length-1]
|
||||
if (lastchar == 'k') or (lastchar == 'K'):
|
||||
m = 1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
elif (lastchar == 'm') or (lastchar == 'M'):
|
||||
m = 1024*1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
elif (lastchar == 'g') or (lastchar == 'G'):
|
||||
m = 1024*1024*1024
|
||||
nsize = int(size[0:length-1]) * m
|
||||
else:
|
||||
nsize = int(size)
|
||||
return nsize
|
||||
|
||||
def hfunc(index):
|
||||
if index == -1:
|
||||
return 'MISS'
|
||||
else:
|
||||
return 'HIT '
|
||||
|
||||
def vfunc(victim):
|
||||
if victim == -1:
|
||||
return '-'
|
||||
else:
|
||||
return str(victim)
|
||||
|
||||
#
|
||||
# main program
|
||||
#
|
||||
parser = OptionParser()
|
||||
parser.add_option('-a', '--addresses', default='-1', help='a set of comma-separated pages to access; -1 means randomly generate', action='store', type='string', dest='addresses')
|
||||
parser.add_option('-f', '--addressfile', default='', help='a file with a bunch of addresses in it', action='store', type='string', dest='addressfile')
|
||||
parser.add_option('-n', '--numaddrs', default='10', help='if -a (--addresses) is -1, this is the number of addrs to generate', action='store', type='string', dest='numaddrs')
|
||||
parser.add_option('-p', '--policy', default='FIFO', help='replacement policy: FIFO, LRU, OPT, UNOPT, RAND, CLOCK', action='store', type='string', dest='policy')
|
||||
parser.add_option('-b', '--clockbits', default=2, help='for CLOCK policy, how many clock bits to use', action='store', type='int', dest='clockbits')
|
||||
parser.add_option('-C', '--cachesize', default='3', help='size of the page cache, in pages', action='store', type='string', dest='cachesize')
|
||||
parser.add_option('-m', '--maxpage', default='10', help='if randomly generating page accesses, this is the max page number', action='store', type='string', dest='maxpage')
|
||||
parser.add_option('-s', '--seed', default='0', help='random number seed', action='store', type='string', dest='seed')
|
||||
parser.add_option('-N', '--notrace', default=False, help='do not print out a detailed trace', action='store_true', dest='notrace')
|
||||
parser.add_option('-c', '--compute', default=False, help='compute answers for me', action='store_true', dest='solve')
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
print 'ARG addresses', options.addresses
|
||||
print 'ARG addressfile', options.addressfile
|
||||
print 'ARG numaddrs', options.numaddrs
|
||||
print 'ARG policy', options.policy
|
||||
print 'ARG clockbits', options.clockbits
|
||||
print 'ARG cachesize', options.cachesize
|
||||
print 'ARG maxpage', options.maxpage
|
||||
print 'ARG seed', options.seed
|
||||
print 'ARG notrace', options.notrace
|
||||
print ''
|
||||
|
||||
addresses = str(options.addresses)
|
||||
addressFile = str(options.addressfile)
|
||||
numaddrs = int(options.numaddrs)
|
||||
cachesize = int(options.cachesize)
|
||||
seed = int(options.seed)
|
||||
maxpage = int(options.maxpage)
|
||||
policy = str(options.policy)
|
||||
notrace = options.notrace
|
||||
clockbits = int(options.clockbits)
|
||||
|
||||
random.seed(seed)
|
||||
|
||||
addrList = []
|
||||
if addressFile != '':
|
||||
fd = open(addressFile)
|
||||
for line in fd:
|
||||
addrList.append(int(line))
|
||||
fd.close()
|
||||
else:
|
||||
if addresses == '-1':
|
||||
# need to generate addresses
|
||||
for i in range(0,numaddrs):
|
||||
n = int(maxpage * random.random())
|
||||
addrList.append(n)
|
||||
else:
|
||||
addrList = addresses.split(',')
|
||||
|
||||
if options.solve == False:
|
||||
print 'Assuming a replacement policy of %s, and a cache of size %d pages,' % (policy, cachesize)
|
||||
print 'figure out whether each of the following page references hit or miss'
|
||||
print 'in the page cache.\n'
|
||||
|
||||
for n in addrList:
|
||||
print 'Access: %d Hit/Miss? State of Memory?' % int(n)
|
||||
print ''
|
||||
|
||||
else:
|
||||
if notrace == False:
|
||||
print 'Solving...\n'
|
||||
|
||||
# init memory structure
|
||||
count = 0
|
||||
memory = []
|
||||
hits = 0
|
||||
miss = 0
|
||||
|
||||
if policy == 'FIFO':
|
||||
leftStr = 'FirstIn'
|
||||
riteStr = 'Lastin '
|
||||
elif policy == 'LRU':
|
||||
leftStr = 'LRU'
|
||||
riteStr = 'MRU'
|
||||
elif policy == 'MRU':
|
||||
leftStr = 'LRU'
|
||||
riteStr = 'MRU'
|
||||
elif policy == 'OPT' or policy == 'RAND' or policy == 'UNOPT' or policy == 'CLOCK':
|
||||
leftStr = 'Left '
|
||||
riteStr = 'Right'
|
||||
else:
|
||||
print 'Policy %s is not yet implemented' % policy
|
||||
exit(1)
|
||||
|
||||
# track reference bits for clock
|
||||
ref = {}
|
||||
|
||||
cdebug = False
|
||||
|
||||
# need to generate addresses
|
||||
addrIndex = 0
|
||||
for nStr in addrList:
|
||||
# first, lookup
|
||||
n = int(nStr)
|
||||
try:
|
||||
idx = memory.index(n)
|
||||
hits = hits + 1
|
||||
if policy == 'LRU' or policy == 'MRU':
|
||||
update = memory.remove(n)
|
||||
memory.append(n) # puts it on MRU side
|
||||
except:
|
||||
idx = -1
|
||||
miss = miss + 1
|
||||
|
||||
victim = -1
|
||||
if idx == -1:
|
||||
# miss, replace?
|
||||
# print 'BUG count, cachesize:', count, cachesize
|
||||
if count == cachesize:
|
||||
# must replace
|
||||
if policy == 'FIFO' or policy == 'LRU':
|
||||
victim = memory.pop(0)
|
||||
elif policy == 'MRU':
|
||||
victim = memory.pop(count-1)
|
||||
elif policy == 'RAND':
|
||||
victim = memory.pop(int(random.random() * count))
|
||||
elif policy == 'CLOCK':
|
||||
if cdebug:
|
||||
print 'REFERENCE TO PAGE', n
|
||||
print 'MEMORY ', memory
|
||||
print 'REF (b)', ref
|
||||
|
||||
# hack: for now, do random
|
||||
# victim = memory.pop(int(random.random() * count))
|
||||
victim = -1
|
||||
while victim == -1:
|
||||
page = memory[int(random.random() * count)]
|
||||
if cdebug:
|
||||
print ' scan page:', page, ref[page]
|
||||
if ref[page] >= 1:
|
||||
ref[page] -= 1
|
||||
else:
|
||||
# this is our victim
|
||||
victim = page
|
||||
memory.remove(page)
|
||||
break
|
||||
|
||||
# remove old page's ref count
|
||||
if page in memory:
|
||||
assert('BROKEN')
|
||||
del ref[victim]
|
||||
if cdebug:
|
||||
print 'VICTIM', page
|
||||
print 'LEN', len(memory)
|
||||
print 'MEM', memory
|
||||
print 'REF (a)', ref
|
||||
|
||||
elif policy == 'OPT':
|
||||
maxReplace = -1
|
||||
replaceIdx = -1
|
||||
replacePage = -1
|
||||
# print 'OPT: access %d, memory %s' % (n, memory)
|
||||
# print 'OPT: replace from FUTURE (%s)' % addrList[addrIndex+1:]
|
||||
for pageIndex in range(0,count):
|
||||
page = memory[pageIndex]
|
||||
# now, have page 'page' at index 'pageIndex' in memory
|
||||
whenReferenced = len(addrList)
|
||||
# whenReferenced tells us when, in the future, this was referenced
|
||||
for futureIdx in range(addrIndex+1,len(addrList)):
|
||||
futurePage = int(addrList[futureIdx])
|
||||
if page == futurePage:
|
||||
whenReferenced = futureIdx
|
||||
break
|
||||
# print 'OPT: page %d is referenced at %d' % (page, whenReferenced)
|
||||
if whenReferenced >= maxReplace:
|
||||
# print 'OPT: ??? updating maxReplace (%d %d %d)' % (replaceIdx, replacePage, maxReplace)
|
||||
replaceIdx = pageIndex
|
||||
replacePage = page
|
||||
maxReplace = whenReferenced
|
||||
# print 'OPT: --> updating maxReplace (%d %d %d)' % (replaceIdx, replacePage, maxReplace)
|
||||
victim = memory.pop(replaceIdx)
|
||||
# print 'OPT: replacing page %d (idx:%d) because I saw it in future at %d' % (victim, replaceIdx, whenReferenced)
|
||||
elif policy == 'UNOPT':
|
||||
minReplace = len(addrList) + 1
|
||||
replaceIdx = -1
|
||||
replacePage = -1
|
||||
for pageIndex in range(0,count):
|
||||
page = memory[pageIndex]
|
||||
# now, have page 'page' at index 'pageIndex' in memory
|
||||
whenReferenced = len(addrList)
|
||||
# whenReferenced tells us when, in the future, this was referenced
|
||||
for futureIdx in range(addrIndex+1,len(addrList)):
|
||||
futurePage = int(addrList[futureIdx])
|
||||
if page == futurePage:
|
||||
whenReferenced = futureIdx
|
||||
break
|
||||
if whenReferenced < minReplace:
|
||||
replaceIdx = pageIndex
|
||||
replacePage = page
|
||||
minReplace = whenReferenced
|
||||
victim = memory.pop(replaceIdx)
|
||||
else:
|
||||
# miss, but no replacement needed (cache not full)
|
||||
victim = -1
|
||||
count = count + 1
|
||||
|
||||
# now add to memory
|
||||
memory.append(n)
|
||||
if cdebug:
|
||||
print 'LEN (a)', len(memory)
|
||||
if victim != -1:
|
||||
assert(victim not in memory)
|
||||
|
||||
# after miss processing, update reference bit
|
||||
if n not in ref:
|
||||
ref[n] = 1
|
||||
else:
|
||||
ref[n] += 1
|
||||
if ref[n] > clockbits:
|
||||
ref[n] = clockbits
|
||||
|
||||
if cdebug:
|
||||
print 'REF (a)', ref
|
||||
|
||||
if notrace == False:
|
||||
print 'Access: %d %s %s -> %12s <- %s Replaced:%s [Hits:%d Misses:%d]' % (n, hfunc(idx), leftStr, memory, riteStr, vfunc(victim), hits, miss)
|
||||
addrIndex = addrIndex + 1
|
||||
|
||||
print ''
|
||||
print 'FINALSTATS hits %d misses %d hitrate %.2f' % (hits, miss, (100.0*float(hits))/(float(hits)+float(miss)))
|
||||
print ''
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
213
related_info/ostep/ostep7-process-run.md
Normal file
213
related_info/ostep/ostep7-process-run.md
Normal file
@ -0,0 +1,213 @@
|
||||
|
||||
This program, called process-run.py, allows you to see how the state of a
|
||||
process state changes as it runs on a CPU. As described in the chapter,
|
||||
processes can be in a few different states:
|
||||
|
||||
RUNNING - the process is using the CPU right now
|
||||
READY - the process could be using the CPU right now
|
||||
but (alas) some other process is
|
||||
WAITING - the process is waiting on I/O
|
||||
(e.g., it issued a request to a disk)
|
||||
DONE - the process is finished executing
|
||||
|
||||
In this homework, we'll see how these process states change as a program
|
||||
runs, and thus learn a little bit better how these things work.
|
||||
|
||||
To run the program and get its options, do this:
|
||||
|
||||
prompt> ./process-run.py -h
|
||||
|
||||
If this doesn't work, type "python" before the command, like this:
|
||||
|
||||
prompt> python process-run.py -h
|
||||
|
||||
What you should see is this:
|
||||
|
||||
Usage: process-run.py [options]
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-s SEED, --seed=SEED the random seed
|
||||
-l PROCESS_LIST, --processlist=PROCESS_LIST
|
||||
a comma-separated list of processes to run, in the
|
||||
form X1:Y1,X2:Y2,... where X is the number of
|
||||
instructions that process should run, and Y the
|
||||
chances (from 0 to 100) that an instruction will use
|
||||
the CPU or issue an IO
|
||||
-L IO_LENGTH, --iolength=IO_LENGTH
|
||||
how long an IO takes
|
||||
-S PROCESS_SWITCH_BEHAVIOR, --switch=PROCESS_SWITCH_BEHAVIOR
|
||||
when to switch between processes: SWITCH_ON_IO,
|
||||
SWITCH_ON_END
|
||||
-I IO_DONE_BEHAVIOR, --iodone=IO_DONE_BEHAVIOR
|
||||
type of behavior when IO ends: IO_RUN_LATER,
|
||||
IO_RUN_IMMEDIATE
|
||||
-c compute answers for me
|
||||
-p, --printstats print statistics at end; only useful with -c flag
|
||||
(otherwise stats are not printed)
|
||||
|
||||
The most important option to understand is the PROCESS_LIST (as specified by
|
||||
the -l or --processlist flags) which specifies exactly what each running
|
||||
program (or "process") will do. A process consist of instructions, and each
|
||||
instruction can just do one of two things:
|
||||
- use the CPU
|
||||
- issue an IO (and wait for it to complete)
|
||||
|
||||
When a process uses the CPU (and does no IO at all), it should simply
|
||||
alternate between RUNNING on the CPU or being READY to run. For example, here
|
||||
is a simple run that just has one program being run, and that program only
|
||||
uses the CPU (it does no IO).
|
||||
|
||||
prompt> ./process-run.py -l 5:100 -p
|
||||
Produce a trace of what would happen when you run these processes:
|
||||
Process 0
|
||||
cpu
|
||||
cpu
|
||||
cpu
|
||||
cpu
|
||||
cpu
|
||||
|
||||
Important behaviors:
|
||||
System will switch when the current process is FINISHED or ISSUES AN IO
|
||||
After IOs, the process issuing the IO will run LATER (when it is its turn)
|
||||
|
||||
prompt>
|
||||
|
||||
Here, the process we specified is "5:100" which means it should consist of 5
|
||||
instructions, and the chances that each instruction is a CPU instruction are
|
||||
100%.
|
||||
|
||||
You can see what happens to the process by using the -c flag, which computes the
|
||||
answers for you:
|
||||
|
||||
prompt> ./process-run.py -l 5:100 -c
|
||||
Time PID: 0 CPU IOs
|
||||
1 RUN:cpu 1
|
||||
2 RUN:cpu 1
|
||||
3 RUN:cpu 1
|
||||
4 RUN:cpu 1
|
||||
5 RUN:cpu 1
|
||||
|
||||
This result is not too interesting: the process is simple in the RUN state and
|
||||
then finishes, using the CPU the whole time and thus keeping the CPU busy the
|
||||
entire run, and not doing any I/Os.
|
||||
|
||||
Let's make it slightly more complex by running two processes:
|
||||
|
||||
prompt> ./process-run.py -l 5:100,5:100
|
||||
Produce a trace of what would happen when you run these processes:
|
||||
Process 0
|
||||
cpu
|
||||
cpu
|
||||
cpu
|
||||
cpu
|
||||
cpu
|
||||
|
||||
Process 1
|
||||
cpu
|
||||
cpu
|
||||
cpu
|
||||
cpu
|
||||
cpu
|
||||
|
||||
Important behaviors:
|
||||
Scheduler will switch when the current process is FINISHED or ISSUES AN IO
|
||||
After IOs, the process issuing the IO will run LATER (when it is its turn)
|
||||
|
||||
In this case, two different processes run, each again just using the CPU. What
|
||||
happens when the operating system runs them? Let's find out:
|
||||
|
||||
prompt> ./process-run.py -l 5:100,5:100 -c
|
||||
Time PID: 0 PID: 1 CPU IOs
|
||||
1 RUN:cpu READY 1
|
||||
2 RUN:cpu READY 1
|
||||
3 RUN:cpu READY 1
|
||||
4 RUN:cpu READY 1
|
||||
5 RUN:cpu READY 1
|
||||
6 DONE RUN:cpu 1
|
||||
7 DONE RUN:cpu 1
|
||||
8 DONE RUN:cpu 1
|
||||
9 DONE RUN:cpu 1
|
||||
10 DONE RUN:cpu 1
|
||||
|
||||
As you can see above, first the process with "process ID" (or "PID") 0 runs,
|
||||
while process 1 is READY to run but just waits until 0 is done. When 0 is
|
||||
finished, it moves to the DONE state, while 1 runs. When 1 finishes, the trace
|
||||
is done.
|
||||
|
||||
Let's look at one more example before getting to some questions. In this
|
||||
example, the process just issues I/O requests.
|
||||
|
||||
prompt> ./process-run.py -l 3:0
|
||||
Produce a trace of what would happen when you run these processes:
|
||||
Process 0
|
||||
io-start
|
||||
io-start
|
||||
io-start
|
||||
|
||||
Important behaviors:
|
||||
System will switch when the current process is FINISHED or ISSUES AN IO
|
||||
After IOs, the process issuing the IO will run LATER (when it is its turn)
|
||||
|
||||
What do you think the execution trace will look like? Let's find out:
|
||||
|
||||
prompt> ./process-run.py -l 3:0 -c
|
||||
Time PID: 0 CPU IOs
|
||||
1 RUN:io-start 1
|
||||
2 WAITING 1
|
||||
3 WAITING 1
|
||||
4 WAITING 1
|
||||
5 WAITING 1
|
||||
6* RUN:io-start 1
|
||||
7 WAITING 1
|
||||
8 WAITING 1
|
||||
9 WAITING 1
|
||||
10 WAITING 1
|
||||
11* RUN:io-start 1
|
||||
12 WAITING 1
|
||||
13 WAITING 1
|
||||
14 WAITING 1
|
||||
15 WAITING 1
|
||||
16* DONE
|
||||
|
||||
As you can see, the program just issues three I/Os. When each I/O is issued,
|
||||
the process moves to a WAITING state, and while the device is busy servicing
|
||||
the I/O, the CPU is idle.
|
||||
|
||||
Let's print some stats (run the same command as above, but with the -p flag)
|
||||
to see some overall behaviors:
|
||||
|
||||
Stats: Total Time 16
|
||||
Stats: CPU Busy 3 (18.75%)
|
||||
Stats: IO Busy 12 (75.00%)
|
||||
|
||||
As you can see, the trace took 16 clock ticks to run, but the CPU was only
|
||||
busy less than 20% of the time. The IO device, on the other hand, was quite
|
||||
busy. In general, we'd like to keep all the devices busy, as that is a better
|
||||
use of resources.
|
||||
|
||||
There are a few other important flags:
|
||||
-s SEED, --seed=SEED the random seed
|
||||
this gives you way to create a bunch of different jobs randomly
|
||||
|
||||
-L IO_LENGTH, --iolength=IO_LENGTH
|
||||
this determines how long IOs take to complete (default is 5 ticks)
|
||||
|
||||
-S PROCESS_SWITCH_BEHAVIOR, --switch=PROCESS_SWITCH_BEHAVIOR
|
||||
when to switch between processes: SWITCH_ON_IO, SWITCH_ON_END
|
||||
this determines when we switch to another process:
|
||||
- SWITCH_ON_IO, the system will switch when a process issues an IO
|
||||
- SWITCH_ON_END, the system will only switch when the current process is done
|
||||
|
||||
-I IO_DONE_BEHAVIOR, --iodone=IO_DONE_BEHAVIOR
|
||||
type of behavior when IO ends: IO_RUN_LATER, IO_RUN_IMMEDIATE
|
||||
this determines when a process runs after it issues an IO:
|
||||
- IO_RUN_IMMEDIATE: switch to this process right now
|
||||
- IO_RUN_LATER: switch to this process when it is natural to
|
||||
(e.g., depending on process-switching behavior)
|
||||
|
||||
Now go answer the questions at the back of the chapter to learn more.
|
||||
|
||||
|
||||
|
||||
|
325
related_info/ostep/ostep7-process-run.py
Executable file
325
related_info/ostep/ostep7-process-run.py
Executable file
@ -0,0 +1,325 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
import random
|
||||
|
||||
# process switch behavior
|
||||
SCHED_SWITCH_ON_IO = 'SWITCH_ON_IO'
|
||||
SCHED_SWITCH_ON_END = 'SWITCH_ON_END'
|
||||
|
||||
# io finished behavior
|
||||
IO_RUN_LATER = 'IO_RUN_LATER'
|
||||
IO_RUN_IMMEDIATE = 'IO_RUN_IMMEDIATE'
|
||||
|
||||
# process states
|
||||
STATE_RUNNING = 'RUNNING'
|
||||
STATE_READY = 'READY'
|
||||
STATE_DONE = 'DONE'
|
||||
STATE_WAIT = 'WAITING'
|
||||
|
||||
# members of process structure
|
||||
PROC_CODE = 'code_'
|
||||
PROC_PC = 'pc_'
|
||||
PROC_ID = 'pid_'
|
||||
PROC_STATE = 'proc_state_'
|
||||
|
||||
# things a process can do
|
||||
DO_COMPUTE = 'cpu'
|
||||
DO_IO = 'io'
|
||||
|
||||
|
||||
class scheduler:
|
||||
def __init__(self, process_switch_behavior, io_done_behavior, io_length):
|
||||
# keep set of instructions for each of the processes
|
||||
self.proc_info = {}
|
||||
self.process_switch_behavior = process_switch_behavior
|
||||
self.io_done_behavior = io_done_behavior
|
||||
self.io_length = io_length
|
||||
return
|
||||
|
||||
def new_process(self):
|
||||
proc_id = len(self.proc_info)
|
||||
self.proc_info[proc_id] = {}
|
||||
self.proc_info[proc_id][PROC_PC] = 0
|
||||
self.proc_info[proc_id][PROC_ID] = proc_id
|
||||
self.proc_info[proc_id][PROC_CODE] = []
|
||||
self.proc_info[proc_id][PROC_STATE] = STATE_READY
|
||||
return proc_id
|
||||
|
||||
def load_file(self, progfile):
|
||||
fd = open(progfile)
|
||||
proc_id = self.new_process()
|
||||
|
||||
for line in fd:
|
||||
tmp = line.split()
|
||||
if len(tmp) == 0:
|
||||
continue
|
||||
opcode = tmp[0]
|
||||
if opcode == 'compute':
|
||||
assert(len(tmp) == 2)
|
||||
for i in range(int(tmp[1])):
|
||||
self.proc_info[proc_id][PROC_CODE].append(DO_COMPUTE)
|
||||
elif opcode == 'io':
|
||||
assert(len(tmp) == 1)
|
||||
self.proc_info[proc_id][PROC_CODE].append(DO_IO)
|
||||
fd.close()
|
||||
return
|
||||
|
||||
def load(self, program_description):
|
||||
proc_id = self.new_process()
|
||||
tmp = program_description.split(':')
|
||||
if len(tmp) != 2:
|
||||
print 'Bad description (%s): Must be number <x:y>'
|
||||
print ' where X is the number of instructions'
|
||||
print ' and Y is the percent change that an instruction is CPU not IO'
|
||||
exit(1)
|
||||
|
||||
num_instructions, chance_cpu = int(tmp[0]), float(tmp[1])/100.0
|
||||
for i in range(num_instructions):
|
||||
if random.random() < chance_cpu:
|
||||
self.proc_info[proc_id][PROC_CODE].append(DO_COMPUTE)
|
||||
else:
|
||||
self.proc_info[proc_id][PROC_CODE].append(DO_IO)
|
||||
return
|
||||
|
||||
def move_to_ready(self, expected, pid=-1):
|
||||
if pid == -1:
|
||||
pid = self.curr_proc
|
||||
assert(self.proc_info[pid][PROC_STATE] == expected)
|
||||
self.proc_info[pid][PROC_STATE] = STATE_READY
|
||||
return
|
||||
|
||||
def move_to_wait(self, expected):
|
||||
assert(self.proc_info[self.curr_proc][PROC_STATE] == expected)
|
||||
self.proc_info[self.curr_proc][PROC_STATE] = STATE_WAIT
|
||||
return
|
||||
|
||||
def move_to_running(self, expected):
|
||||
assert(self.proc_info[self.curr_proc][PROC_STATE] == expected)
|
||||
self.proc_info[self.curr_proc][PROC_STATE] = STATE_RUNNING
|
||||
return
|
||||
|
||||
def move_to_done(self, expected):
|
||||
assert(self.proc_info[self.curr_proc][PROC_STATE] == expected)
|
||||
self.proc_info[self.curr_proc][PROC_STATE] = STATE_DONE
|
||||
return
|
||||
|
||||
def next_proc(self, pid=-1):
|
||||
if pid != -1:
|
||||
self.curr_proc = pid
|
||||
self.move_to_running(STATE_READY)
|
||||
return
|
||||
for pid in range(self.curr_proc + 1, len(self.proc_info)):
|
||||
if self.proc_info[pid][PROC_STATE] == STATE_READY:
|
||||
self.curr_proc = pid
|
||||
self.move_to_running(STATE_READY)
|
||||
return
|
||||
for pid in range(0, self.curr_proc + 1):
|
||||
if self.proc_info[pid][PROC_STATE] == STATE_READY:
|
||||
self.curr_proc = pid
|
||||
self.move_to_running(STATE_READY)
|
||||
return
|
||||
return
|
||||
|
||||
def get_num_processes(self):
|
||||
return len(self.proc_info)
|
||||
|
||||
def get_num_instructions(self, pid):
|
||||
return len(self.proc_info[pid][PROC_CODE])
|
||||
|
||||
def get_instruction(self, pid, index):
|
||||
return self.proc_info[pid][PROC_CODE][index]
|
||||
|
||||
def get_num_active(self):
|
||||
num_active = 0
|
||||
for pid in range(len(self.proc_info)):
|
||||
if self.proc_info[pid][PROC_STATE] != STATE_DONE:
|
||||
num_active += 1
|
||||
return num_active
|
||||
|
||||
def get_num_runnable(self):
|
||||
num_active = 0
|
||||
for pid in range(len(self.proc_info)):
|
||||
if self.proc_info[pid][PROC_STATE] == STATE_READY or \
|
||||
self.proc_info[pid][PROC_STATE] == STATE_RUNNING:
|
||||
num_active += 1
|
||||
return num_active
|
||||
|
||||
def get_ios_in_flight(self, current_time):
|
||||
num_in_flight = 0
|
||||
for pid in range(len(self.proc_info)):
|
||||
for t in self.io_finish_times[pid]:
|
||||
if t > current_time:
|
||||
num_in_flight += 1
|
||||
return num_in_flight
|
||||
|
||||
def check_for_switch(self):
|
||||
return
|
||||
|
||||
def space(self, num_columns):
|
||||
for i in range(num_columns):
|
||||
print '%10s' % ' ',
|
||||
|
||||
def check_if_done(self):
|
||||
if len(self.proc_info[self.curr_proc][PROC_CODE]) == 0:
|
||||
if self.proc_info[self.curr_proc][PROC_STATE] == STATE_RUNNING:
|
||||
self.move_to_done(STATE_RUNNING)
|
||||
self.next_proc()
|
||||
return
|
||||
|
||||
def run(self):
|
||||
clock_tick = 0
|
||||
|
||||
if len(self.proc_info) == 0:
|
||||
return
|
||||
|
||||
# track outstanding IOs, per process
|
||||
self.io_finish_times = {}
|
||||
for pid in range(len(self.proc_info)):
|
||||
self.io_finish_times[pid] = []
|
||||
|
||||
# make first one active
|
||||
self.curr_proc = 0
|
||||
self.move_to_running(STATE_READY)
|
||||
|
||||
# OUTPUT: headers for each column
|
||||
print '%s' % 'Time',
|
||||
for pid in range(len(self.proc_info)):
|
||||
print '%10s' % ('PID:%2d' % (pid)),
|
||||
print '%10s' % 'CPU',
|
||||
print '%10s' % 'IOs',
|
||||
print ''
|
||||
|
||||
# init statistics
|
||||
io_busy = 0
|
||||
cpu_busy = 0
|
||||
|
||||
while self.get_num_active() > 0:
|
||||
clock_tick += 1
|
||||
|
||||
# check for io finish
|
||||
io_done = False
|
||||
for pid in range(len(self.proc_info)):
|
||||
if clock_tick in self.io_finish_times[pid]:
|
||||
io_done = True
|
||||
self.move_to_ready(STATE_WAIT, pid)
|
||||
if self.io_done_behavior == IO_RUN_IMMEDIATE:
|
||||
# IO_RUN_IMMEDIATE
|
||||
if self.curr_proc != pid:
|
||||
if self.proc_info[self.curr_proc][PROC_STATE] == STATE_RUNNING:
|
||||
self.move_to_ready(STATE_RUNNING)
|
||||
self.next_proc(pid)
|
||||
else:
|
||||
# IO_RUN_LATER
|
||||
if self.process_switch_behavior == SCHED_SWITCH_ON_END:
|
||||
# this means the process that issued the io should be run
|
||||
self.next_proc(pid)
|
||||
if self.get_num_runnable() == 1:
|
||||
# this is the only thing to run: so run it
|
||||
self.next_proc(pid)
|
||||
self.check_if_done()
|
||||
|
||||
# if current proc is RUNNING and has an instruction, execute it
|
||||
instruction_to_execute = ''
|
||||
if self.proc_info[self.curr_proc][PROC_STATE] == STATE_RUNNING and \
|
||||
len(self.proc_info[self.curr_proc][PROC_CODE]) > 0:
|
||||
instruction_to_execute = self.proc_info[self.curr_proc][PROC_CODE].pop(0)
|
||||
cpu_busy += 1
|
||||
|
||||
# OUTPUT: print what everyone is up to
|
||||
if io_done:
|
||||
print '%3d*' % clock_tick,
|
||||
else:
|
||||
print '%3d ' % clock_tick,
|
||||
for pid in range(len(self.proc_info)):
|
||||
if pid == self.curr_proc and instruction_to_execute != '':
|
||||
print '%10s' % ('RUN:'+instruction_to_execute),
|
||||
else:
|
||||
print '%10s' % (self.proc_info[pid][PROC_STATE]),
|
||||
if instruction_to_execute == '':
|
||||
print '%10s' % ' ',
|
||||
else:
|
||||
print '%10s' % 1,
|
||||
num_outstanding = self.get_ios_in_flight(clock_tick)
|
||||
if num_outstanding > 0:
|
||||
print '%10s' % str(num_outstanding),
|
||||
io_busy += 1
|
||||
else:
|
||||
print '%10s' % ' ',
|
||||
print ''
|
||||
|
||||
# if this is an IO instruction, switch to waiting state
|
||||
# and add an io completion in the future
|
||||
if instruction_to_execute == DO_IO:
|
||||
self.move_to_wait(STATE_RUNNING)
|
||||
self.io_finish_times[self.curr_proc].append(clock_tick + self.io_length)
|
||||
if self.process_switch_behavior == SCHED_SWITCH_ON_IO:
|
||||
self.next_proc()
|
||||
|
||||
# ENDCASE: check if currently running thing is out of instructions
|
||||
self.check_if_done()
|
||||
return (cpu_busy, io_busy, clock_tick)
|
||||
|
||||
#
|
||||
# PARSE ARGUMENTS
|
||||
#
|
||||
|
||||
parser = OptionParser()
|
||||
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
|
||||
parser.add_option('-l', '--processlist', default='',
|
||||
help='a comma-separated list of processes to run, in the form X1:Y1,X2:Y2,... where X is the number of instructions that process should run, and Y the chances (from 0 to 100) that an instruction will use the CPU or issue an IO',
|
||||
action='store', type='string', dest='process_list')
|
||||
parser.add_option('-L', '--iolength', default=5, help='how long an IO takes', action='store', type='int', dest='io_length')
|
||||
parser.add_option('-S', '--switch', default='SWITCH_ON_IO',
|
||||
help='when to switch between processes: SWITCH_ON_IO, SWITCH_ON_END',
|
||||
action='store', type='string', dest='process_switch_behavior')
|
||||
parser.add_option('-I', '--iodone', default='IO_RUN_LATER',
|
||||
help='type of behavior when IO ends: IO_RUN_LATER, IO_RUN_IMMEDIATE',
|
||||
action='store', type='string', dest='io_done_behavior')
|
||||
parser.add_option('-c', help='compute answers for me', action='store_true', default=False, dest='solve')
|
||||
parser.add_option('-p', '--printstats', help='print statistics at end; only useful with -c flag (otherwise stats are not printed)', action='store_true', default=False, dest='print_stats')
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
random.seed(options.seed)
|
||||
|
||||
assert(options.process_switch_behavior == SCHED_SWITCH_ON_IO or \
|
||||
options.process_switch_behavior == SCHED_SWITCH_ON_END)
|
||||
assert(options.io_done_behavior == IO_RUN_IMMEDIATE or \
|
||||
options.io_done_behavior == IO_RUN_LATER)
|
||||
|
||||
s = scheduler(options.process_switch_behavior, options.io_done_behavior, options.io_length)
|
||||
|
||||
# example process description (10:100,10:100)
|
||||
for p in options.process_list.split(','):
|
||||
s.load(p)
|
||||
|
||||
if options.solve == False:
|
||||
print 'Produce a trace of what would happen when you run these processes:'
|
||||
for pid in range(s.get_num_processes()):
|
||||
print 'Process %d' % pid
|
||||
for inst in range(s.get_num_instructions(pid)):
|
||||
print ' %s' % s.get_instruction(pid, inst)
|
||||
print ''
|
||||
print 'Important behaviors:'
|
||||
print ' System will switch when',
|
||||
if options.process_switch_behavior == SCHED_SWITCH_ON_IO:
|
||||
print 'the current process is FINISHED or ISSUES AN IO'
|
||||
else:
|
||||
print 'the current process is FINISHED'
|
||||
print ' After IOs, the process issuing the IO will',
|
||||
if options.io_done_behavior == IO_RUN_IMMEDIATE:
|
||||
print 'run IMMEDIATELY'
|
||||
else:
|
||||
print 'run LATER (when it is its turn)'
|
||||
print ''
|
||||
exit(0)
|
||||
|
||||
(cpu_busy, io_busy, clock_tick) = s.run()
|
||||
|
||||
if options.print_stats:
|
||||
print ''
|
||||
print 'Stats: Total Time %d' % clock_tick
|
||||
print 'Stats: CPU Busy %d (%.2f%%)' % (cpu_busy, 100.0 * float(cpu_busy)/clock_tick)
|
||||
print 'Stats: IO Busy %d (%.2f%%)' % (io_busy, 100.0 * float(io_busy)/clock_tick)
|
||||
print ''
|
116
related_info/ostep/ostep8-scheduler.md
Normal file
116
related_info/ostep/ostep8-scheduler.md
Normal file
@ -0,0 +1,116 @@
|
||||
|
||||
This program, scheduler.py, allows you to see how different schedulers perform
|
||||
under scheduling metrics such as response time, turnaround time, and total
|
||||
wait time. Three schedulers are "implemented": FIFO, SJF, and RR.
|
||||
|
||||
There are two steps to running the program.
|
||||
|
||||
First, run without the -c flag: this shows you what problem to solve without
|
||||
revealing the answers. For example, if you want to compute response,
|
||||
turnaround, and wait for three jobs using the FIFO policy, run this:
|
||||
|
||||
./scheduler.py -p FIFO -j 3 -s 100
|
||||
|
||||
If that doesn't work, try this:
|
||||
python ./scheduler.py -p FIFO -j 3 -s 100
|
||||
|
||||
This specifies the FIFO policy with three jobs, and, importantly, a specific
|
||||
random seed of 100. If you want to see the solution for this exact problem,
|
||||
you have to specify this exact same random seed again. Let's run it and see
|
||||
what happens. This is what you should see:
|
||||
|
||||
prompt> ./scheduler.py -p FIFO -j 3 -s 100
|
||||
ARG policy FIFO
|
||||
ARG jobs 3
|
||||
ARG maxlen 10
|
||||
ARG seed 100
|
||||
|
||||
Here is the job list, with the run time of each job:
|
||||
Job 0 (length = 1)
|
||||
Job 1 (length = 4)
|
||||
Job 2 (length = 7)
|
||||
|
||||
Compute the turnaround time, response time, and wait time for each job. When
|
||||
you are done, run this program again, with the same arguments, but with -c,
|
||||
which will thus provide you with the answers. You can use -s <somenumber> or
|
||||
your own job list (-l 10,15,20 for example) to generate different problems for
|
||||
yourself.
|
||||
|
||||
As you can see from this example, three jobs are generated: job 0 of length 1,
|
||||
job 1 of length 4, and job 2 of length 7. As the program states, you can now
|
||||
use this to compute some statistics and see if you have a grip on the basic
|
||||
concepts.
|
||||
|
||||
Once you are done, you can use the same program to "solve" the problem and see
|
||||
if you did your work correctly. To do so, use the "-c" flag. The output:
|
||||
|
||||
prompt> ./scheduler.py -p FIFO -j 3 -s 100 -c
|
||||
ARG policy FIFO
|
||||
ARG jobs 3
|
||||
ARG maxlen 10
|
||||
ARG seed 100
|
||||
|
||||
Here is the job list, with the run time of each job:
|
||||
Job 0 (length = 1)
|
||||
Job 1 (length = 4)
|
||||
Job 2 (length = 7)
|
||||
|
||||
** Solutions **
|
||||
|
||||
Execution trace:
|
||||
[time 0] Run job 0 for 1.00 secs (DONE)
|
||||
[time 1] Run job 1 for 4.00 secs (DONE)
|
||||
[time 5] Run job 2 for 7.00 secs (DONE)
|
||||
|
||||
Final statistics:
|
||||
Job 0 -- Response: 0.00 Turnaround 1.00 Wait 0.00
|
||||
Job 1 -- Response: 1.00 Turnaround 5.00 Wait 1.00
|
||||
Job 2 -- Response: 5.00 Turnaround 12.00 Wait 5.00
|
||||
|
||||
Average -- Response: 2.00 Turnaround 6.00 Wait 2.00
|
||||
|
||||
As you can see from the figure, the -c flag shows you what happened. Job 0 ran
|
||||
first for 1 second, Job 1 ran second for 4, and then Job 2 ran for 7
|
||||
seconds. Not too hard; it is FIFO, after all! The execution trace shows these
|
||||
results.
|
||||
|
||||
The final statistics are useful too: they compute the "response time" (the
|
||||
time a job spends waiting after arrival before first running), the "turnaround
|
||||
time" (the time it took to complete the job since first arrival), and the
|
||||
total "wait time" (any time spent ready but not running). The stats are shown
|
||||
per job and then as an average across all jobs. Of course, you should have
|
||||
computed these things all before running with the "-c" flag!
|
||||
|
||||
If you want to try the same type of problem but with different inputs, try
|
||||
changing the number of jobs or the random seed or both. Different random seeds
|
||||
basically give you a way to generate an infinite number of different problems
|
||||
for yourself, and the "-c" flag lets you check your own work. Keep doing this
|
||||
until you feel like you really understand the concepts.
|
||||
|
||||
One other useful flag is "-l" (that's a lower-case L), which lets you specify
|
||||
the exact jobs you wish to see scheduled. For example, if you want to find out
|
||||
how SJF would perform with three jobs of lengths 5, 10, and 15, you can run:
|
||||
|
||||
prompt> ./scheduler.py -p SJF -l 5,10,15
|
||||
ARG policy SJF
|
||||
ARG jlist 5,10,15
|
||||
|
||||
Here is the job list, with the run time of each job:
|
||||
Job 0 (length = 5.0)
|
||||
Job 1 (length = 10.0)
|
||||
Job 2 (length = 15.0)
|
||||
...
|
||||
|
||||
And then you can use -c to solve it again. Note that when you specify the
|
||||
exact jobs, there is no need to specify a random seed or the number of jobs:
|
||||
the jobs lengths are taken from your comma-separated list.
|
||||
|
||||
Of course, more interesting things happen when you use SJF (shortest-job
|
||||
first) or even RR (round robin) schedulers. Try them and see!
|
||||
|
||||
And you can always run
|
||||
|
||||
./scheduler.py -h
|
||||
|
||||
to get a complete list of flags and options (including options such as setting
|
||||
the time quantum for the RR scheduler).
|
155
related_info/ostep/ostep8-scheduler.py
Executable file
155
related_info/ostep/ostep8-scheduler.py
Executable file
@ -0,0 +1,155 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
import random
|
||||
|
||||
parser = OptionParser()
|
||||
parser.add_option("-s", "--seed", default=0, help="the random seed",
|
||||
action="store", type="int", dest="seed")
|
||||
parser.add_option("-j", "--jobs", default=3, help="number of jobs in the system",
|
||||
action="store", type="int", dest="jobs")
|
||||
parser.add_option("-l", "--jlist", default="", help="instead of random jobs, provide a comma-separated list of run times",
|
||||
action="store", type="string", dest="jlist")
|
||||
parser.add_option("-m", "--maxlen", default=10, help="max length of job",
|
||||
action="store", type="int", dest="maxlen")
|
||||
parser.add_option("-p", "--policy", default="FIFO", help="sched policy to use: SJF, FIFO, RR",
|
||||
action="store", type="string", dest="policy")
|
||||
parser.add_option("-q", "--quantum", help="length of time slice for RR policy", default=1,
|
||||
action="store", type="int", dest="quantum")
|
||||
parser.add_option("-c", help="compute answers for me", action="store_true", default=False, dest="solve")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
random.seed(options.seed)
|
||||
|
||||
print 'ARG policy', options.policy
|
||||
if options.jlist == '':
|
||||
print 'ARG jobs', options.jobs
|
||||
print 'ARG maxlen', options.maxlen
|
||||
print 'ARG seed', options.seed
|
||||
else:
|
||||
print 'ARG jlist', options.jlist
|
||||
|
||||
print ''
|
||||
|
||||
print 'Here is the job list, with the run time of each job: '
|
||||
|
||||
import operator
|
||||
|
||||
joblist = []
|
||||
if options.jlist == '':
|
||||
for jobnum in range(0,options.jobs):
|
||||
runtime = int(options.maxlen * random.random()) + 1
|
||||
joblist.append([jobnum, runtime])
|
||||
print ' Job', jobnum, '( length = ' + str(runtime) + ' )'
|
||||
else:
|
||||
jobnum = 0
|
||||
for runtime in options.jlist.split(','):
|
||||
joblist.append([jobnum, float(runtime)])
|
||||
jobnum += 1
|
||||
for job in joblist:
|
||||
print ' Job', job[0], '( length = ' + str(job[1]) + ' )'
|
||||
print '\n'
|
||||
|
||||
if options.solve == True:
|
||||
print '** Solutions **\n'
|
||||
if options.policy == 'SJF':
|
||||
joblist = sorted(joblist, key=operator.itemgetter(1))
|
||||
options.policy = 'FIFO'
|
||||
|
||||
if options.policy == 'FIFO':
|
||||
thetime = 0
|
||||
print 'Execution trace:'
|
||||
for job in joblist:
|
||||
print ' [ time %3d ] Run job %d for %.2f secs ( DONE at %.2f )' % (thetime, job[0], job[1], thetime + job[1])
|
||||
thetime += job[1]
|
||||
|
||||
print '\nFinal statistics:'
|
||||
t = 0.0
|
||||
count = 0
|
||||
turnaroundSum = 0.0
|
||||
waitSum = 0.0
|
||||
responseSum = 0.0
|
||||
for tmp in joblist:
|
||||
jobnum = tmp[0]
|
||||
runtime = tmp[1]
|
||||
|
||||
response = t
|
||||
turnaround = t + runtime
|
||||
wait = t
|
||||
print ' Job %3d -- Response: %3.2f Turnaround %3.2f Wait %3.2f' % (jobnum, response, turnaround, wait)
|
||||
responseSum += response
|
||||
turnaroundSum += turnaround
|
||||
waitSum += wait
|
||||
t += runtime
|
||||
count = count + 1
|
||||
print '\n Average -- Response: %3.2f Turnaround %3.2f Wait %3.2f\n' % (responseSum/count, turnaroundSum/count, waitSum/count)
|
||||
|
||||
if options.policy == 'RR':
|
||||
print 'Execution trace:'
|
||||
turnaround = {}
|
||||
response = {}
|
||||
lastran = {}
|
||||
wait = {}
|
||||
quantum = float(options.quantum)
|
||||
jobcount = len(joblist)
|
||||
for i in range(0,jobcount):
|
||||
lastran[i] = 0.0
|
||||
wait[i] = 0.0
|
||||
turnaround[i] = 0.0
|
||||
response[i] = -1
|
||||
|
||||
runlist = []
|
||||
for e in joblist:
|
||||
runlist.append(e)
|
||||
|
||||
thetime = 0.0
|
||||
while jobcount > 0:
|
||||
# print '%d jobs remaining' % jobcount
|
||||
job = runlist.pop(0)
|
||||
jobnum = job[0]
|
||||
runtime = float(job[1])
|
||||
if response[jobnum] == -1:
|
||||
response[jobnum] = thetime
|
||||
currwait = thetime - lastran[jobnum]
|
||||
wait[jobnum] += currwait
|
||||
if runtime > quantum:
|
||||
runtime -= quantum
|
||||
ranfor = quantum
|
||||
print ' [ time %3d ] Run job %3d for %.2f secs' % (thetime, jobnum, ranfor)
|
||||
runlist.append([jobnum, runtime])
|
||||
else:
|
||||
ranfor = runtime;
|
||||
print ' [ time %3d ] Run job %3d for %.2f secs ( DONE at %.2f )' % (thetime, jobnum, ranfor, thetime + ranfor)
|
||||
turnaround[jobnum] = thetime + ranfor
|
||||
jobcount -= 1
|
||||
thetime += ranfor
|
||||
lastran[jobnum] = thetime
|
||||
|
||||
print '\nFinal statistics:'
|
||||
turnaroundSum = 0.0
|
||||
waitSum = 0.0
|
||||
responseSum = 0.0
|
||||
for i in range(0,len(joblist)):
|
||||
turnaroundSum += turnaround[i]
|
||||
responseSum += response[i]
|
||||
waitSum += wait[i]
|
||||
print ' Job %3d -- Response: %3.2f Turnaround %3.2f Wait %3.2f' % (i, response[i], turnaround[i], wait[i])
|
||||
count = len(joblist)
|
||||
|
||||
print '\n Average -- Response: %3.2f Turnaround %3.2f Wait %3.2f\n' % (responseSum/count, turnaroundSum/count, waitSum/count)
|
||||
|
||||
if options.policy != 'FIFO' and options.policy != 'SJF' and options.policy != 'RR':
|
||||
print 'Error: Policy', options.policy, 'is not available.'
|
||||
sys.exit(0)
|
||||
else:
|
||||
print 'Compute the turnaround time, response time, and wait time for each job.'
|
||||
print 'When you are done, run this program again, with the same arguments,'
|
||||
print 'but with -c, which will thus provide you with the answers. You can use'
|
||||
print '-s <somenumber> or your own job list (-l 10,15,20 for example)'
|
||||
print 'to generate different problems for yourself.'
|
||||
print ''
|
||||
|
||||
|
||||
|
174
related_info/ostep/ostep9-mlfq.md
Normal file
174
related_info/ostep/ostep9-mlfq.md
Normal file
@ -0,0 +1,174 @@
|
||||
|
||||
This program, mlfq.py, allows you to see how the MLFQ scheduler
|
||||
presented in this chapter behaves. As before, you can use this to generate
|
||||
problems for yourself using random seeds, or use it to construct a
|
||||
carefully-designed experiment to see how MLFQ works under different
|
||||
circumstances. To run the program, type:
|
||||
|
||||
prompt> ./mlfq.py
|
||||
|
||||
Use the help flag (-h) to see the options:
|
||||
|
||||
Usage: mlfq.py [options]
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-s SEED, --seed=SEED the random seed
|
||||
-n NUMQUEUES, --numQueues=NUMQUEUES
|
||||
number of queues in MLFQ (if not using -Q)
|
||||
-q QUANTUM, --quantum=QUANTUM
|
||||
length of time slice (if not using -Q)
|
||||
-Q QUANTUMLIST, --quantumList=QUANTUMLIST
|
||||
length of time slice per queue level,
|
||||
specified as x,y,z,... where x is the
|
||||
quantum length for the highest-priority
|
||||
queue, y the next highest, and so forth
|
||||
-j NUMJOBS, --numJobs=NUMJOBS
|
||||
number of jobs in the system
|
||||
-m MAXLEN, --maxlen=MAXLEN
|
||||
max run-time of a job (if random)
|
||||
-M MAXIO, --maxio=MAXIO
|
||||
max I/O frequency of a job (if random)
|
||||
-B BOOST, --boost=BOOST
|
||||
how often to boost the priority of all
|
||||
jobs back to high priority (0 means never)
|
||||
-i IOTIME, --iotime=IOTIME
|
||||
how long an I/O should last (fixed constant)
|
||||
-S, --stay reset and stay at same priority level
|
||||
when issuing I/O
|
||||
-l JLIST, --jlist=JLIST
|
||||
a comma-separated list of jobs to run,
|
||||
in the form x1,y1,z1:x2,y2,z2:... where
|
||||
x is start time, y is run time, and z
|
||||
is how often the job issues an I/O request
|
||||
-c compute answers for me
|
||||
]
|
||||
|
||||
There are a few different ways to use the simulator. One way is to generate
|
||||
some random jobs and see if you can figure out how they will behave given the
|
||||
MLFQ scheduler. For example, if you wanted to create a randomly-generated
|
||||
three-job workload, you would simply type:
|
||||
|
||||
prompt> ./mlfq.py -j 3
|
||||
|
||||
What you would then see is the specific problem definition:
|
||||
|
||||
Here is the list of inputs:
|
||||
OPTIONS jobs 3
|
||||
OPTIONS queues 3
|
||||
OPTIONS quantum length for queue 2 is 10
|
||||
OPTIONS quantum length for queue 1 is 10
|
||||
OPTIONS quantum length for queue 0 is 10
|
||||
OPTIONS boost 0
|
||||
OPTIONS ioTime 0
|
||||
OPTIONS stayAfterIO False
|
||||
|
||||
|
||||
For each job, three defining characteristics are given:
|
||||
startTime : at what time does the job enter the system
|
||||
runTime : the total CPU time needed by the job to finish
|
||||
ioFreq : every ioFreq time units, the job issues an I/O
|
||||
(the I/O takes ioTime units to complete)
|
||||
|
||||
Job List:
|
||||
Job 0: startTime 0 - runTime 84 - ioFreq 7
|
||||
Job 1: startTime 0 - runTime 42 - ioFreq 2
|
||||
Job 2: startTime 0 - runTime 51 - ioFreq 4
|
||||
|
||||
Compute the execution trace for the given workloads.
|
||||
If you would like, also compute the response and turnaround
|
||||
times for each of the jobs.
|
||||
|
||||
Use the -c flag to get the exact results when you are finished.
|
||||
|
||||
This generates a random workload of three jobs (as specified), on the default
|
||||
number of queues with a number of default settings. If you run again with the
|
||||
solve flag on (-c), you'll see the same print out as above, plus the
|
||||
following:
|
||||
|
||||
Execution Trace:
|
||||
|
||||
[time 0] JOB BEGINS by JOB 0
|
||||
[time 0] JOB BEGINS by JOB 1
|
||||
[time 0] JOB BEGINS by JOB 2
|
||||
[time 0] Run JOB 0 at PRI 2 [TICKSLEFT 9 RUNTIME 84 TIMELEFT 83]
|
||||
[time 1] Run JOB 0 at PRI 2 [TICKSLEFT 8 RUNTIME 84 TIMELEFT 82]
|
||||
[time 2] Run JOB 0 at PRI 2 [TICKSLEFT 7 RUNTIME 84 TIMELEFT 81]
|
||||
[time 3] Run JOB 0 at PRI 2 [TICKSLEFT 6 RUNTIME 84 TIMELEFT 80]
|
||||
[time 4] Run JOB 0 at PRI 2 [TICKSLEFT 5 RUNTIME 84 TIMELEFT 79]
|
||||
[time 5] Run JOB 0 at PRI 2 [TICKSLEFT 4 RUNTIME 84 TIMELEFT 78]
|
||||
[time 6] Run JOB 0 at PRI 2 [TICKSLEFT 3 RUNTIME 84 TIMELEFT 77]
|
||||
[time 7] IO_START by JOB 0
|
||||
[time 7] Run JOB 1 at PRI 2 [TICKSLEFT 9 RUNTIME 42 TIMELEFT 41]
|
||||
[time 8] Run JOB 1 at PRI 2 [TICKSLEFT 8 RUNTIME 42 TIMELEFT 40]
|
||||
[time 9] IO_START by JOB 1
|
||||
|
||||
...
|
||||
|
||||
Final statistics:
|
||||
Job 0: startTime 0 - response 0 - turnaround 175
|
||||
Job 1: startTime 0 - response 7 - turnaround 191
|
||||
Job 2: startTime 0 - response 9 - turnaround 168
|
||||
|
||||
Avg 2: startTime n/a - response 5.33 - turnaround 178.00
|
||||
]
|
||||
|
||||
The trace shows exactly, on a millisecond-by-millisecond time scale, what the
|
||||
scheduler decided to do. In this example, it begins by running Job 0 for 7 ms
|
||||
until Job 0 issues an I/O; this is entirely predictable, as Job 0's I/O
|
||||
frequency is set to 7 ms, meaning that every 7 ms it runs, it will issue an
|
||||
I/O and wait for it to complete before continuing. At that point, the
|
||||
scheduler switches to Job 1, which only runs 2 ms before issuing an I/O.
|
||||
The scheduler prints the entire execution trace in this manner, and
|
||||
finally also computes the response and turnaround times for each job
|
||||
as well as an average.
|
||||
|
||||
You can also control various other aspects of the simulation. For example, you
|
||||
can specify how many queues you'd like to have in the system (-n) and what the
|
||||
quantum length should be for all of those queues (-q); if you want even more
|
||||
control and varied quanta length per queue, you can instead specify the length
|
||||
of the quantum for each queue with -Q, e.g., -Q 10,20,30] simulates a
|
||||
scheduler with three queues, with the highest-priority queue having a 10-ms
|
||||
time slice, the next-highest a 20-ms time-slice, and the low-priority queue a
|
||||
30-ms time slice.
|
||||
|
||||
If you are randomly generating jobs, you can also control how long they might
|
||||
run for (-m), or how often they generate I/O (-M). If you, however, want more
|
||||
control over the exact characteristics of the jobs running in the system, you
|
||||
can use -l (lower-case L) or --jlist, which allows you to specify the exact
|
||||
set of jobs you wish to simulate. The list is of the form:
|
||||
x1,y1,z1:x2,y2,z2:... where x is the start time of the job, y is the run time
|
||||
(i.e., how much CPU time it needs), and z the I/O frequency (i.e., after
|
||||
running z ms, the job issues an I/O; if z is 0, no I/Os are issued).
|
||||
|
||||
For example, if you wanted to recreate the example in Figure 8.3
|
||||
you would specify a job list as follows:
|
||||
|
||||
prompt> ./mlfq.py --jlist 0,180,0:100,20,0 -Q 10,10,10
|
||||
|
||||
Running the simulator in this way creates a three-level MLFQ, with each level
|
||||
having a 10-ms time slice. Two jobs are created: Job 0 which starts at time 0,
|
||||
runs for 180 ms total, and never issues an I/O; Job 1 starts at 100 ms, needs
|
||||
only 20 ms of CPU time to complete, and also never issues I/Os.
|
||||
|
||||
Finally, there are three more parameters of interest. The -B flag, if set to a
|
||||
non-zero value, boosts all jobs to the highest-priority queue every N
|
||||
milliseconds, when invoked as such:
|
||||
prompt> ./mlfq.py -B N
|
||||
The scheduler uses this feature to avoid starvation as discussed in the
|
||||
chapter. However, it is off by default.
|
||||
|
||||
The -S flag invokes older Rules 4a and 4b, which means that if a job issues an
|
||||
I/O before completing its time slice, it will return to that same priority
|
||||
queue when it resumes execution, with its full time-slice intact. This
|
||||
enables gaming of the scheduler.
|
||||
|
||||
Finally, you can easily change how long an I/O lasts by using the -i flag. By
|
||||
default in this simplistic model, each I/O takes a fixed amount of time of 5
|
||||
milliseconds or whatever you set it to with this flag.
|
||||
|
||||
You can also play around with whether jobs that just complete an I/O are moved
|
||||
to the head of the queue they are in or to the back, with the -I flag. Check
|
||||
it out.
|
||||
|
||||
|
||||
|
326
related_info/ostep/ostep9-mlfq.py
Executable file
326
related_info/ostep/ostep9-mlfq.py
Executable file
@ -0,0 +1,326 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
import random
|
||||
|
||||
# finds the highest nonempty queue
|
||||
# -1 if they are all empty
|
||||
def FindQueue():
|
||||
q = hiQueue
|
||||
while q > 0:
|
||||
if len(queue[q]) > 0:
|
||||
return q
|
||||
q -= 1
|
||||
if len(queue[0]) > 0:
|
||||
return 0
|
||||
return -1
|
||||
|
||||
def LowerQueue(currJob, currQueue, issuedIO):
|
||||
if currQueue > 0:
|
||||
# in this case, have to change the priority of the job
|
||||
job[currJob]['currPri'] = currQueue - 1
|
||||
if issuedIO == False:
|
||||
queue[currQueue-1].append(currJob)
|
||||
job[currJob]['ticksLeft'] = quantum[currQueue-1]
|
||||
else:
|
||||
if issuedIO == False:
|
||||
queue[currQueue].append(currJob)
|
||||
job[currJob]['ticksLeft'] = quantum[currQueue]
|
||||
|
||||
def Abort(str):
|
||||
sys.stderr.write(str + '\n')
|
||||
exit(1)
|
||||
|
||||
|
||||
#
|
||||
# PARSE ARGUMENTS
|
||||
#
|
||||
|
||||
parser = OptionParser()
|
||||
parser.add_option('-s', '--seed', default=0, help='the random seed',
|
||||
action='store', type='int', dest='seed')
|
||||
parser.add_option('-n', '--numQueues', help='number of queues in MLFQ (if not using -Q)', default=3,
|
||||
action='store', type='int', dest='numQueues')
|
||||
parser.add_option('-q', '--quantum', help='length of time slice (if not using -Q)', default=10,
|
||||
action='store', type='int', dest='quantum')
|
||||
parser.add_option('-Q', '--quantumList', help='length of time slice per queue level, specified as x,y,z,... where x is the quantum length for the highest priority queue, y the next highest, and so forth',
|
||||
default='', action='store', type='string', dest='quantumList')
|
||||
parser.add_option('-j', '--numJobs', default=3, help='number of jobs in the system',
|
||||
action='store', type='int', dest='numJobs')
|
||||
parser.add_option('-m', '--maxlen', default=100, help='max run-time of a job (if randomly generating)',
|
||||
action='store', type='int', dest='maxlen')
|
||||
parser.add_option('-M', '--maxio', default=10, help='max I/O frequency of a job (if randomly generating)',
|
||||
action='store', type='int', dest='maxio')
|
||||
parser.add_option('-B', '--boost', default=0, help='how often to boost the priority of all jobs back to high priority',
|
||||
action='store', type='int', dest='boost')
|
||||
parser.add_option('-i', '--iotime', default=5, help='how long an I/O should last (fixed constant)',
|
||||
action='store', type='int', dest='ioTime')
|
||||
parser.add_option('-S', '--stay', default=False, help='reset and stay at same priority level when issuing I/O',
|
||||
action='store_true', dest='stay')
|
||||
parser.add_option('-I', '--iobump', default=False, help='if specified, jobs that finished I/O move immediately to front of current queue',
|
||||
action='store_true', dest='iobump')
|
||||
parser.add_option('-l', '--jlist', default='', help='a comma-separated list of jobs to run, in the form x1,y1,z1:x2,y2,z2:... where x is start time, y is run time, and z is how often the job issues an I/O request',
|
||||
action='store', type='string', dest='jlist')
|
||||
parser.add_option('-c', help='compute answers for me', action='store_true', default=False, dest='solve')
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
random.seed(options.seed)
|
||||
|
||||
|
||||
# MLFQ: How Many Queues
|
||||
numQueues = options.numQueues
|
||||
|
||||
quantum = {}
|
||||
if options.quantumList != '':
|
||||
# instead, extract number of queues and their time slic
|
||||
quantumLengths = options.quantumList.split(',')
|
||||
numQueues = len(quantumLengths)
|
||||
qc = numQueues - 1
|
||||
for i in range(numQueues):
|
||||
quantum[qc] = int(quantumLengths[i])
|
||||
qc -= 1
|
||||
else:
|
||||
for i in range(numQueues):
|
||||
quantum[i] = int(options.quantum)
|
||||
|
||||
hiQueue = numQueues - 1
|
||||
|
||||
# MLFQ: I/O Model
|
||||
# the time for each IO: not great to have a single fixed time but...
|
||||
ioTime = int(options.ioTime)
|
||||
|
||||
# This tracks when IOs and other interrupts are complete
|
||||
ioDone = {}
|
||||
|
||||
# This stores all info about the jobs
|
||||
job = {}
|
||||
|
||||
# seed the random generator
|
||||
random.seed(options.seed)
|
||||
|
||||
# jlist 'startTime,runTime,ioFreq:startTime,runTime,ioFreq:...'
|
||||
jobCnt = 0
|
||||
if options.jlist != '':
|
||||
allJobs = options.jlist.split(':')
|
||||
for j in allJobs:
|
||||
jobInfo = j.split(',')
|
||||
if len(jobInfo) != 3:
|
||||
sys.stderr.write('Badly formatted job string. Should be x1,y1,z1:x2,y2,z2:...\n')
|
||||
sys.stderr.write('where x is the startTime, y is the runTime, and z is the I/O frequency.\n')
|
||||
exit(1)
|
||||
assert(len(jobInfo) == 3)
|
||||
startTime = int(jobInfo[0])
|
||||
runTime = int(jobInfo[1])
|
||||
ioFreq = int(jobInfo[2])
|
||||
job[jobCnt] = {'currPri':hiQueue, 'ticksLeft':quantum[hiQueue], 'startTime':startTime,
|
||||
'runTime':runTime, 'timeLeft':runTime, 'ioFreq':ioFreq, 'doingIO':False,
|
||||
'firstRun':-1}
|
||||
if startTime not in ioDone:
|
||||
ioDone[startTime] = []
|
||||
ioDone[startTime].append((jobCnt, 'JOB BEGINS'))
|
||||
jobCnt += 1
|
||||
else:
|
||||
# do something random
|
||||
for j in range(options.numJobs):
|
||||
startTime = 0
|
||||
runTime = int(random.random() * options.maxlen)
|
||||
ioFreq = int(random.random() * options.maxio)
|
||||
|
||||
job[jobCnt] = {'currPri':hiQueue, 'ticksLeft':quantum[hiQueue], 'startTime':startTime,
|
||||
'runTime':runTime, 'timeLeft':runTime, 'ioFreq':ioFreq, 'doingIO':False,
|
||||
'firstRun':-1}
|
||||
if startTime not in ioDone:
|
||||
ioDone[startTime] = []
|
||||
ioDone[startTime].append((jobCnt, 'JOB BEGINS'))
|
||||
jobCnt += 1
|
||||
|
||||
|
||||
numJobs = len(job)
|
||||
|
||||
print 'Here is the list of inputs:'
|
||||
print 'OPTIONS jobs', numJobs
|
||||
print 'OPTIONS queues', numQueues
|
||||
for i in range(len(quantum)-1,-1,-1):
|
||||
print 'OPTIONS quantum length for queue %2d is %3d' % (i, quantum[i])
|
||||
print 'OPTIONS boost', options.boost
|
||||
print 'OPTIONS ioTime', options.ioTime
|
||||
print 'OPTIONS stayAfterIO', options.stay
|
||||
print 'OPTIONS iobump', options.iobump
|
||||
|
||||
print '\n'
|
||||
print 'For each job, three defining characteristics are given:'
|
||||
print ' startTime : at what time does the job enter the system'
|
||||
print ' runTime : the total CPU time needed by the job to finish'
|
||||
print ' ioFreq : every ioFreq time units, the job issues an I/O'
|
||||
print ' (the I/O takes ioTime units to complete)\n'
|
||||
|
||||
print 'Job List:'
|
||||
for i in range(numJobs):
|
||||
print ' Job %2d: startTime %3d - runTime %3d - ioFreq %3d' % (i, job[i]['startTime'],
|
||||
job[i]['runTime'], job[i]['ioFreq'])
|
||||
print ''
|
||||
|
||||
if options.solve == False:
|
||||
print 'Compute the execution trace for the given workloads.'
|
||||
print 'If you would like, also compute the response and turnaround'
|
||||
print 'times for each of the jobs.'
|
||||
print ''
|
||||
print 'Use the -c flag to get the exact results when you are finished.\n'
|
||||
exit(0)
|
||||
|
||||
# initialize the MLFQ queues
|
||||
queue = {}
|
||||
for q in range(numQueues):
|
||||
queue[q] = []
|
||||
|
||||
# TIME IS CENTRAL
|
||||
currTime = 0
|
||||
|
||||
# use these to know when we're finished
|
||||
totalJobs = len(job)
|
||||
finishedJobs = 0
|
||||
|
||||
print '\nExecution Trace:\n'
|
||||
|
||||
while finishedJobs < totalJobs:
|
||||
# find highest priority job
|
||||
# run it until either
|
||||
# (a) the job uses up its time quantum
|
||||
# (b) the job performs an I/O
|
||||
|
||||
# check for priority boost
|
||||
if options.boost > 0 and currTime != 0:
|
||||
if currTime % options.boost == 0:
|
||||
print '[ time %d ] BOOST ( every %d )' % (currTime, options.boost)
|
||||
# remove all jobs from queues (except high queue)
|
||||
for q in range(numQueues-1):
|
||||
for j in queue[q]:
|
||||
if job[j]['doingIO'] == False:
|
||||
queue[hiQueue].append(j)
|
||||
queue[q] = []
|
||||
# print 'BOOST: QUEUES look like:', queue
|
||||
|
||||
# change priority to high priority
|
||||
# reset number of ticks left for all jobs (XXX just for lower jobs?)
|
||||
# add to highest run queue (if not doing I/O)
|
||||
for j in range(numJobs):
|
||||
# print '-> Boost %d (timeLeft %d)' % (j, job[j]['timeLeft'])
|
||||
if job[j]['timeLeft'] > 0:
|
||||
# print '-> FinalBoost %d (timeLeft %d)' % (j, job[j]['timeLeft'])
|
||||
job[j]['currPri'] = hiQueue
|
||||
job[j]['ticksLeft'] = quantum[hiQueue]
|
||||
# print 'BOOST END: QUEUES look like:', queue
|
||||
|
||||
# check for any I/Os done
|
||||
if currTime in ioDone:
|
||||
for (j, type) in ioDone[currTime]:
|
||||
q = job[j]['currPri']
|
||||
job[j]['doingIO'] = False
|
||||
print '[ time %d ] %s by JOB %d' % (currTime, type, j)
|
||||
if options.iobump == False:
|
||||
queue[q].append(j)
|
||||
else:
|
||||
queue[q].insert(0, j)
|
||||
|
||||
# now find the highest priority job
|
||||
currQueue = FindQueue()
|
||||
if currQueue == -1:
|
||||
print '[ time %d ] IDLE' % (currTime)
|
||||
currTime += 1
|
||||
continue
|
||||
#print 'FOUND QUEUE: %d' % currQueue
|
||||
#print 'ALL QUEUES:', queue
|
||||
|
||||
# there was at least one runnable job, and hence ...
|
||||
currJob = queue[currQueue][0]
|
||||
if job[currJob]['currPri'] != currQueue:
|
||||
Abort('currPri[%d] does not match currQueue[%d]' % (job[currJob]['currPri'], currQueue))
|
||||
|
||||
job[currJob]['timeLeft'] -= 1
|
||||
job[currJob]['ticksLeft'] -= 1
|
||||
|
||||
if job[currJob]['firstRun'] == -1:
|
||||
job[currJob]['firstRun'] = currTime
|
||||
|
||||
runTime = job[currJob]['runTime']
|
||||
ioFreq = job[currJob]['ioFreq']
|
||||
ticksLeft = job[currJob]['ticksLeft']
|
||||
timeLeft = job[currJob]['timeLeft']
|
||||
|
||||
print '[ time %d ] Run JOB %d at PRIORITY %d [ TICKSLEFT %d RUNTIME %d TIMELEFT %d ]' % (currTime, currJob, currQueue, ticksLeft, runTime, timeLeft)
|
||||
|
||||
if timeLeft < 0:
|
||||
Abort('Error: should never have less than 0 time left to run')
|
||||
|
||||
|
||||
# UPDATE TIME
|
||||
currTime += 1
|
||||
|
||||
# CHECK FOR JOB ENDING
|
||||
if timeLeft == 0:
|
||||
print '[ time %d ] FINISHED JOB %d' % (currTime, currJob)
|
||||
finishedJobs += 1
|
||||
job[currJob]['endTime'] = currTime
|
||||
# print 'BEFORE POP', queue
|
||||
done = queue[currQueue].pop(0)
|
||||
# print 'AFTER POP', queue
|
||||
assert(done == currJob)
|
||||
continue
|
||||
|
||||
# CHECK FOR IO
|
||||
issuedIO = False
|
||||
if ioFreq > 0 and (((runTime - timeLeft) % ioFreq) == 0):
|
||||
# time for an IO!
|
||||
print '[ time %d ] IO_START by JOB %d' % (currTime, currJob)
|
||||
issuedIO = True
|
||||
desched = queue[currQueue].pop(0)
|
||||
assert(desched == currJob)
|
||||
job[currJob]['doingIO'] = True
|
||||
# this does the bad rule -- reset your tick counter if you stay at the same level
|
||||
if options.stay == True:
|
||||
job[currJob]['ticksLeft'] = quantum[currQueue]
|
||||
# add to IO Queue: but which queue?
|
||||
futureTime = currTime + ioTime
|
||||
if futureTime not in ioDone:
|
||||
ioDone[futureTime] = []
|
||||
ioDone[futureTime].append((currJob, 'IO_DONE'))
|
||||
# print 'NEW IO EVENT at ', futureTime, ' is ', ioDone[futureTime]
|
||||
|
||||
# CHECK FOR QUANTUM ENDING AT THIS LEVEL
|
||||
if ticksLeft == 0:
|
||||
# print '--> DESCHEDULE %d' % currJob
|
||||
if issuedIO == False:
|
||||
# print '--> BUT IO HAS NOT BEEN ISSUED (therefor pop from queue)'
|
||||
desched = queue[currQueue].pop(0)
|
||||
assert(desched == currJob)
|
||||
# move down one queue! (unless lowest queue)
|
||||
LowerQueue(currJob, currQueue, issuedIO)
|
||||
|
||||
|
||||
# print out statistics
|
||||
print ''
|
||||
print 'Final statistics:'
|
||||
responseSum = 0
|
||||
turnaroundSum = 0
|
||||
for i in range(numJobs):
|
||||
response = job[i]['firstRun'] - job[i]['startTime']
|
||||
turnaround = job[i]['endTime'] - job[i]['startTime']
|
||||
print ' Job %2d: startTime %3d - response %3d - turnaround %3d' % (i, job[i]['startTime'],
|
||||
response, turnaround)
|
||||
responseSum += response
|
||||
turnaroundSum += turnaround
|
||||
|
||||
print '\n Avg %2d: startTime n/a - response %.2f - turnaround %.2f' % (i,
|
||||
float(responseSum)/numJobs,
|
||||
float(turnaroundSum)/numJobs)
|
||||
|
||||
print '\n'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user