-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathp1.patch
1820 lines (1751 loc) · 61.8 KB
/
p1.patch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
diff --git src/devices/timer.c src/devices/timer.c
index befaaae..a070963 100644
--- src/devices/timer.c
+++ src/devices/timer.c
@@ -92,8 +92,7 @@ timer_sleep (int64_t ticks)
int64_t start = timer_ticks ();
ASSERT (intr_get_level () == INTR_ON);
- while (timer_elapsed (start) < ticks)
- thread_yield ();
+ thread_sleep(start+ticks);
}
/* Sleeps for approximately MS milliseconds. Interrupts must be
@@ -171,7 +170,7 @@ static void
timer_interrupt (struct intr_frame *args UNUSED)
{
ticks++;
- thread_tick ();
+ thread_tick (ticks);
}
/* Returns true if LOOPS iterations waits for more than one timer
diff --git src/lib/kernel/list.c src/lib/kernel/list.c
index 316d9ef..204c55c 100644
--- src/lib/kernel/list.c
+++ src/lib/kernel/list.c
@@ -56,6 +56,11 @@ is_tail (struct list_elem *elem)
return elem != NULL && elem->prev != NULL && elem->next == NULL;
}
+bool is_in_list(struct list_elem *elem)
+{
+ return elem != NULL && elem->prev != NULL && elem->next != NULL;
+}
+
/* Initializes LIST as an empty list. */
void
list_init (struct list *list)
diff --git src/lib/kernel/list.h src/lib/kernel/list.h
index f1f12e9..5e15a52 100644
--- src/lib/kernel/list.h
+++ src/lib/kernel/list.h
@@ -135,6 +135,13 @@ struct list_elem *list_rend (struct list *);
struct list_elem *list_head (struct list *);
struct list_elem *list_tail (struct list *);
+//extra function used to check if an element is
+//in any list. In order for it to work properly
+//when an element is removed from the list its
+//prev and next pointers need to be set to NULL
+//(note: this function is used only in conjuction
+//with the code for lock_list in synch.c)
+bool is_in_list(struct list_elem *);
/* List insertion. */
void list_insert (struct list_elem *, struct list_elem *);
diff --git src/threads/DESIGNDOC src/threads/DESIGNDOC
new file mode 100644
index 0000000..1b6de6b
--- /dev/null
+++ src/threads/DESIGNDOC
@@ -0,0 +1,652 @@
+ +-------------------+
+ | OS 211 |
+ | TASK 1: THREADS |
+ | DESIGN DOCUMENT |
+ +-------------------+
+
+---- GROUP ----
+
+Rory Allford <rda10@imperial.ac.uk>
+Andrei Bara <ab6610@imperial.ac.uk>
+Alina Boghiu <ab3110@imperial.ac.uk>
+
+---- PRELIMINARIES ----
+>> Note that when calculating the maximum or minimum
+>> from a list we have not used the already existing
+>> function (i.e. list_get_max and functions for defining
+>> the order of elements) as we considered it to be more
+>> resource efficient although the amount of code is increased
+>> (function calls and the structures created within each function
+>> call could be quite high) the point was for most of the code
+>> to execute as fast as possible wher interrupts are disabled
+>> (so is the case when waking threads contained in
+>> semaphores, and the waiting_list).
+
+
+ ALARM CLOCK
+ ===========
+
+---- DATA STRUCTURES ----
+
+~~~~~~~~~~~~thread.c~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+*******static struct list waiting_list;*********
+PURPOSE:
+>> This struct will hold a list of sleeper elements
+>> used to store information about the threads that
+>> are put to sleep in ascending order of wake_time
+
+*******static struct lock waiting_lock;*********
+PURPOSE:
+>> Create a lock to be used to avoid race conditions
+>> when trying to insert a new element into the waiting_list
+>> (It can't be used in an interrupt handler as it is a lock)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+~~~~~~~~~~~~~~~~~~~thread.h~~~~~~~~~~~~~~~~~~~~~
+void thread_sleep(int64_t ticks);
+ - will put a thread asleep untill "ticks"
+void thread_wake(int64_t ticks);
+ - will wake up al the threads if "ticks" ticks
+ have passed
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+~~~~~~~~~~~~~~~~~~synch.h~~~~~~~~~~~~~~~~~~~~~~
+struct sleeper
+ {
+ int64_t wake_time;
+ struct semaphore waiting_semaphore;
+ struct list_elem elem;
+ };
+
+PURPOSE:
+>> Will store information about the threads that are being put asleep.
+>> It will contain information about the sleeping time (which is actually
+>> the time when a thread should be woken up) and a semaphore that will be
+>> downed by the sleeping thread.
+>> To be used in conjuction with "waiting_list".
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+---- ALGORITHMS ----
+
+>> A2: Briefly describe what happens in a call to timer_sleep(),
+>> including the effects of the timer interrupt handler.
+
+The timer_sleep(int64_t ticks) method calls thread_sleep(int64_t ticks) which
+puts the current thread to sleep untill (start+ticks) number of timer ticks,
+where start represents the number of ticks so far. It then calls thread_sleep()
+with an argument representing the time when the thread is due to wake up.
+thread_sleep() will then add the current running thread to the waiting_list by
+creating a semaphore, adding it to the list and then downing it (i.e. the
+sleeper struct is used in order to accomplish this). The current thread is then
+blocked.
+
+The timer interrupt handler will be the one incrementing the number of
+system ticks and the one that will call thread_tick() which in turn will call
+thread_wake(). At this point the list of "sleepers" will be traversed in
+ascending order and the appropriate threads woken up (note that due to the
+implementation of sema_up the current thread would yield only when exiting
+the interrupt handler and by that time all the threads that need waking
+should be in the ready lists).
+
+
+>> A3: What steps are taken to minimize the amount of time spent in
+>> the timer interrupt handler?
+
+In order to minimize the time spent waking the threads up, the threads that
+have been put to sleep have been inserted in ascending order of their wake up
+time (stored as an absolute value from when the operating system has started,
+in the wake_time member). Thus, when waking up threads the list of sleepers
+will be popped untill all the threads with the same wake_time value have been
+woken up (and then break early as the list is ordered).
+
+---- SYNCHRONIZATION ----
+
+>> A4: How are race conditions avoided when multiple threads call
+>> timer_sleep() simultaneously?
+
+In order to avoid race condition when timer_sleep() is called and thus
+thread_sleep(), we have used locks and semaphores. When inserting a new
+sleeper struct in the waiting_list we acquire waiting_lock, perform the
+insertion and then release the lock. After this point the thread is being put
+to sleep. (Note: when the thread is woken up it will continue running from
+where it was stopped in thread_sleep(); it will then remove itself from the list of
+waiters).
+
+>> A5: How are race conditions avoided when a timer interrupt occurs
+>> during a call to timer_sleep()?
+
+First scenario would involve changing the state of the waiting_list inside the
+thread_sleep function by being interrupted while inserting a sleeper element in
+the list. No special measures are needed to avoid this scenario as the current
+definition of list_insert would not leave the waiting_list in an inconsistent
+state at any time (this is due to the fact that the operations when the
+pointers themselves are changed, are atomic - possibly a "mov" instruction -, and
+thus in a worst case scenario when the list is traversed during insertion, it
+might skip the element that is to be inserted, but this is not a problem when
+waking up the threads as even if the thread is not inserted fast enough it will
+be woken up at the the next tick; "if (tmp_sleeper->wake_time > timer_ticks)"
+will ensure that no threads forget to wake up).
+
+---- RATIONALE ----
+
+>> A6: Why did you choose this design? In what ways is it superior to
+>> another design you considered?
+
+We have chosen this design as this seemed one of the best ways to do it:
+1. We are using synchronization mechanism already implemented in PintOS
+(like locks and semaphores) as opposed to using thread_block() and
+thread_unblock() to emulate the behaviour of the already existing structures.
+Hence we are not adding many new critical sections.
+2. We are avoiding any possible race conditions by using synchronization.
+3. Use of locks and semaphores leads to a more intuitive algorithm and clearer
+code.
+4. Keeping absolute values of sleeping times reduces computational costs and
+also simplifies the algorithms as opposed to relative sleeping times which would
+require repeated recalculations in an interrupt context.
+5. Using a sorted list implementation and a break-early for loop over repeated
+calls to list_min provides both an expected amortised algorithmic speedup and
+reduces function calls in the interrupt context.
+
+ PRIORITY SCHEDULING
+ ===================
+
+---- DATA STRUCTURES ----
+
+~~~~~~~~~~~~~~~~~~~~~~~~thread.h~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+struct thread
+ {
+ /* Owned by thread.c. */
+ tid_t tid; /* Thread identifier. */
+ enum thread_status status; /* Thread state. */
+ char name[16]; /* Name (for debugging purposes). */
+ uint8_t *stack; /* Saved stack pointer. */
+ int priority; /* Priority. */
+ int init_priority; /* Initial priority when donation starts
+ (i.e. priority to revert to when all
+ of the donations have been removed */
+ struct lock *try_lock; /* Hold the lock the current thread is
+ trying to lock on */
+ struct list lock_list; /* Hold a list of locks that another
+ thread is trying to acquire from the
+ current thread */
+ struct list_elem allelem; /* List element for all threads list. */
+
+ /* Shared between thread.c and synch.c. */
+ struct list_elem elem; /* List element. */
+
+#ifdef USERPROG
+ /* Owned by userprog/process.c. */
+ uint32_t *pagedir; /* Page directory. */
+#endif
+
+ /* Owned by thread.c. */
+ unsigned magic; /* Detects stack overflow. */
+ };
+----------------------------------------------------------------------------
+New memebers in struct threads:
+----------------------------------------------------------------------------
+int init_priority;
+ - Holds the initial priority to which the thread reverts to after donation.
+
+struct lock *try_lock;
+ - Points to the lock the current thread is trying to lock on.
+
+struct list lock_list;
+ - Holds a list of locks that other threads are trying to acquires from the
+ current thread.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+
+
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~synch.h~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+struct lock
+ {
+ struct thread *holder; /* Thread holding lock (for debugging). */
+ struct semaphore semaphore; /* Binary semaphore controlling access. */
+ int priority;
+ struct list_elem elem; /* will e */
+ };
+
+-----------------------------------------------------------------------------
+New memebers:
+-----------------------------------------------------------------------------
+int priority
+ - will store the priority of the thread that it is trying to acquire
+ that lock if its priority is higher than the already existing priority
+ of the lock
+
+struct list_elem elem
+ - used for inserting the lock in a list
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~synch.h~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+struct semaphore
+ {
+ volatile unsigned value; /* Current value. */
+ struct list waiters; /* List of waiting threads. */
+ };
+
+ value has been declared as volatile in order to avoid any problems that
+ might occur due to context switches and compiler optimisations (this
+ was not a necessary change, but it better to avoid possible error that
+ might occur; the optimisation barrier should reduce the proabability of such
+ an error occuring).
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+
+
+
+
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~thread.c~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+static struct list priority_list [PRI_MAX+1];
+
+The variable will hold 64 priority lists that will be used by the threads,
+scheduling and the rest of the operating system. All of the THREAD_READY
+threads should be in one of these lists
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+
+
+>> B2: Explain the data structure used to track priority donation.
+>> Use ASCII art to diagram a nested donation. (Alternately, submit a
+>> .png file.)
+
+The "data structure" used to track priority donation is formed of 2 components:
+1. lock_list - as explained above
+2. struct lock - by making use of the newly added priority field.
+
+
+
+------------------------------------------------------------|
+ |
+THREAD 1 |
+trying LOCK A |
+priority: 20; |
+------------------------------------------------------------|
+ |
+ |
+ |
+------------------------|---------------------------------------|
+ \\/ |
+ ------------- ------------- |
+THREAD 1 | LOCK | | LOCK | |
+trying LOCK B | A | | C | |
+ | pri: 20 | | | |
+priority:17; ------------- -------------- |
+new priority: 20; locks held by the current thread |
+ that another thread is trying to acquire |
+ (locks stored in lock_list) |
+----------------------------------------------------------------|
+ |
+ |
+ |
+------------------------|---------------------------------------|
+ \\/ |
+ ------------- ------------- |
+THREAD 2 | LOCK | | LOCK | |
+priority: 16; | B | | D | |
+new priority: 20 | pri: 20 | | | |
+ ------------- ------------- |
+ locks held by the current thread |
+ that another thread is trying to acquire |
+ (locks stored in lock_list) |
+----------------------------------------------------------------|
+
+---- ALGORITHMS ----
+
+>> B3: How do you ensure that the highest priority thread waiting for
+>> a lock, semaphore, or condition variable wakes up first?
+
+For the case of locks and semaphores (as locks are using semaphores
+in their construction) when a thread has to be woken up by a call to
+sema_up() the list of waiters is traversed and the thread with the
+maximum priority is unblocked.
+
+In the case of condition variables where multiple semaphores are used
+(i.e. a semaphore for each thread that calls cond_wait()) the semaphore
+containing the maximum thread is upped (in this case the default list_max
+function is used in conjuction with cond_var_less to return the maximum
+semaphore as extreme time efficiency is not required here).
+
+
+In all of the cases sema_up will call thread_yield() as soon as possible
+if not inside an interrupt (if in an external interrupt handler then
+intr_yield_on_return() is called so that the thread yields when it returns from
+the interrupt handler) or if the interrupts are not disabled. This will allow
+the thread with the highest priority to be scheduled immediately if possible.
+
+>> B4: Describe the sequence of events when a call to lock_acquire()
+>> causes a priority donation. How is nested donation handled?
+
+Firstly, lock_acquire will disble interrupts prior to the start of the donation
+process and then check if the lock that the current thread is trying
+to acquire is indeed held by another thread. If such is the case then the
+current thread's try_lock member will point to this lock and the donation
+process is initiated by calling lock_donate.
+
+Lock_donate() will handle nested donation up to 8 nested threads as long as
+the thread that is being donated to is different from the current thread
+(both conditions will prevent and will speed up the donation process ).
+Also the priority donation process only takes place if the thread that is
+donating has higher pirority than the thread it's donating to.
+Then the lock that is trying to be acquired records the priority of the
+donating thread and gets added to the lock_list of its holding thread. Then, if
+the holding thread is THREAD_READY then thread_swap() is called and this
+function will move the holding thread in the lists of ready threads otherwise
+its priroity gets changed, but the thread is not moved to a new list/location.
+
+If the holding thread is also trying to acquire another lock (by checking
+try_lock) the process continues, otherwise the nested donation
+stops.
+
+It is important to note that if it is the case that the lock_list is empty then
+the init_priority field in the holding thread (i.e. the one that will receive
+the donation) will be set to its priority prior to donation (this step is
+required when releasing the donations as there is nothing more to cascade
+through). Also a lock is only added once to the list, and if already in the
+list then its priority gets updated.
+
+Interrupts are renabled after the donation has completed.
+
+
+>> B5: Describe the sequence of events when lock_release() is called
+>> on a lock that a higher-priority thread is waiting for.
+
+
+When a lock is released it is removed from the lock_list of the holding thread.
+If the list is empty then lock_donate_restore will return the initial priority
+of the thread. Otherwise it will return the maximum priority recorded by the
+locks in lock_list (see the donation process above).
+Then the priority of the current thread is set to the new priority before
+calling sema_up.
+
+Like in the donation process the donation restoration process will disable and
+then re-enable interrupts.
+
+---- SYNCHRONIZATION ----
+
+>> B6: Describe a potential race in thread_set_priority() and explain
+>> how your implementation avoids it. Can you use a lock to avoid
+>> this race?
+
+A potential race condition in thread_set_priority might arrise when a donation
+process is taking place. Although only the current thread can make use of
+ thread_set_priority() during a donation the thread's priority could be changed
+and thus leaving the priority switching operations in a incosistent state and
+leading to undefined behaviour).
+
+Therefore in order to avoid this and any other potential race conditions we
+have decided to disable interrupt for a very short period of time while
+manipulating the priority of the thread.
+
+Using a lock at this point might be inappropriate as for instance the thread
+could yield after acquiring the lock, but then another thread might at some
+point want to donate to this thread and since this thread has already acquired
+a lock to prevent priority change during the donation process the priority of
+the thread can no longer be modified thus leading to a potential deadlock
+situation or some sort of infinite recursion. One other possible solution might
+use semaphores to block access to the donation process (but this might lead to
+situations in which resuming the donation process is no longer necessary or the
+new priorities are wrong).
+
+---- RATIONALE ----
+
+>> B7: Why did you choose this design? In what ways is it superior to
+>> another design you considered?
+
+We have chosen this design due to the constraits of efficiency and also
+for reasons of simplicity as explained below:
+
+1. In the case of semaphores we have avoided using ordered insertion and
+other already existing list function. Due to priority donation and the
+priorities changing at any time keeping the waiters list of a semaphore ordered
+would require a lot of computational resources and thus slow the program
+significantly and even miss on some interrupts. Also calls like list_max are to
+avoided due to the fact that each function call adds to the complexity and also
+the operating system would have to jump to different memory locations to
+execute that function call whereas with our current implementation the code is
+truly inline.
+ For the case of conditional variables (and sleeping threads as above)
+such constraints are not required as the signaling and waiting call are or
+should be used in a non-interrupted context and thus the small improvement in
+efficiency can be traded-off for a more clear code.
+
+2. We have modified the struct of the lock to hold a priority field and an elem
+field. This is due to the fact that it is easier during the donation
+restoration process to remove the locks from the lock_list of the holder thread
+and also dynamically change their recorded priority. This small trick saves a
+lot of both memory space and algorithm complexity.
+
+3. In the thread structure we have added 2 new fields. try_lock is used to
+allow nested donation and it is an easy way to record which lock a thread is
+trying to acquire and since threads are visible within the scope of the locks
+(i.e. by using lock.holder). init_priority records the priority of a thread
+before receiving its first donation. By adding these 2 fields a lot of
+complexity has been avoided and also more memory space has been saved by
+not creating some alternative data structures to store the information.
+
+4. sema_up() function has been modified so that the thread would yield as soon as possible.
+
+5. We have used an array of size 64 to store 64 priority lists. This is due to
+the fact that this provides 64 adjacent memory location, that are usually
+faster to access when operating on this array and on the lists (i.e. for
+instance when next_thread_to_run() iterates through the array to find the next
+non-empty list of threads). Also the lists provide functionalities for adding
+at the front or back at the list in constant time; same happens when popping
+the list. Thus the complexity of moving a thread in the ready lists is constant.
+Although keeping an array of 64 lists in memory takes 1KB of space,
+this is still a vital part of the operating system which should allow very
+fast access to its elements. For future optimisation we might look at a new
+type of data structure that would allow better performace.
+
+ ADVANCED SCHEDULER
+ ==================
+
+---- DATA STRUCTURES ----
+
+>> C1: Copy here the declaration of each new or changed `struct' or
+>> `struct' member, global or static variable, `typedef', or
+>> enumeration. Identify the purpose of each in 25 words or less.
+
+ IN THREAD.C
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+struct thread
+ {
+ fixed recent_cpu;
+ int nice;
+ };
+
+#define NICE_MIN -20
+#define NICE_MAX 20
+
+static volatile fixed load_avg;
+static volatile fixed ready_threads;
+
+fixed la_past_weight;
+fixed la_cur_weight;
+fixed fp_pri_max;
+
+void thread_tick_mlfqs (int64_t ticks);
+void thread_set_priority_mlfqs (int new_priority);
+
+----------------------------------------------------------------------
+New members:
+----------------------------------------------------------------------
+ thread.recent_cpu
+ - fixed point recent_cpu per-thread statistic calculated as specified
+ thread.nice
+ - integer per-thread nice value
+ - Invariant: clamped between NICE_MIN and NICE_MAX
+ load_avg
+ - fixed point global load average statistic
+ ready_threads
+ - integer count of the number of threads with status READY or RUNNING
+ - Invariant: value is updated consistently with thread.priority
+ - READY_THREADS_CHECK can be set to assert this every tick
+ la_past_weight, la_cur_weight, fp_pri_max
+ - fixed point constants to optimise certain calculations every tick
+ thread_tick_mlfqs, thread_set_priority_mlfqs
+ - mlfqs entrypoints for these functions that are set on startup
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ IN THREAD.H
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+typedef void fp_thread_tick (int64_t ticks);
+fp_thread_tick *thread_tick;
+
+typedef void fp_thread_set_priority (int new_priority);
+fp_thread_set_priority *thread_set_priority;
+
+----------------------------------------------------------------------
+Function pointers used to make switching entry points between the
+mlfqs and normal kernel scheduling modes clearer and more efficient
+- we save an if(thread_mlfqs) statement every tick.
+---------------------------------------------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+---- ALGORITHMS ----
+
+>> C2: Suppose threads A, B, and C have nice values 0, 1, and 2. Each
+>> has a recent_cpu value of 0. Fill in the table below showing the
+>> scheduling decision and the priority and recent_cpu values for each
+>> thread after each given number of timer ticks:
+
+timer recent_cpu priority thread
+ticks A B C A B C to run
+----- -- -- -- -- -- -- ------
+ 0 0 0 0 63 61 59 A
+ 4 4 0 0 62 61 59 A
+ 8 8 0 0 61 61 59 B
+12 8 4 0 61 60 59 A
+16 12 4 0 60 60 59 B
+20 12 8 0 60 59 59 A
+24 16 8 0 59 59 59 C
+28 16 8 4 59 59 58 B
+32 16 12 4 59 58 58 A
+36 20 12 4 58 58 58 C
+
+>> C3: Did any ambiguities in the scheduler specification make values
+>> in the table uncertain? If so, what rule did you use to resolve
+>> them? Does this match the behaviour of your scheduler?
+
+One ambiguity in the specification is whether to increment the recent_cpu
+value before re-calculating the priorities or after calculating the priority.
+In our implementation as reflected in the table we increment it before so that
+the value used in the other calculations is as accurate and recent as possible.
+
+>> C4: How is the way you divided the cost of scheduling between code
+>> inside and outside interrupt context likely to affect performance?
+
+At all stages we have attempted to minimize the number of operations inside the
+timer interrupt. For instance rather than re-counting ready_threads we maintain
+this as a global variable outside the interrupt context which also gives an
+expected amortised speedup. We have also used the function pointer entrypoints
+detailed above to avoid if(thread_mlfqs) checks.
+
+There is a negligible O(1) addition to the critical section in thread creation.
+
+Reducing the size of critical sections in particular the complexity of
+thread_tick leads to an amortised speedup in the scheduling performance.
+
+Since Pintos is not a multiprocessing OS further changes that could increase
+performance such as pre-emptible scheduling code or a finer grained locking
+approach would not yield any improvement in this case.
+
+---- RATIONALE ----
+
+>> C5: Briefly critique your design, pointing out advantages and
+>> disadvantages in your design choices. If you were to have extra
+>> time to work on this part of the task, how might you choose to
+>> refine or improve your design?
+
+In many of the situations we have decided to implement macros rather
+than functions. Although this is not usually good practice this would
+reduce the overhead caused by function calls. One notable example is
+THREAD_FOREACH where the original implementation had a function pointer
+and thus was not (explicitly) inlineable.
+
+In order to speed up calculations we have used bitwise arithmetic when
+multiplying or dividing by multiples of 2 (although in most of the cases
+the compiler would optimise this) we cache certain constants and again
+our fixed point implementation is macro based to allow the compiler maxmimum
+leeway to perform in-situ optimisations. With some mathematical insight it
+could be possible to implement the advanced scheduling behaviour with fewer
+operations each tick. Alternatively a circular buffer could be used to store
+load_avg values instead of recomputing recent_cpu values every 4 ticks and
+only computing them when used in the priority calculation every second.
+
+Ultimately call graph profiling would shed light on which optimisations would
+make the biggest performance difference especially to thread_tick() but this
+was not feasible in the time given Pintos's design and the complexities of
+profiling virtualised code.
+
+Functions have been declared as inline where the hint would make a notable
+difference (e.g. thread_calc_recent_cpu or thread_calc_priority_mlfqs) again
+optimising the inner loop of THREAD_FOREACH.
+
+Some of the the variables like ready_threads have been declared as volatile
+where appropriate though the memory barriers and the fact that Pintos has no
+multiprocessing makes this unncessary.
+
+For future optimisation we might have to look at ways of computing the values
+in as few steps as possible and also see if some more of the computations can
+be moved out of the interrupt. Also we might research a new type of storing
+the ready threads which would allow faster traversals and searches.
+
+>> C6: The assignment explains arithmetic for fixed-point mathematics in
+>> detail, but it leaves it open to you to implement it. Why did you
+>> decide to implement it the way you did? If you created an
+>> abstraction layer for fixed-point mathematics, that is, an abstract
+>> data type and/or a set of functions or macros to manipulate
+>> fixed-point numbers, why did you do so? If not, why not?
+
+For the implementation of fixed-point arithmetic we have decided
+to use macros in order to compute the values required. As most of the
+fixed-point arithmetic is done inside the timer-interrupt speed is an
+issue and reducing the overhead of function calls is important.
+
+Macros provide the maximum performance and compiler optimisation leeway
+compared to function calls (even if inlined given the compiler settings
+used to build Pintos).
+
+We used two int32 typedefs, fixed and fp_int, to represent fixed point numbers
+and make their use explicit in code. Operations on hardware integers give
+superior performance to ADTs and take greater advantage of compiler features.
+
+A disadvantage is a lack of typechecking and harder debugging.
+
+ SURVEY QUESTIONS
+ ================
+
+Answering these questions is optional, but it will help us improve the
+course in future quarters. Feel free to tell us anything you
+want--these questions are just to spur your thoughts. You may also
+choose to respond anonymously in the course evaluations at the end of
+the quarter.
+
+>> In your opinion, was this assignment, or any one of the three problems
+>> in it, too easy or too hard? Did it take too long or too little time?
+
+>> Did you find that working on a particular part of the assignment gave
+>> you greater insight into some aspect of OS design?
+
+>> Is there some particular fact or hint we should give students in
+>> future quarters to help them solve the problems? Conversely, did you
+>> find any of our guidance to be misleading?
+
+>> Do you have any suggestions for the TAs to more effectively assist
+>> students, either for future quarters or the remaining tasks?
+
+>> Any other comments?
diff --git src/threads/fixedpoint.h src/threads/fixedpoint.h
new file mode 100644
index 0000000..316ae7e
--- /dev/null
+++ src/threads/fixedpoint.h
@@ -0,0 +1,83 @@
+#ifndef THREADS_FIXEDPOINT_H
+#define THREADS_FIXEDPOINT_H
+
+typedef int32_t fixed;
+typedef int32_t fp_int;
+typedef int64_t fixed_large;
+// 31 bit signed integer + 1 sign bit
+#define FP_COUNT 14
+// sign bit, 17bits . 14 bits
+
+static fp_int const _fp_f = 0x1<<FP_COUNT;
+#define FP_F _fp_f
+// FP_F = 2^FP_COUNT
+
+/**
+ * FP_CLAMPI(N,L,H)
+ * clamp fp_int N in range [L,H]
+ * return fp_int
+ **/
+
+#define FP_CLAMPI(N,L,H) (N)<(L) ? (L) : ( (N)>(H) ? (H) : (N) )
+
+/**
+ * FP_FROMINT(N)
+ * convert fp_int N to fixed
+ * return fixed
+ **/
+
+#define FP_FROMINT(N) (N) * FP_F
+
+/**
+ * FP_FLOOR(X)
+ * round fixed X to 0
+ * return fp_int
+ **/
+
+#define FP_FLOOR(X) (X) / FP_F
+
+/**
+ * FP_ROUND(X)
+ * round fixed X to nearest
+ * return fp_int
+ **/
+
+#define FP_ROUND(X) ((X) < 0) ? ((X) - (FP_F>>2))/FP_F : ((X) + (FP_F>>2))/FP_F
+
+/**
+ * FP_INC(X)
+ * incremet fixed X by 1
+ * return fixed
+ **/
+
+#define FP_INC(X) (X) + FP_F
+
+/**
+ * FP_OP(X,Y)
+ * fixed X OP fixed Y
+ * return fixed
+ **/
+
+#define FP_ADD(X,Y) (X) + (Y)
+
+#define FP_SUB(X,Y) (X) - (Y)
+
+#define FP_MUL(X,Y) (fixed) ( ((fixed_large) (X)) * (Y) / FP_F )
+
+#define FP_DIV(X,Y) (fixed) ( ((fixed_large) (X)) * FP_F / (Y) )
+
+/**
+ * FP_OP(X,N)
+ * fixed x OP fp_int n
+ * return fixed
+**/
+
+#define FP_ADDI(X,N) (X) + (N) * FP_F
+
+#define FP_SUBI(X,N) (X) - (N) * FP_F
+
+#define FP_MULI(X,N) (X) * (N)
+
+#define FP_DIVI(X,N) (X) / (N)
+
+#endif /* threads/fixedpoint.h */
\ No newline at end of file
diff --git src/threads/synch.c src/threads/synch.c
index 317c68a..53e1d5a 100644
--- src/threads/synch.c
+++ src/threads/synch.c
@@ -31,6 +31,8 @@
#include <string.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
+#include "threads/malloc.h"
+
/* Initializes semaphore SEMA to VALUE. A semaphore is a
nonnegative integer along with two atomic operators for
@@ -75,6 +77,7 @@ sema_down (struct semaphore *sema)
intr_set_level (old_level);
}
+
/* Down or "P" operation on a semaphore, but only if the
semaphore is not already 0. Returns true if the semaphore is
decremented, false otherwise.
@@ -113,11 +116,38 @@ sema_up (struct semaphore *sema)
ASSERT (sema != NULL);
old_level = intr_disable ();
- if (!list_empty (&sema->waiters))
- thread_unblock (list_entry (list_pop_front (&sema->waiters),
- struct thread, elem));
+ if (!list_empty (&sema->waiters)) {
+ /*
+ use for loops instead of list_max function
+ as it is more efficient, which is quite an
+ important aspect, as at this point interrupts
+ are disabled
+ */
+ struct thread *first_awake = NULL;
+ struct thread *tmp_thread;
+ struct list_elem *e;
+ int max_pri = 0;
+ for (e= list_begin (&sema->waiters); e!= list_end (&sema->waiters);
+ e = list_next (e))
+ {
+ tmp_thread = list_entry(e,struct thread,elem);
+ if(tmp_thread->priority>=max_pri)
+ {
+ max_pri = tmp_thread->priority;
+ first_awake = tmp_thread;
+ }
+ }
+ list_remove(&first_awake->elem);
+ thread_unblock(first_awake);
+ }
sema->value++;
intr_set_level (old_level);
+ //yield the thread as soon as possible
+ if(intr_context()) {
+ intr_yield_on_return();
+ } else if(old_level != INTR_OFF) {
+ thread_yield();
+ }
}
static void sema_test_helper (void *sema_);
@@ -178,6 +208,8 @@ lock_init (struct lock *lock)
ASSERT (lock != NULL);
lock->holder = NULL;
+ (&lock->elem)->prev = NULL;
+ (&lock->elem)->next = NULL;
sema_init (&lock->semaphore, 1);
}
@@ -196,9 +228,24 @@ lock_acquire (struct lock *lock)
ASSERT (!intr_context ());
ASSERT (!lock_held_by_current_thread (lock));
+ //disable interrupts
+ //if one does not want to wake another
+ //thread that might be trying to acquire
+ //the same lock before actually blocking
+ //the current thread (if it cannot acquire
+ //the lock)
+ enum intr_level old_level;
+ old_level = intr_disable();
+ if(lock->holder != NULL )
+ {
+ thread_current()->try_lock = lock;
+ lock_donate(lock);
+ }
+ intr_set_level(old_level);
sema_down (&lock->semaphore);
+ thread_current()->try_lock = NULL;
lock->holder = thread_current ();
-}
+}
/* Tries to acquires LOCK and returns true if successful or false
on failure. The lock must not already be held by the current
@@ -231,7 +278,17 @@ lock_release (struct lock *lock)
ASSERT (lock != NULL);
ASSERT (lock_held_by_current_thread (lock));
+
+ int new_pri;
+ enum intr_level old_level;
+ old_level = intr_disable();
+
+ new_pri =lock_donate_restore(lock);
+
+ intr_set_level(old_level);
+ lock->holder->priority= new_pri;
lock->holder = NULL;
+
sema_up (&lock->semaphore);
}
@@ -245,6 +302,104 @@ lock_held_by_current_thread (const struct lock *lock)
return lock->holder == thread_current ();
}
+
+/*
+ this might loop up to 8 times
+ Donate priority to the threads holding the lock
+ that the current thread is trying to acquire
+*/
+void lock_donate(struct lock *cur_lock)
+{
+
+ struct lock *next_lock = cur_lock;
+ struct thread *cur_thread = thread_current();
+ int depth = 0; //should donate to up to 8 nested threads
+
+ while(depth<8 &&
+ next_lock->holder != cur_thread && next_lock->holder->priority<cur_thread->priority)
+ {
+
+
+ //(can be changed at a later time by
+ //another donation)
+ next_lock->priority = cur_thread->priority;
+ //if the list is empty then no donation has taken place before
+ //for the thread that holds the lock. Thus set the init_priority
+ //of the thread that has the lock to its current priority
+ if(list_empty(&next_lock->holder->lock_list))
+ next_lock->holder->init_priority = next_lock->holder->priority;
+ //if the next_lock is not in the list of locked list then add it
+ if(!is_in_list(&next_lock->elem))
+ list_push_front(&next_lock->holder->lock_list,&next_lock->elem);
+ //it the thread that has the lock is not blocked then move it from
+ //its current priority list to the priority list corresponding to the
+ //priority of the currently running thread
+ if(next_lock->holder->status != THREAD_BLOCKED)
+ thread_swap(next_lock->holder);
+ else
+ //otherwise just change it's priority as it must be in
+ // a waiting list
+ next_lock->holder->priority = cur_thread->priority;
+
+
+ depth++;
+ //check if nested donation can take place
+ if(next_lock->holder->try_lock!=NULL)
+ next_lock = next_lock->holder->try_lock;
+ else
+ break;
+ }
+
+
+
+}
+
+/*
+ Restore the donating sequence (i.e. revert the
+ priorities gained by a thread) and return the priority
+ the current thread should have.
+*/
+int lock_donate_restore(struct lock *cur_lock)
+{
+
+ if(is_in_list(&cur_lock->elem)){
+ list_remove(&cur_lock->elem);
+ //remove the current lock from the lock_list
+ //and make sure that the prev and next pointers are set
+ //to NULL so that is_in_list function will work properly
+ //on the next call on the same element (i.e. see list.c/list.h)
+ (&cur_lock->elem)->prev = (&cur_lock->elem)->next = NULL;
+
+ }
+
+ struct list_elem *e;
+ struct lock *tmp_lock;
+ int max_pri = 0;
+ //if there are no more donations in the list
+ // return the initial priority