《操作系统》的实验代码。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

600 lines
21 KiB

  1. #! /usr/bin/env python
  2. import random
  3. from optparse import OptionParser
  4. import string
  5. def tprint(str):
  6. print str
  7. def dprint(str):
  8. return
  9. def dospace(howmuch):
  10. for i in range(howmuch + 1):
  11. print '%28s' % ' ',
  12. # given list, pick random element and return it
  13. def pickrand(tlist):
  14. n = int(random.random() * len(tlist))
  15. p = tlist[n]
  16. return p
  17. # given number, conclude if nth bit is set
  18. def isset(num, index):
  19. mask = 1 << index
  20. return (num & mask) > 0
  21. # useful instead of assert
  22. def zassert(cond, str):
  23. if cond == False:
  24. print 'ABORT::', str
  25. exit(1)
  26. #
  27. # Which files are used in the simulation
  28. #
  29. # Not representing a realistic piece of anything
  30. # but rather just for convenience when generating
  31. # random traces ...
  32. #
  33. # Files are named 'a', 'b', etc. for ease of use
  34. # Could probably add a numeric aspect to allow
  35. # for more than 26 files but who cares
  36. #
  37. class files:
  38. def __init__(self, numfiles):
  39. self.numfiles = numfiles
  40. self.value = 0
  41. self.filelist = list(string.ascii_lowercase)[0:numfiles]
  42. def getfiles(self):
  43. return self.filelist
  44. def getvalue(self):
  45. rc = self.value
  46. self.value += 1
  47. return rc
  48. #
  49. # Models the actions of the AFS server
  50. #
  51. # The only real interactions are get/put
  52. # get() causes the server to track which files cache what;
  53. # put() may cause callbacks to invalidate client caches
  54. #
  55. class server:
  56. def __init__(self, files, solve, detail):
  57. self.files = files
  58. self.solve = solve
  59. self.detail = detail
  60. flist = self.files.getfiles()
  61. self.contents = {}
  62. for f in flist:
  63. v = self.files.getvalue()
  64. self.contents[f] = v
  65. self.getcnt, self.putcnt = 0, 0
  66. def stats(self):
  67. print 'Server -- Gets:%d Puts:%d' % (self.getcnt, self.putcnt)
  68. def filestats(self, printcontents):
  69. for fname in self.contents:
  70. if printcontents:
  71. print('file:%s contains:%d' % (fname, self.contents[fname]))
  72. else:
  73. print('file:%s contains:?' % fname)
  74. def setclients(self, clients):
  75. # need list of clients
  76. self.clients = clients
  77. # per client callback list
  78. self.cache = {}
  79. for c in self.clients:
  80. self.cache[c.getname()] = []
  81. def get(self, client, fname):
  82. zassert(fname in self.contents, 'server:get() -- file:%s not found on server' % fname)
  83. self.getcnt += 1
  84. if self.solve and isset(self.detail, 0):
  85. print('getfile:%s c:%s [%d]' % (fname, client, self.contents[fname]))
  86. if fname not in self.cache[client]:
  87. self.cache[client].append(fname)
  88. # dprint(' -> List for client %s' % client, ' is ', self.cache[client])
  89. return self.contents[fname]
  90. def put(self, client, fname, value):
  91. zassert(fname in self.contents, 'server:put() -- file:%s not found on server' % fname)
  92. self.putcnt += 1
  93. self.contents[fname] = value
  94. if self.solve and isset(self.detail, 0):
  95. print('putfile:%s c:%s [%s]' % (fname, client, self.contents[fname]))
  96. # scan others for callback
  97. for c in self.clients:
  98. cname = c.getname()
  99. if fname in self.cache[cname] and cname != client:
  100. if self.solve and isset(self.detail, 1):
  101. print 'callback: c:%s file:%s' % (cname, fname)
  102. c.invalidate(fname)
  103. self.cache[cname].remove(fname)
  104. #
  105. # Per-client file descriptors
  106. #
  107. # Would be useful if the simulation allowed more
  108. # than one active file open() at a time; it kind
  109. # of does but this isn't really utilized
  110. #
  111. class filedesc:
  112. def __init__(self, max=1024):
  113. self.max = max
  114. self.fd = {}
  115. for i in range(self.max):
  116. self.fd[i] = ''
  117. def alloc(self, fname, sfd=-1):
  118. if sfd != -1:
  119. zassert(self.fd[sfd] == '', 'filedesc:alloc() -- fd:%d already in use, cannot allocate' % sfd)
  120. self.fd[sfd] = fname
  121. return sfd
  122. else:
  123. for i in range(self.max):
  124. if self.fd[i] == '':
  125. self.fd[i] = fname
  126. return i
  127. return -1
  128. def lookup(self, sfd):
  129. zassert(i >= 0 and i < self.max, 'filedesc:lookup() -- file descriptor out of valid range (%d not between 0 and %d)' % (sfd, self.max))
  130. zassert(self.fd[sfd] != '', 'filedesc:lookup() -- fd:%d not in use, cannot lookup' % sfd)
  131. return self.fd[sfd]
  132. def free(self, i):
  133. zassert(i >= 0 and i < self.max, 'filedesc:free() -- file descriptor out of valid range (%d not between 0 and %d)' % (sfd, self.max))
  134. zassert(self.fd[sfd] != '', 'filedesc:free() -- fd:%d not in use, cannot free' % sfd)
  135. self.fd[i] = ''
  136. #
  137. # The client cache
  138. #
  139. # Just models what files are cached.
  140. # When a file is opened, its contents are fetched
  141. # from the server and put in the cache. At that point,
  142. # the cache contents are VALID, DIRTY/NOT (depending
  143. # on whether this is for reading or writing), and the
  144. # REFERENCE COUNT is set to 1. If multiple open's take
  145. # place on this file, REFERENCE COUNT will be updated
  146. # accordingly. VALID gets set to 0 if the cache is
  147. # invalidated by a callback; however, the contents
  148. # still might be used by a given client if the file
  149. # is already open. Note that a callback does NOT
  150. # prevent a client from overwriting an already opened file.
  151. #
  152. class cache:
  153. def __init__(self, name, num, solve, detail):
  154. self.name = name
  155. self.num = num
  156. self.solve = solve
  157. self.detail = detail
  158. self.cache = {}
  159. self.hitcnt = 0
  160. self.misscnt = 0
  161. self.invalidcnt = 0
  162. def stats(self):
  163. print ' Cache -- Hits:%d Misses:%d Invalidates:%d' % (self.hitcnt, self.misscnt, self.invalidcnt)
  164. def put(self, fname, data, dirty, refcnt):
  165. self.cache[fname] = dict(data=data, dirty=dirty, refcnt=refcnt, valid=True)
  166. def update(self, fname, data):
  167. self.cache[fname] = dict(data=data, dirty=True, refcnt=self.cache[fname]['refcnt'], valid=self.cache[fname]['valid'])
  168. def invalidate(self, fname):
  169. zassert(fname in self.cache, 'cache:invalidate() -- cannot invalidate file not in cache (%s)' % fname)
  170. self.invalidcnt += 1
  171. self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
  172. refcnt=self.cache[fname]['refcnt'], valid=False)
  173. if self.solve and isset(self.detail, 1):
  174. dospace(self.num)
  175. if isset(self.detail,3):
  176. print '%2s invalidate %s' % (self.name, fname)
  177. else:
  178. print 'invalidate %s' % (fname)
  179. self.printstate(self.num)
  180. def checkvalid(self, fname):
  181. zassert(fname in self.cache, 'cache:checkvalid() -- cannot checkvalid on file not in cache (%s)' % fname)
  182. if self.cache[fname]['valid'] == False and self.cache[fname]['refcnt'] == 0:
  183. del self.cache[fname]
  184. def printstate(self, fname):
  185. for fname in self.cache:
  186. data = self.cache[fname]['data']
  187. dirty = self.cache[fname]['dirty']
  188. refcnt = self.cache[fname]['refcnt']
  189. valid = self.cache[fname]['valid']
  190. if valid == True:
  191. validPrint = 1
  192. else:
  193. validPrint = 0
  194. if dirty == True:
  195. dirtyPrint = 1
  196. else:
  197. dirtyPrint = 0
  198. if self.solve and isset(self.detail, 2):
  199. dospace(self.num)
  200. if isset(self.detail, 3):
  201. print '%s [%s:%2d (v=%d,d=%d,r=%d)]' % (self.name, fname, data, validPrint, dirtyPrint, refcnt)
  202. else:
  203. print '[%s:%2d (v=%d,d=%d,r=%d)]' % (fname, data, validPrint, dirtyPrint, refcnt)
  204. def checkget(self, fname):
  205. if fname in self.cache:
  206. self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
  207. refcnt=self.cache[fname]['refcnt'], valid=self.cache[fname]['valid'])
  208. self.hitcnt += 1
  209. return (True, self.cache[fname])
  210. self.misscnt += 1
  211. return (False, -1)
  212. def get(self, fname):
  213. assert(fname in self.cache)
  214. return (True, self.cache[fname])
  215. def incref(self, fname):
  216. assert(fname in self.cache)
  217. self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
  218. refcnt=self.cache[fname]['refcnt'] + 1, valid=self.cache[fname]['valid'])
  219. def decref(self, fname):
  220. assert(fname in self.cache)
  221. self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
  222. refcnt=self.cache[fname]['refcnt'] - 1, valid=self.cache[fname]['valid'])
  223. def setdirty(self, fname, dirty):
  224. assert(fname in self.cache)
  225. self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=dirty,
  226. refcnt=self.cache[fname]['refcnt'], valid=self.cache[fname]['valid'])
  227. def setclean(self, fname):
  228. assert(fname in self.cache)
  229. self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=False,
  230. refcnt=self.cache[fname]['refcnt'], valid=self.cache[fname]['valid'])
  231. def isdirty(self, fname):
  232. assert(fname in self.cache)
  233. return (self.cache[fname]['dirty'] == True)
  234. def setvalid(self, fname):
  235. assert(fname in self.cache)
  236. self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
  237. refcnt=self.cache[fname]['refcnt'], valid=True)
  238. # actions
  239. MICRO_OPEN = 1
  240. MICRO_READ = 2
  241. MICRO_WRITE = 3
  242. MICRO_CLOSE = 4
  243. def op2name(op):
  244. if op == MICRO_OPEN:
  245. return 'MICRO_OPEN'
  246. elif op == MICRO_READ:
  247. return 'MICRO_READ'
  248. elif op == MICRO_WRITE:
  249. return 'MICRO_WRITE'
  250. elif op == MICRO_CLOSE:
  251. return 'MICRO_CLOSE'
  252. else:
  253. abort('error: bad op -> ' + op)
  254. #
  255. # Client class
  256. #
  257. # Models the behavior of each client in the system.
  258. #
  259. #
  260. #
  261. class client:
  262. def __init__(self, name, cid, server, files, bias, numsteps, actions, solve, detail):
  263. self.name = name # readable name of client
  264. self.cid = cid # client ID
  265. self.server = server # server object
  266. self.files = files # files object
  267. self.bias = bias # bias
  268. self.actions = actions # schedule exactly?
  269. self.solve = solve # show answers?
  270. self.detail = detail # how much of an answer to show
  271. # cache
  272. self.cache = cache(self.name, self.cid, self.solve, self.detail)
  273. # file desc
  274. self.fd = filedesc()
  275. # stats
  276. self.readcnt = 0
  277. self.writecnt = 0
  278. # init actions
  279. self.done = False # track state
  280. self.acnt = 0 # this is used when running
  281. self.acts = [] # this just tracks the opcodes
  282. if self.actions == '':
  283. # in case with no specific actions, generate one...
  284. for i in range(numsteps):
  285. fname = pickrand(self.files.getfiles())
  286. r = random.random()
  287. fd = self.fd.alloc(fname)
  288. zassert(fd >= 0, 'client:init() -- ran out of file descriptors, sorry!')
  289. if r < self.bias[0]:
  290. # FILE_READ
  291. self.acts.append((MICRO_OPEN, fname, fd))
  292. self.acts.append((MICRO_READ, fd))
  293. self.acts.append((MICRO_CLOSE, fd))
  294. else:
  295. # FILE_WRITE
  296. self.acts.append((MICRO_OPEN, fname, fd))
  297. self.acts.append((MICRO_WRITE, fd))
  298. self.acts.append((MICRO_CLOSE, fd))
  299. else:
  300. # in this case, unpack actions and make it happen
  301. # should look like this: "oa1:ra1:ca1" (open 'a' for reading with file desc 1, read from file a (fd:1), etc.)
  302. # yes the file descriptor and file name are redundant for read/write and close
  303. for a in self.actions.split(':'):
  304. act = a[0]
  305. if act == 'o':
  306. zassert(len(a) == 3, 'client:init() -- malformed open action (%s) should be oa1 or something like that' % a)
  307. fname, fd = a[1], int(a[2])
  308. self.fd.alloc(fname, fd)
  309. assert(fd >= 0)
  310. self.acts.append((MICRO_OPEN, fname, fd))
  311. elif act == 'r':
  312. zassert(len(a) == 2, 'client:init() -- malformed read action (%s) should be r1 or something like that' % a)
  313. fd = int(a[1])
  314. self.acts.append((MICRO_READ, fd))
  315. elif act == 'w':
  316. zassert(len(a) == 2, 'client:init() -- malformed write action (%s) should be w1 or something like that' % a)
  317. fd = int(a[1])
  318. self.acts.append((MICRO_WRITE, fd))
  319. elif act == 'c':
  320. zassert(len(a) == 2, 'client:init() -- malformed close action (%s) should be c1 or something like that' % a)
  321. fd = int(a[1])
  322. self.acts.append((MICRO_CLOSE, fd))
  323. else:
  324. print 'Unrecognized command: %s (from %s)' % (act, a)
  325. exit(1)
  326. # debug ACTS
  327. # print self.acts
  328. def getname(self):
  329. return self.name
  330. def stats(self):
  331. print '%s -- Reads:%d Writes:%d' % (self.name, self.readcnt, self.writecnt)
  332. self.cache.stats()
  333. def getfile(self, fname, dirty):
  334. (incache, item) = self.cache.checkget(fname)
  335. if incache == True and item['valid'] == 1:
  336. dprint(' -> CLIENT %s:: HAS LOCAL COPY of %s' % (self.name, fname))
  337. self.cache.setdirty(fname, dirty)
  338. else:
  339. data = self.server.get(self.name, fname)
  340. self.cache.put(fname, data, dirty, 0)
  341. self.cache.incref(fname)
  342. def putfile(self, fname, value):
  343. self.server.put(self.name, fname, value)
  344. self.cache.setclean(fname)
  345. self.cache.setvalid(fname)
  346. def invalidate(self, fname):
  347. self.cache.invalidate(fname)
  348. def step(self, space):
  349. if self.done == True:
  350. return -1
  351. if self.acnt == len(self.acts):
  352. self.done = True
  353. return 0
  354. # now figure out what to do and do it
  355. # action, fname, fd = self.acts[self.acnt]
  356. action = self.acts[self.acnt][0]
  357. # print ''
  358. # print '*************************'
  359. # print '%s ACTION -> %s' % (self.name, op2name(action))
  360. # print '*************************'
  361. # first, do spacing for command (below)
  362. dospace(space)
  363. if isset(self.detail, 3) == True:
  364. print self.name,
  365. # now handle the action
  366. if action == MICRO_OPEN:
  367. fname, fd = self.acts[self.acnt][1], self.acts[self.acnt][2]
  368. tprint('open:%s [fd:%d]' % (fname, fd))
  369. self.getfile(fname, dirty=False)
  370. elif action == MICRO_READ:
  371. fd = self.acts[self.acnt][1]
  372. fname = self.fd.lookup(fd)
  373. self.readcnt += 1
  374. incache, contents = self.cache.get(fname)
  375. assert(incache == True)
  376. if self.solve:
  377. tprint('read:%d -> %d' % (fd, contents['data']))
  378. else:
  379. tprint('read:%d -> value?' % (fd))
  380. elif action == MICRO_WRITE:
  381. fd = self.acts[self.acnt][1]
  382. fname = self.fd.lookup(fd)
  383. self.writecnt += 1
  384. incache, contents = self.cache.get(fname)
  385. assert(incache == True)
  386. v = self.files.getvalue()
  387. self.cache.update(fname, v)
  388. if self.solve:
  389. tprint('write:%d %d -> %d' % (fd, contents['data'], v))
  390. else:
  391. tprint('write:%d value? -> %d' % (fd, v))
  392. elif action == MICRO_CLOSE:
  393. fd = self.acts[self.acnt][1]
  394. fname = self.fd.lookup(fd)
  395. incache, contents = self.cache.get(fname)
  396. assert(incache == True)
  397. tprint('close:%d' % (fd))
  398. if self.cache.isdirty(fname):
  399. self.putfile(fname, contents['data'])
  400. self.cache.decref(fname)
  401. self.cache.checkvalid(fname)
  402. # useful to see
  403. self.cache.printstate(self.name)
  404. if self.solve and self.detail > 0:
  405. print ''
  406. # return that there is more left to do
  407. self.acnt += 1
  408. return 1
  409. #
  410. # main program
  411. #
  412. parser = OptionParser()
  413. parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
  414. parser.add_option('-C', '--clients', default=2, help='number of clients', action='store', type='int', dest='numclients')
  415. parser.add_option('-n', '--numsteps', default=2, help='ops each client will do', action='store', type='int', dest='numsteps')
  416. parser.add_option('-f', '--numfiles', default=1, help='number of files in server', action='store', type='int', dest='numfiles')
  417. parser.add_option('-r', '--readratio', default=0.5, help='ratio of reads/writes', action='store', type='float', dest='readratio')
  418. 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',
  419. action='store', type='string', dest='actions')
  420. 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',
  421. action='store', type='string', dest='schedule')
  422. parser.add_option('-p', '--printstats', default=False, help='print extra stats', action='store_true', dest='printstats')
  423. parser.add_option('-c', '--compute', default=False, help='compute answers for me', action='store_true', dest='solve')
  424. 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')
  425. (options, args) = parser.parse_args()
  426. print 'ARG seed', options.seed
  427. print 'ARG numclients', options.numclients
  428. print 'ARG numsteps', options.numsteps
  429. print 'ARG numfiles', options.numfiles
  430. print 'ARG readratio', options.readratio
  431. print 'ARG actions', options.actions
  432. print 'ARG schedule', options.schedule
  433. print 'ARG detail', options.detail
  434. print ''
  435. seed = int(options.seed)
  436. numclients = int(options.numclients)
  437. numsteps = int(options.numsteps)
  438. numfiles = int(options.numfiles)
  439. readratio = float(options.readratio)
  440. actions = options.actions
  441. schedule = options.schedule
  442. printstats = options.printstats
  443. solve = options.solve
  444. detail = options.detail
  445. # with specific schedule, files are all specified by a single letter in specific actions list
  446. # but we ignore this for now...
  447. zassert(numfiles > 0 and numfiles <= 26, 'main: can only simulate 26 or fewer files, sorry')
  448. zassert(readratio >= 0.0 and readratio <= 1.0, 'main: read ratio must be between 0 and 1 inclusive')
  449. # start it
  450. random.seed(seed)
  451. # files in server to begin with
  452. f = files(numfiles)
  453. # make server
  454. s = server(f, solve, detail)
  455. clients = []
  456. if actions != '':
  457. # if specific actions are specified, figure some stuff out now
  458. # e.g., oa1:ra1:ca1,oa1:ra1:ca1 which is list of 0's actions, then 1's, then...
  459. cactions = actions.split(',')
  460. if numclients != len(cactions):
  461. numclients = len(cactions)
  462. i = 0
  463. for clist in cactions:
  464. clients.append(client('c%d' % i, i, s, f, [], len(clist), clist, solve, detail))
  465. i += 1
  466. else:
  467. # else, make random clients
  468. for i in range(numclients):
  469. clients.append(client('c%d' % i, i, s, f, [readratio, 1.0], numsteps, '', solve, detail))
  470. # tell server about these clients
  471. s.setclients(clients)
  472. # init print out for clients
  473. print '%12s' % 'Server', '%12s' % ' ',
  474. for c in clients:
  475. print '%13s' % c.getname(), '%13s' % ' ',
  476. print ''
  477. # main loop
  478. #
  479. # over time, pick a random client
  480. # have it do one thing, show what happens
  481. # move on to next and so forth
  482. s.filestats(True)
  483. # for use with specific schedule
  484. schedcurr = 0
  485. # check for legal schedule (must include all clients)
  486. if schedule != '':
  487. for i in range(len(clients)):
  488. cnt = 0
  489. for j in range(len(schedule)):
  490. curr = schedule[j]
  491. if int(curr) == i:
  492. cnt += 1
  493. zassert(cnt != 0, 'main: client %d not in schedule:%s, which would never terminate' % (i, schedule))
  494. # RUN the schedule (either random or specified by user)
  495. numrunning = len(clients)
  496. while numrunning > 0:
  497. if schedule == '':
  498. c = pickrand(clients)
  499. else:
  500. idx = int(schedule[schedcurr])
  501. # print 'SCHEDULE DEBUG:: schedule:', schedule, 'schedcurr', schedcurr, 'index', idx
  502. c = clients[idx]
  503. schedcurr += 1
  504. if schedcurr == len(schedule):
  505. schedcurr = 0
  506. rc = c.step(clients.index(c))
  507. if rc == 0:
  508. numrunning -= 1
  509. s.filestats(solve)
  510. if printstats:
  511. s.stats()
  512. for c in clients:
  513. c.stats()