├── .gitignore ├── Exp_1 ├── Makefile.common ├── dllist-driver.cc ├── dllist.cc ├── dllist.h ├── main.cc ├── nachos01.md ├── screenshots │ ├── driver_result.png │ ├── error_1.png │ ├── error_2.png │ ├── error_3.png │ ├── error_4.png │ ├── error_5.png │ ├── error_6.png │ ├── main_cc.png │ └── make_common.png └── threadtest.cc ├── Exp_2 ├── BoundedBuffer.cc ├── BoundedBuffer.h ├── Makefile.common ├── Question_02.md ├── Screenshots │ ├── q1.png │ ├── q3.png │ ├── q7.png │ └── q8.png ├── Table.cc ├── Table.h ├── dllist-driver.cc ├── dllist.cc ├── dllist.h ├── lab2-headers │ ├── BoundedBuffer.h │ └── Table.h ├── main.cc ├── nachos02.md ├── synch-sem.cc ├── synch-sem.h ├── synch-sleep.cc ├── synch-sleep.h └── threadtest.cc └── Exp_3 ├── Alarm.cc ├── Alarm.h ├── Elevator.cc ├── Elevator.h ├── EventBarrier.cc ├── EventBarrier.h ├── Makefile.common ├── main.cc ├── nachos03.md ├── nachos03.pdf ├── system.cc ├── system.h └── threadtest.cc /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | *.smod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | -------------------------------------------------------------------------------- /Exp_1/Makefile.common: -------------------------------------------------------------------------------- 1 | # This is part of a GNU Makefile, included by the Makefiles in 2 | # each of the subdirectories. 3 | # 4 | # This file includes all of the baseline code provided by Nachos. 5 | # Whenever you add a .h or .cc file, put it in the appropriate 6 | # _H,_C, or _O list. 7 | # 8 | # The dependency graph between assignments is: 9 | # 1. THREADS before everything else 10 | # 2. USERPROG must come before VM 11 | # 3. USERPROG can come before or after FILESYS, but if USERPROG comes 12 | # before (as in this distribution), then it must define FILESYS_STUB 13 | # 14 | # Other than that, you have complete flexibility. 15 | # 16 | # Also whenever you change the include structure of your program, you should 17 | # do a gmake depend in the subdirectory -- this will modify the Makefile 18 | # to keep track of the new dependency. 19 | 20 | # You might want to play with the CFLAGS, but if you use -O it may 21 | # break the thread system. You might want to use -fno-inline if 22 | # you need to call some inline functions from the debugger. 23 | 24 | # Copyright (c) 1992 The Regents of the University of California. 25 | # All rights reserved. See copyright.h for copyright notice and limitation 26 | # of liability and disclaimer of warranty provisions. 27 | 28 | # CFLAGS = -g -Wall -Wshadow -fwritable-strings $(INCPATH) $(DEFINES) $(HOST) -DCHANGED 29 | CFLAGS = -g -Wall -Wshadow -traditional $(INCPATH) $(DEFINES) $(HOST) -DCHANGED 30 | 31 | # These definitions may change as the software is updated. 32 | # Some of them are also system dependent 33 | CPP= gcc -E -m32 34 | CC = g++ -m32 35 | LD = g++ -m32 36 | AS = as -32 37 | 38 | PROGRAM = nachos 39 | 40 | THREAD_H =../threads/copyright.h\ 41 | ../threads/list.h\ 42 | ../threads/scheduler.h\ 43 | ../threads/synch.h \ 44 | ../threads/synchlist.h\ 45 | ../threads/system.h\ 46 | ../threads/thread.h\ 47 | ../threads/utility.h\ 48 | ../machine/interrupt.h\ 49 | ../machine/sysdep.h\ 50 | ../machine/stats.h\ 51 | ../machine/timer.h\ 52 | ../threads/dllist.h 53 | 54 | THREAD_C =../threads/main.cc\ 55 | ../threads/list.cc\ 56 | ../threads/scheduler.cc\ 57 | ../threads/synch.cc \ 58 | ../threads/synchlist.cc\ 59 | ../threads/system.cc\ 60 | ../threads/thread.cc\ 61 | ../threads/utility.cc\ 62 | ../threads/threadtest.cc\ 63 | ../machine/interrupt.cc\ 64 | ../machine/sysdep.cc\ 65 | ../machine/stats.cc\ 66 | ../machine/timer.cc\ 67 | ../threads/dllist.cc\ 68 | ../threads/dllist-driver.cc 69 | 70 | THREAD_S = ../threads/switch.s 71 | 72 | THREAD_O =main.o list.o scheduler.o synch.o synchlist.o system.o thread.o \ 73 | utility.o threadtest.o interrupt.o stats.o sysdep.o timer.o dllist.o dllist-driver.o 74 | 75 | USERPROG_H = ../userprog/addrspace.h\ 76 | ../userprog/bitmap.h\ 77 | ../filesys/filesys.h\ 78 | ../filesys/openfile.h\ 79 | ../machine/console.h\ 80 | ../machine/machine.h\ 81 | ../machine/mipssim.h\ 82 | ../machine/translate.h 83 | 84 | USERPROG_C = ../userprog/addrspace.cc\ 85 | ../userprog/bitmap.cc\ 86 | ../userprog/exception.cc\ 87 | ../userprog/progtest.cc\ 88 | ../machine/console.cc\ 89 | ../machine/machine.cc\ 90 | ../machine/mipssim.cc\ 91 | ../machine/translate.cc 92 | 93 | USERPROG_O = addrspace.o bitmap.o exception.o progtest.o console.o machine.o \ 94 | mipssim.o translate.o 95 | 96 | VM_H = 97 | VM_C = 98 | VM_O = 99 | 100 | FILESYS_H =../filesys/directory.h \ 101 | ../filesys/filehdr.h\ 102 | ../filesys/filesys.h \ 103 | ../filesys/openfile.h\ 104 | ../filesys/synchdisk.h\ 105 | ../machine/disk.h 106 | FILESYS_C =../filesys/directory.cc\ 107 | ../filesys/filehdr.cc\ 108 | ../filesys/filesys.cc\ 109 | ../filesys/fstest.cc\ 110 | ../filesys/openfile.cc\ 111 | ../filesys/synchdisk.cc\ 112 | ../machine/disk.cc 113 | FILESYS_O =directory.o filehdr.o filesys.o fstest.o openfile.o synchdisk.o\ 114 | disk.o 115 | 116 | NETWORK_H = ../network/post.h ../machine/network.h 117 | NETWORK_C = ../network/nettest.cc ../network/post.cc ../machine/network.cc 118 | NETWORK_O = nettest.o post.o network.o 119 | 120 | S_OFILES = switch.o 121 | 122 | OFILES = $(C_OFILES) $(S_OFILES) 123 | 124 | $(PROGRAM): $(OFILES) 125 | $(LD) $(OFILES) $(LDFLAGS) -o $(PROGRAM) 126 | 127 | $(C_OFILES): %.o: 128 | $(CC) $(CFLAGS) -c $< 129 | 130 | switch.o: ../threads/switch.s 131 | $(CPP) -P $(INCPATH) $(HOST) ../threads/switch.c > swtch.s 132 | $(AS) -o switch.o swtch.s 133 | 134 | depend: $(CFILES) $(HFILES) 135 | $(CC) $(INCPATH) $(DEFINES) $(HOST) -DCHANGED -M $(CFILES) > makedep 136 | echo '/^# DO NOT DELETE THIS LINE/+2,$$d' >eddep 137 | echo '$$r makedep' >>eddep 138 | echo 'w' >>eddep 139 | ed - Makefile < eddep 140 | rm eddep makedep 141 | echo '# DEPENDENCIES MUST END AT END OF FILE' >> Makefile 142 | echo '# IF YOU PUT STUFF HERE IT WILL GO AWAY' >> Makefile 143 | echo '# see make depend above' >> Makefile 144 | -------------------------------------------------------------------------------- /Exp_1/dllist-driver.cc: -------------------------------------------------------------------------------- 1 | #include "dllist.h" 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | #define NUM_RANGE 2000 9 | 10 | void genItem2List(DLList *list, int n) 11 | { 12 | int *item, key; 13 | 14 | // generating new rand() seed for each iteration 15 | static int random = 0; 16 | random++; 17 | srand(unsigned(time(0)) + random); 18 | 19 | for(int i=0;i" << key << " item->" << *item << endl; 24 | list->SortedInsert((void *)item, key); 25 | } 26 | } 27 | 28 | 29 | void delItemFromList(DLList *list, int n) 30 | { 31 | void *item; 32 | int *out = new int; // malloc mem to store the key-value 33 | 34 | for(int i=0;iIsEmpty()) { 36 | item = list->Remove(out); 37 | // print removed elements to console 38 | if(item != NULL) { 39 | cout << "Remove: key->"<< *out << " item->" << *(int *)item << endl; 40 | } else { 41 | cout << "Remove: key->"<< *out << " item->NULL" << endl; 42 | } 43 | } else { 44 | // return when list had been emptied 45 | cout << "List emptied." << endl; 46 | return; 47 | } 48 | } 49 | } 50 | 51 | 52 | void driverTest() 53 | { 54 | int n; 55 | DLList *list= new DLList(); 56 | 57 | cout << "To insert how many items?" << endl; 58 | cin >> n; 59 | genItem2List(list, n); 60 | 61 | cout << "To remove how many items?" << endl; 62 | cin >> n; 63 | delItemFromList(list, n); 64 | } 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Exp_1/dllist.cc: -------------------------------------------------------------------------------- 1 | #include "dllist.h" 2 | #include 3 | #include "system.h" 4 | #include 5 | 6 | using namespace std; 7 | extern int testnum; // introduce testnum to judeg the error type 8 | extern int canYield; // canYield is defined in threadtest.cc 9 | 10 | // initialize a list element 11 | DLLElement::DLLElement(void *itemPtr, int sortKey) 12 | { 13 | key = sortKey; 14 | item = itemPtr; 15 | next = prev = NULL; 16 | } 17 | 18 | // initialize the list 19 | DLList::DLList() 20 | { 21 | first = last = NULL; 22 | } 23 | 24 | // de-allocate the list 25 | DLList::~DLList() 26 | { 27 | DLLElement *now = first; 28 | DLLElement *next; 29 | while(now != NULL) { // from first to last one by one utill NULL 30 | next = now->next; 31 | delete now; // call delete to get rid of element now 32 | now = next; 33 | } 34 | } 35 | 36 | // add to tail of list (set key = max_key+1) 37 | void DLList::Append(void *item) 38 | { 39 | if(IsEmpty()) { // if not empty, add now to the tail 40 | DLLElement *now = new DLLElement(item, last->key + 1); // call new to allocate element now 41 | last->next = now; 42 | now->prev = last; 43 | last = now; 44 | } else { // if empty, now is both first and last 45 | DLLElement *now = new DLLElement(item, 0); 46 | first = last = now; 47 | } 48 | } 49 | 50 | // add to head of list (set key = min_key-1) 51 | void DLList::Prepend(void *item) 52 | { 53 | if(IsEmpty()) { // if not empty, add now to the head 54 | DLLElement *now = new DLLElement(item, first->key - 1); 55 | first->prev = now; 56 | now->next = first; 57 | first = now; 58 | } else { // if empty, now is both first and last 59 | DLLElement *now = new DLLElement(item, 0); 60 | first = last = now; 61 | } 62 | } 63 | 64 | // remove from head of list 65 | // set *keyPtr to key of the removed item 66 | void * DLList::Remove(int *keyPtr) 67 | { 68 | void* thing; 69 | if(!IsEmpty()) { // if empty, return NULL 70 | return NULL; 71 | } else { // if not empty 72 | DLLElement *d = first; // remove from head of list 73 | 74 | // if testnum is 3, call Yield() to cause a thread switch 75 | // all threads remove the first element at the same time 76 | if(testnum == 3) { 77 | currentThread->Yield(); 78 | } 79 | 80 | first = first->next; 81 | 82 | if(!first) { // if empty after removing first element 83 | // if testnum is 4, call Yield() to cause a thread switch 84 | // segmentation fault , one is adding but the other is deleting 85 | if(testnum == 4 && canYield) { 86 | currentThread->Yield(); 87 | } 88 | first = last = NULL; 89 | } else { // if not empty 90 | if(testnum == 3) { 91 | DEBUG('t', "type 3, 2nd switch\n"); // debug and print the information 92 | currentThread->Yield(); 93 | assert(first != NULL); 94 | } 95 | 96 | first->prev = NULL; 97 | } 98 | 99 | d->next = NULL; 100 | if(testnum == 3) { 101 | DEBUG('t', "type 3, 3rd switch\n"); 102 | currentThread->Yield(); 103 | } 104 | if(keyPtr != NULL) { 105 | *keyPtr = d->key; // set *keyPtr to key of the removed item 106 | } 107 | thing = d->item; 108 | delete d; // garbage collect 109 | return thing; // return item 110 | } 111 | } 112 | 113 | // return true if list has elements 114 | bool DLList::IsEmpty() 115 | { 116 | return (!first && !last)?false:true; 117 | } 118 | 119 | // routines to put/get items on/off list in order (sorted by key) 120 | void DLList::SortedInsert(void *item, int sortKey) 121 | { 122 | static int control = 0; // also control the switch of threads 123 | DLLElement *newone = new DLLElement(item, sortKey); 124 | if(!IsEmpty()) { // if empty 125 | // if testnum is 2, call Yield() to cause a thread switch 126 | // all threads judge the list to be empty 127 | // insert to the empty list and switching thread cause one item to cover the other one 128 | if(testnum == 2) { 129 | currentThread->Yield(); 130 | first = newone; 131 | currentThread->Yield(); 132 | last = newone; 133 | currentThread->Yield(); 134 | } else { // insert without threads switch 135 | first = newone; 136 | last = newone; 137 | } 138 | } else { // if not empty 139 | if(testnum == 4 && canYield) { 140 | currentThread->Yield(); 141 | } 142 | 143 | DLLElement *now= first; 144 | // if testnum is 6, call Yield() to cause a thread switch 145 | // insert items but in disorder 146 | if(testnum == 6 && canYield) { 147 | currentThread->Yield(); 148 | DEBUG('t', "location 1 to insert key\n", newone->key); 149 | } 150 | 151 | // sorted by key 152 | while(now != NULL) { 153 | if(now->key <= sortKey) { 154 | if(now->next == NULL || now->next->key >= sortKey) { 155 | break; 156 | } 157 | now = now->next; 158 | } else { 159 | now = NULL; 160 | break; 161 | } 162 | } 163 | 164 | if(testnum == 6 && canYield) { 165 | currentThread->Yield(); 166 | DEBUG('t', "location 2 to insert key\n", newone->key); 167 | } 168 | 169 | if(now == NULL) { 170 | if(testnum == 4) { 171 | DEBUG('t', "now is null\n"); 172 | assert(first != NULL); 173 | } 174 | if(testnum == 6 && canYield) { 175 | currentThread->Yield(); 176 | DEBUG('t', "location 3 to insert key\n", newone->key); 177 | } 178 | 179 | first->prev = newone; 180 | newone->next = first; 181 | first = newone; 182 | } else { 183 | if(testnum == 6 && canYield) { 184 | currentThread->Yield(); 185 | DEBUG('t', "location 4 to insert key\n", newone->key); 186 | } 187 | 188 | if(now == last) { 189 | last->next = newone; 190 | newone->prev = last; 191 | last = newone; 192 | } else { 193 | // if testnum is 5, call Yield() to cause a thread switch 194 | // chain scission 195 | if(testnum == 5 && canYield && control % 2 == 0) { 196 | currentThread->Yield(); 197 | control++; 198 | } 199 | 200 | newone->next = now->next; 201 | newone->next->prev = newone; 202 | if(testnum == 5 && canYield && control % 2 == 1) { 203 | currentThread->Yield(); 204 | control++; 205 | } 206 | now->next = newone; 207 | newone->prev = now; 208 | } 209 | } 210 | if(testnum == 6 && canYield) { 211 | currentThread->Yield(); 212 | } 213 | } 214 | } 215 | 216 | // remove first item with key == sortKey 217 | void* DLList::SortedRemove(int sortKey) 218 | { 219 | void* thing; 220 | if(IsEmpty()) { // if list is not empty 221 | DLLElement *now = first; 222 | while(now != NULL && now->key != sortKey) { // find out the element with key == sortKey 223 | now = now->next; 224 | } 225 | if(now != NULL) { 226 | if(now == first) { // the first is the required one 227 | first = now->next; 228 | if(first == NULL) { 229 | last = NULL; 230 | } else { 231 | first->prev = NULL; 232 | } 233 | now->next = NULL; 234 | } else if(now == last) { // the last is the required one 235 | last = now->prev; 236 | last->next = NULL; 237 | now->prev = NULL; 238 | } else { 239 | now->prev->next = now->next; 240 | now->next->prev = now->prev; 241 | } 242 | thing = now->item; 243 | delete now; 244 | return thing; 245 | } 246 | } 247 | return NULL; // return NULL if no such item exists 248 | } 249 | -------------------------------------------------------------------------------- /Exp_1/dllist.h: -------------------------------------------------------------------------------- 1 | #ifndef DLLIST_H_INCLUDED 2 | #define DLLIST_H_INCLUDED 3 | 4 | class DLLElement { 5 | public: 6 | DLLElement( void *itemPtr, int sortKey ); // initialize a list element 7 | DLLElement *next; // next element on list 8 | // NULL if this is the last 9 | DLLElement *prev; // previous element on list 10 | // NULL if this is the first 11 | int key; // priority, for a sorted list 12 | void *item; // pointer to item on the list 13 | }; 14 | 15 | class DLList { 16 | public: 17 | DLList(); // initialize the list 18 | ~DLList(); // de-allocate the list 19 | void Prepend(void *item); // add to head of list (set key = min_key-1) 20 | void Append(void *item); // add to tail of list (set key = max_key+1) 21 | void *Remove(int *keyPtr); // remove from head of list 22 | // set *keyPtr to key of the removed item 23 | // return item (or NULL if list is empty) 24 | bool IsEmpty(); // return true if list has elements 25 | // routines to put/get items on/off list in order (sorted by key) 26 | void SortedInsert(void *item, int sortKey); 27 | void *SortedRemove(int sortKey); // remove first item with key==sortKey 28 | // return NULL if no such item exists 29 | private: 30 | DLLElement *first; // head of the list, NULL if empty 31 | DLLElement *last; // last element of the list, NULL if empty 32 | }; 33 | 34 | #endif // DLLIST_H_INCLUDED 35 | -------------------------------------------------------------------------------- /Exp_1/main.cc: -------------------------------------------------------------------------------- 1 | // main.cc 2 | // Bootstrap code to initialize the operating system kernel. 3 | // 4 | // Allows direct calls into internal operating system functions, 5 | // to simplify debugging and testing. In practice, the 6 | // bootstrap code would just initialize data structures, 7 | // and start a user program to print the login prompt. 8 | // 9 | // Most of this file is not needed until later assignments. 10 | // 11 | // Usage: nachos -d -rs 12 | // -s -x -c 13 | // -f -cp 14 | // -p -r -l -D -t 15 | // -n -m 16 | // -o 17 | // -z 18 | // 19 | // -d causes certain debugging messages to be printed (cf. utility.h) 20 | // -rs causes Yield to occur at random (but repeatable) spots 21 | // -z prints the copyright message 22 | // 23 | // USER_PROGRAM 24 | // -s causes user programs to be executed in single-step mode 25 | // -x runs a user program 26 | // -c tests the console 27 | // 28 | // FILESYS 29 | // -f causes the physical disk to be formatted 30 | // -cp copies a file from UNIX to Nachos 31 | // -p prints a Nachos file to stdout 32 | // -r removes a Nachos file from the file system 33 | // -l lists the contents of the Nachos directory 34 | // -D prints the contents of the entire file system 35 | // -t tests the performance of the Nachos file system 36 | // 37 | // NETWORK 38 | // -n sets the network reliability 39 | // -m sets this machine's host id (needed for the network) 40 | // -o runs a simple test of the Nachos network software 41 | // 42 | // NOTE -- flags are ignored until the relevant assignment. 43 | // Some of the flags are interpreted here; some in system.cc. 44 | // 45 | // Copyright (c) 1992-1993 The Regents of the University of California. 46 | // All rights reserved. See copyright.h for copyright notice and limitation 47 | // of liability and disclaimer of warranty provisions. 48 | 49 | #define MAIN 50 | #include "copyright.h" 51 | #undef MAIN 52 | 53 | #include "utility.h" 54 | #include "system.h" 55 | 56 | #ifdef THREADS 57 | // external arguments defined in threadtest.cc 58 | extern int testnum; 59 | extern int threadNum; 60 | extern int oprNum; 61 | #endif 62 | 63 | // External functions used by this file 64 | 65 | extern void ThreadTest(void), Copy(char *unixFile, char *nachosFile); 66 | extern void Print(char *file), PerformanceTest(void); 67 | extern void StartProcess(char *file), ConsoleTest(char *in, char *out); 68 | extern void MailTest(int networkID); 69 | 70 | //---------------------------------------------------------------------- 71 | // main 72 | // Bootstrap the operating system kernel. 73 | // 74 | // Check command line arguments 75 | // Initialize data structures 76 | // (optionally) Call test procedure 77 | // 78 | // "argc" is the number of command line arguments (including the name 79 | // of the command) -- ex: "nachos -d +" -> argc = 3 80 | // "argv" is an array of strings, one for each command line argument 81 | // ex: "nachos -d +" -> argv = {"nachos", "-d", "+"} 82 | //---------------------------------------------------------------------- 83 | 84 | int 85 | main(int argc, char **argv) 86 | { 87 | int argCount; // the number of arguments 88 | // for a particular command 89 | 90 | DEBUG('t', "Entering main"); 91 | (void) Initialize(argc, argv); 92 | 93 | #ifdef THREADS 94 | for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount) { 95 | argCount = 1; 96 | switch (argv[0][1]) { 97 | case 'q': 98 | testnum = atoi(argv[1]); 99 | argCount++; 100 | break; 101 | case 't': 102 | threadNum = atoi(argv[1]); 103 | argCount++; 104 | break; 105 | case 'n': 106 | oprNum = atoi(argv[1]); 107 | argCount++; 108 | break; 109 | default: 110 | testnum = 1; 111 | oprNum = 2 ; 112 | threadNum = 2; 113 | break; 114 | } 115 | } 116 | ThreadTest(); 117 | #endif 118 | for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount) { 119 | argCount = 1; 120 | if (!strcmp(*argv, "-z")) // print copyright 121 | printf (copyright); 122 | #ifdef USER_PROGRAM 123 | if (!strcmp(*argv, "-x")) { // run a user program 124 | ASSERT(argc > 1); 125 | StartProcess(*(argv + 1)); 126 | argCount = 2; 127 | } else if (!strcmp(*argv, "-c")) { // test the console 128 | if (argc == 1) 129 | ConsoleTest(NULL, NULL); 130 | else { 131 | ASSERT(argc > 2); 132 | ConsoleTest(*(argv + 1), *(argv + 2)); 133 | argCount = 3; 134 | } 135 | interrupt->Halt(); // once we start the console, then 136 | // Nachos will loop forever waiting 137 | // for console input 138 | } 139 | #endif // USER_PROGRAM 140 | #ifdef FILESYS 141 | if (!strcmp(*argv, "-cp")) { // copy from UNIX to Nachos 142 | ASSERT(argc > 2); 143 | Copy(*(argv + 1), *(argv + 2)); 144 | argCount = 3; 145 | } else if (!strcmp(*argv, "-p")) { // print a Nachos file 146 | ASSERT(argc > 1); 147 | Print(*(argv + 1)); 148 | argCount = 2; 149 | } else if (!strcmp(*argv, "-r")) { // remove Nachos file 150 | ASSERT(argc > 1); 151 | fileSystem->Remove(*(argv + 1)); 152 | argCount = 2; 153 | } else if (!strcmp(*argv, "-l")) { // list Nachos directory 154 | fileSystem->List(); 155 | } else if (!strcmp(*argv, "-D")) { // print entire filesystem 156 | fileSystem->Print(); 157 | } else if (!strcmp(*argv, "-t")) { // performance test 158 | PerformanceTest(); 159 | } 160 | #endif // FILESYS 161 | #ifdef NETWORK 162 | if (!strcmp(*argv, "-o")) { 163 | ASSERT(argc > 1); 164 | Delay(2); // delay for 2 seconds 165 | // to give the user time to 166 | // start up another nachos 167 | MailTest(atoi(*(argv + 1))); 168 | argCount = 2; 169 | } 170 | #endif // NETWORK 171 | } 172 | 173 | currentThread->Finish(); // NOTE: if the procedure "main" 174 | // returns, then the program "nachos" 175 | // will exit (as any other normal program 176 | // would). But there may be other 177 | // threads on the ready list. We switch 178 | // to those threads by saying that the 179 | // "main" thread is finished, preventing 180 | // it from returning. 181 | return(0); // Not reached... 182 | } 183 | -------------------------------------------------------------------------------- /Exp_1/nachos01.md: -------------------------------------------------------------------------------- 1 | # 操作系统原理课程实验报告 2 | 3 | > **实验 1 - 体验 NachOS 下的并发程序设计** 4 | > 5 | > 实验日期:Mar. 2nd - Mar. 25th, 2017 6 | 7 | > **小组成员信息** 8 | > 9 | > 10 | 11 | 12 | 13 | ### 一、实验内容与要求 14 | 15 | 本实验目的在于初步熟悉 NachOS 教学用操作系统,体验 NachOS 环境下的并发程序设计。本次实验的具体内容如下: 16 | 17 | * 在 Linux 环境下使用 make 工具编译运行原始 NachOS 并阅读相关代码,理解 NachOS 内线程控制、调试功能等部分的具体实现细节; 18 | * 用 C++ 实现双向有序链表 *dllist.h*, *dllist.cc* 与 *dllist-driver.cc*, 其中 *dllist.h* 可参考实验指导中给出的头文件; 19 | * 修改 NachOS 线程系统中的 *threadtest.cc* 及 *main.cc*, 设计可能使并发链表操作发生错误的线程顺序,体验并发程序中可能出现的各种问题。 20 | 21 | ### 二、实验设计与代码实现 22 | 23 | ##### A. *Makefile* 相关修改 24 | 25 | 为使 `make` 命令能够正常工作,我们需要向 *Makefile.common* 文件中相关的部分加入我们新增的 *dllist.cc*, *dllist-driver.cc*, *dllist.h* 文件信息与这些文件在编译过程中产生的中间文件信息,从而使得 `make depend` 命令能够正确判断源文件间的依赖关系进而支持 `make` 正常编译链接出目标文件。 26 | 27 | *Makefile.common* 中的修改如 **Fig. 1** 中蓝框部分所示: 28 | 29 | ![Makefile Common](screenshots/make_common.png) 30 | 31 | > ***Fig. 1*** *Makefile.common* 中的部分修改 32 | 33 | 34 | 35 | ##### B. 双向链表具体实现 (*dllist.cc* / *dllist.h*) 36 | 37 | 我们在 *dllist.h* 中定义了双向链表类及链表元素类的具体细节,基本与实验指导中给出的一致,详细内容参见 *dllist.h* 源文件。 38 | 39 | 根据 *dllist.h* 中的定义,*dllist.cc* 的一例具体实现如下(代码细节参见注释): 40 | 41 | > ***Code. 1*** *dllist.cc* 具体实现 42 | 43 | ```C++ 44 | // initialize a list element 45 | DLLElement::DLLElement(void *itemPtr, int sortKey) { 46 | key = sortKey; 47 | item = itemPtr; 48 | next = prev = NULL; 49 | } 50 | 51 | // initialize the list 52 | DLList::DLList() { 53 | first = last = NULL; 54 | } 55 | 56 | // de-allocate the list 57 | DLList::~DLList() { 58 | DLLElement *now = first; 59 | DLLElement *next; 60 | while(now != NULL) { // from first to last one by one utill NULL 61 | next = now->next; 62 | delete now; // call delete to get rid of element now 63 | now = next; 64 | } 65 | } 66 | 67 | // add to tail of list (set key = max_key+1) 68 | void DLList::Append(void *item) { 69 | if(IsEmpty()) { // if not empty, add now to the tail 70 | DLLElement *now = new DLLElement(item, last->key + 1); // call new to allocate element now 71 | last->next = now; 72 | now->prev = last; 73 | last = now; 74 | } else { // if empty, now is both first and last 75 | DLLElement *now = new DLLElement(item, 0); 76 | first = last = now; 77 | } 78 | } 79 | 80 | // add to head of list (set key = min_key-1) 81 | void DLList::Prepend(void *item) { 82 | if(IsEmpty()) { // if not empty, add now to the head 83 | DLLElement *now = new DLLElement(item, first->key - 1); 84 | first->prev = now; 85 | now->next = first; 86 | first = now; 87 | } else { // if empty, now is both first and last 88 | DLLElement *now = new DLLElement(item, 0); 89 | first = last = now; 90 | } 91 | } 92 | 93 | // remove from head of list 94 | // set *keyPtr to key of the removed item 95 | void* DLList::Remove(int *keyPtr) { 96 | void* thing; 97 | if(!IsEmpty()) { // if empty, return NULL 98 | return NULL; 99 | } else { // if not empty 100 | DLLElement *d = first; // remove from head of list 101 | first = first->next; 102 | if(!first) { // if empty after removing first element 103 | first = last = NULL; 104 | } else { // if not empty 105 | first->prev = NULL; 106 | } 107 | d->next = NULL; 108 | if(keyPtr != NULL) { 109 | *keyPtr = d->key; // set *keyPtr to key of the removed item 110 | } 111 | thing = d->item; 112 | delete d; // collect garbage 113 | return thing; // return item 114 | } 115 | } 116 | 117 | // return true if list has elements 118 | bool DLList::IsEmpty() { 119 | return (!first && !last)?false:true; 120 | } 121 | 122 | // routines to put/get items on/off list in order (sorted by key) 123 | void DLList::SortedInsert(void *item, int sortKey) { 124 | DLLElement *newone = new DLLElement(item, sortKey); 125 | if(!IsEmpty()) { // if empty 126 | first = newone; 127 | last = newone; 128 | } else { // if not empty 129 | DLLElement *now= first; 130 | // sorted by key 131 | while(now != NULL) { 132 | if(now->key <= sortKey) { 133 | if(now->next == NULL || now->next->key >= sortKey) { 134 | break; 135 | } 136 | now = now->next; 137 | } else { 138 | now = NULL; 139 | break; 140 | } 141 | } 142 | if(now == NULL) { 143 | first->prev = newone; 144 | newone->next = first; 145 | first = newone; 146 | } else { 147 | if(now == last) { 148 | last->next = newone; 149 | newone->prev = last; 150 | last = newone; 151 | } else { 152 | newone->next = now->next; 153 | newone->next->prev = newone; 154 | now->next = newone; 155 | newone->prev = now; 156 | } 157 | } 158 | } 159 | } 160 | 161 | // remove first item with key == sortKey 162 | void* DLList::SortedRemove(int sortKey) { 163 | void* thing; 164 | if(IsEmpty()) { // if list is not empty 165 | DLLElement *now = first; 166 | while(now != NULL && now->key != sortKey) { // find out the element with key == sortKey 167 | now = now->next; 168 | } 169 | if(now != NULL) { 170 | if(now == first) { // the first is the required one 171 | first = now->next; 172 | if(first == NULL) { 173 | last = NULL; 174 | } else { 175 | first->prev = NULL; 176 | } 177 | now->next = NULL; 178 | } else if(now == last) { // the last is the required one 179 | last = now->prev; 180 | last->next = NULL; 181 | now->prev = NULL; 182 | } else { 183 | now->prev->next = now->next; 184 | now->next->prev = now->prev; 185 | } 186 | thing = now->item; 187 | delete now; // collect garbage 188 | return thing; 189 | } 190 | } 191 | return NULL; // return NULL if no such item exists 192 | } 193 | ``` 194 | 195 | 196 | 197 | ##### C. *dllist-driver.cc* 具体实现 198 | 199 | 实验要求 *dllist-driver.cc* 提供两个函数,分别负责向双向链表中随机插入指定个元素/从表头开始删除指定个元素。在我们的实现中,这两个函数分别是 `genItem2List(DLList *list, int n)` 与 `delItemFromList(DLList *list, int n)` 。 200 | 201 | 其具体实现如下: 202 | 203 | > ***Code. 2*** `genItem2List(DLList *list, int n)` 具体实现 204 | 205 | ```C++ 206 | void genItem2List(DLList *list, int n) { 207 | int *item, key; 208 | // generating new rand() seed for each iteration 209 | static int random = 0; 210 | random++; 211 | srand(unsigned(time(0)) + random); 212 | 213 | // insert n elements with random *item and key 214 | for(int i=0;i" << key << " item->" << *item << endl; 219 | // call SortedInsert() to insert elements in order 220 | list->SortedInsert((void *)item, key); 221 | } 222 | } 223 | ``` 224 | 225 | 226 | 227 | > ***Code. 3*** `delItemFromList(DLList *list, int n)` 具体实现 228 | 229 | ```C++ 230 | void delItemFromList(DLList *list, int n) { 231 | void *item; 232 | int *out = new int; // malloc mem to store the key-value 233 | for(int i=0;iIsEmpty()) { 235 | item = list->Remove(out); 236 | // print removed elements to console 237 | if(item != NULL) { 238 | cout << "Remove: key->"<< *out << " item->" << *(int *)item << endl; 239 | } else { 240 | cout << "Remove: key->"<< *out << " item->NULL" << endl; 241 | } 242 | } else { 243 | // return when list had been emptied 244 | cout << "List emptied." << endl; return; 245 | } 246 | } 247 | } 248 | ``` 249 | 250 | 251 | 252 | ##### D. *threadtest.cc* 与 *main.cc* 相关修改 253 | 254 | ###### *main.cc* 中作出的修改 255 | 256 | 在 *main.cc* 里我们需要处理命令行调用 `./nachos` 时传入的参数(默认参数可见 *main.cc* 中的注释部分),本次实验中我们主要处理 THREADS 子系统和 *threadtest.cc* 所需要的参数,以方便进行并行链表操作测试。为此,我们定义了以下参数: 257 | 258 | | 参数标记 | 对应变量名 | 参数含义 | 259 | | ---- | ------------- | ---------------- | 260 | | -q | int testnum | 测例编号,用于进入不同的测试分支 | 261 | | -t | int threadNum | 需要创建的并行线程数量 | 262 | | -n | int oprNum | 链表操作的元素个数 | 263 | 264 | + 不做任何指定时这三个参数为 `testnum = 1, threadNum = oprNum = 2` 。 265 | 266 | 对应的 *main.cc* 里的修改如 **Fig. 2** 中所示: 267 | 268 | ![Driver Test](screenshots/main_cc.png) 269 | 270 | > **Fig. 2** *main.cc* 中修改的部分 271 | 272 | 273 | 274 | ###### 并行线程执行顺序设计与 *threadtest.cc* 中的修改 275 | 276 | 最后一项实验要求需要我们设计在 NachOS 的线程系统下可能使并发链表操作发生错误的线程顺序。 277 | 278 | 对于并发链表操作可能导致的错误,我们可以将其归结为以下几类: 279 | 280 | | 错误类型 | 描述 | 测例编号 | 281 | | ---- | ------------------------------- | ------- | 282 | | 共享内存 | 并行执行时一个线程可能删除/修改其余线程插入的元素 | ` -q 1` | 283 | | 覆盖 | 并行的线程在链表同一个地方插入元素,导致其中一个被覆盖 | ` -q 2` | 284 | | 非法删除 | 并行的线程准备删除链表中同一个元素,导致段错误 | `-q 3` | 285 | | 段错误 | 并行的线程一边删除一边插入,导致插入线程出现非法访问 | `-q 4` | 286 | | 断链 | 并行的线程在同一个地方插入元素,导致元素指针发生不一致 | `-q 5` | 287 | | 乱序 | 并行的线程在同一个地方插入元素,导致元素位置颠倒,键值大的在前 | `-q 6` | 288 | 289 | NachOS 中线程系统采用 `currentThread->Yield()` 来强制线程发生切换,因此我们可以采用这一函数模拟实际操作系统中可能的切换位置,观察在不同地方发生切换时将引发的以上错误。 290 | 291 | 为了控制不同线程间的切换位置,我们在 *threadtest.cc* 与 *dllist.cc* 中引入了以下变量来控制切换的发生: 292 | 293 | | 变量名 | 含义 | 294 | | ------------ | ---------------------------- | 295 | | int which | 变量标识号 | 296 | | int canYield | 指示当前线程能否 Yield(),控制不同线程的切换位置 | 297 | | int control | 记录 Yield() 发生次数,控制不同线程的切换位置 | 298 | 299 | 并行线程采用下面的代码调用 `thread->Fork()` 生成: 300 | 301 | ```C++ 302 | void toDllistTest(VoidFunctionPtr func) { // func points to the specific test 303 | Thread *t; 304 | for(int i = 0; i < threadNum; i++) { 305 | t = new Thread(getName(i + 1)); 306 | t->Fork(func, i + 1); 307 | } 308 | } 309 | ``` 310 | 311 | > ***Code. 5*** 并行线程生成 312 | 313 | 314 | 315 | ###### 设计的具体执行顺序 316 | 317 | 下面就 6 种错误类型详细描述我们设计的线程执行顺序,其中 > 表示切换的发生。 318 | 319 | **共享内存:**这种情况较为简单,可以描述为线程 A 向链表插入数据 > 线程 B 向链表插入数据 > 线程 A 从链表删除数据 > 线程 B 从链表删除数据。这种情况下两个线程均有可能修改到对方的元素。具体代码实现如下: 320 | 321 | > ***Code. 6*** 共享内存测例实现 322 | 323 | ```C++ 324 | void DllistTest1(int which) { 325 | printf("Inserting items in thread %d\n", which); 326 | genItem2List(l, oprNum); 327 | currentThread->Yield(); // Yield here 328 | printf("Removing items in thread %d\n", which); 329 | delItemFromList(l, oprNum); 330 | } 331 | ``` 332 | 333 | + 以下情况的代码实现见 *threadtest.cc* 与 *dllist.cc* 源文件。 334 | 335 | **覆盖:**覆盖发生的情况可以描述为线程 A 准备向链表中某一位置插入数据 > 线程 B 准备向链表同一位置插入数据 > 线程 A 完成插入 > 线程 B 完成插入。这种情况下线程 A 的修改被 B 的修改所覆盖。 336 | 337 | **非法删除:**类似于覆盖,这种情况可以描述为线程 A 准备删除链表中的数据 P > 线程 B 准备删除链表中的数据 P > 线程 A 删除数据 > 线程 B 删除数据。这种情况下线程 B 讲访问到野指针或空指针,发生段错误。 338 | 339 | **段错误:**这种情况可以描述为线程 A 调用 `Prepend()` 准备向链表表头插入数据,判断到链表不为空 > 线程 B 从链表删除数据,并将链表删除为空 > 线程 A 向表头插入,并尝试修改 first. 这种情况 first 已经变为 NULL,A 将引发段错误。 340 | 341 | **断链:**这种情况可以描述为线程 A 准备向链表中某一位置插入数据 P1 > 线程 B 准备向链表同一位置插入数据 P2 > 线程 A 修改近邻的指针 > 线程 B 修改近邻的指针并完成插入 > 线程 A 修改近邻的指针。这种情况的具体表现较为多样,有可能出现链表中出现环、从表头遍历链表时插入位置访问到 P1 而从表尾遍历时插入位置访问到 P2 等,具体的表现取决于双向链表实现与切换发生的位置,但这都是由于链表指针出现了不一致的情况引起的。 342 | 343 | **乱序:**乱序的情况可以描述为有两个数据准备插入,其中 `P1->key` 大于 `P2->key` ,但他们在该链表中应该插入的位置相同。此时线程 A 准备向链表中某一位置插入数据 P1 > 线程 B 准备向链表同一位置插入数据 P2 > 两个线程交叉修改插入位置前后元素与待插入元素的 next 或 prev 指针。这种情况下两线程完成插入时有可能 P1 处在 P2 的前面,造成链表乱序。 344 | 345 | ### 三、实验结果 346 | 347 | ##### A. *dllist-driver.cc* 实现结果 348 | 349 | 为了对 *dllist-driver.cc* 进行测试,我们提供了 `driverTest()` 函数,先调用 `genItem2List()` 向链表内插入指定个随机元素,再调用 `delItemFromList()` 从表头开始删除指定个元素。 350 | 351 | 使用命令 `./nachos -q 7` 进入 `driverTest()` 的测试分支,其运行结果如 **Fig. n** 所示。 352 | 353 | ![Driver Test](screenshots/driver_result.png) 354 | 355 | > ***Fig. 3*** `driverTest()` 运行结果 356 | 357 | ##### B. 链表并发操作运行结果 358 | 359 | ###### 共享内存情况 360 | 361 | ![E1](screenshots/error_1.png) 362 | 363 | > ***Fig. 4*** 共享内存错误运行结果 364 | 365 | 容易看到线程 1 删除了线程 2 插入的元素,线程 2 也删除了线程 1 插入的元素。 366 | 367 | ###### 覆盖情况 368 | 369 | ![E2](screenshots/error_2.png) 370 | 371 | > ***Fig. 5*** 覆盖错误运行结果 372 | 373 | 易见可以线程 1 插入的键值为 -870 的元素被覆盖。 374 | 375 | ###### 非法删除情况 376 | 377 | ![E3](screenshots/error_3.png) 378 | 379 | > ***Fig. 6*** 非法删除错误运行结果 380 | 381 | 易见线程 1 与线程 2 执行了重复的删除。 382 | 383 | ###### 段错误情况 384 | 385 | ![E4](screenshots/error_4.png) 386 | 387 | > ***Fig. 7*** 共享内存错误运行结果 388 | 389 | 可以看到插入元素一个线程在链表为空时以链表非空的状态插入,即在 first 为 NULL 时试图改写 `first->next`,这将引发段错误。 390 | 391 | ###### 断链情况 392 | 393 | ![E5](screenshots/error_5.png) 394 | 395 | > ***Fig. 8*** 断链错误运行结果 396 | 397 | 可以看到断链导致键值为 10 的元素没能被删除且在删除时引发了段错误。 398 | 399 | ###### 乱序情况 400 | 401 | ![E6](screenshots/error_6.png) 402 | 403 | > ***Fig. 9*** 乱序错误运行结果 404 | 405 | 可以看到键值为 194 的元素先于键值为 163 的元素被删除,并行线程导致了乱序。 406 | 407 | ### 四、实验中遇到的问题 408 | 409 | > 如何判断小组成员各自的双向链表 *dllist.cc* 实现的正确性? 410 | 411 | 经小组讨论,小组成员均提供了针对 *dllist.cc* 实现的多种测试样例,然后对各自的实现进行单元测试,以通过所有测例的作为实验提交的代码。 412 | 413 | > 在发生段错误等难以溯源的错误时,如何快速确定具体的错误发生的地方,有效地进行 Debug? 414 | 415 | 根据 NachOS 对内置 Debug 系统部分的描述,我们在需要跟踪的地方使用 `DEBUG()` 函数,然后在命令行中执行时使用 `-d` 参数打印 Debug 信息,同时使用 `assert()` 进行断言判断。 416 | 417 | > 如何将多种并行错误执行顺序整合到一组源代码实现中,并方便地指定测试的参数并在不同测例间切换? 418 | 419 | 在 *threadtest.cc* 中我们引入了 testnum 变量进行判断,并且修改了 *main.cc* 传入测试所需的参数。 420 | 421 | > 如何控制不同测试情况下不同线程各自的执行顺序? 422 | 423 | 为了控制不同线程各自的执行顺序,我们设置了 canYield 与 control 变量来控制切换发生的时间,并用 which 变量区别不同进程,使其按照既定的方案进行切换,并用类似于信号量的想法在完成切换前指定另外的线程如何处理该切换。但目前我们粗略地使用了 which 取模进行处理,只能保证控制 2 个线程下的线程行为,实验在多个线程下依然会出现无法区分不同线程的情况。 424 | 425 | 在实现了 NachOS 的线程控制系统与信号量等部分后这些问题可以得到改进。 426 | 427 | ### 五、实验收获与总结 428 | 429 | 在本次实验中,我们对 NachOS 的整体状况有了初步了解,同时探究了NachOS 线程子系统、调试功能等部分的具体实现细节,通过 NachOS 操作系统的代码实现加深了对一个简单的操作系统全貌的直观感受,也更为深入地理解了操作系统中的线程与线程控制概念。通过对并行线程的执行顺序的设计,我们也更为细致的了解到了多线程环境下的并发概念、线程调度与潜在存在的问题,理解了多线程环境下对线程进行合理控制与确保“临界区”的互斥等问题的重要性,在实验的过程中,我们也对 Makefile 的具体规则有了基本的了解,整个不大不小的实验也是回顾快要忘掉的 C++ 的过程,为之后的实验做好了相应准备。 430 | 431 | 总体说来,实验 1 在组员的互相讨论交流中较为顺利地得以完成。为了合理分工、提高效率小组成员也使用了 Git/GitHub 等工具进行代码管理,学习了基础的单元测试方法。但回顾实验过程,仍然存在有时间安排不合理、分工不明确的地方。在完成第一次实验,更加熟悉操作系统实验的流程与套路后随着组员之间各自工作习惯的相互磨合与相互交流,相信在之后的实验中我们会有更为良好的配合与产出:) 432 | 433 | ----- -------------------------------------------------------------------------------- /Exp_1/screenshots/driver_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSPanda/NachOS/2c54352e936ab1ff7bbd455f355c212e1e4e47a4/Exp_1/screenshots/driver_result.png -------------------------------------------------------------------------------- /Exp_1/screenshots/error_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSPanda/NachOS/2c54352e936ab1ff7bbd455f355c212e1e4e47a4/Exp_1/screenshots/error_1.png -------------------------------------------------------------------------------- /Exp_1/screenshots/error_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSPanda/NachOS/2c54352e936ab1ff7bbd455f355c212e1e4e47a4/Exp_1/screenshots/error_2.png -------------------------------------------------------------------------------- /Exp_1/screenshots/error_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSPanda/NachOS/2c54352e936ab1ff7bbd455f355c212e1e4e47a4/Exp_1/screenshots/error_3.png -------------------------------------------------------------------------------- /Exp_1/screenshots/error_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSPanda/NachOS/2c54352e936ab1ff7bbd455f355c212e1e4e47a4/Exp_1/screenshots/error_4.png -------------------------------------------------------------------------------- /Exp_1/screenshots/error_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSPanda/NachOS/2c54352e936ab1ff7bbd455f355c212e1e4e47a4/Exp_1/screenshots/error_5.png -------------------------------------------------------------------------------- /Exp_1/screenshots/error_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSPanda/NachOS/2c54352e936ab1ff7bbd455f355c212e1e4e47a4/Exp_1/screenshots/error_6.png -------------------------------------------------------------------------------- /Exp_1/screenshots/main_cc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSPanda/NachOS/2c54352e936ab1ff7bbd455f355c212e1e4e47a4/Exp_1/screenshots/main_cc.png -------------------------------------------------------------------------------- /Exp_1/screenshots/make_common.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSPanda/NachOS/2c54352e936ab1ff7bbd455f355c212e1e4e47a4/Exp_1/screenshots/make_common.png -------------------------------------------------------------------------------- /Exp_1/threadtest.cc: -------------------------------------------------------------------------------- 1 | // threadtest.cc 2 | // Simple test case for the threads assignment. 3 | // 4 | // Create two threads, and have them context switch 5 | // back and forth between themselves by calling Thread::Yield, 6 | // to illustratethe inner workings of the thread system. 7 | // 8 | // Copyright (c) 1992-1993 The Regents of the University of California. 9 | // All rights reserved. See copyright.h for copyright notice and limitation 10 | // of liability and disclaimer of warranty provisions. 11 | 12 | #include "copyright.h" 13 | #include "system.h" 14 | #include "dllist.h" 15 | #include 16 | extern void genItem2List(DLList *dlist, int N); 17 | extern void delItemFromList(DLList *dlist, int N); 18 | extern void driverTest(); 19 | 20 | int testnum = 1; // set by main(), telling which test to enter 21 | int threadNum = 1; // set by main(), telling number of threads 22 | int oprNum = 1; // set by main(), telling number of elements 23 | 24 | bool canYield = false; // controlling Yield() for different threads 25 | DLList *l = new DLList(); // shared double link 26 | 27 | char* 28 | getName(int i) { 29 | switch(i) { 30 | case 0: return "forked thread 0"; 31 | case 1: return "forked thread 1"; 32 | case 2: return "forked thread 2"; 33 | case 3: return "forked thread 3"; 34 | case 4: return "forked thread 4"; 35 | case 5: return "forked thread 5"; 36 | case 6: return "forked thread 6"; 37 | case 7: return "forked thread 7"; 38 | case 8: return "forked thread 8"; 39 | case 9: return "forked thread 9"; 40 | case 10: return "forked thread 10"; 41 | default: 42 | return "forked thread 00"; 43 | } 44 | } 45 | 46 | void 47 | DllistTest1(int which) { 48 | printf("Inserting items in thread %d\n", which); 49 | genItem2List(l, oprNum); 50 | currentThread->Yield(); 51 | printf("Removing items in thread %d\n", which); 52 | delItemFromList(l, oprNum); 53 | } 54 | 55 | void 56 | DllistTest2(int which) { 57 | for(int i = 0; i < oprNum; i++) { 58 | printf("Inserting item No.%d in thread %d\n", i + 1, which); 59 | genItem2List(l, 1); 60 | } 61 | printf("Removing item in thread %d\n", which); 62 | delItemFromList(l, oprNum); 63 | } 64 | 65 | 66 | void 67 | DllistTest3(int which) { 68 | // segment fault, delete same element at the same time 69 | printf("Inserting item in thread %d\n", which); 70 | genItem2List(l, oprNum); 71 | currentThread->Yield(); 72 | for(int i = 0; i < oprNum; i++){ 73 | printf("Removing item No.%d in thread %d\n", i + 1, which); 74 | delItemFromList(l, 1); 75 | } 76 | } 77 | 78 | void 79 | DllistTest4(int which) { 80 | // segment fault one is in the add the other is in delete 81 | if(which % 2 == 1) { 82 | canYield = false; 83 | printf("Inserting item in thread %d\n", which); 84 | genItem2List(l, oprNum); 85 | printf("Removing item in thread %d\n", which); 86 | for(int i = 0; i < oprNum - 1; i++) { 87 | delItemFromList(l, 1); 88 | } 89 | canYield = true; 90 | delItemFromList(l, 1); 91 | } else { 92 | printf("Inserting item in thread %d\n", which); 93 | genItem2List(l, oprNum); 94 | printf("Removing item in thread %d\n", which); 95 | delItemFromList(l, oprNum); 96 | } 97 | } 98 | 99 | void 100 | DllistTest5(int which) { 101 | canYield = false; 102 | printf("Inserting item NO.1 in thread %d\n", which); 103 | genItem2List(l, 1); 104 | currentThread->Yield(); 105 | canYield = true; 106 | for(int i = 0; i < oprNum - 1; i++){ 107 | printf("Inserting item No.%d in thread %d\n", i + 2, which); 108 | genItem2List(l, 1); 109 | } 110 | printf("Removing item in thread %d\n", which); 111 | delItemFromList(l, oprNum); 112 | } 113 | 114 | void 115 | DllistTest6(int which) { 116 | if(which % 2 == 1) { 117 | canYield = false; 118 | printf("Inserting item NO.1 in thread %d\n", which); 119 | genItem2List(l, 1); 120 | currentThread->Yield(); 121 | for(int i = 1; i < oprNum; i++){ 122 | printf("Inserting item NO.%d in thread %d\n", i + 1, which); 123 | genItem2List(l, 1); 124 | // currentThread->Yield(); 125 | } 126 | printf("Removing item in thread %d\n", which); 127 | delItemFromList(l, oprNum); 128 | } else { 129 | printf("Inserting item NO.1 in thread %d\n", which); 130 | genItem2List(l, 1); 131 | canYield = true; 132 | currentThread->Yield(); 133 | for(int i = 1; i < oprNum; i++) { 134 | printf("Inserting item NO.%d in thread %d\n", i + 1, which); 135 | genItem2List(l, 1); 136 | // currentThread->Yield(); 137 | } 138 | printf("Removing item in thread %d\n", which); 139 | delItemFromList(l, oprNum); 140 | } 141 | } 142 | 143 | void 144 | toDllistTest(VoidFunctionPtr func) { 145 | DEBUG('t', "Entering toDllistTest\n"); 146 | Thread *t; 147 | for(int i = 0; i < threadNum; i++) { 148 | t = new Thread(getName(i + 1)); 149 | t->Fork(func, i + 1); 150 | } 151 | } 152 | 153 | //---------------------------------------------------------------------- 154 | // ThreadTest 155 | // Invoke a test routine. 156 | //---------------------------------------------------------------------- 157 | 158 | void 159 | ThreadTest() { 160 | printf("Entering test %d\n", testnum); 161 | switch(testnum) { 162 | case 1: // switch out of function 163 | toDllistTest(DllistTest1); break; 164 | case 2: // insert to the empty list causing one item coving the other one 165 | toDllistTest(DllistTest2); break; 166 | case 3: // delete item at one time 167 | toDllistTest(DllistTest3); break; 168 | case 4: // segmentation fault, one is in the add the other is in delete 169 | toDllistTest(DllistTest4); break; 170 | case 5: // chain scission, sometimes it will happen 171 | toDllistTest(DllistTest5); break; 172 | case 6: // disorder output from insert items 173 | toDllistTest(DllistTest6); break; 174 | case 7: 175 | driverTest(); break; 176 | default: 177 | printf("No test specified.\n"); break; 178 | } 179 | } 180 | 181 | -------------------------------------------------------------------------------- /Exp_2/BoundedBuffer.cc: -------------------------------------------------------------------------------- 1 | #include "BoundedBuffer.h" 2 | #include "synch.h" 3 | #include "system.h" 4 | extern int testnum; //for test buffer 5 | BoundedBuffer::BoundedBuffer(int maxsize) 6 | { 7 | monitor_lock = new Lock("into buffer lock"); 8 | // condition for buffer empty 9 | not_full = new Condition("buffer full cond"); 10 | not_empty = new Condition("buffer empty cond"); 11 | buffer = new int[maxsize]; 12 | maxSize = maxsize; 13 | readFrom = 0; // read position 14 | writeTo = 0; // write position 15 | hasCount = 0; 16 | } 17 | 18 | BoundedBuffer::~BoundedBuffer() 19 | { 20 | delete monitor_lock; 21 | delete not_full; 22 | delete not_empty; 23 | delete[] buffer; 24 | } 25 | 26 | void 27 | BoundedBuffer::Read(void *data, int size) { 28 | 29 | int *readData = (int *)data; 30 | while (size!=0) { 31 | // learn from others on net but he's paper still have many mistakes 32 | // condition for buffer empty 33 | // monitor Field 34 | monitor_lock->Acquire(); 35 | while (hasCount == 0) { 36 | not_empty->Wait(monitor_lock); 37 | } 38 | *readData++ = buffer[readFrom]; 39 | // printf("get %d from buffer\n", buffer[readFrom]); 40 | readFrom = (readFrom + 1) % maxSize; 41 | hasCount--; 42 | not_full->Broadcast(monitor_lock); 43 | size--; 44 | monitor_lock->Release(); 45 | } 46 | } 47 | 48 | void 49 | BoundedBuffer::Write(void *data, int size) { 50 | int* writeData = (int *)data; 51 | while (size != 0) { 52 | monitor_lock->Acquire(); 53 | //condition for buffer full 54 | while (hasCount == maxSize) { 55 | not_full->Wait(monitor_lock); 56 | } 57 | buffer[writeTo] = *writeData++; 58 | // printf("put %d to buffer\n",buffer[writeTo]); 59 | writeTo = (writeTo + 1) % maxSize; 60 | hasCount++; 61 | // to broadcast other readers 62 | not_empty->Broadcast(monitor_lock); 63 | size--; 64 | monitor_lock->Release(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Exp_2/BoundedBuffer.h: -------------------------------------------------------------------------------- 1 | #include "synch.h" 2 | 3 | #ifndef BOUNDEDBUFFER_H 4 | #define BOUNEDBUFFER_H 5 | class BoundedBuffer { 6 | public: 7 | // create a bounded buffer with a limit of 'maxsize' bytes 8 | BoundedBuffer(int maxsize); 9 | ~BoundedBuffer(); 10 | 11 | // read 'size' bytes from the bounded buffer, storing into 'data'. 12 | // ('size' may be greater than 'maxsize') 13 | void Read(void *data, int size); 14 | 15 | // write 'size' bytes from 'data' into the bounded buffer. 16 | // ('size' may be greater than 'maxsize') 17 | void Write(void *data, int size); 18 | private: 19 | Lock * monitor_lock; 20 | Condition *not_full; 21 | Condition *not_empty; 22 | int readFrom; 23 | int writeTo; 24 | int hasCount; 25 | int maxSize; 26 | int* buffer; 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /Exp_2/Makefile.common: -------------------------------------------------------------------------------- 1 | # This is part of a GNU Makefile, included by the Makefiles in 2 | # each of the subdirectories. 3 | # 4 | # This file includes all of the baseline code provided by Nachos. 5 | # Whenever you add a .h or .cc file, put it in the appropriate 6 | # _H,_C, or _O list. 7 | # 8 | # The dependency graph between assignments is: 9 | # 1. THREADS before everything else 10 | # 2. USERPROG must come before VM 11 | # 3. USERPROG can come before or after FILESYS, but if USERPROG comes 12 | # before (as in this distribution), then it must define FILESYS_STUB 13 | # 14 | # Other than that, you have complete flexibility. 15 | # 16 | # Also whenever you change the include structure of your program, you should 17 | # do a gmake depend in the subdirectory -- this will modify the Makefile 18 | # to keep track of the new dependency. 19 | 20 | # You might want to play with the CFLAGS, but if you use -O it may 21 | # break the thread system. You might want to use -fno-inline if 22 | # you need to call some inline functions from the debugger. 23 | 24 | # Copyright (c) 1992 The Regents of the University of California. 25 | # All rights reserved. See copyright.h for copyright notice and limitation 26 | # of liability and disclaimer of warranty provisions. 27 | 28 | # CFLAGS = -g -Wall -Wshadow -fwritable-strings $(INCPATH) $(DEFINES) $(HOST) -DCHANGED 29 | CFLAGS = -g -Wall -Wshadow -traditional $(INCPATH) $(DEFINES) $(HOST) -DCHANGED 30 | 31 | # These definitions may change as the software is updated. 32 | # Some of them are also system dependent 33 | CPP= gcc -E -m32 34 | CC = g++ -m32 35 | LD = g++ -m32 36 | AS = as -32 37 | 38 | PROGRAM = nachos 39 | 40 | THREAD_H =../threads/copyright.h\ 41 | ../threads/list.h\ 42 | ../threads/scheduler.h\ 43 | ../threads/synch.h \ 44 | ../threads/synchlist.h\ 45 | ../threads/system.h\ 46 | ../threads/thread.h\ 47 | ../threads/utility.h\ 48 | ../machine/interrupt.h\ 49 | ../machine/sysdep.h\ 50 | ../machine/stats.h\ 51 | ../machine/timer.h\ 52 | ../threads/dllist.h\ 53 | ../threads/Table.h\ 54 | ../threads/BoundedBuffer.h 55 | 56 | THREAD_C =../threads/main.cc\ 57 | ../threads/list.cc\ 58 | ../threads/scheduler.cc\ 59 | ../threads/synch.cc \ 60 | ../threads/synchlist.cc\ 61 | ../threads/system.cc\ 62 | ../threads/thread.cc\ 63 | ../threads/utility.cc\ 64 | ../threads/threadtest.cc\ 65 | ../machine/interrupt.cc\ 66 | ../machine/sysdep.cc\ 67 | ../machine/stats.cc\ 68 | ../machine/timer.cc\ 69 | ../threads/dllist.cc\ 70 | ../threads/dllist-driver.cc\ 71 | ../threads/Table.cc\ 72 | ../threads/BoundedBuffer.cc 73 | 74 | THREAD_S = ../threads/switch.s 75 | 76 | THREAD_O =main.o list.o scheduler.o synch.o synchlist.o system.o thread.o \ 77 | utility.o threadtest.o interrupt.o stats.o sysdep.o timer.o dllist.o dllist-driver.o\ 78 | Table.o BoundedBuffer.o 79 | 80 | USERPROG_H = ../userprog/addrspace.h\ 81 | ../userprog/bitmap.h\ 82 | ../filesys/filesys.h\ 83 | ../filesys/openfile.h\ 84 | ../machine/console.h\ 85 | ../machine/machine.h\ 86 | ../machine/mipssim.h\ 87 | ../machine/translate.h 88 | 89 | USERPROG_C = ../userprog/addrspace.cc\ 90 | ../userprog/bitmap.cc\ 91 | ../userprog/exception.cc\ 92 | ../userprog/progtest.cc\ 93 | ../machine/console.cc\ 94 | ../machine/machine.cc\ 95 | ../machine/mipssim.cc\ 96 | ../machine/translate.cc 97 | 98 | USERPROG_O = addrspace.o bitmap.o exception.o progtest.o console.o machine.o \ 99 | mipssim.o translate.o 100 | 101 | VM_H = 102 | VM_C = 103 | VM_O = 104 | 105 | FILESYS_H =../filesys/directory.h \ 106 | ../filesys/filehdr.h\ 107 | ../filesys/filesys.h \ 108 | ../filesys/openfile.h\ 109 | ../filesys/synchdisk.h\ 110 | ../machine/disk.h 111 | FILESYS_C =../filesys/directory.cc\ 112 | ../filesys/filehdr.cc\ 113 | ../filesys/filesys.cc\ 114 | ../filesys/fstest.cc\ 115 | ../filesys/openfile.cc\ 116 | ../filesys/synchdisk.cc\ 117 | ../machine/disk.cc 118 | FILESYS_O =directory.o filehdr.o filesys.o fstest.o openfile.o synchdisk.o\ 119 | disk.o 120 | 121 | NETWORK_H = ../network/post.h ../machine/network.h 122 | NETWORK_C = ../network/nettest.cc ../network/post.cc ../machine/network.cc 123 | NETWORK_O = nettest.o post.o network.o 124 | 125 | S_OFILES = switch.o 126 | 127 | OFILES = $(C_OFILES) $(S_OFILES) 128 | 129 | $(PROGRAM): $(OFILES) 130 | $(LD) $(OFILES) $(LDFLAGS) -o $(PROGRAM) 131 | 132 | $(C_OFILES): %.o: 133 | $(CC) $(CFLAGS) -c $< 134 | 135 | switch.o: ../threads/switch.s 136 | $(CPP) -P $(INCPATH) $(HOST) ../threads/switch.c > swtch.s 137 | $(AS) -o switch.o swtch.s 138 | 139 | depend: $(CFILES) $(HFILES) 140 | $(CC) $(INCPATH) $(DEFINES) $(HOST) -DCHANGED -M $(CFILES) > makedep 141 | echo '/^# DO NOT DELETE THIS LINE/+2,$$d' >eddep 142 | echo '$$r makedep' >>eddep 143 | echo 'w' >>eddep 144 | ed - Makefile < eddep 145 | rm eddep makedep 146 | echo '# DEPENDENCIES MUST END AT END OF FILE' >> Makefile 147 | echo '# IF YOU PUT STUFF HERE IT WILL GO AWAY' >> Makefile 148 | echo '# see make depend above' >> Makefile 149 | -------------------------------------------------------------------------------- /Exp_2/Question_02.md: -------------------------------------------------------------------------------- 1 | 1. Lock的设计问题 2 | ```c 3 | void Lock::Acquire() 4 | { 5 | IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts 6 | while( value == false){ 7 | // go to sleep does not guarantee that a thread awakened in V will get a chance 8 | // to run before another thread calls P 9 | queue->Append((void \*)currentThread); // so go to sleep 10 | currentThread->Sleep(); 11 | } 12 | value = false; 13 | currentHeldLockThread = currentThread; 14 | (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 15 | } 16 | 17 | 18 | void Lock::Release() 19 | { 20 | Thread \*thread; 21 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 22 | // if queue is not empty release all blocked one 23 | if(!queue->IsEmpty()){//wake up one thread 24 | thread = (Thread \*)queue->Remove(); 25 | if (thread != NULL) // make thread ready 26 | scheduler->ReadyToRun(thread); 27 | value = false; // 有问题,这样会导致死锁 28 | }else{ 29 | value = true; 30 | } 31 | (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 32 | } 33 | ``` 34 | 参考讲义,这样写,可能会导致其他的进程无法执行。 35 | 36 | 应改成 37 | ```c 38 | void Lock::Release() 39 | { 40 | Thread \*thread; 41 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 42 | // if queue is not empty release all blocked one 43 | if(!queue->IsEmpty()){//wake up one thread 44 | thread = (Thread \*)queue->Remove(); 45 | if (thread != NULL) // make thread ready 46 | scheduler->ReadyToRun(thread); 47 | } 48 | value = true; 49 | (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 50 | } 51 | ``` 52 | 53 | 2. 条件变量正确的使用情况,条件变量是锁+队列的实现。每次使用条件变量的时候应先取得锁,并且 54 | 当使用完条件变量的时候,应该释放锁 55 | 56 | ```c 57 | void 58 | BoundedBuffer::Read(void \*data, int size) 59 | { 60 | int \*readData = (int \*)data; 61 | while(size!=0){ 62 | // learn from others on net but he's paper still have many mistakes 63 | // condition for buffer empty 64 | con_empty->Acquire();// 加锁 65 | while(hasCount == 0){ 66 | not_empty->Wait(con_empty); 67 | } 68 | IntoBuffer->Acquire(); 69 | \*readData++ = buffer[readFrom]; 70 | // printf("get %d from buffer\n", buffer[readFrom]); 71 | readFrom = (readFrom + 1) % maxSize; 72 | hasCount--; 73 | IntoBuffer->Release();  74 | 75 | // end of condition 76 | con_empty->Release();//释放锁 77 | 78 | // to broadcast other blocked writers 79 | con_full->Acquire();//must get lock 80 | not_full->Broadcast(con_full); 81 | con_full->Release();//must release lock 82 | 83 | size--; 84 | if(size % 3 == 1 && testnum == 8){ 85 | currentThread->Yield(); 86 | } 87 | } 88 | } 89 | 90 | ``` 91 | 92 | 条件变量释放锁的位置也比较特殊,需要等到hasCount改变之后才能释放锁,否则在这之前释放的话,有可能导致多个线程同时满足要求,虽然只有一个线程进入临界区,但是其他的进程在其之后进入临界区,从而导致错误。 93 | 94 | 95 | 3. 在将dllist类中设置锁的时候,本来想将锁作为dllist类的内部成员,但是后来发现加入后,在driver-list.cc的文件中导入的cstdlib类库和锁(synch.h)的头文件引入的类库有冲突,即thread.h 中相关文件和cstdlib中相关函数有冲突。最后采用了外部变量, 将锁变量作为外部变量导入dllist.cc文件中使用。 96 | 97 | 98 | 99 | 100 | 注意问题: 101 | 102 | 第四,五,六个测试,打印结果中,in表示最终完成插入,add表示开始插入,使用debug参数后发现。 103 | 104 | ./nachos -d t -q 6 -t 2 -n 2 105 | 106 | 从打印中重点关注 insert finished,in ,out ,开头的语句,发现,结果是合理的。 107 | 108 | ./nachos -d t -q 4 -t 2 -n 2 109 | 110 | 同理类似的  111 | -------------------------------------------------------------------------------- /Exp_2/Screenshots/q1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSPanda/NachOS/2c54352e936ab1ff7bbd455f355c212e1e4e47a4/Exp_2/Screenshots/q1.png -------------------------------------------------------------------------------- /Exp_2/Screenshots/q3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSPanda/NachOS/2c54352e936ab1ff7bbd455f355c212e1e4e47a4/Exp_2/Screenshots/q3.png -------------------------------------------------------------------------------- /Exp_2/Screenshots/q7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSPanda/NachOS/2c54352e936ab1ff7bbd455f355c212e1e4e47a4/Exp_2/Screenshots/q7.png -------------------------------------------------------------------------------- /Exp_2/Screenshots/q8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSPanda/NachOS/2c54352e936ab1ff7bbd455f355c212e1e4e47a4/Exp_2/Screenshots/q8.png -------------------------------------------------------------------------------- /Exp_2/Table.cc: -------------------------------------------------------------------------------- 1 | #include "Table.h" 2 | #include 3 | #include 4 | #include "synch.h" 5 | #include "system.h" 6 | 7 | extern int testnum; // introduce testnum to judeg the error type 8 | 9 | Table::Table(int size) 10 | { 11 | num = size; 12 | left = size; 13 | table = new void*[size]; 14 | memset(table, 0, sizeof(void *)*size); 15 | nowindex = 0; 16 | lock = new Lock("table lock"); 17 | } 18 | 19 | Table::~Table() 20 | { 21 | delete[] table; 22 | delete lock; 23 | } 24 | 25 | int 26 | Table::Alloc(void *object) 27 | { 28 | lock->Acquire(); 29 | if (testnum == 7) {// to test mux 30 | currentThread->Yield(); 31 | } 32 | if (!left) { 33 | return -1; 34 | } else { // find right place to insert data 35 | while (table[nowindex]) { 36 | nowindex = (nowindex+1) % num; 37 | } 38 | table[nowindex] = object; 39 | nowindex = (nowindex+1) % num; 40 | left--; 41 | } 42 | lock->Release(); 43 | return nowindex - 1; 44 | } 45 | 46 | void 47 | Table::Release(int index) 48 | { 49 | assert(index < num && index > -1); 50 | lock->Acquire(); 51 | table[index] = NULL; 52 | lock->Release(); 53 | } 54 | 55 | 56 | void * 57 | Table::Get(int index) 58 | { 59 | assert(index < num && index > -1); 60 | lock->Acquire(); 61 | 62 | if (testnum == 7) { // to test mux 63 | currentThread->Yield(); 64 | } 65 | 66 | void* r = table[index]; 67 | lock->Release(); 68 | return r; 69 | } 70 | -------------------------------------------------------------------------------- /Exp_2/Table.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Table implements a simple fixed-size table, a common kernel data 4 | structure. The table consists of "size" entries, each of which 5 | holds a pointer to an object. Each object in the table can be 6 | named by an index in the range [0..size-1], corresponding to its 7 | position in the table. Table::Alloc allocates a free entry, 8 | stores an object pointer in it, and returns its index. The 9 | object pointer can be retrieved by passing its index to Table::Get. 10 | An entry is released by passing its index to Table::Release. 11 | 12 | Table knows nothing about the objects it indexes. In particular, 13 | it is the responsibility of the caller to delete each object when 14 | it is no longer needed (some time after the table entry is released). 15 | 16 | It is also the caller's responsibility to correctly handle the types 17 | of the objects stored in the Table. The object pointer in each Table 18 | entry is untyped (void*). It is necessary to cast an object pointer 19 | to a (void *) before storing it in the table, and to cast it back to 20 | its correct type (e.g., (Process *)) after retrieving it with Get. 21 | A more sophisticated solution would use parameterized types.aux 22 | 23 | In later assignments, the Table class may be used to implement internal 24 | operating system tables of processes, threads, memory page frames, open 25 | files, etc. 26 | 27 | */ 28 | #include "synch.h" 29 | #ifndef TABLE_H 30 | #define TABLE_H 31 | class Table { 32 | public: 33 | // create a table to hold at most 'size' entries. 34 | Table(int size); 35 | ~Table(); 36 | // allocate a table slot for 'object'. 37 | // return the table index for the slot or -1 on error. 38 | int Alloc(void *object); 39 | // return the object from table index 'index' or NULL on error. 40 | // (assert index is in range). Leave the table entry allocated 41 | // and the pointer in place. 42 | void *Get(int index); 43 | // free a table slot 44 | void Release(int index); 45 | 46 | private: 47 | void** table; 48 | int nowindex; 49 | int left; 50 | int num; 51 | Lock* lock; 52 | }; 53 | 54 | #endif // TABLE_H 55 | -------------------------------------------------------------------------------- /Exp_2/dllist-driver.cc: -------------------------------------------------------------------------------- 1 | #include "dllist.h" 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | void genItem2List(DLList *dlist, int N) 8 | { 9 | int* item , num; 10 | static int random=0; 11 | random ++; 12 | srand(unsigned(time(0))+random); 13 | 14 | for(int i=0;iSortedInsert((void *)item, num); 19 | cout << "in : key->" << num << " item->" << *item << endl; 20 | } 21 | } 22 | 23 | 24 | void delItem2List(DLList *dlist,int N) 25 | { 26 | void *item; 27 | int *out = new int; 28 | for(int i=0;iRemove(out); 31 | if(item!=NULL) 32 | { 33 | cout << "out: key->"<< *out << " item->" << *(int *)item << endl ; 34 | } 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Exp_2/dllist.cc: -------------------------------------------------------------------------------- 1 | #include "dllist.h" 2 | #include 3 | #include "system.h" 4 | #include 5 | #include "synch.h" 6 | 7 | using namespace std; 8 | extern int testnum; // introduce testnum to judeg the error type 9 | extern int canYield; 10 | extern Lock* dlistLock; 11 | // extern Condition *dlistEmpty; // to judge list is empty 12 | DLLElement::DLLElement(void *itemPtr, int sortKey) 13 | { 14 | key = sortKey; 15 | item = itemPtr; 16 | next = prev = NULL; 17 | } 18 | 19 | DLList::DLList() 20 | { 21 | first = last = NULL; 22 | } 23 | 24 | DLList::~DLList() 25 | { 26 | DLLElement *now = first; 27 | DLLElement *next; 28 | while(now != NULL) { 29 | next = now->next; 30 | delete now; 31 | now = next; 32 | } 33 | } 34 | 35 | void DLList::Append(void *item) 36 | { 37 | dlistLock->Acquire(); 38 | if(IsEmpty()) { 39 | DLLElement *now = new DLLElement(item, last->key + 1); 40 | last->next = now; 41 | now->prev = last; 42 | last = now; 43 | } else { 44 | DLLElement *now = new DLLElement(item, 0); 45 | first = last = now; 46 | } 47 | dlistLock->Release(); 48 | } 49 | 50 | void DLList::Prepend(void *item) 51 | { 52 | dlistLock->Acquire(); 53 | if(IsEmpty()) { 54 | DLLElement *now = new DLLElement(item, first->key - 1); 55 | first->prev = now; 56 | now->next = first; 57 | first = now; 58 | } else { 59 | DLLElement *now = new DLLElement(item, 0); 60 | first = last = now; 61 | } 62 | dlistLock->Release(); 63 | } 64 | 65 | void * DLList::Remove(int *keyPtr) 66 | { 67 | dlistLock->Acquire(); 68 | if(!IsEmpty()) { 69 | dlistLock->Release(); 70 | return NULL; 71 | } else { 72 | DLLElement *d = first; 73 | 74 | if(testnum == 3){ 75 | currentThread->Yield(); 76 | } 77 | first = first->next; 78 | if(!first) { 79 | if(testnum == 4 && canYield){ 80 | currentThread->Yield(); 81 | } 82 | first = last = NULL; 83 | } else { 84 | if(testnum == 3){ 85 | DEBUG('t' , "type 3 , 2nd switch\n"); 86 | currentThread->Yield(); 87 | assert(first != NULL); 88 | } 89 | 90 | first->prev = NULL; 91 | } 92 | 93 | d->next = NULL; 94 | if(testnum == 3){ 95 | DEBUG('t' , "type 3 , 3rd switch\n"); 96 | currentThread->Yield(); 97 | } 98 | if(keyPtr != NULL) { 99 | *keyPtr = d->key; 100 | } 101 | dlistLock->Release(); 102 | return d->item; 103 | } 104 | } 105 | 106 | bool DLList::IsEmpty() 107 | { 108 | return (!first && !last)?false:true; 109 | } 110 | 111 | void DLList::SortedInsert(void *item, int sortKey) 112 | { 113 | static int control = 0;// also control the switch 114 | dlistLock->Acquire(); 115 | DLLElement *newone = new DLLElement(item, sortKey); 116 | if(!IsEmpty()) { 117 | if( testnum == 2){ 118 | currentThread->Yield(); 119 | first = newone; 120 | currentThread->Yield(); 121 | last = newone; 122 | currentThread->Yield(); 123 | }else{ 124 | first = newone; 125 | last = newone; 126 | } 127 | if(testnum == 6 || testnum == 4 || testnum == 5){ 128 | DEBUG('t',"insert finished %d \n",newone->key); 129 | } 130 | } else { 131 | 132 | if((testnum == 4 && canYield)){ 133 | currentThread->Yield(); 134 | } 135 | 136 | DLLElement *now= first; 137 | 138 | if( testnum == 6 && canYield){ 139 | currentThread->Yield(); 140 | DEBUG('t',"location 1 to insert key %d \n",newone->key); 141 | } 142 | 143 | while(now != NULL) { 144 | if(now->key <= sortKey) { 145 | if(now->next == NULL || now->next->key >= sortKey) { 146 | break; 147 | } 148 | now = now->next; 149 | } else { 150 | now = NULL; 151 | break; 152 | } 153 | } 154 | 155 | if( testnum == 6 && canYield){ 156 | currentThread->Yield(); 157 | DEBUG('t',"location 2 to insert key %d \n",newone->key); 158 | } 159 | 160 | if(now == NULL) { 161 | if(testnum == 4){ 162 | DEBUG('t',"now is null\n"); 163 | assert(first != NULL); 164 | } 165 | if( testnum == 6 && canYield){ 166 | currentThread->Yield(); 167 | DEBUG('t',"location 3 to insert key\n",newone->key); 168 | } 169 | 170 | first->prev = newone; 171 | newone->next = first; 172 | first = newone; 173 | if(testnum == 6 || testnum == 4 || testnum == 5){ 174 | DEBUG('t',"insert finished %d \n",newone->key); 175 | } 176 | } else { 177 | 178 | if( testnum == 6 && canYield){ 179 | currentThread->Yield(); 180 | DEBUG('t',"location 4 to insert key %d \n",newone->key); 181 | } 182 | 183 | if(now == last) { 184 | last->next = newone; 185 | newone->prev = last; 186 | last = newone; 187 | } else { 188 | if(testnum == 5 && canYield && control%2 == 0){ 189 | currentThread->Yield(); 190 | control++; 191 | } 192 | 193 | newone->next = now->next; 194 | newone->next->prev = newone; 195 | if(testnum == 5 && canYield && control%2 == 1){ 196 | currentThread->Yield(); 197 | control++; 198 | } 199 | now->next = newone; 200 | newone->prev = now; 201 | } 202 | if(testnum == 6 || testnum == 4 || testnum == 5){ 203 | DEBUG('t',"insert finished %d \n",newone->key); 204 | } 205 | } 206 | if(testnum == 6 && canYield){ 207 | currentThread->Yield(); 208 | } 209 | } 210 | 211 | dlistLock->Release(); 212 | } 213 | 214 | void * DLList::SortedRemove(int sortKey) 215 | { 216 | dlistLock->Acquire(); 217 | 218 | if(IsEmpty()) { 219 | DLLElement *now = first; 220 | while(now != NULL && now->key != sortKey) { 221 | now = now->next; 222 | } 223 | if(now != NULL) { 224 | if(now == first) { 225 | first = now->next; 226 | if(first == NULL) { 227 | last = NULL; 228 | } else { 229 | first->prev = NULL; 230 | } 231 | now->next = NULL; 232 | } else if(now == last) { 233 | last = now->prev; 234 | last->next = NULL; 235 | now->prev = NULL; 236 | } else { 237 | now->prev->next = now->next; 238 | now->next->prev = now->prev; 239 | } 240 | dlistLock->Release(); 241 | return now->item; 242 | } 243 | } 244 | dlistLock->Release(); 245 | return NULL; 246 | } 247 | -------------------------------------------------------------------------------- /Exp_2/dllist.h: -------------------------------------------------------------------------------- 1 | #ifndef DLLIST_H_INCLUDED 2 | #define DLLIST_H_INCLUDED 3 | class DLLElement { 4 | public: 5 | DLLElement( void *itemPtr, int sortKey ); // initialize a list element 6 | DLLElement *next; // next element on list 7 | // NULL if this is the last 8 | DLLElement *prev; // previous element on list 9 | // NULL if this is the first 10 | int key; // priority, for a sorted list 11 | void *item; // pointer to item on the list 12 | }; 13 | 14 | class DLList { 15 | public: 16 | DLList(); // initialize the list 17 | ~DLList(); // de-allocate the list 18 | void Prepend(void *item); // add to head of list (set key = min_key-1) 19 | void Append(void *item); // add to tail of list (set key = max_key+1) 20 | void *Remove(int *keyPtr); // remove from head of list 21 | // set *keyPtr to key of the removed item 22 | // return item (or NULL if list is empty) 23 | bool IsEmpty(); // return true if list has elements 24 | // routines to put/get items on/off list in order (sorted by key) 25 | void SortedInsert(void *item, int sortKey); 26 | void *SortedRemove(int sortKey); // remove first item with key==sortKey 27 | // return NULL if no such item exists 28 | private: 29 | DLLElement *first; // head of the list, NULL if empty 30 | DLLElement *last; // last element of the list, NULL if empty 31 | }; 32 | 33 | #endif // DLLIST_H_INCLUDED 34 | -------------------------------------------------------------------------------- /Exp_2/lab2-headers/BoundedBuffer.h: -------------------------------------------------------------------------------- 1 | class BoundedBuffer { 2 | public: 3 | // create a bounded buffer with a limit of 'maxsize' bytes 4 | BoundedBuffer(int maxsize); 5 | 6 | // read 'size' bytes from the bounded buffer, storing into 'data'. 7 | // ('size' may be greater than 'maxsize') 8 | void Read(void *data, int size); 9 | 10 | // write 'size' bytes from 'data' into the bounded buffer. 11 | // ('size' may be greater than 'maxsize') 12 | void Write(void *data, int size); 13 | private: 14 | // ??? 15 | }; 16 | 17 | -------------------------------------------------------------------------------- /Exp_2/lab2-headers/Table.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Table implements a simple fixed-size table, a common kernel data 4 | structure. The table consists of "size" entries, each of which 5 | holds a pointer to an object. Each object in the table can be 6 | named by an index in the range [0..size-1], corresponding to its 7 | position in the table. Table::Alloc allocates a free entry, 8 | stores an object pointer in it, and returns its index. The 9 | object pointer can be retrieved by passing its index to Table::Get. 10 | An entry is released by passing its index to Table::Release. 11 | 12 | Table knows nothing about the objects it indexes. In particular, 13 | it is the responsibility of the caller to delete each object when 14 | it is no longer needed (some time after the table entry is released). 15 | 16 | It is also the caller's responsibility to correctly handle the types 17 | of the objects stored in the Table. The object pointer in each Table 18 | entry is untyped (void*). It is necessary to cast an object pointer 19 | to a (void *) before storing it in the table, and to cast it back to 20 | its correct type (e.g., (Process *)) after retrieving it with Get. 21 | A more sophisticated solution would use parameterized types.aux 22 | 23 | In later assignments, the Table class may be used to implement internal 24 | operating system tables of processes, threads, memory page frames, open 25 | files, etc. 26 | 27 | */ 28 | 29 | 30 | class Table { 31 | public: 32 | // create a table to hold at most 'size' entries. 33 | Table(size); 34 | 35 | // allocate a table slot for 'object'. 36 | // return the table index for the slot or -1 on error. 37 | int Alloc(void *object); 38 | 39 | // return the object from table index 'index' or NULL on error. 40 | // (assert index is in range). Leave the table entry allocated 41 | // and the pointer in place. 42 | void *Get(int index); 43 | 44 | // free a table slot 45 | void Release(int index); 46 | private: 47 | // Your code here. 48 | }; 49 | 50 | -------------------------------------------------------------------------------- /Exp_2/main.cc: -------------------------------------------------------------------------------- 1 | // main.cc 2 | // Bootstrap code to initialize the operating system kernel. 3 | // 4 | // Allows direct calls into internal operating system functions, 5 | // to simplify debugging and testing. In practice, the 6 | // bootstrap code would just initialize data structures, 7 | // and start a user program to print the login prompt. 8 | // 9 | // Most of this file is not needed until later assignments. 10 | // 11 | // Usage: nachos -d -rs 12 | // -s -x -c 13 | // -f -cp 14 | // -p -r -l -D -t 15 | // -n -m 16 | // -o 17 | // -z 18 | // 19 | // -d causes certain debugging messages to be printed (cf. utility.h) 20 | // -rs causes Yield to occur at random (but repeatable) spots 21 | // -z prints the copyright message 22 | // 23 | // USER_PROGRAM 24 | // -s causes user programs to be executed in single-step mode 25 | // -x runs a user program 26 | // -c tests the console 27 | // 28 | // FILESYS 29 | // -f causes the physical disk to be formatted 30 | // -cp copies a file from UNIX to Nachos 31 | // -p prints a Nachos file to stdout 32 | // -r removes a Nachos file from the file system 33 | // -l lists the contents of the Nachos directory 34 | // -D prints the contents of the entire file system 35 | // -t tests the performance of the Nachos file system 36 | // 37 | // NETWORK 38 | // -n sets the network reliability 39 | // -m sets this machine's host id (needed for the network) 40 | // -o runs a simple test of the Nachos network software 41 | // 42 | // NOTE -- flags are ignored until the relevant assignment. 43 | // Some of the flags are interpreted here; some in system.cc. 44 | // 45 | // Copyright (c) 1992-1993 The Regents of the University of California. 46 | // All rights reserved. See copyright.h for copyright notice and limitation 47 | // of liability and disclaimer of warranty provisions. 48 | 49 | #define MAIN 50 | #include "copyright.h" 51 | #undef MAIN 52 | 53 | #include "utility.h" 54 | #include "system.h" 55 | 56 | #ifdef THREADS 57 | extern int testnum; 58 | extern int threadNum; 59 | extern int oprNum; 60 | #endif 61 | 62 | // External functions used by this file 63 | 64 | extern void ThreadTest(void), Copy(char *unixFile, char *nachosFile); 65 | extern void Print(char *file), PerformanceTest(void); 66 | extern void StartProcess(char *file), ConsoleTest(char *in, char *out); 67 | extern void MailTest(int networkID); 68 | 69 | //---------------------------------------------------------------------- 70 | // main 71 | // Bootstrap the operating system kernel. 72 | // 73 | // Check command line arguments 74 | // Initialize data structures 75 | // (optionally) Call test procedure 76 | // 77 | // "argc" is the number of command line arguments (including the name 78 | // of the command) -- ex: "nachos -d +" -> argc = 3 79 | // "argv" is an array of strings, one for each command line argument 80 | // ex: "nachos -d +" -> argv = {"nachos", "-d", "+"} 81 | //---------------------------------------------------------------------- 82 | 83 | int 84 | main(int argc, char **argv) 85 | { 86 | int argCount; // the number of arguments 87 | // for a particular command 88 | 89 | DEBUG('t', "Entering main"); 90 | (void) Initialize(argc, argv); 91 | 92 | #ifdef THREADS 93 | for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount) { 94 | argCount = 1; 95 | switch (argv[0][1]) { 96 | case 'q': 97 | testnum = atoi(argv[1]); 98 | argCount++; 99 | break; 100 | case 't': 101 | threadNum = atoi(argv[1]); 102 | argCount++; 103 | break; 104 | case 'n': 105 | oprNum = atoi(argv[1]); 106 | argCount++; 107 | break; 108 | default: 109 | testnum = 1; 110 | oprNum = 2 ; 111 | threadNum= 2; 112 | break; 113 | } 114 | } 115 | 116 | ThreadTest(); 117 | #endif 118 | 119 | for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount) { 120 | argCount = 1; 121 | if (!strcmp(*argv, "-z")) // print copyright 122 | printf (copyright); 123 | #ifdef USER_PROGRAM 124 | if (!strcmp(*argv, "-x")) { // run a user program 125 | ASSERT(argc > 1); 126 | StartProcess(*(argv + 1)); 127 | argCount = 2; 128 | } else if (!strcmp(*argv, "-c")) { // test the console 129 | if (argc == 1) 130 | ConsoleTest(NULL, NULL); 131 | else { 132 | ASSERT(argc > 2); 133 | ConsoleTest(*(argv + 1), *(argv + 2)); 134 | argCount = 3; 135 | } 136 | interrupt->Halt(); // once we start the console, then 137 | // Nachos will loop forever waiting 138 | // for console input 139 | } 140 | #endif // USER_PROGRAM 141 | #ifdef FILESYS 142 | if (!strcmp(*argv, "-cp")) { // copy from UNIX to Nachos 143 | ASSERT(argc > 2); 144 | Copy(*(argv + 1), *(argv + 2)); 145 | argCount = 3; 146 | } else if (!strcmp(*argv, "-p")) { // print a Nachos file 147 | ASSERT(argc > 1); 148 | Print(*(argv + 1)); 149 | argCount = 2; 150 | } else if (!strcmp(*argv, "-r")) { // remove Nachos file 151 | ASSERT(argc > 1); 152 | fileSystem->Remove(*(argv + 1)); 153 | argCount = 2; 154 | } else if (!strcmp(*argv, "-l")) { // list Nachos directory 155 | fileSystem->List(); 156 | } else if (!strcmp(*argv, "-D")) { // print entire filesystem 157 | fileSystem->Print(); 158 | } else if (!strcmp(*argv, "-t")) { // performance test 159 | PerformanceTest(); 160 | } 161 | #endif // FILESYS 162 | #ifdef NETWORK 163 | if (!strcmp(*argv, "-o")) { 164 | ASSERT(argc > 1); 165 | Delay(2); // delay for 2 seconds 166 | // to give the user time to 167 | // start up another nachos 168 | MailTest(atoi(*(argv + 1))); 169 | argCount = 2; 170 | } 171 | #endif // NETWORK 172 | } 173 | 174 | currentThread->Finish(); // NOTE: if the procedure "main" 175 | // returns, then the program "nachos" 176 | // will exit (as any other normal program 177 | // would). But there may be other 178 | // threads on the ready list. We switch 179 | // to those threads by saying that the 180 | // "main" thread is finished, preventing 181 | // it from returning. 182 | return(0); // Not reached... 183 | } 184 | -------------------------------------------------------------------------------- /Exp_2/nachos02.md: -------------------------------------------------------------------------------- 1 | # 操作系统原理课程实验报告 2 | 3 | > **实验 2 - 线程与同步** 4 | > 5 | > 实验日期:Mar. 27th - Apr. 21st, 2017 6 | 7 | > **小组成员信息** 8 | > 9 | > ==OMIT== 10 | 11 |
12 | 13 | ### 一、实验内容与要求 14 | 15 | 本实验目的在于补充 NachOS 下的锁机制与条件变量的相关实现,并利用此类同步机制将上一个实验中的双向有序链表类修改为线程安全的,同时实现一个线程安全的 `Table` 结构(定义见头文件 *Table.h*)与一个有限大小的缓冲区(定义见头文件 *BoundedBuffer.h*)。 16 | 17 | 本次实验的具体内容如下: 18 | 19 | * 分别使用 `Thread::Sleep` 与 `Semaphore` 机制实现锁机制与条件变量,并利用这些同步机制将上一实验中的双向有序链表类修改为线程安全的,并分别对两种实现进行测试。需要注意的是条件变量需要采用 Mesa 语义而非 Hoare 语义; 20 | * 使用同步机制实现一个线程安全的 `Table` 结构; 21 | * 使用同步机制实现一个线程安全的大小受限缓冲区。 22 | 23 | ### 二、实验设计与代码实现 24 | 25 | ##### A. 采用 `Thread::Sleep` 实现的锁机制与条件变量 26 | 27 | 对于 Lock 类,我们采用如下的头文件定义: 28 | 29 | ```C++ 30 | class Lock { 31 | public: 32 | Lock(char* debugName); 33 | ~Lock(); 34 | char* getName() { return name; } 35 | bool isHeldByCurrentThread(); 36 | void Acquire(); 37 | void Release(); 38 | 39 | private: 40 | char* name; 41 | bool value; 42 | List* queue; 43 | Thread* currentHeldLockThread; 44 | }; 45 | ``` 46 | 47 | > ***Code. 1*** Lock 类的头文件定义 48 | 49 | 对于锁而言,其主要方法只有 `Acquire()` 与 `Release()` 这两个方法,线程通过 `Acquire()` 获取对应的互斥锁,通过 `Release()` 释放自己所持有的锁。`isHeldByCurrentThread()` 与 `currentHeldLockThread` 变量用于检查锁是否被当前线程所持有,便于检查类似于一个线程持有的锁被其他线程释放的错误情况。`queue` 用于保存在该锁上阻塞的线程。 50 | 51 | Lock 类主要方法的具体实现如下: 52 | 53 | ```C++ 54 | void Lock::Acquire() 55 | { 56 | IntStatus oldLevel = interrupt->SetLevel(IntOff); // close interrupts 57 | while (value == false) { 58 | queue->Append((void *)currentThread); // go to sleep while lock is occupied 59 | currentThread->Sleep(); 60 | } 61 | value = false; 62 | currentHeldLockThread = currentThread; 63 | (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 64 | } 65 | 66 | void Lock::Release() 67 | { 68 | Thread *thread; 69 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 70 | // if queue is not empty release all blocked one 71 | if (!queue->IsEmpty()) {//wake up one thread 72 | thread = (Thread *)queue->Remove(); 73 | if (thread != NULL) { // make thread ready 74 | scheduler->ReadyToRun(thread); 75 | } 76 | } 77 | value = true; // release the lock 78 | (void) interrupt->SetLevel(oldLevel); 79 | } 80 | 81 | bool Lock::isHeldByCurrentThread() 82 | { 83 | return (currentThread == currentHeldLockThread); 84 | } 85 | ``` 86 | 87 | > ***Code. 2*** Lock 类的主要实现 88 | 89 | 值得注意的是,在 Lock 类中需要在合理的时间开/关中断(本实验中为关中断/恢复原中断状态)来保证相关操作的原子性,避免锁的相关操作被切换导致异常。 90 | 91 | 对于 Condition 类,我们采用如下的头文件定义: 92 | 93 | ```C++ 94 | class Condition { 95 | public: 96 | Condition(char* debugName); 97 | ~Condition(); 98 | char* getName() { return (name); } 99 | 100 | void Wait(Lock *conditionLock); 101 | void Signal(Lock *conditionLock); 102 | void Broadcast(Lock *conditionLock); 103 | 104 | private: 105 | char* name; 106 | List* queue; 107 | }; 108 | ``` 109 | 110 | > ***Code. 3*** Condition 类的头文件定义 111 | 112 | 本实验中的条件变量遵循 Mesa 语义,即提供 `Wait()` , `Signal()` 与 `Broadcast()` 三个方法,且 `Signal()` 方法的原语采用 *cnotify* 原语而非 *csignal* 原语。调用 `Wait()` 的线程将进入阻塞队列直到被唤醒;调用 `Signal()` 的线程将把一个阻塞队列中的线程放入就绪队列,自身继续执行;调用 `Broadcast()` 的线程将把所有阻塞队列中的线程放入就绪队列,自身继续执行。需要注意的是在这些方法中在必要的位置仍然需要通过控制中断来保证操作的原子性。 113 | 114 | Condition 类主要方法的具体实现如下: 115 | 116 | ```C++ 117 | void Condition::Wait(Lock* conditionLock) 118 | { 119 | assert(conditionLock->isHeldByCurrentThread()); 120 | // still need off the interupt 121 | queue->Append((void *)currentThread); 122 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 123 | conditionLock->Release(); 124 | currentThread->Sleep(); 125 | (void) interrupt->SetLevel(oldLevel); 126 | conditionLock->Acquire(); 127 | } 128 | 129 | void Condition::Signal(Lock* conditionLock) 130 | { 131 | Thread *thread; 132 | assert(conditionLock->isHeldByCurrentThread()); 133 | if(!queue->IsEmpty()){ 134 | thread = (Thread *)queue->Remove(); 135 | if (thread != NULL) // make thread ready 136 | scheduler->ReadyToRun(thread); 137 | } 138 | } 139 | 140 | void Condition::Broadcast(Lock* conditionLock) 141 | { 142 | Thread *thread; 143 | assert(conditionLock->isHeldByCurrentThread()); 144 | // wake up all the thread 145 | while(!queue->IsEmpty()){ 146 | thread = (Thread *)queue->Remove(); 147 | if (thread != NULL) // make thread ready 148 | scheduler->ReadyToRun(thread); 149 | } 150 | } 151 | ``` 152 | 153 | > ***Code. 4*** Condition 类的主要实现 154 | 155 | ##### B. 采用 `Semaphore` 实现的锁机制与条件变量 156 | 157 | 在这一部分,我们将采用 `Semaphore` 信号量机制替换上一部分中的 `Thread::Sleep` 原语来实现锁机制与条件变量。 158 | 159 | 对于 Semaphore 类,我们采用如下的头文件定义: 160 | 161 | ```C++ 162 | class Semaphore { 163 | public: 164 | Semaphore(char* debugName, int initialValue); 165 | ~Semaphore(); 166 | char* getName() { return name;} 167 | 168 | void P(); 169 | void V(); 170 | 171 | private: 172 | char* name; 173 | int value; 174 | List *queue; 175 | }; 176 | ``` 177 | 178 | > ***Code. 5*** Semaphore 类的头文件定义 179 | 180 | 对于信号量而言,其主要方法只有 `P()` 与 `V()` 两者。`P()` 等待信号值大于 0 并减少信号值;`V()` 则将信号值加 1 ,可能唤醒一个阻塞在该信号量上的线程。 181 | 182 | ```C++ 183 | void 184 | Semaphore::P() 185 | { 186 | IntStatus oldLevel = interrupt->SetLevel(IntOff);// disable interrupts 187 | 188 | while (value == 0) { // semaphore not available 189 | queue->Append((void *)currentThread); // so go to sleep 190 | currentThread->Sleep(); 191 | } 192 | value--; 193 | (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 194 | } 195 | 196 | void 197 | Semaphore::V() 198 | { 199 | Thread *thread; 200 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 201 | 202 | thread = (Thread *)queue->Remove(); 203 | if (thread != NULL) {// make thread ready, consuming the V immediately 204 | scheduler->ReadyToRun(thread); 205 | } 206 | value++; 207 | (void) interrupt->SetLevel(oldLevel); 208 | } 209 | ``` 210 | 211 | > ***Code. 6*** Semaphore 类的主要实现 212 | 213 | 对于采用 `Semaphore` 实现的锁与条件变量,其定义与主要实现如下: 214 | 215 | ```C++ 216 | class Lock { 217 | public: 218 | Lock(char* debugName); 219 | ~Lock(); 220 | char* getName() { return name; } 221 | 222 | void Acquire(); 223 | void Release(); 224 | bool isHeldByCurrentThread(); 225 | 226 | private: 227 | char *name; 228 | Semaphore *sem; 229 | Thread * currentHeldLockThread; 230 | }; 231 | 232 | class Condition { 233 | public: 234 | Condition(char* debugName); 235 | ~Condition(); 236 | char* getName() { return (name); } 237 | 238 | void Wait(Lock *conditionLock); 239 | void Signal(Lock *conditionLock); 240 | void Broadcast(Lock *conditionLock); 241 | 242 | private: 243 | char *name; 244 | Semaphore *sem; 245 | Semaphore *wait_atom; // Ensure atomtic 246 | int numWaiting; // Number of threads waiting on this condition 247 | }; 248 | ``` 249 | 250 | > ***Code. 7*** 利用 Semaphore 类实现的 Lock 类与 Condition 类的头文件定义 251 | 252 | ```C++ 253 | void Lock::Acquire() 254 | { 255 | sem->P(); 256 | currentHeldLockThread = currentThread; 257 | } 258 | 259 | void Lock::Release() 260 | { 261 | sem->V(); 262 | } 263 | 264 | void Condition::Wait(Lock* conditionLock) 265 | { 266 | assert(conditionLock->isHeldByCurrentThread()); 267 | wait_atom->P();// after release lock, to ensure operation Atomicity 268 | conditionLock->Release(); 269 | numWaiting++; 270 | sem->P(); 271 | numWaiting--; 272 | wait_atom->V(); 273 | conditionLock->Acquire();// restart to require lock ,to rejudge the condition is satisfilied 274 | } 275 | void Condition::Signal(Lock* conditionLock) 276 | { 277 | assert(conditionLock->isHeldByCurrentThread()); 278 | // avoid accumulating Signal 279 | if(numWaiting > 0){ 280 | sem->V(); 281 | numWaiting--; 282 | } 283 | 284 | } 285 | void Condition::Broadcast(Lock* conditionLock) 286 | { 287 | assert(conditionLock->isHeldByCurrentThread()); 288 | for(int i = 0; i < numWaiting; i++){ 289 | sem->V(); 290 | } 291 | } 292 | 293 | ``` 294 | 295 | > ***Code. 8*** 利用 Semaphore 类实现的 Lock 类与 Condition 类的主要实现 296 | 297 | 值得注意的是,在 Lock 与 Condition 中的信号量的值在初始化时被设为 1。此外 Semaphore 中的 `Signal()` 动作的效果可以累加,而 Condition 中则不能累加。 298 | 299 | 容易看到,引入信号量后我们不再需要干预中断控制与阻塞队列维护等问题。 300 | 301 | ##### C. 利用锁机制与条件变量使得双向有序链表实现线程安全 302 | 303 | 在上一个实验中,我们发现了我们实现的双向有序链表在并发程序下面因为缺乏同步机制与互斥机制出现了各种各样的问题。在本次实验中,我们将使用上面实现的锁与条件变量类将双向有序链表修改为线程安全的版本。 304 | 305 | 为此,我们对 *dllist.cc* 中的方法原型做出如下修改: 306 | 307 | ```C++ 308 | void DLList::Function() 309 | { 310 | dlistLock->Acquire(); 311 | // ... 312 | dlistLock->Release(); 313 | return; 314 | } 315 | ``` 316 | 317 | > ***Code. 9*** 线程安全的 dllist 类中的方法原型 318 | 319 | 即通过对需要访问共享的双向有序链表的方法均通过加上互斥锁的方式保证双向有序链表是线程安全的。 320 | 321 | 对于应用级程序员而言,线程安全性的修改应该是透明的,因此对于 *dllist-driver.cc* 与 *dllist.h* 无需额外的修改。 322 | 323 | ##### D. 线程安全的表结构 324 | 325 | 对于 `Table` 这一结构的实现,我们根据实验提供的头文件,修改有以下定义: 326 | 327 | ```C++ 328 | class Table { 329 | public: 330 | // create a table to hold at most 'size' entries. 331 | Table(int size); 332 | ~Table(); 333 | // allocate a table slot for 'object'. 334 | // return the table index for the slot or -1 on error. 335 | int Alloc(void *object); 336 | // return the object from table index 'index' or NULL on error. 337 | // (assert index is in range). Leave the table entry allocated 338 | // and the pointer in place. 339 | void *Get(int index); 340 | // free a table slot 341 | void Release(int index); 342 | 343 | private: 344 | void** table; 345 | int nowindex; 346 | int left; 347 | int num; 348 | Lock* lock; 349 | }; 350 | ``` 351 | 352 | > ***Code. 10*** 线程安全的 Table 类的头文件定义 353 | 354 | 其中 num 标记 Table 的 Size,nowindex 标记 Table 中的下一个空位,left 标记 Table 的余量,lock 为保证 Table 线程安全的互斥锁。其主要具体实现如下: 355 | 356 | ```C++ 357 | int Table::Alloc(void *object) 358 | { 359 | lock->Acquire(); 360 | if (!left) { 361 | return -1; 362 | } else { // find right place to insert data 363 | while (table[nowindex]) { 364 | nowindex = (nowindex+1) % num; 365 | } 366 | table[nowindex] = object; 367 | nowindex = (nowindex+1) % num; 368 | left--; 369 | } 370 | lock->Release(); 371 | return nowindex - 1; 372 | } 373 | 374 | void Table::Release(int index) 375 | { 376 | assert(index < num && index > -1); 377 | lock->Acquire(); 378 | table[index] = NULL; 379 | lock->Release(); 380 | } 381 | 382 | 383 | void* Table::Get(int index) 384 | { 385 | assert(index < num && index > -1); 386 | lock->Acquire(); 387 | void* r = table[index]; 388 | lock->Release(); 389 | return r; 390 | } 391 | ``` 392 | 393 | > ***Code. 11*** 线程安全的 Table 类的主要实现 394 | 395 | ##### E. 线程安全的受限大小缓冲区 396 | 397 | 对于 `BoundedBuffer` 这一结构的实现,我们将实验提供的头文件修改为以下定义: 398 | 399 | ```C++ 400 | class BoundedBuffer { 401 | public: 402 | // create a bounded buffer with a limit of 'maxsize' bytes 403 | BoundedBuffer(int maxsize); 404 | ~BoundedBuffer(); 405 | // read 'size' bytes from the bounded buffer to 'data'. 406 | void Read(void *data, int size); 407 | // write 'size' bytes from 'data' into the bounded buffer. 408 | void Write(void *data, int size); 409 | private: 410 | Lock * monitor_lock; 411 | Condition *not_full; 412 | Condition *not_empty; 413 | int readFrom; 414 | int writeTo; 415 | int hasCount; 416 | int maxSize; 417 | int* buffer; 418 | }; 419 | ``` 420 | 421 | 其中 readFrom 与 writeTo 分别标记生产者与消费者指针位置,maxSize 标记 Buffer 的大小,hasCount 标记 Buffer 内的现有数据量,monitor_lock 为管程的互斥锁,两个 Condition 为相应的条件变量。其主要具体实现如下: 422 | 423 | > ***Code. 12*** BoundedBuffer 类的头文件定义 424 | 425 | ```C++ 426 | void 427 | BoundedBuffer::Read(void *data, int size) { 428 | 429 | int *readData = (int *)data; 430 | while (size!=0) { 431 | // condition for buffer empty 432 | // monitor Field 433 | monitor_lock->Acquire(); 434 | while (hasCount == 0) { 435 | not_empty->Wait(monitor_lock); 436 | } 437 | *readData++ = buffer[readFrom]; 438 | readFrom = (readFrom + 1) % maxSize; 439 | hasCount--; 440 | not_full->Broadcast(monitor_lock); 441 | size--; 442 | monitor_lock->Release(); 443 | } 444 | } 445 | 446 | void 447 | BoundedBuffer::Write(void *data, int size) { 448 | 449 | int* writeData = (int *)data; 450 | while (size != 0) { 451 | monitor_lock->Acquire(); 452 | //condition for buffer full 453 | while (hasCount == maxSize) { 454 | not_full->Wait(monitor_lock); 455 | } 456 | buffer[writeTo] = *writeData++; 457 | writeTo = (writeTo + 1) % maxSize; 458 | hasCount++; 459 | // to broadcast other readers 460 | not_empty->Broadcast(monitor_lock); 461 | size--; 462 | monitor_lock->Release(); 463 | } 464 | 465 | } 466 | ``` 467 | 468 | > ***Code. 13*** BoundedBuffer 类的主要实现 469 | 470 | 容易发现 BoundedBuffer 类的相关定义非常接近于生产者/消费者问题,需要特别注意的是 Read 与 Write 的相关执行顺序与对互斥锁的有关控制。 471 | 472 | ##### F. *threadtest.cc* 与 *main.cc* 相关修改 473 | 474 | ###### *main.cc* 中作出的修改 475 | 476 | 类似于上一个实验,在 *main.cc* 里我们需要处理命令行调用 `./nachos` 时传入的参数(默认参数可见 *main.cc* 中的注释部分),我们主要处理 THREADS 子系统和 *threadtest.cc* 所需要的参数,以方便进行并发测试。为此,我们定义了以下参数: 477 | 478 | | 参数标记 | 对应变量名 | 参数含义 | 479 | | ---- | ------------- | ---------------- | 480 | | -q | int testnum | 测例编号,用于进入不同的测试分支 | 481 | | -t | int threadNum | 需要创建的并行线程数量 | 482 | | -n | int oprNum | 链表操作的元素个数 | 483 | 484 | + 运行时附加参数 `-d t` 可以打印出相关的调试信息。 485 | 486 | ###### *threadtest.cc* 中的修改 487 | 488 | 在本次实验中,为了验证锁机制与条件变量的实现的正确性,我们在保留了上一次实验中针对链表的六项测例的基础上增加了针对表结构与大小受限的缓冲区的测试。对应的测试编号见下表: 489 | 490 | | 测例类型 | 描述 | 测例编号 | 491 | | ---- | ------------------------------- | ------- | 492 | | 共享内存 | 并行执行时一个线程可能删除/修改其余线程插入的元素 | ` -q 1` | 493 | | 覆盖 | 并行的线程在链表同一个地方插入元素,导致其中一个被覆盖 | ` -q 2` | 494 | | 非法删除 | 并行的线程准备删除链表中同一个元素,导致段错误 | `-q 3` | 495 | | 段错误 | 并行的线程一边删除一边插入,导致插入线程出现非法访问 | `-q 4` | 496 | | 断链 | 并行的线程在同一个地方插入元素,导致元素指针发生不一致 | `-q 5` | 497 | | 乱序 | 并行的线程在同一个地方插入元素,导致元素位置颠倒,键值大的在前 | `-q 6` | 498 | | 表操作 | Table 结构相关并发测试 | `-q 7` | 499 | | 缓冲区 | BoundedBuffer 结构相关并发测试 | `-q 8` | 500 | 501 | + 以上测例的代码实现参见 *threadtest.cc* 与其他相关数据结构对应的源文件。 502 | 503 | ### 三、实验结果 504 | 505 | 在双向有序链表的六项测试中,对于线程安全的实现,所有测试都正确地得以完成。在报告中我们仅选取其中两项进行讨论,其余情况可自行在 NachOS 下运行测例、分析结果。 506 | 507 | ###### 共享内存情况 508 | 509 | 测例代码实现如下: 510 | 511 | ```C++ 512 | void 513 | DllistTest1(int which) { 514 | outListLock->Acquire(); 515 | printf("add item in thread %d\n", which); 516 | genItem2List(l, oprNum); 517 | currentThread->Yield(); 518 | printf("delete item in thread %d\n", which); 519 | delItem2List(l, oprNum); 520 | outListLock->Release(); 521 | } 522 | ``` 523 | 524 | ![E1](Screenshots/q1.png) 525 | 526 | > ***Fig. 1*** 共享内存错误运行结果 527 | 528 | 可以看到各线程正常地插入并删除了自己的元素,没有出现删除别的线程插入的元素的情况。 529 | 530 | ###### 非法删除情况 531 | 532 | ![E3](Screenshots/q3.png) 533 | 534 | > ***Fig. 2*** 共享内存错误运行结果 535 | 536 | 可以看到同步机制的存在保证了各线程正常地对链表进行操作,避免了非法删除的情况。 537 | 538 | ###### Table 结构测试 539 | 540 | ![E7](Screenshots/q7.png) 541 | 542 | > ***Fig. 3*** Table 结构测试运行结果 543 | 544 | 可以看到同步机制的存在保证了 Table 结构是线程安全的。 545 | 546 | ###### 受限大小缓冲区测试 547 | 548 | ![E8](Screenshots/q8.png) 549 | 550 | > ***Fig. 4*** 受限大小缓冲区测试运行结果 551 | 552 | 模拟简单的生产者/消费者问题对受限大小缓冲区实现进行测试,可以看到消费者正确地从缓冲区取出了生产者产生的数据,未取得数据的消费者也被正确地挂起。 553 | 554 | ### 四、实验中遇到的问题 555 | 556 | > 关于 Lock 设计的相关问题 557 | 558 | 在原来的代码中我们参照了课件使用了如下的代码实现。 559 | 560 | ```C++ 561 | void Lock::Acquire() 562 | { 563 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 564 | while(value == false){ 565 | queue->Append((void *)currentThread); // so go to sleep 566 | currentThread->Sleep(); 567 | } 568 | value = false; 569 | currentHeldLockThread = currentThread; 570 | (void) interrupt->SetLevel(oldLevel); 571 | } 572 | 573 | void Lock::Release() 574 | { 575 | Thread *thread; 576 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 577 | if(!queue->IsEmpty()){//wake up one thread 578 | thread = (Thread *)queue->Remove(); 579 | if (thread != NULL) // make thread ready 580 | scheduler->ReadyToRun(thread); 581 | value = false; // 有问题,这样会导致死锁 582 | }else{ 583 | value = true; 584 | } 585 | (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 586 | } 587 | ``` 588 | 589 | 但这样做会导致其他的进程无法执行。释放锁后,value 值需要更改成 true, 其他阻塞的线程才有可能得到执行。 590 | 591 | 所以代码 Release 的代码最终改成 592 | 593 | ```C++ 594 | void Lock::Release() 595 | { 596 | Thread *thread; 597 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 598 | // if queue is not empty release all blocked one 599 | if(!queue->IsEmpty()){//wake up one thread 600 | thread = (Thread *)queue->Remove(); 601 | if (thread != NULL) // make thread ready 602 | scheduler->ReadyToRun(thread); 603 | } 604 | value = true; 605 | (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 606 | } 607 | ``` 608 | 609 | > 什么时候控制中断? 610 | 611 | 中断是为了保证原子性,当有其他的机制保证原子性的时候,那就不需要在使用中断,例如: 612 | 613 | condition 中的 signal,和 broadcast 不需要使用中断,因为在进入之前已经有锁保证原子性了,但是 wait 操作需要控制中断。如下 sleep 实现条件变量的代码: 614 | 615 | ```C++ 616 | void Condition::Wait(Lock* conditionLock) 617 | { 618 | assert(conditionLock->isHeldByCurrentThread()); 619 | // still need to turn off the interrupts 620 | queue->Append((void *)currentThread); 621 | conditionLock->Release(); 622 | currentThread->Sleep(); 623 | conditionLock->Acquire();// restart to require lock 624 | } 625 | ``` 626 | 627 | 在锁 release 之前还是原子的,但是 release 之后就无法保证,这个时候应该在 sleep 之前加中断保证 sleep 的原子性,就是保证 wait 整个操作是原子的。所以应在 release 之前加入中断,以免在 release 之后发生切换。 628 | 629 | 所以代码如下: 630 | 631 | ```c++ 632 | void Condition::Wait(Lock* conditionLock) 633 | { 634 | assert(conditionLock->isHeldByCurrentThread()); 635 | // still need to turn off the interrupts 636 | queue->Append((void *)currentThread); 637 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 638 | conditionLock->Release(); 639 | currentThread->Sleep(); 640 | (void) interrupt->SetLevel(oldLevel); 641 | conditionLock->Acquire();// restart to require lock 642 | } 643 | ``` 644 | 645 | 同理,用 Semaphore 也要做到此,这次采用了二元信号量保证原子性: 646 | 647 | ```C++ 648 | void Condition::Wait(Lock* conditionLock) 649 | { 650 | assert(conditionLock->isHeldByCurrentThread()); 651 | wait_atom->P();// after release lock, to ensure operation Atomicity 652 | conditionLock->Release(); 653 | numWaiting++; 654 | sem->P(); 655 | numWaiting--; 656 | wait_atom->V(); 657 | conditionLock->Acquire();// restart to require lock ,to rejudge the condition is satisfilied 658 | 659 | } 660 | ``` 661 | 662 | 此处的 wait_atom 起到了之前的中断的作用。 663 | 664 | > 库冲突问题。 665 | 666 | 在将 dllist 类中设置锁的时候,本来想将锁作为 dllist 类的内部成员,但是后来发现加入后,在 driver-list.cc 的文件中导入的 cstdlib 类库和锁 (synch.h) 的头文件引入的类库有冲突,即 thread.h 中相关文件和 cstdlib 中相关函数有冲突。最后采用了外部变量, 将锁变量作为外部变量导入 dllist.cc 文件中使用。 667 | 668 | > 如何实现如果在调用 Semaphore::P() 前调用 Semaphore::V(),则 V 操作的效果将积累下来;而如果在调用 Condition::Wait() 前调用 Condition::Signal(),则 Signal 操作的效果将不积累? 669 | 670 | 我们引入 numWaiting 变量,统计进入 condition 队列的数目, 然后根据这个数目达到这个效果。 671 | 672 | ```C++ 673 | void Condition::Wait(Lock* conditionLock) 674 | { 675 | assert(conditionLock->isHeldByCurrentThread()); 676 | wait_atom->P(); 677 | conditionLock->Release(); 678 | numWaiting++; 679 | sem->P(); 680 | numWaiting--; 681 | wait_atom->V(); 682 | conditionLock->Acquire(); 683 | } 684 | 685 | void Condition::Signal(Lock* conditionLock) 686 | { 687 | assert(conditionLock->isHeldByCurrentThread()); 688 | if(numWaiting> 0){ 689 | sem->V(); 690 | numWaiting--; 691 | } 692 | } 693 | ``` 694 | 695 | ### 五、实验收获与总结 696 | 697 | 通过本次实验,我们依次实现了使用 `Thread::Sleep` 与 `Semaphore` 的锁机制与条件变量,并利用这些同步机制实现了线程安全的双向有序链表类、线程安全的 `Table` 结构与线程安全的大小受限缓冲区。在这次实验的过程中,我们深刻地体会到了同步机制的设计的确是件看似简单实际上有着诸多需要考虑的问题的难事,以至于直到提交报告时仍然有些许我们没能完全理清思绪的细节问题。通过这次实验,我们最大的收获莫过于亲自感受了互斥与同步机制的实现与测试,很大程度上加深了对“并发”、信号量、锁、条件变量与管程等概念的思考与理解。这次实验也是之后的实验的重要基石。 698 | 699 | 总的说来,这次实验的完成还有诸多值得改进的地方。尽管提交了报告,我们仍然觉得按照英文实验指导中提出的问题再次理清同步机制的有关概念与细节把握是相当必要的,期待小组能够在充分理解这些问题的基础上更好地完成第三次实验。 700 | 701 | ----- -------------------------------------------------------------------------------- /Exp_2/synch-sem.cc: -------------------------------------------------------------------------------- 1 | // synch.cc 2 | // Routines for synchronizing threads. Three kinds of 3 | // synchronization routines are defined here: semaphores, locks 4 | // and condition variables (the implementation of the last two 5 | // are left to the reader). 6 | // 7 | // Any implementation of a synchronization routine needs some 8 | // primitive atomic operation. We assume Nachos is running on 9 | // a uniprocessor, and thus atomicity can be provided by 10 | // turning off interrupts. While interrupts are disabled, no 11 | // context switch can occur, and thus the current thread is guaranteed 12 | // to hold the CPU throughout, until interrupts are reenabled. 13 | // 14 | // Because some of these routines might be called with interrupts 15 | // already disabled (Semaphore::V for one), instead of turning 16 | // on interrupts at the end of the atomic operation, we always simply 17 | // re-set the interrupt state back to its original value (whether 18 | // that be disabled or enabled). 19 | // 20 | // Copyright (c) 1992-1993 The Regents of the University of California. 21 | // All rights reserved. See copyright.h for copyright notice and limitation 22 | // of liability and disclaimer of warranty provisions. 23 | 24 | #include "copyright.h" 25 | #include "synch.h" 26 | #include "system.h" 27 | #include 28 | 29 | //---------------------------------------------------------------------- 30 | // Semaphore::Semaphore 31 | // Initialize a semaphore, so that it can be used for synchronization. 32 | // 33 | // "debugName" is an arbitrary name, useful for debugging. 34 | // "initialValue" is the initial value of the semaphore. 35 | //---------------------------------------------------------------------- 36 | 37 | Semaphore::Semaphore(char* debugName, int initialValue) 38 | { 39 | name = debugName; 40 | value = initialValue; 41 | queue = new List; 42 | } 43 | 44 | //---------------------------------------------------------------------- 45 | // Semaphore::Semaphore 46 | // De-allocate semaphore, when no longer needed. Assume no one 47 | // is still waiting on the semaphore! 48 | //---------------------------------------------------------------------- 49 | 50 | Semaphore::~Semaphore() 51 | { 52 | delete queue; 53 | } 54 | 55 | //---------------------------------------------------------------------- 56 | // Semaphore::P 57 | // Wait until semaphore value > 0, then decrement. Checking the 58 | // value and decrementing must be done atomically, so we 59 | // need to disable interrupts before checking the value. 60 | // 61 | // Note that Thread::Sleep assumes that interrupts are disabled 62 | // when it is called. 63 | //---------------------------------------------------------------------- 64 | 65 | void 66 | Semaphore::P() 67 | { 68 | IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts 69 | 70 | while (value == 0) { // semaphore not available 71 | queue->Append((void *)currentThread); // so go to sleep 72 | currentThread->Sleep(); 73 | } 74 | value--; // semaphore available, 75 | // copy_n()sume its value 76 | 77 | (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 78 | } 79 | 80 | //---------------------------------------------------------------------- 81 | // Semaphore::V 82 | // Increment semaphore value, waking up a waiter if necessary. 83 | // As with P(), this operation must be atomic, so we need to disable 84 | // interrupts. Scheduler::ReadyToRun() assumes that threads 85 | // are disabled when it is called. 86 | //---------------------------------------------------------------------- 87 | 88 | void 89 | Semaphore::V() 90 | { 91 | Thread *thread; 92 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 93 | 94 | thread = (Thread *)queue->Remove(); 95 | if (thread != NULL) // make thread ready, consuming the V immediately 96 | scheduler->ReadyToRun(thread); 97 | value++; 98 | (void) interrupt->SetLevel(oldLevel); 99 | } 100 | 101 | // Dummy functions -- so we can compile our later assignments 102 | // Note -- without a correct implementation of Condition::Wait(), 103 | // the test case in the network assignment won't work! 104 | Lock::Lock(char* debugName) 105 | { 106 | name = debugName; 107 | sem = new Semaphore(debugName , 1); 108 | currentThread = NULL; 109 | } 110 | 111 | Lock::~Lock() 112 | { 113 | delete sem; 114 | } 115 | void Lock::Acquire() 116 | { 117 | sem->P(); 118 | currentHeldLockThread = currentThread; 119 | } 120 | void Lock::Release() 121 | { 122 | sem->V(); 123 | 124 | } 125 | 126 | bool Lock::isHeldByCurrentThread() 127 | { 128 | return (currentThread == currentHeldLockThread); 129 | } 130 | 131 | Condition::Condition(char* debugName) 132 | { 133 | name = debugName; 134 | sem = new Semaphore(debugName , 0); 135 | wait_atom = new Semaphore("ensure wait Atomicity",1); 136 | currentThread = NULL; 137 | numWaiting = 0; 138 | } 139 | Condition::~Condition() 140 | { 141 | delete sem; 142 | } 143 | void Condition::Wait(Lock* conditionLock) 144 | { 145 | assert(conditionLock->isHeldByCurrentThread()); 146 | wait_atom->P();// after release lock, to ensure operation Atomicity 147 | conditionLock->Release(); 148 | numWaiting++; 149 | sem->P(); 150 | numWaiting--; 151 | wait_atom->V(); 152 | conditionLock->Acquire();// restart to require lock ,to rejudge the condition is satisfilied 153 | } 154 | void Condition::Signal(Lock* conditionLock) 155 | { 156 | assert(conditionLock->isHeldByCurrentThread()); 157 | // avoid accumulating Signal 158 | if(numWaiting > 0){ 159 | sem->V(); 160 | numWaiting--; 161 | } 162 | 163 | } 164 | void Condition::Broadcast(Lock* conditionLock) 165 | { 166 | assert(conditionLock->isHeldByCurrentThread()); 167 | for(int i = 0; i < numWaiting; i++){ 168 | sem->V(); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Exp_2/synch-sem.h: -------------------------------------------------------------------------------- 1 | // synch.h 2 | // Data structures for synchronizing threads. 3 | // 4 | // Three kinds of synchronization are defined here: semaphores, 5 | // locks, and condition variables. The implementation for 6 | // semaphores is given; for the latter two, only the procedure 7 | // interface is given -- they are to be implemented as part of 8 | // the first assignment. 9 | // 10 | // Note that all the synchronization objects take a "name" as 11 | // part of the initialization. This is solely for debugging purposes. 12 | // 13 | // Copyright (c) 1992-1993 The Regents of the University of California. 14 | // All rights reserved. See copyright.h for copyright notice and limitation 15 | // synch.h -- synchronization primitives. 16 | 17 | #ifndef SYNCH_H 18 | #define SYNCH_H 19 | 20 | #include "copyright.h" 21 | #include "thread.h" 22 | #include "list.h" 23 | 24 | // The following class defines a "semaphore" whose value is a non-negative 25 | // integer. The semaphore has only two operations P() and V(): 26 | // 27 | // P() -- waits until value > 0, then decrement 28 | // 29 | // V() -- increment, waking up a thread waiting in P() if necessary 30 | // 31 | // Note that the interface does *not* allow a thread to read the value of 32 | // the semaphore directly -- even if you did read the value, the 33 | // only thing you would know is what the value used to be. You don't 34 | // know what the value is now, because by the time you get the value 35 | // into a register, a context switch might have occurred, 36 | // and some other thread might have called P or V, so the true value might 37 | // now be different. 38 | 39 | class Semaphore { 40 | public: 41 | Semaphore(char* debugName, int initialValue); // set initial value 42 | ~Semaphore(); // de-allocate semaphore 43 | char* getName() { return name;} // debugging assist 44 | 45 | void P(); // these are the only operations on a semaphore 46 | void V(); // they are both *atomic* 47 | 48 | private: 49 | char* name; // useful for debugging 50 | int value; // semaphore value, always >= 0 51 | List *queue; // threads waiting in P() for the value to be > 0 52 | }; 53 | 54 | // The following class defines a "lock". A lock can be BUSY or FREE. 55 | // There are only two operations allowed on a lock: 56 | // 57 | // Acquire -- wait until the lock is FREE, then set it to BUSY 58 | // 59 | // Release -- set lock to be FREE, waking up a thread waiting 60 | // in Acquire if necessary 61 | // 62 | // In addition, by convention, only the thread that acquired the lock 63 | // may release it. As with semaphores, you can't read the lock value 64 | // (because the value might change immediately after you read it). 65 | 66 | class Lock { 67 | public: 68 | Lock(char* debugName); // initialize lock to be FREE 69 | ~Lock(); // deallocate lock 70 | char* getName() { return name; } // debugging assist 71 | 72 | void Acquire(); // these are the only operations on a lock 73 | void Release(); // they are both *atomic* 74 | 75 | bool isHeldByCurrentThread(); // true if the current thread 76 | // holds this lock. Useful for 77 | // checking in Release, and in 78 | // Condition variable ops below. 79 | 80 | private: 81 | char *name; 82 | Semaphore *sem; 83 | Thread* currentHeldLockThread; // current that hold lock 84 | }; 85 | 86 | // The following class defines a "condition variable". A condition 87 | // variable does not have a value, but threads may be queued, waiting 88 | // on the variable. These are only operations on a condition variable: 89 | // 90 | // Wait() -- release the lock, relinquish the CPU until signaled, 91 | // then re-acquire the lock 92 | // 93 | // Signal() -- wake up a thread, if there are any waiting on 94 | // the condition 95 | // 96 | // Broadcast() -- wake up all threads waiting on the condition 97 | // 98 | // All operations on a condition variable must be made while 99 | // the current thread has acquired a lock. Indeed, all accesses 100 | // to a given condition variable must be protected by the same lock. 101 | // In other words, mutual exclusion must be enforced among threads calling 102 | // the condition variable operations. 103 | // 104 | // In Nachos, condition variables are assumed to obey *Mesa*-style 105 | // semantics. When a Signal or Broadcast wakes up another thread, 106 | // it simply puts the thread on the ready list, and it is the responsibility 107 | // of the woken thread to re-acquire the lock (this re-acquire is 108 | // taken care of within Wait()). By contrast, some define condition 109 | // variables according to *Hoare*-style semantics -- where the signalling 110 | // thread gives up control over the lock and the CPU to the woken thread, 111 | // which runs immediately and gives back control over the lock to the 112 | // signaller when the woken thread leaves the critical section. 113 | // 114 | // The consequence of using Mesa-style semantics is that some other thread 115 | // can acquire the lock, and change data structures, before the woken 116 | // thread gets a chance to run. 117 | 118 | class Condition { 119 | public: 120 | Condition(char* debugName); // initialize condition to 121 | // "no one waiting" 122 | ~Condition(); // deallocate the condition 123 | char* getName() { return (name); } 124 | 125 | void Wait(Lock *conditionLock); // these are the 3 operations on 126 | // condition variables; releasing the 127 | // lock and going to sleep are 128 | // *atomic* in Wait() 129 | void Signal(Lock *conditionLock); // conditionLock must be held by 130 | void Broadcast(Lock *conditionLock);// the currentThread for all of 131 | // these operations 132 | 133 | private: 134 | char *name; 135 | int numWaiting; // Number of threads waiting on this condition 136 | Semaphore *sem; 137 | Semaphore *wait_atom; // Ensure atomtic 138 | }; 139 | #endif // SYNCH_H 140 | -------------------------------------------------------------------------------- /Exp_2/synch-sleep.cc: -------------------------------------------------------------------------------- 1 | // synch.cc 2 | // Routines for synchronizing threads. Three kinds of 3 | // synchronization routines are defined here: semaphores, locks 4 | // and condition variables (the implementation of the last two 5 | // are left to the reader). 6 | // 7 | // Any implementation of a synchronization routine needs some 8 | // primitive atomic operation. We assume Nachos is running on 9 | // a uniprocessor, and thus atomicity can be provided by 10 | // turning off interrupts. While interrupts are disabled, no 11 | // context switch can occur, and thus the current thread is guaranteed 12 | // to hold the CPU throughout, until interrupts are reenabled. 13 | // 14 | // Because some of these routines might be called with interrupts 15 | // already disabled (Semaphore::V for one), instead of turning 16 | // on interrupts at the end of the atomic operation, we always simply 17 | // re-set the interrupt state back to its original value (whether 18 | // that be disabled or enabled). 19 | // 20 | // Copyright (c) 1992-1993 The Regents of the University of California. 21 | // All rights reserved. See copyright.h for copyright notice and limitation 22 | // of liability and disclaimer of warranty provisions. 23 | 24 | #include "copyright.h" 25 | #include "synch.h" 26 | #include "system.h" 27 | #include 28 | 29 | //---------------------------------------------------------------------- 30 | // Semaphore::Semaphore 31 | // Initialize a semaphore, so that it can be used for synchronization. 32 | // 33 | // "debugName" is an arbitrary name, useful for debugging. 34 | // "initialValue" is the initial value of the semaphore. 35 | //---------------------------------------------------------------------- 36 | 37 | Semaphore::Semaphore(char* debugName, int initialValue) 38 | { 39 | name = debugName; 40 | value = initialValue; 41 | queue = new List; 42 | } 43 | 44 | //---------------------------------------------------------------------- 45 | // Semaphore::Semaphore 46 | // De-allocate semaphore, when no longer needed. Assume no one 47 | // is still waiting on the semaphore! 48 | //---------------------------------------------------------------------- 49 | 50 | Semaphore::~Semaphore() 51 | { 52 | delete queue; 53 | } 54 | 55 | //---------------------------------------------------------------------- 56 | // Semaphore::P 57 | // Wait until semaphore value > 0, then decrement. Checking the 58 | // value and decrementing must be done atomically, so we 59 | // need to disable interrupts before checking the value. 60 | // 61 | // Note that Thread::Sleep assumes that interrupts are disabled 62 | // when it is called. 63 | //---------------------------------------------------------------------- 64 | 65 | void 66 | Semaphore::P() 67 | { 68 | IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts 69 | 70 | while (value == 0) { // semaphore not available 71 | queue->Append((void *)currentThread); // so go to sleep 72 | currentThread->Sleep(); 73 | } 74 | value--; // semaphore available, 75 | // consume its value 76 | (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 77 | } 78 | 79 | //---------------------------------------------------------------------- 80 | // Semaphore::V 81 | // Increment semaphore value, waking up a waiter if necessary. 82 | // As with P(), this operation must be atomic, so we need to disable 83 | // interrupts. Scheduler::ReadyToRun() assumes that threads 84 | // are disabled when it is called. 85 | //---------------------------------------------------------------------- 86 | 87 | void 88 | Semaphore::V() 89 | { 90 | Thread *thread; 91 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 92 | 93 | thread = (Thread *)queue->Remove(); 94 | if (thread != NULL) { // make thread ready, consuming the V immediately 95 | scheduler->ReadyToRun(thread); 96 | } 97 | value++; 98 | (void) interrupt->SetLevel(oldLevel); 99 | } 100 | 101 | // Dummy functions -- so we can compile our later assignments 102 | // Note -- without a correct implementation of Condition::Wait(), 103 | // the test case in the network assignment won't work! 104 | Lock::Lock(char* debugName) 105 | { 106 | name = debugName; 107 | queue = new List; 108 | value = true; 109 | } 110 | 111 | Lock::~Lock() 112 | { 113 | delete queue; 114 | } 115 | 116 | void Lock::Acquire() 117 | { 118 | IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts 119 | while (value == false) { 120 | // go to sleep does not guarantee that a thread awakened in V will get a chance 121 | // to run before another thread calls P 122 | queue->Append((void *)currentThread); // so go to sleep 123 | currentThread->Sleep(); 124 | } 125 | value = false; 126 | currentHeldLockThread = currentThread; 127 | (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 128 | } 129 | 130 | void Lock::Release() 131 | { 132 | Thread *thread; 133 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 134 | // if queue is not empty release all blocked one 135 | if (!queue->IsEmpty()) {//wake up one thread 136 | thread = (Thread *)queue->Remove(); 137 | if (thread != NULL) { // make thread ready 138 | scheduler->ReadyToRun(thread); 139 | } 140 | } 141 | value = true; 142 | 143 | (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 144 | } 145 | 146 | bool Lock::isHeldByCurrentThread() 147 | { 148 | return (currentThread == currentHeldLockThread); 149 | } 150 | 151 | Condition::Condition(char* debugName) 152 | { 153 | name = debugName; 154 | queue = new List; 155 | } 156 | 157 | Condition::~Condition() 158 | { 159 | delete queue; 160 | } 161 | 162 | void Condition::Wait(Lock* conditionLock) 163 | { 164 | assert(conditionLock->isHeldByCurrentThread()); 165 | // still need off the interupt 166 | queue->Append((void *)currentThread); 167 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 168 | conditionLock->Release(); 169 | currentThread->Sleep(); 170 | (void) interrupt->SetLevel(oldLevel); 171 | conditionLock->Acquire();// restart to require lock 172 | } 173 | 174 | void Condition::Signal(Lock* conditionLock) 175 | { 176 | // has lock acquire so don't require interupt 177 | Thread *thread; 178 | assert(conditionLock->isHeldByCurrentThread()); 179 | if (!queue->IsEmpty()) { 180 | thread = (Thread *)queue->Remove(); 181 | if (thread != NULL) { // make thread ready 182 | scheduler->ReadyToRun(thread); 183 | } 184 | } 185 | 186 | } 187 | 188 | void Condition::Broadcast(Lock* conditionLock) 189 | { 190 | Thread *thread; 191 | assert(conditionLock->isHeldByCurrentThread()); 192 | // wake up all the thread 193 | while (!queue->IsEmpty()) { 194 | thread = (Thread *)queue->Remove(); 195 | if (thread != NULL) { // make thread ready 196 | scheduler->ReadyToRun(thread); 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /Exp_2/synch-sleep.h: -------------------------------------------------------------------------------- 1 | // synch.h 2 | // Data structures for synchronizing threads. 3 | // 4 | // Three kinds of synchronization are defined here: semaphores, 5 | // locks, and condition variables. The implementation for 6 | // semaphores is given; for the latter two, only the procedure 7 | // interface is given -- they are to be implemented as part of 8 | // the first assignment. 9 | // 10 | // Note that all the synchronization objects take a "name" as 11 | // part of the initialization. This is solely for debugging purposes. 12 | // 13 | // Copyright (c) 1992-1993 The Regents of the University of California. 14 | // All rights reserved. See copyright.h for copyright notice and limitation 15 | // synch.h -- synchronization primitives. 16 | 17 | #ifndef SYNCH_H 18 | #define SYNCH_H 19 | 20 | #include "copyright.h" 21 | #include "thread.h" 22 | #include "list.h" 23 | 24 | // The following class defines a "semaphore" whose value is a non-negative 25 | // integer. The semaphore has only two operations P() and V(): 26 | // 27 | // P() -- waits until value > 0, then decrement 28 | // 29 | // V() -- increment, waking up a thread waiting in P() if necessary 30 | // 31 | // Note that the interface does *not* allow a thread to read the value of 32 | // the semaphore directly -- even if you did read the value, the 33 | // only thing you would know is what the value used to be. You don't 34 | // know what the value is now, because by the time you get the value 35 | // into a register, a context switch might have occurred, 36 | // and some other thread might have called P or V, so the true value might 37 | // now be different. 38 | 39 | class Semaphore { 40 | public: 41 | Semaphore(char* debugName, int initialValue); // set initial value 42 | ~Semaphore(); // de-allocate semaphore 43 | char* getName() { return name;} // debugging assist 44 | 45 | void P(); // these are the only operations on a semaphore 46 | void V(); // they are both *atomic* 47 | 48 | private: 49 | char* name; // useful for debugging 50 | int value; // semaphore value, always >= 0 51 | List* queue; // threads waiting in P() for the value to be > 0 52 | }; 53 | 54 | // The following class defines a "lock". A lock can be BUSY or FREE. 55 | // There are only two operations allowed on a lock: 56 | // 57 | // Acquire -- wait until the lock is FREE, then set it to BUSY 58 | // 59 | // Release -- set lock to be FREE, waking up a thread waiting 60 | // in Acquire if necessary 61 | // 62 | // In addition, by convention, only the thread that acquired the lock 63 | // may release it. As with semaphores, you can't read the lock value 64 | // (because the value might change immediately after you read it). 65 | 66 | class Lock { 67 | public: 68 | Lock(char* debugName); // initialize lock to be FREE 69 | ~Lock(); // deallocate lock 70 | char* getName() { return name; } // debugging assist 71 | 72 | void Acquire(); // these are the only operations on a lock 73 | void Release(); // they are both *atomic* 74 | 75 | bool isHeldByCurrentThread(); // true if the current thread 76 | // holds this lock. Useful for 77 | // checking in Release, and in 78 | // Condition variable ops below. 79 | 80 | private: 81 | char* name; // for debugging 82 | bool value; // if free value is true ,or value is false 83 | List* queue; 84 | Thread* currentHeldLockThread; // current that hold lock 85 | // plus some other stuff you'll need to define 86 | }; 87 | 88 | // The following class defines a "condition variable". A condition 89 | // variable does not have a value, but threads may be queued, waiting 90 | // on the variable. These are only operations on a condition variable: 91 | // 92 | // Wait() -- release the lock, relinquish the CPU until signaled, 93 | // then re-acquire the lock 94 | // 95 | // Signal() -- wake up a thread, if there are any waiting on 96 | // the condition 97 | // 98 | // Broadcast() -- wake up all threads waiting on the condition 99 | // 100 | // All operations on a condition variable must be made while 101 | // the current thread has acquired a lock. Indeed, all accesses 102 | // to a given condition variable must be protected by the same lock. 103 | // In other words, mutual exclusion must be enforced among threads calling 104 | // the condition variable operations. 105 | // 106 | // In Nachos, condition variables are assumed to obey *Mesa*-style 107 | // semantics. When a Signal or Broadcast wakes up another thread, 108 | // it simply puts the thread on the ready list, and it is the responsibility 109 | // of the woken thread to re-acquire the lock (this re-acquire is 110 | // taken care of within Wait()). By contrast, some define condition 111 | // variables according to *Hoare*-style semantics -- where the signalling 112 | // thread gives up control over the lock and the CPU to the woken thread, 113 | // which runs immediately and gives back control over the lock to the 114 | // signaller when the woken thread leaves the critical section. 115 | // 116 | // The consequence of using Mesa-style semantics is that some other thread 117 | // can acquire the lock, and change data structures, before the woken 118 | // thread gets a chance to run. 119 | 120 | class Condition { 121 | public: 122 | Condition(char* debugName); // initialize condition to 123 | // "no one waiting" 124 | ~Condition(); // deallocate the condition 125 | char* getName() { return (name); } 126 | 127 | void Wait(Lock *conditionLock); // these are the 3 operations on 128 | // condition variables; releasing the 129 | // lock and going to sleep are 130 | // *atomic* in Wait() 131 | void Signal(Lock *conditionLock); // conditionLock must be held by 132 | void Broadcast(Lock *conditionLock);// the currentThread for all of 133 | // these operations 134 | 135 | private: 136 | char* name; 137 | List* queue; // store wait thread 138 | // plus some other stuff you'll need to define 139 | }; 140 | #endif // SYNCH_H 141 | -------------------------------------------------------------------------------- /Exp_2/threadtest.cc: -------------------------------------------------------------------------------- 1 | // threadtest.cc 2 | // Simple test case for the threads assignment. 3 | // 4 | // Create two threads, and have them context switch 5 | // back and forth between themselves by calling Thread::Yield, 6 | // to illustratethe inner workings of the thread system. 7 | // 8 | // Copyright (c) 1992-1993 The Regents of the University of California. 9 | // All rights reserved. See copyright.h for copyright notice and limitation 10 | // of liability and disclaimer of warranty provisions. 11 | 12 | #include "copyright.h" 13 | #include "system.h" 14 | #include "dllist.h" 15 | #include "synch.h" 16 | #include "Table.h" 17 | #include "BoundedBuffer.h" 18 | #include 19 | extern void genItem2List(DLList *dlist, int N); 20 | extern void delItem2List(DLList *dlist,int N); 21 | 22 | // testnum is set in main.cc 23 | int testnum = 1; 24 | int threadNum = 1; 25 | int oprNum = 1; 26 | bool canYield = false; 27 | DLList* l = new DLList();// share data structure 28 | Lock* outListLock = new Lock("out of dlist define"); 29 | Lock* dlistLock = new Lock("lock of dlist"); 30 | Table* table = new Table(10); 31 | BoundedBuffer* buffer = new BoundedBuffer(20); 32 | int data[] = {1,3,4,13,12,17,18,23,19,20,13,33,27,43,26,21,16,14,10,29}; 33 | //---------------------------------------------------------------------- 34 | // SimpleThread 35 | // Loop 5 times, yielding the CPU to another ready thread 36 | // each iteration. 37 | // 38 | // "which" is simply a number identifying the thread, for debugging 39 | // purposes. 40 | //---------------------------------------------------------------------- 41 | 42 | char * 43 | getName(int i) { 44 | switch(i) 45 | { 46 | case 0: return "forked thread 0"; 47 | case 1: return "forked thread 1"; 48 | case 2: return "forked thread 2"; 49 | case 3: return "forked thread 3"; 50 | case 4: return "forked thread 4"; 51 | case 5: return "forked thread 5"; 52 | case 6: return "forked thread 6"; 53 | case 7: return "forked thread 7"; 54 | case 8: return "forked thread 8"; 55 | case 9: return "forked thread 9"; 56 | case 10: return "forked thread 10"; 57 | default: 58 | return "forked thread 00"; 59 | } 60 | } 61 | 62 | void 63 | SimpleThread(int which) { 64 | int num; 65 | for (num = 0; num < 5; num++) { 66 | printf("*** thread %d looped %d times\n", which, num); 67 | currentThread->Yield(); 68 | } 69 | } 70 | 71 | //---------------------------------------------------------------------- 72 | // ThreadTest1 73 | // Set up a ping-pong between two threads, by forking a thread 74 | // to call SimpleThread, and then calling SimpleThread ourselves. 75 | //---------------------------------------------------------------------- 76 | 77 | void 78 | DllistTest1(int which) { 79 | outListLock->Acquire(); 80 | printf("add item in thread %d\n", which); 81 | genItem2List(l, oprNum); 82 | currentThread->Yield(); 83 | printf("delete item in thread %d\n", which); 84 | delItem2List(l, oprNum); 85 | outListLock->Release(); 86 | } 87 | 88 | void 89 | DllistTest2(int which) { 90 | for(int i = 0 ; i < oprNum ; i++){ 91 | printf("add NO.%d item in thread %d\n", i + 1, which); 92 | genItem2List(l, 1); 93 | } 94 | printf("delete item in thread %d\n", which); 95 | delItem2List(l, oprNum); 96 | } 97 | 98 | 99 | void 100 | DllistTest3(int which) { 101 | // segmentation fault delete one element at one time 102 | printf("add item in thread %d\n", which); 103 | genItem2List(l, oprNum); 104 | currentThread->Yield(); 105 | for (int i = 0; i < oprNum; i++) { 106 | printf("delete NO.%d item in thread %d\n", i + 1, which); 107 | delItem2List(l, 1); 108 | } 109 | } 110 | 111 | void 112 | DllistTest4(int which) { 113 | // segmentation fault one is in the add the other is in delete 114 | if (which%2 == 1) { 115 | canYield = false; 116 | printf("add item in thread %d\n", which); 117 | genItem2List(l, oprNum); 118 | printf("delete item in thread %d\n", which); 119 | for (int i = 0; i < oprNum - 1; i++) { 120 | delItem2List(l, 1); 121 | } 122 | canYield = true; 123 | delItem2List(l, 1); 124 | } else { 125 | printf("add item in thread %d\n", which); 126 | genItem2List(l, oprNum); 127 | printf("delete item in thread %d\n", which); 128 | delItem2List(l, oprNum); 129 | } 130 | } 131 | 132 | void 133 | DllistTest5 (int which) { 134 | canYield = false; 135 | printf("add NO.1 item in thread %d\n", which); 136 | genItem2List(l, 1); 137 | currentThread->Yield(); 138 | canYield = true; 139 | for (int i = 0; i < oprNum - 1; i++) { 140 | printf("add NO.%d item in thread %d\n",i+2,which); 141 | genItem2List(l, 1); 142 | } 143 | printf("delete item in thread %d\n", which); 144 | delItem2List(l, oprNum); 145 | } 146 | 147 | void 148 | DllistTest6(int which) { 149 | if (which % 2 == 1) { 150 | canYield = false; 151 | printf("add NO.1 item in thread %d\n", which); 152 | genItem2List(l, 1); 153 | currentThread->Yield(); 154 | for (int i = 1 ; i < oprNum; i++) { 155 | printf("add NO.%d item in thread %d\n", i + 1, which); 156 | genItem2List(l, 1); 157 | // currentThread->Yield(); 158 | } 159 | printf("delete item in thread %d\n", which); 160 | delItem2List(l, oprNum); 161 | } else { 162 | printf("add NO.1 item in thread %d\n", which); 163 | genItem2List(l, 1); 164 | canYield = true; 165 | currentThread->Yield(); 166 | for (int i = 1; i < oprNum; i++) { 167 | printf("add NO.%d item in thread %d\n", i + 1, which); 168 | genItem2List(l, 1); 169 | // currentThread->Yield(); 170 | } 171 | printf("delete item in thread %d\n", which); 172 | delItem2List(l, oprNum); 173 | } 174 | } 175 | 176 | void 177 | TestTable(int which) 178 | { 179 | int *object = new int, index; 180 | *object = data[which - 1]; 181 | printf("add object %d to table in thread %d\n", *object, which); 182 | index = table->Alloc((void *)object); 183 | if (index != -1) { 184 | assert(((int *)table->Get(index))==object); 185 | printf("get object %d to table in thread %d\n", *(int *)(table->Get(index)), which); 186 | table->Release(index); 187 | } 188 | } 189 | 190 | void 191 | TestBoundedBuffer(int which) 192 | { 193 | // one is write and others is consumer the data 194 | if (which == 1) { 195 | printf("produce begin in thread %d\n", which); 196 | buffer->Write((void *)data, 15); 197 | } else { 198 | printf("comsume begin in thread %d\n", which); 199 | int* consume = new int[which - 1]; 200 | buffer->Read((void *)consume , which - 1); 201 | printf("the datas from buffer in thread %d\n",which); 202 | for(int i = 0; i < which - 1; i++){ 203 | printf("%d\n",consume[i]); 204 | } 205 | printf("consumer completed in thread %d\n", which); 206 | } 207 | } 208 | 209 | void 210 | toDllistTest(VoidFunctionPtr func) 211 | { 212 | DEBUG('t', "Entering toDllistTest\n"); 213 | Thread *t; 214 | for(int i=0 ;i < threadNum ;i++) { 215 | t = new Thread(getName(i+1)); 216 | t->Fork(func,i+1); 217 | } 218 | } 219 | 220 | void 221 | ThreadTest() 222 | { 223 | printf("%d \n",testnum); 224 | switch (testnum) { 225 | case 1: //switch out of function 226 | toDllistTest(DllistTest1); 227 | break; 228 | case 2: // insert to the empty list causing one item coving the other one 229 | toDllistTest(DllistTest2); 230 | break; 231 | case 3: //delete item at one time 232 | toDllistTest(DllistTest3); 233 | break; 234 | case 4: 235 | // segmentation fault , one is in the add the other is in delete 236 | toDllistTest(DllistTest4); 237 | break; 238 | case 5: 239 | //chain scission ,sometimes it will happen 240 | toDllistTest(DllistTest5); 241 | break; 242 | case 6: 243 | // disorder output from insert items 244 | toDllistTest(DllistTest6); 245 | break; 246 | case 7: 247 | // to test Table.h file 248 | toDllistTest(TestTable); 249 | break; 250 | case 8: 251 | // to test BoundedBuffer.h 252 | toDllistTest(TestBoundedBuffer); 253 | break; 254 | default: 255 | printf("No test specified.\n"); 256 | break; 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /Exp_3/Alarm.cc: -------------------------------------------------------------------------------- 1 | #include "Alarm.h" 2 | #include "system.h" 3 | 4 | int Alarm::num = 0; 5 | Alarm::Alarm() 6 | { 7 | list = new List(); 8 | } 9 | 10 | Alarm::~Alarm() 11 | { 12 | delete list; 13 | } 14 | 15 | void 16 | check(int which) 17 | { 18 | // check how many thread in waiting, if zero ,exit,else switch 19 | // using static var 20 | while(Alarm::num != 0){ 21 | currentThread->Yield(); 22 | } 23 | DEBUG('t', "[ALRM] dummy thread finish\n"); 24 | return; 25 | } 26 | 27 | 28 | void 29 | Alarm::Pause(int howLong) 30 | { 31 | //use interup to make it atomicly 32 | IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts 33 | 34 | int when = stats->totalTicks + howLong*TimerTicks;// calculate the time to wake up 35 | 36 | list->SortedInsert(currentThread, when); // insert into list 37 | num++; 38 | 39 | // create dummy thread to check 40 | if(num == 1){ 41 | Thread *t = new Thread("dummy thread"); 42 | t->Fork(check,0); 43 | } 44 | 45 | currentThread->Sleep(); // thread sleep 46 | 47 | (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 48 | } 49 | 50 | void 51 | Alarm::awake() 52 | { 53 | IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts 54 | 55 | // traverse the list ,if find time out,remove it 56 | // and set thread runnable 57 | 58 | Thread *temp; 59 | int when,len=num; 60 | for(int i=0;i < len; i++) 61 | { 62 | temp = (Thread*)list->SortedRemove(&when); 63 | if(when <= stats->totalTicks){//time out 64 | scheduler->ReadyToRun(temp);// ----------> to discuss 65 | num--; 66 | }else{ 67 | // the others are postponed to now 68 | list->SortedInsert(temp,when); 69 | break; 70 | } 71 | } 72 | 73 | (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 74 | } 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Exp_3/Alarm.h: -------------------------------------------------------------------------------- 1 | #ifndef ALARM_H 2 | #define ALARM_H 3 | 4 | #include "list.h" 5 | 6 | class Alarm{ 7 | public: 8 | Alarm(); 9 | ~Alarm(); 10 | void Pause(int howLong); 11 | void awake(); 12 | static int num;// record the num of list; 13 | private: 14 | List *list; 15 | 16 | }; 17 | 18 | 19 | 20 | #endif 21 | 22 | -------------------------------------------------------------------------------- /Exp_3/Elevator.cc: -------------------------------------------------------------------------------- 1 | #include "Elevator.h" 2 | #include 3 | 4 | Elevator::Elevator(char *debugName, int numFloors, int myID) 5 | { 6 | name = debugName; 7 | this->numFloors = numFloors; // floor of building 8 | id = myID; // mark one elevator 9 | request = new bool[numFloors+2]; 10 | exit = new EventBarrier[numFloors+2];//barrier for going out 11 | con_lock = new Lock("lock for occupancy"); 12 | con_closeDoor = new Condition("condition for close door"); 13 | occupancy = 0;// can setting 14 | capacity = 3; // setting capacity 15 | currentfloor = 1; 16 | direction = 0; 17 | } 18 | 19 | Elevator::~Elevator() 20 | { 21 | delete[] request; 22 | delete[] exit; 23 | delete con_lock; 24 | delete con_closeDoor; 25 | } 26 | // signal exiters and enterers to action 27 | 28 | void 29 | Elevator::OpenDoors() 30 | { 31 | //let rider inside go out 32 | printf("[ELEV] on floor %d open door\n",currentfloor); 33 | exit[currentfloor].Signal(); 34 | con_lock->Acquire(); 35 | // calculate close door num; 36 | int waiters = b->getFloors()[currentfloor].e[direction].Waiters(); 37 | closeDoorNum = waiters > (capacity - occupancy)?(capacity - occupancy):waiters; 38 | con_lock->Release(); 39 | 40 | //set src or srcdown , deal with problem that capacity limited 41 | b->getLock()->Acquire(); // Will there be any possible deadlock? 42 | if(direction == 1){ 43 | b->getSrcUp()[currentfloor] = false; 44 | }else{ 45 | b->getSrcDown()[currentfloor] = false; 46 | } 47 | b->getLock()->Release(); 48 | 49 | b->getFloors()[currentfloor].e[direction].Signal(); 50 | } 51 | 52 | void 53 | Elevator::CloseDoors() 54 | { //if capacity has no limit,make sure people all in 55 | // but if has limit,when it reach the capacity 56 | printf("[ELEV] on floor %d close door\n",currentfloor); 57 | con_lock->Acquire(); 58 | while(closeDoorNum != 0){ 59 | con_closeDoor->Wait(con_lock); 60 | } 61 | request[currentfloor] = false; 62 | con_lock->Release(); 63 | } 64 | 65 | void 66 | Elevator::VisitFloor(int floor) 67 | { 68 | // reach the floor 69 | printf("[ELEV] visit floor %d\n",currentfloor); 70 | alarms->Pause(abs(floor - currentfloor) * _COSTPERFLOOR); 71 | currentfloor = floor; 72 | } 73 | 74 | bool 75 | Elevator::Enter() 76 | { 77 | // judge if there has enough occupancy 78 | // if no return false; 79 | con_lock->Acquire(); 80 | if(occupancy == capacity){ //to avoid the rider request again 81 | con_lock->Release(); 82 | // to wait next time 83 | b->getFloors()[currentfloor].e[direction].Complete(); 84 | return false; 85 | }else{ 86 | printf("[PERS] some one enter on floor %d \n",currentfloor); 87 | occupancy++; 88 | con_lock->Release(); 89 | b->getFloors()[currentfloor].e[direction].Complete(); 90 | return true; 91 | } 92 | } 93 | 94 | void 95 | Elevator::Exit() 96 | { 97 | printf("[PERS] exit on floor %d \n",currentfloor); 98 | con_lock->Acquire(); 99 | occupancy--; 100 | con_lock->Release(); 101 | exit[currentfloor].Complete();//go out 102 | } 103 | 104 | void 105 | Elevator::RequestFloor(int floor) 106 | { 107 | printf("[PERS] request floor %d\n", floor); 108 | request[floor] = true; 109 | con_lock->Acquire(); 110 | closeDoorNum--; 111 | if(closeDoorNum == 0){ 112 | con_closeDoor->Signal(con_lock); 113 | } 114 | con_lock->Release(); 115 | exit[floor].Wait(); 116 | } 117 | 118 | //----------------building define------------------------- 119 | Building::Building(char *debugname, int numFloors, int numElevators) 120 | { 121 | elevator = new Elevator(debugname, numFloors, 1); 122 | elevator->setBuilding(this); 123 | name = debugname; 124 | srcUp = new bool[numFloors+2]; 125 | srcDown = new bool[numFloors+2]; 126 | floors = new Floor[numFloors+2]; 127 | mutex = new Lock("lock for building"); 128 | floorNum = numFloors; 129 | } 130 | 131 | Building::~Building() 132 | { 133 | delete elevator; 134 | delete[] floors; 135 | delete[] srcUp; 136 | delete[] srcDown; 137 | delete mutex; 138 | } 139 | 140 | void 141 | Building::CallUp(int fromFloor) 142 | { 143 | printf("[BLDG] call up in %d floor\n",fromFloor); 144 | mutex->Acquire(); 145 | srcUp[fromFloor] = true; 146 | mutex->Release(); 147 | } 148 | 149 | 150 | void 151 | Building::CallDown(int fromFloor) 152 | { 153 | printf("[BLDG] call down %d floor\n",fromFloor); 154 | mutex->Acquire(); 155 | srcDown[fromFloor] = true; 156 | mutex->Release(); 157 | } 158 | 159 | Elevator * 160 | Building::AwaitUp(int fromFloor) 161 | { // wait for elevator arrival & going up 162 | printf("[BLDG] await up in %d floor \n",fromFloor); 163 | floors[fromFloor].e[1].Wait(); 164 | return elevator; 165 | } 166 | 167 | Elevator * 168 | Building::AwaitDown(int fromFloor) // ... down 169 | { 170 | printf("[BLDG] await down in %d floor\n",fromFloor); 171 | floors[fromFloor].e[0].Wait(); 172 | return elevator; 173 | } 174 | 175 | 176 | // elevator run function 177 | // author by huangxi 178 | void 179 | Building::RunElev(int eid) { 180 | Elevator *elev = elevator; 181 | int next; // Destination 182 | while (true) { 183 | next = 0; 184 | mutex->Acquire(); 185 | if (!elev->getDirection()) { // False when elevator is going down 186 | // Find the farest floor having riders waiting to enter or exit the elevator in current direction 187 | for (int i = elev->getCurrentFloor(); i >= 1; --i) { 188 | if (srcDown[i] || elev->request[i]) { next = i; } 189 | } 190 | } else { 191 | for (int i = elev->getCurrentFloor(); i <= floorNum; ++i) { 192 | if (srcUp[i] || elev->request[i]) { next = i; } 193 | } 194 | } 195 | mutex->Release(); 196 | if (!next && !elev->getOccupancy()) { // No one onboard and no more waiting rider in current direction 197 | if (!elev->getDirection()) { 198 | // Find the farest floor having riders waiting to enter in another direction 199 | for (int i = elev->getCurrentFloor(); i >= 1; --i) { 200 | if (srcUp[i]) { next = i; } 201 | } 202 | } else { 203 | for (int i = elev->getCurrentFloor(); i <= floorNum; ++i) { 204 | if (srcDown[i]) { next = i; } 205 | } 206 | } 207 | if (next) { 208 | elev->VisitFloor(next); // directly visit this floor 209 | } 210 | // Change direction 211 | elev->changeDirection(); 212 | DEBUG('t', "[ELEV] Elevator %d changed direction to %d at floor %d\n", eid, elev->getDirection(), elev->getCurrentFloor()); 213 | continue; 214 | } 215 | assert(next > 0 && "[ELEV] Elevator having people onboard but the Request array is not set correctly."); 216 | printf("[ELEV] Desitination set to %d.\n", next); 217 | if (!elev->getDirection()) { 218 | for (int i = elev->getCurrentFloor(); i >= next; --i) { 219 | elev->VisitFloor(i); 220 | // If any rider wanna enter or exit on floor i, open then close door 221 | if (srcDown[i] || elev->request[i]) { 222 | printf("[ELEV] Elevator %d stopped at floor %d\n", eid, i); 223 | elev->OpenDoors(); 224 | elev->CloseDoors(); 225 | } 226 | } 227 | } else { 228 | for (int i = elev->getCurrentFloor(); i <= next; ++i) { 229 | elev->VisitFloor(i); 230 | if (srcUp[i] || elev->request[i]) { 231 | printf("[ELEV] Elevator %d stopped at floor %d\n", eid, i); 232 | elev->OpenDoors(); 233 | elev->CloseDoors(); 234 | } 235 | } 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /Exp_3/Elevator.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Here are the method signatures for the Elevator and Building classes. 4 | You should feel free to add to these classes, but do not change the 5 | existing interfaces. 6 | 7 | */ 8 | #ifndef ELEVATOR_H 9 | #define EVEVATOR_H 10 | #include "EventBarrier.h" 11 | #include "system.h" 12 | #include 13 | #include 14 | #define _COSTPERFLOOR 100 15 | 16 | class Building; 17 | 18 | class Elevator { 19 | public: 20 | Elevator(char *debugName, int numFloors, int myID); 21 | ~Elevator(); 22 | char *getName() { return name; } 23 | 24 | // elevator control interface: called by Elevator thread 25 | void OpenDoors(); // signal exiters and enterers to action 26 | void CloseDoors(); // after exiters are out and enterers are in 27 | void VisitFloor(int floor); // go to a particular floor 28 | 29 | // elevator rider interface (part 1): called by rider threads. 30 | bool Enter(); // get in 31 | void Exit(); // get out (iff destinationFloor) 32 | void RequestFloor(int floor); // tell the elevator our destinationFloor 33 | 34 | // insert your methods here, if needed 35 | int getCurrentFloor() { return currentfloor; } 36 | int getOccupancy() { return occupancy; } 37 | int getDirection() { return direction; } 38 | void changeDirection() { direction = 1 - direction; } 39 | void setBuilding(Building *building) { b = building; } 40 | bool *request; // mark request floor,if it is true 41 | 42 | private: 43 | char *name; 44 | 45 | int currentfloor; // floor where currently stopped 46 | int occupancy; // how many riders currently onboard 47 | 48 | // insert() your data structures here, if needed 49 | int capacity; // the capacity of elevator 50 | int numFloors; 51 | int id; 52 | int direction; // 0 means down,1 means up 53 | int status; // elevator status 0 means working 1 means free 54 | int closeDoorNum; 55 | EventBarrier *exit; 56 | Lock *con_lock; 57 | Condition *con_closeDoor; 58 | Building *b; 59 | }; 60 | 61 | class Floor{ 62 | public: 63 | EventBarrier *e; 64 | Floor() { e = new EventBarrier[2]; } // 0 means down,1 means up 65 | ~Floor() { delete[] e; } 66 | }; 67 | 68 | class Building { 69 | public: 70 | Building(char *debugname, int numFloors, int numElevators); 71 | ~Building(); 72 | char *getName() { return name; } 73 | 74 | // elevator rider interface (part 2): called by rider threads 75 | void CallUp(int fromFloor); // signal an elevator we want to go up 76 | void CallDown(int fromFloor); // ... down 77 | Elevator *AwaitUp(int fromFloor); // wait for elevator arrival & going up 78 | Elevator *AwaitDown(int fromFloor); // ... down 79 | 80 | Floor *getFloors() { return floors; } 81 | bool *getSrcUp() { return srcUp; } 82 | bool *getSrcDown() { return srcDown; } 83 | Lock *getLock() { return mutex; } 84 | void RunElev(int eid = 0); 85 | 86 | private: 87 | char *name; 88 | int floorNum; 89 | Elevator *elevator; 90 | Floor *floors; 91 | bool *srcUp; 92 | bool *srcDown; 93 | Lock *mutex; 94 | // insert your data structures here, if needed 95 | }; 96 | 97 | #endif 98 | 99 | 100 | // here's a sample portion of a rider thread body showing how we 101 | // expect things to be called. 102 | // 103 | // void rider(int id, int srcFloor, int dstFloor) { 104 | // Elevator *e; 105 | // 106 | // if (srcFloor == dstFloor) 107 | // return; 108 | // 109 | // DEBUG('t',"Rider %d travelling from %d to %d\n",id,srcFloor,dstFloor); 110 | // do { 111 | // if (srcFloor < dstFloor) { 112 | // DEBUG('t', "Rider %d CallUp(%d)\n", id, srcFloor); 113 | // building->CallUp(srcFloor); 114 | // DEBUG('t', "Rider %d AwaitUp(%d)\n", id, srcFloor); 115 | // e = building->AwaitUp(srcFloor); 116 | // } else { 117 | // DEBUG('t', "Rider %d CallDown(%d)\n", id, srcFloor); 118 | // building->CallDown(srcFloor); 119 | // DEBUG('t', "Rider %d AwaitDown(%d)\n", id, srcFloor); 120 | // e = building->AwaitDown(srcFloor); 121 | // } 122 | // DEBUG('t', "Rider %d Enter()\n", id); 123 | // } while (!e->Enter()); // elevator might be full! 124 | // 125 | // DEBUG('t', "Rider %d RequestFloor(%d)\n", id, dstFloor); 126 | // e->RequestFloor(dstFloor); // doesn't return until arrival 127 | // DEBUG('t', "Rider %d Exit()\n", id); 128 | // e->Exit(); 129 | // DEBUG('t', "Rider %d finished\n", id); 130 | // } 131 | 132 | -------------------------------------------------------------------------------- /Exp_3/EventBarrier.cc: -------------------------------------------------------------------------------- 1 | #include "EventBarrier.h" 2 | #include "system.h" 3 | EventBarrier::EventBarrier() 4 | { 5 | status = false; 6 | waitNum = 0; 7 | value = 0; 8 | barrierLock = new Lock("barrier Lock"); 9 | signal_con = new Condition("singal conditon"); 10 | complete_con = new Condition("complete condition"); 11 | } 12 | 13 | EventBarrier::~EventBarrier() 14 | { 15 | delete barrierLock; 16 | delete signal_con; 17 | delete complete_con; 18 | } 19 | 20 | void 21 | EventBarrier::Wait() 22 | { 23 | barrierLock->Acquire(); 24 | if(status == false){ 25 | waitNum++; 26 | signal_con->Wait(barrierLock); 27 | waitNum--; 28 | } 29 | barrierLock->Release(); 30 | } 31 | 32 | void 33 | EventBarrier::Signal() 34 | {// only one can call signal 35 | DEBUG('t',"[EB] in EventBarrier signal\n"); 36 | barrierLock->Acquire(); 37 | status = true; 38 | if(waitNum!=0){ 39 | // wake up other waiters 40 | signal_con->Broadcast(barrierLock); 41 | // blocked in complete condition 42 | complete_con->Wait(barrierLock); 43 | }else{ 44 | // if no one, just continue 45 | status = false; 46 | } 47 | // resum the status of barrier 48 | 49 | //changeStatus_con->Broadcast(barrierLock); 50 | // wake up the wating status change 51 | // make sure other wait one call wait another time 52 | //changeStatus_con->Broadcast(barrierLock); 53 | barrierLock->Release(); 54 | DEBUG('t',"[EB] out EventBarrier signal\n"); 55 | } 56 | 57 | void 58 | EventBarrier::Complete() 59 | { 60 | barrierLock->Acquire(); 61 | 62 | if(waitNum== 0){// is "if" suitable? 63 | // the last one to release lock 64 | status = false;// change status 65 | complete_con->Broadcast(barrierLock); 66 | //changeStatus_con->Wait(barrierLock);// make sure status change 67 | 68 | }else{ 69 | // no the last one 70 | complete_con->Wait(barrierLock); 71 | //changeStatus_con->Wait(barrierLock); 72 | } 73 | barrierLock->Release(); 74 | } 75 | 76 | int 77 | EventBarrier::Waiters() 78 | { 79 | barrierLock->Acquire(); 80 | int num = waitNum; 81 | barrierLock->Release(); 82 | return num; 83 | } 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Exp_3/EventBarrier.h: -------------------------------------------------------------------------------- 1 | #include "synch.h" 2 | #include "list.h" 3 | 4 | #ifndef EVENTBARRIER_H 5 | #define EVENTBARRIER_H 6 | 7 | class EventBarrier { 8 | public: 9 | 10 | EventBarrier();// constructor 11 | 12 | ~EventBarrier(); // destuctor 13 | 14 | void Wait();//Wait until the event is signaled. 15 | //if already in the signaled state,return immediately 16 | 17 | 18 | void Signal();/*Signal the event and block until all threads 19 | that wait for this event have responded. The 20 | EventBarrier reverts to the unsignaled state when Signal() returns.*/ 21 | 22 | void Complete();/*Indicate that the calling thread has finished 23 | responding to a signaled event, and block 24 | until all other threads that wait for this 25 | event have also responded*/ 26 | 27 | int Waiters();/*Return a count of threads that are waiting 28 | for the event or that have not yet responded to 29 | it.*/ 30 | private: 31 | bool status;// true means barrier opening, false means barrier closed 32 | int waitNum; // the num of waiting thread 33 | int value; // same to Semaphore , record the num of operation of signal; 34 | Lock *barrierLock; 35 | Condition *signal_con; 36 | Condition *complete_con; 37 | }; 38 | 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /Exp_3/Makefile.common: -------------------------------------------------------------------------------- 1 | # This is part of a GNU Makefile, included by the Makefiles in 2 | # each of the subdirectories. 3 | # 4 | # This file includes all of the baseline code provided by Nachos. 5 | # Whenever you add a .h or .cc file, put it in the appropriate 6 | # _H,_C, or _O list. 7 | # 8 | # The dependency graph between assignments is: 9 | # 1. THREADS before everything else 10 | # 2. USERPROG must come before VM 11 | # 3. USERPROG can come before or after FILESYS, but if USERPROG comes 12 | # before (as in this distribution), then it must define FILESYS_STUB 13 | # 14 | # Other than that, you have complete flexibility. 15 | # 16 | # Also whenever you change the include structure of your program, you should 17 | # do a gmake depend in the subdirectory -- this will modify the Makefile 18 | # to keep track of the new dependency. 19 | 20 | # You might want to play with the CFLAGS, but if you use -O it may 21 | # break the thread system. You might want to use -fno-inline if 22 | # you need to call some inline functions from the debugger. 23 | 24 | # Copyright (c) 1992 The Regents of the University of California. 25 | # All rights reserved. See copyright.h for copyright notice and limitation 26 | # of liability and disclaimer of warranty provisions. 27 | 28 | # CFLAGS = -g -Wall -Wshadow -fwritable-strings $(INCPATH) $(DEFINES) $(HOST) -DCHANGED 29 | CFLAGS = -g -Wall -Wshadow -traditional $(INCPATH) $(DEFINES) $(HOST) -DCHANGED 30 | 31 | # These definitions may change as the software is updated. 32 | # Some of them are also system dependent 33 | CPP= gcc -E -m32 34 | CC = g++ -m32 35 | LD = g++ -m32 36 | AS = as -32 37 | 38 | PROGRAM = nachos 39 | 40 | THREAD_H =../threads/copyright.h\ 41 | ../threads/list.h\ 42 | ../threads/scheduler.h\ 43 | ../threads/synch.h\ 44 | ../threads/synchlist.h\ 45 | ../threads/system.h\ 46 | ../threads/thread.h\ 47 | ../threads/utility.h\ 48 | ../machine/interrupt.h\ 49 | ../machine/sysdep.h\ 50 | ../machine/stats.h\ 51 | ../machine/timer.h\ 52 | ../threads/dllist.h\ 53 | ../threads/Table.h\ 54 | ../threads/BoundedBuffer.h\ 55 | ../threads/EventBarrier.h\ 56 | ../threads/Alarm.h\ 57 | ../threads/Elevator.h\ 58 | 59 | 60 | 61 | THREAD_C =../threads/main.cc\ 62 | ../threads/list.cc\ 63 | ../threads/scheduler.cc\ 64 | ../threads/synch.cc\ 65 | ../threads/synchlist.cc\ 66 | ../threads/system.cc\ 67 | ../threads/thread.cc\ 68 | ../threads/utility.cc\ 69 | ../threads/threadtest.cc\ 70 | ../machine/interrupt.cc\ 71 | ../machine/sysdep.cc\ 72 | ../machine/stats.cc\ 73 | ../machine/timer.cc\ 74 | ../threads/dllist.cc\ 75 | ../threads/dllist-driver.cc\ 76 | ../threads/Table.cc\ 77 | ../threads/BoundedBuffer.cc\ 78 | ../threads/EventBarrier.cc\ 79 | ../threads/Alarm.cc\ 80 | ../threads/Elevator.cc\ 81 | 82 | 83 | 84 | THREAD_S = ../threads/switch.s 85 | 86 | THREAD_O =main.o list.o scheduler.o synch.o synchlist.o system.o thread.o \ 87 | utility.o threadtest.o interrupt.o stats.o sysdep.o timer.o dllist.o dllist-driver.o\ 88 | Table.o BoundedBuffer.o EventBarrier.o Alarm.o Elevator.o 89 | 90 | USERPROG_H = ../userprog/addrspace.h\ 91 | ../userprog/bitmap.h\ 92 | ../filesys/filesys.h\ 93 | ../filesys/openfile.h\ 94 | ../machine/console.h\ 95 | ../machine/machine.h\ 96 | ../machine/mipssim.h\ 97 | ../machine/translate.h 98 | 99 | USERPROG_C = ../userprog/addrspace.cc\ 100 | ../userprog/bitmap.cc\ 101 | ../userprog/exception.cc\ 102 | ../userprog/progtest.cc\ 103 | ../machine/console.cc\ 104 | ../machine/machine.cc\ 105 | ../machine/mipssim.cc\ 106 | ../machine/translate.cc 107 | 108 | USERPROG_O = addrspace.o bitmap.o exception.o progtest.o console.o machine.o \ 109 | mipssim.o translate.o 110 | 111 | VM_H = 112 | VM_C = 113 | VM_O = 114 | 115 | FILESYS_H =../filesys/directory.h \ 116 | ../filesys/filehdr.h\ 117 | ../filesys/filesys.h \ 118 | ../filesys/openfile.h\ 119 | ../filesys/synchdisk.h\ 120 | ../machine/disk.h 121 | FILESYS_C =../filesys/directory.cc\ 122 | ../filesys/filehdr.cc\ 123 | ../filesys/filesys.cc\ 124 | ../filesys/fstest.cc\ 125 | ../filesys/openfile.cc\ 126 | ../filesys/synchdisk.cc\ 127 | ../machine/disk.cc 128 | FILESYS_O =directory.o filehdr.o filesys.o fstest.o openfile.o synchdisk.o\ 129 | disk.o 130 | 131 | NETWORK_H = ../network/post.h ../machine/network.h 132 | NETWORK_C = ../network/nettest.cc ../network/post.cc ../machine/network.cc 133 | NETWORK_O = nettest.o post.o network.o 134 | 135 | S_OFILES = switch.o 136 | 137 | OFILES = $(C_OFILES) $(S_OFILES) 138 | 139 | $(PROGRAM): $(OFILES) 140 | $(LD) $(OFILES) $(LDFLAGS) -o $(PROGRAM) 141 | 142 | $(C_OFILES): %.o: 143 | $(CC) $(CFLAGS) -c $< 144 | 145 | switch.o: ../threads/switch.s 146 | $(CPP) -P $(INCPATH) $(HOST) ../threads/switch.c > swtch.s 147 | $(AS) -o switch.o swtch.s 148 | 149 | depend: $(CFILES) $(HFILES) 150 | $(CC) $(INCPATH) $(DEFINES) $(HOST) -DCHANGED -M $(CFILES) > makedep 151 | echo '/^# DO NOT DELETE THIS LINE/+2,$$d' >eddep 152 | echo '$$r makedep' >>eddep 153 | echo 'w' >>eddep 154 | ed - Makefile < eddep 155 | rm eddep makedep 156 | echo '# DEPENDENCIES MUST END AT END OF FILE' >> Makefile 157 | echo '# IF YOU PUT STUFF HERE IT WILL GO AWAY' >> Makefile 158 | echo '# see make depend above' >> Makefile 159 | -------------------------------------------------------------------------------- /Exp_3/main.cc: -------------------------------------------------------------------------------- 1 | // main.cc 2 | // Bootstrap code to initialize the operating system kernel. 3 | // 4 | // Allows direct calls into internal operating system functions, 5 | // to simplify debugging and testing. In practice, the 6 | // bootstrap code would just initialize data structures, 7 | // and start a user program to print the login prompt. 8 | // 9 | // Most of this file is not needed until later assignments. 10 | // 11 | // Usage: nachos -d -rs 12 | // -s -x -c 13 | // -f -cp 14 | // -p -r -l -D -t 15 | // -n -m 16 | // -o 17 | // -z 18 | // 19 | // -d causes certain debugging messages to be printed (cf. utility.h) 20 | // -rs causes Yield to occur at random (but repeatable) spots 21 | // -z prints the copyright message 22 | // 23 | // USER_PROGRAM 24 | // -s causes user programs to be executed in single-step mode 25 | // -x runs a user program 26 | // -c tests the console 27 | // 28 | // FILESYS 29 | // -f causes the physical disk to be formatted 30 | // -cp copies a file from UNIX to Nachos 31 | // -p prints a Nachos file to stdout 32 | // -r removes a Nachos file from the file system 33 | // -l lists the contents of the Nachos directory 34 | // -D prints the contents of the entire file system 35 | // -t tests the performance of the Nachos file system 36 | // 37 | // NETWORK 38 | // -n sets the network reliability 39 | // -m sets this machine's host id (needed for the network) 40 | // -o runs a simple test of the Nachos network software 41 | // 42 | // NOTE -- flags are ignored until the relevant assignment. 43 | // Some of the flags are interpreted here; some in system.cc. 44 | // 45 | // Copyright (c) 1992-1993 The Regents of the University of California. 46 | // All rights reserved. See copyright.h for copyright notice and limitation 47 | // of liability and disclaimer of warranty provisions. 48 | 49 | #define MAIN 50 | #include "copyright.h" 51 | #undef MAIN 52 | 53 | #include "utility.h" 54 | #include "system.h" 55 | 56 | #ifdef THREADS 57 | extern int testnum; 58 | extern int threadNum; 59 | extern int floorNums; 60 | #endif 61 | 62 | // External functions used by this file 63 | 64 | extern void ThreadTest(void), Copy(char *unixFile, char *nachosFile); 65 | extern void Print(char *file), PerformanceTest(void); 66 | extern void StartProcess(char *file), ConsoleTest(char *in, char *out); 67 | extern void MailTest(int networkID); 68 | 69 | //---------------------------------------------------------------------- 70 | // main 71 | // Bootstrap the operating system kernel. 72 | // 73 | // Check command line arguments 74 | // Initialize data structures 75 | // (optionally) Call test procedure 76 | // 77 | // "argc" is the number of command line arguments (including the name 78 | // of the command) -- ex: "nachos -d +" -> argc = 3 79 | // "argv" is an array of strings, one for each command line argument 80 | // ex: "nachos -d +" -> argv = {"nachos", "-d", "+"} 81 | //---------------------------------------------------------------------- 82 | 83 | int 84 | main(int argc, char **argv) 85 | { 86 | int argCount; // the number of arguments 87 | // for a particular command 88 | 89 | DEBUG('t', "Entering main"); 90 | (void) Initialize(argc, argv); 91 | 92 | #ifdef THREADS 93 | for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount) { 94 | argCount = 1; 95 | switch (argv[0][1]) { 96 | case 'q': 97 | testnum = atoi(argv[1]); 98 | argCount++; 99 | break; 100 | case 't': 101 | threadNum = atoi(argv[1]); 102 | argCount++; 103 | break; 104 | case 'n': 105 | floorNums = atoi(argv[1]); 106 | argCount++; 107 | break; 108 | default: 109 | int testnum = 1; 110 | int threadNum=1; 111 | int floorNums = 10; 112 | break; 113 | } 114 | } 115 | 116 | ThreadTest(); 117 | #endif 118 | 119 | for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount) { 120 | argCount = 1; 121 | if (!strcmp(*argv, "-z")) // print copyright 122 | printf (copyright); 123 | #ifdef USER_PROGRAM 124 | if (!strcmp(*argv, "-x")) { // run a user program 125 | ASSERT(argc > 1); 126 | StartProcess(*(argv + 1)); 127 | argCount = 2; 128 | } else if (!strcmp(*argv, "-c")) { // test the console 129 | if (argc == 1) 130 | ConsoleTest(NULL, NULL); 131 | else { 132 | ASSERT(argc > 2); 133 | ConsoleTest(*(argv + 1), *(argv + 2)); 134 | argCount = 3; 135 | } 136 | interrupt->Halt(); // once we start the console, then 137 | // Nachos will loop forever waiting 138 | // for console input 139 | } 140 | #endif // USER_PROGRAM 141 | #ifdef FILESYS 142 | if (!strcmp(*argv, "-cp")) { // copy from UNIX to Nachos 143 | ASSERT(argc > 2); 144 | Copy(*(argv + 1), *(argv + 2)); 145 | argCount = 3; 146 | } else if (!strcmp(*argv, "-p")) { // print a Nachos file 147 | ASSERT(argc > 1); 148 | Print(*(argv + 1)); 149 | argCount = 2; 150 | } else if (!strcmp(*argv, "-r")) { // remove Nachos file 151 | ASSERT(argc > 1); 152 | fileSystem->Remove(*(argv + 1)); 153 | argCount = 2; 154 | } else if (!strcmp(*argv, "-l")) { // list Nachos directory 155 | fileSystem->List(); 156 | } else if (!strcmp(*argv, "-D")) { // print entire filesystem 157 | fileSystem->Print(); 158 | } else if (!strcmp(*argv, "-t")) { // performance test 159 | PerformanceTest(); 160 | } 161 | #endif // FILESYS 162 | #ifdef NETWORK 163 | if (!strcmp(*argv, "-o")) { 164 | ASSERT(argc > 1); 165 | Delay(2); // delay for 2 seconds 166 | // to give the user time to 167 | // start up another nachos 168 | MailTest(atoi(*(argv + 1))); 169 | argCount = 2; 170 | } 171 | #endif // NETWORK 172 | } 173 | 174 | currentThread->Finish(); // NOTE: if the procedure "main" 175 | // returns, then the program "nachos" 176 | // will exit (as any other normal program 177 | // would). But there may be other 178 | // threads on the ready list. We switch 179 | // to those threads by saying that the 180 | // "main" thread is finished, preventing 181 | // it from returning. 182 | return(0); // Not reached... 183 | } 184 | -------------------------------------------------------------------------------- /Exp_3/nachos03.md: -------------------------------------------------------------------------------- 1 | # 操作系统原理课程实验报告 2 | 3 | > **实验 3 - NachOS 下的线程编程** 4 | > 5 | > 实验日期:Apr. 13th - Jun. 3rd, 2017 6 | 7 | > **小组成员信息** 8 | > 9 | > ==OMIT== 10 | 11 |
12 | 13 | ### 一、实验内容与要求 14 | 15 | 本实验目的在于通过使用第二次实验中实现的锁机制与条件变量的相关实现设计出更为有效的原语,进而解决较为复杂的并发问题,提高妥善处理并发编程中常见的竞争、死锁、饥饿等问题从而正确编写并发程序的能力。 16 | 17 | 本次实验的具体内容如下: 18 | 19 | * 利用条件变量与锁机制实现 `EventBarrier` 原语,从而允许一组线程能够以同步的方式等待并相应某事件; 20 | * 利用 NachOS 中提供的 `Timer` 类实现 `Alarm` 原语,从而允许线程可以主动调用 `Alarm::Pause` 方法休眠一定时间后由 NachOS 唤醒; 21 | * 利用 `EventBarrier` 原语与 `Alarm` 原语解决模拟电梯运行的同步问题。 22 | 23 | ### 二、实验设计与代码实现 24 | 25 | ##### A. `EventBarrier` 原语的实现 26 | 27 | 对于 `EventBarrier` 类,我们采用如下的头文件定义: 28 | 29 | ```C++ 30 | class EventBarrier{ 31 | public: 32 | EventBarrier(); 33 | ~EventBarrier(); 34 | void Wait(); 35 | void Signal(); 36 | void Complete(); 37 | int Waiters(); 38 | private: 39 | bool status; // the open status of EventBarrier 40 | int waitNum; // the num of waiting thread 41 | int value; // record signals 42 | Lock *barrierLock; // mutex lock and conditions 43 | Condition *signal_con; 44 | Condition *complete_con; 45 | } 46 | ``` 47 | 48 | > ***Code. 1*** EventBarrier 类的头文件定义 49 | 50 | 对于事件栅栏而言,其主要方法有 `Wait()` , `Signal()` , `Complete()`, `Waiters()` 这四个方法。线程调用 `Wait()` 时若栅栏处于打开状态,则 `Wait()` 方法直接返回,否则线程将阻塞在该栅栏的第一个条件变量上等待事件发生。当事件发生时,`Signal()` 方法将被调用,等待所有阻塞在第一个条件变量上的线程被唤醒执行 `Complete()` 。执行完 `Complete()` 的线程将阻塞在另一条件变量上,等待所有其他线程完成执行。所有线程完成执行后,`Signal()` 方法返回并打开栅栏,阻塞在另一条件变量上的变量得以释放,顺利通过栅栏。 51 | 52 | `Waiters()` 方法返回当前阻塞在栅栏上的线程数; `barrierLock` 互斥锁则保证对共享资源操作的互斥性。 53 | 54 | `EventBarrier` 类主要方法的具体实现如下: 55 | 56 | ```C++ 57 | EventBarrier::EventBarrier() { 58 | status = false; 59 | waitNum = 0; 60 | value = 0; 61 | barrierLock = new Lock("barrier Lock"); 62 | signal_con = new Condition("singal conditon"); 63 | complete_con = new Condition("complete condition"); 64 | } 65 | 66 | EventBarrier::~EventBarrier() { 67 | delete barrierLock; 68 | delete signal_con; 69 | delete complete_con; 70 | } 71 | 72 | void 73 | EventBarrier::Wait() { 74 | barrierLock->Acquire(); 75 | if(status == false){ 76 | waitNum++; 77 | signal_con->Wait(barrierLock); 78 | waitNum--; 79 | } 80 | barrierLock->Release(); 81 | } 82 | 83 | void 84 | EventBarrier::Signal() { 85 | barrierLock->Acquire(); // only one can call Signal 86 | status = true; 87 | if(Waiters()!=0) { 88 | // wake up other waiters 89 | signal_con->Broadcast(barrierLock); 90 | // blocked in complete condition 91 | complete_con->Wait(barrierLock); 92 | } else { 93 | // if no one, just continue 94 | status = false; 95 | } 96 | // resum the status of barrier 97 | barrierLock->Release(); 98 | } 99 | 100 | void 101 | EventBarrier::Complete() { 102 | barrierLock->Acquire(); 103 | if(Waiters() == 0) { 104 | // the last one to release lock 105 | status = false;// change status 106 | complete_con->Broadcast(barrierLock); 107 | } else { 108 | // no the last one 109 | complete_con->Wait(barrierLock); 110 | //changeStatus_con->Wait(barrierLock); 111 | } 112 | barrierLock->Release(); 113 | } 114 | 115 | int 116 | EventBarrier::Waiters() { 117 | barrierLock->Acquire(); 118 | int num = waitNum; 119 | barrierLock->Release(); 120 | return num; 121 | } 122 | ``` 123 | 124 | > ***Code. 2*** EventBarrier 类的主要实现 125 | 126 | ##### B. `Alarm` 原语的实现 127 | 128 | 对于 `Alarm` 类,我们采用如下的头文件定义: 129 | 130 | ```C++ 131 | class Alarm{ 132 | public: 133 | Alarm(); 134 | ~Alarm(); 135 | void Pause(int howLong); 136 | void awake(); 137 | static int num; // record the num of threads in list 138 | private: 139 | List *list; 140 | }; 141 | ``` 142 | 143 | > ***Code. 3*** Alarm 类的头文件定义 144 | 145 | 闹钟原语中的主要方法只有 `Pause()` 与 `awake()` 。线程调用 `Pause()` 来使自身至少阻塞指定的 Tick 数,这可以通过在闹钟中维护一个有序列表来实现。之后,通过修改 NachOS 中的时钟中断的处理函数,我们可以在其中调用 `awake()` 方法来维护闹钟中的有序数组,并将到达唤醒时间的线程从阻塞队列移入就绪队列。 146 | 147 | `Alarm` 类主要方法的具体实现如下: 148 | 149 | ```C++ 150 | int Alarm::num = 0; 151 | Alarm::Alarm() 152 | { list = new List(); } 153 | 154 | Alarm::~Alarm() 155 | { delete list; } 156 | 157 | void 158 | check(int which) { 159 | // check if any threads is waiting for alarm 160 | // if so, switch to dummy thread 161 | while(Alarm::num != 0) { currentThread->Yield(); } 162 | DEBUG('t', "dummy thread finish\n"); 163 | return; 164 | } 165 | 166 | void 167 | Alarm::Pause(int howLong) { 168 | //use interup to make it atomicly 169 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 170 | // calculate the waking up time 171 | int when = stats->totalTicks + howLong*TimerTicks; 172 | list->SortedInsert(currentThread, when); // insert into list 173 | num++; 174 | // create dummy thread to prevent NachOS from halting 175 | if (num == 1) { 176 | Thread *t = new Thread("dummy thread\n"); 177 | t->Fork(check,0); 178 | } 179 | currentThread->Sleep(); // thread sleep 180 | (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 181 | } 182 | 183 | void 184 | Alarm::awake() { 185 | IntStatus oldLevel = interrupt->SetLevel(IntOff); 186 | // traverse the list 187 | // and move timed-out thread to ready list 188 | Thread *temp; 189 | int when, len = num; 190 | for (int i=0; i < len; i++) { 191 | temp = (Thread*)list->SortedRemove(&when); 192 | if(when <= stats->totalTicks) {//time out 193 | scheduler->ReadyToRun(temp); 194 | num--; 195 | } else { 196 | // the others are postponed to now 197 | list->SortedInsert(temp,when); 198 | break; 199 | } 200 | } 201 | (void) interrupt->SetLevel(oldLevel); 202 | } 203 | ``` 204 | 205 | > ***Code. 4*** Alarm 类的主要实现 206 | 207 | 对于 `system.cc` 中的时钟中断处理函数,我们做出以下修改: 208 | 209 | ```C++ 210 | static void 211 | TimerInterruptHandler(int dummy) { 212 | if (interrupt->getStatus() != IdleMode) { 213 | interrupt->YieldOnReturn(); 214 | } 215 | alarms->awake(); 216 | } 217 | ``` 218 | 219 | > ***Code. 5*** system.cc 中的修改 220 | 221 | 为方便引用,我们也在 `system.cc` 与 `system.h` 中加入了全局变量 `alarms` ,具体修改参见源代码。 222 | 223 | ##### C. 电梯问题 224 | 225 | 为模拟一座大楼、一台有限容量下的电梯问题,我们首先定义 `Building` 类与 `Elevator` 类,采用如下的头文件定义: 226 | 227 | ```C++ 228 | class Elevator { 229 | public: 230 | Elevator(char *debugName, int numFloors, int myID); 231 | ~Elevator(); 232 | char *getName() { return name; } 233 | 234 | // elevator control interface: called by Elevator thread 235 | void OpenDoors(); // signal exiters and enterers to action 236 | void CloseDoors(); // after exiters are out and enterers are in 237 | void VisitFloor(int floor); // go to a particular floor 238 | 239 | // elevator rider interface (part 1): called by rider threads. 240 | bool Enter(); // get in 241 | void Exit(); // get out (iff destinationFloor) 242 | void RequestFloor(int floor); // tell the elevator our destination 243 | 244 | int getCurrentFloor() { return currentfloor; } 245 | int getOccupancy() { return occupancy; } 246 | int getDirection() { return direction; } 247 | void changeDirection() { direction = 1 - direction; } 248 | void setBuilding(Building *building) { b = building; } 249 | bool *request; // destination for riders onboard 250 | 251 | private: 252 | char *name; 253 | int currentfloor; // floor where currently stopped 254 | int occupancy; // how many riders currently onboard 255 | int capacity; // the capacity of elevator 256 | int numFloors; 257 | int id; 258 | int direction; // 0 for going down and 1 for going up 259 | int status; // 0 for working and 1 for free 260 | int closeDoorNum; 261 | EventBarrier *exit; 262 | Lock *con_lock; 263 | Condition *con_closeDoor; 264 | Building *b; 265 | }; 266 | 267 | class Floor{ 268 | public: 269 | EventBarrier *e; 270 | Floor() { e = new EventBarrier[2]; } // 0 for down, 1 for up 271 | ~Floor() { delete[] e; } 272 | }; 273 | 274 | class Building { 275 | public: 276 | Building(char *debugname, int numFloors, int numElevators); 277 | ~Building(); 278 | char *getName() { return name; } 279 | // elevator rider interface (part 2): called by rider threads 280 | void CallUp(int fromFloor);// signal an elevator we want to go up 281 | void CallDown(int fromFloor); // ... down 282 | Elevator *AwaitUp(int fromFloor); // wait for elevator 283 | Elevator *AwaitDown(int fromFloor); 284 | 285 | Floor *getFloors() { return floors; } 286 | bool *getSrcUp() { return srcUp; } 287 | bool *getSrcDown() { return srcDown; } 288 | Lock *getLock() { return mutex; } 289 | void RunElev(int eid = 0); 290 | 291 | private: 292 | char *name; 293 | int floorNum; 294 | Elevator *elevator; 295 | Floor *floors; 296 | bool *srcUp; 297 | bool *srcDown; 298 | Lock *mutex; 299 | }; 300 | ``` 301 | 302 | > ***Code. 6*** 电梯问题相关类的头文件定义 303 | 304 | 对于电梯问题,我们定义了一个大楼类 `Building` 与一个电梯类 `Elevator` 进行模拟。对于大楼类,我们引入了楼层 `Floor` 类。 305 | 306 | 对于一幢 F 层楼的大楼,每层楼我们设置了上行与下行方向的两个事件栅栏存放于 `Floor` 对象中,而对于每座大楼,则均有一个由 F 个 `Floor` 对象构成的数组。大楼类对乘客提供了 `CallUp()` , `CallDown()` , `AwaitUp()`, `AwaitDown()` 四个方法,分别对应乘客按键请求上行/下行电梯与乘客在上行/下行事件栅栏处等待电梯抵达。而对于电梯,大楼提供了 `srcUp` 与 `srcDown` 两个布尔数组指示在某一楼层有无乘客请求上行/下行电梯。 307 | 308 | 对电梯类,在其中我们定义了以下电梯行为与关键成员变量: 309 | 310 | + `OpenDoors()` :开启梯门,到达目的层的乘客先出梯,之后等待在本层并与电梯运行方向相同的乘客入梯(在电梯容量允许的前提下); 311 | + `CloseDoors()` :在完成乘客入梯与出梯后关闭梯门; 312 | + `VisitFloor(int)`: 前往指定楼层,实验中使用 `Alarm` 类模拟电梯移动耗时; 313 | + `Building *b`:指向本电梯绑定到的大楼对象; 314 | + `int capacity`:电梯最大容量; 315 | + `EventBarrier *exit`:为等待在某层出梯的乘客设置的事件栅栏; 316 | + `bool *request`:指示目前梯内乘客目的楼层的布尔数组。 317 | 318 | 我们也在电梯类中为乘客定义了以下方法: 319 | 320 | - `Enter()` :尝试进入电梯,如果电梯容量已满函数将返回 False;若成功进入电梯,乘客线程将通过 `RequestFloor(int)` 设置自己的目的地楼层,并阻塞到 `exit` 中对应楼层的事件栅栏上; 321 | - `Exit()` :到达楼层后离开电梯; 322 | - `RequestFloor(int)`:请求电梯前往某层。 323 | 324 | 本部分关键代码实现如下: 325 | 326 | ```C++ 327 | Elevator::Elevator(char *debugName, int numFloors, int myID) { 328 | name = debugName; 329 | this->numFloors = numFloors; // floor of building 330 | id = myID; // mark one elevator 331 | request = new bool[numFloors+2]; 332 | exit = new EventBarrier[numFloors+2];//barrier for going out 333 | con_lock = new Lock("lock for occupancy"); 334 | con_closeDoor = new Condition("condition for close door"); 335 | occupancy = 0;// can setting 336 | capacity = 3; // setting capacity 337 | currentfloor = 1; 338 | direction = 0; 339 | } 340 | 341 | Elevator::~Elevator() { 342 | delete[] request; 343 | delete[] exit; 344 | delete con_lock; 345 | delete con_closeDoor; 346 | } 347 | // signal exiters and enterers to action 348 | 349 | void 350 | Elevator::OpenDoors() { 351 | //let rider inside go out 352 | exit[currentfloor].Signal(); 353 | con_lock->Acquire(); 354 | // calculate close door num; 355 | int waiters = b->getFloors()[currentfloor].e[direction].Waiters(); 356 | closeDoorNum = waiters > (capacity - occupancy)?(capacity - occupancy):waiters; 357 | con_lock->Release(); 358 | //set src or srcdown , deal with problem that capacity limited 359 | b->getLock()->Acquire(); // Will there be any possible deadlock? 360 | if(direction == 1){ 361 | b->getSrcUp()[currentfloor] = false; 362 | }else{ 363 | b->getSrcDown()[currentfloor] = false; 364 | } 365 | b->getLock()->Release(); 366 | b->getFloors()[currentfloor].e[direction].Signal(); 367 | } 368 | 369 | void 370 | Elevator::CloseDoors() { 371 | con_lock->Acquire(); 372 | while(closeDoorNum != 0) { 373 | con_closeDoor->Wait(con_lock); 374 | } 375 | request[currentfloor] = false; 376 | con_lock->Release(); 377 | } 378 | 379 | void 380 | Elevator::VisitFloor(int floor) { 381 | // reach designated floor 382 | alarms->Pause(abs(floor - currentfloor) * _COSTPERFLOOR); 383 | currentfloor = floor; 384 | } 385 | 386 | bool 387 | Elevator::Enter() { 388 | // judge if there has enough occupancy 389 | con_lock->Acquire(); 390 | if(occupancy == capacity){ //to avoid the rider request again 391 | con_lock->Release(); 392 | // to wait next time 393 | b->getFloors()[currentfloor].e[direction].Complete(); 394 | return false; 395 | } else { 396 | occupancy++; 397 | con_lock->Release(); 398 | b->getFloors()[currentfloor].e[direction].Complete(); 399 | return true; 400 | } 401 | } 402 | 403 | void 404 | Elevator::Exit() { 405 | con_lock->Acquire(); 406 | occupancy--; 407 | con_lock->Release(); 408 | exit[currentfloor].Complete(); //go out 409 | } 410 | 411 | void 412 | Elevator::RequestFloor(int floor) { 413 | request[floor] = true; 414 | con_lock->Acquire(); 415 | closeDoorNum--; 416 | if(closeDoorNum == 0) { 417 | con_closeDoor->Signal(con_lock); 418 | } 419 | con_lock->Release(); 420 | exit[floor].Wait(); 421 | } 422 | 423 | /*----------------building define-------------------------*/ 424 | Building::Building(char *debugname, int numFloors, int numElevators) { 425 | elevator = new Elevator(debugname, numFloors, 1); 426 | elevator->setBuilding(this); // Bind elevator 427 | name = debugname; 428 | srcUp = new bool[numFloors+2]; 429 | srcDown = new bool[numFloors+2]; 430 | floors = new Floor[numFloors+2]; 431 | mutex = new Lock("lock for building"); 432 | floorNum = numFloors; 433 | } 434 | 435 | Building::~Building() { 436 | delete elevator; 437 | delete[] floors; 438 | delete[] srcUp; 439 | delete[] srcDown; 440 | delete mutex; 441 | } 442 | 443 | void 444 | Building::CallUp(int fromFloor) { 445 | mutex->Acquire(); 446 | srcUp[fromFloor] = true; 447 | mutex->Release(); 448 | } 449 | 450 | 451 | void 452 | Building::CallDown(int fromFloor) { 453 | mutex->Acquire(); 454 | srcDown[fromFloor] = true; 455 | mutex->Release(); 456 | } 457 | 458 | Elevator * 459 | Building::AwaitUp(int fromFloor) { 460 | // wait for elevator arrival & going up 461 | floors[fromFloor].e[1].Wait(); 462 | return elevator; 463 | } 464 | 465 | Elevator * 466 | Building::AwaitDown(int fromFloor) { 467 | floors[fromFloor].e[0].Wait(); 468 | return elevator; 469 | } 470 | ``` 471 | 472 | > ***Code. 7*** Building 类与 Elevator 类的主要实现 473 | 474 | 乘客行为沿用实验指导中给出的方法如下: 475 | 476 | ```C++ 477 | void rider(int id, int srcFloor, int dstFloor) { 478 | Elevator *e; 479 | if (srcFloor == dstFloor) 480 | return; 481 | do { 482 | if (srcFloor < dstFloor) { 483 | building->CallUp(srcFloor); 484 | e = building->AwaitUp(srcFloor); 485 | } else { 486 | building->CallDown(srcFloor); 487 | e = building->AwaitDown(srcFloor); 488 | } 489 | } while (!e->Enter()); // elevator might be full! 490 | e->RequestFloor(dstFloor); // doesn't return until arrival 491 | e->Exit(); 492 | } 493 | ``` 494 | 495 | > ***Code. 8*** 乘客行为 496 | 497 | 实验中使用 `Alarm` 类模拟乘客于不同时间到达。 498 | 499 | ##### D. 电梯调度 500 | 501 | 本实验中一个关键的问题便是实现电梯的调度。 502 | 503 | 为此,我们采用这样的步骤来调度电梯: 504 | 505 | 1. 对于上行中的电梯,电梯首先将**目的层**设置为*当前楼层到最高层间有上行乘客等待的层数*与*当前梯内乘客目的地的最高层数*中的**较大者**。对于下行中的电梯,则将目的层设置为*当前楼层到最低层间有下行乘客等待的层数*与*当前梯内乘客目的地的最高层数*中的**较小者**。之后,电梯在运行到目的层的过程中若**遇到梯内乘客的目的地**或有**==同向==乘客等待**的层,则**停梯载客/卸客**,并视情况**更新目的层**。 506 | 2. 当步骤 1 中的电梯找不到新的目的层时,此时电梯中无乘客。此时上行电梯检视目前层到最高层间有等待**==下行==**的乘客的楼层,若有,则将目的层设置为其中的最高层;下行电梯则检视目前层到最高层间有等待**==上行==**的乘客的楼层,若有,则将目的层设置为其中的最低层。若找到目的层,则电梯直接访问该层,途中**不停梯**。 507 | 3. 电梯**调转运行方向**,返回 步骤 1. 508 | 509 | 为调度电梯,我们在大楼类中设置了 `RunElev(int)` 方法,其具体实现如下: 510 | 511 | ```C++ 512 | void 513 | Building::RunElev(int eid) { 514 | Elevator *elev = elevator; 515 | int next; // Destination 516 | while (true) { 517 | next = 0; 518 | mutex->Acquire(); 519 | if (!elev->getDirection()) { // False when elevator is going down 520 | // Find the farest floor having riders waiting to enter or exit the elevator in current direction 521 | for (int i = elev->getCurrentFloor(); i >= 1; --i) { 522 | if (srcDown[i] || elev->request[i]) { next = i; } 523 | } 524 | } else { 525 | for (int i = elev->getCurrentFloor(); i <= floorNum; ++i) { 526 | if (srcUp[i] || elev->request[i]) { next = i; } 527 | } 528 | } 529 | mutex->Release(); 530 | if (!next && !elev->getOccupancy()) { // No one onboard and no more waiting rider in current direction 531 | if (!elev->getDirection()) { // False when elevator is going down 532 | // Find the farest floor having riders waiting to enter in another direction 533 | for (int i = elev->getCurrentFloor(); i >= 1; --i) { 534 | if (srcUp[i]) { next = i; } 535 | } 536 | } else { 537 | for (int i = elev->getCurrentFloor(); i <= floorNum; ++i) { 538 | if (srcDown[i]) { next = i; } 539 | } 540 | } 541 | if (next) { 542 | elev->VisitFloor(next); // directly visit the floor 543 | } 544 | // Change direction 545 | elev->changeDirection(); 546 | continue; 547 | } 548 | if (!elev->getDirection()) { 549 | for (int i = elev->getCurrentFloor(); i >= next; --i) { 550 | elev->VisitFloor(i); 551 | // If any rider wanna enter or exit on floor i, open then close door 552 | if (srcDown[i] || elev->request[i]) { 553 | elev->OpenDoors(); 554 | elev->CloseDoors(); 555 | } 556 | } 557 | } else { 558 | for (int i = elev->getCurrentFloor(); i <= next; ++i) { 559 | elev->VisitFloor(i); 560 | if (srcUp[i] || elev->request[i]) { 561 | elev->OpenDoors(); 562 | elev->CloseDoors(); 563 | } 564 | } 565 | } 566 | } 567 | } 568 | ``` 569 | 570 | > ***Code. 9*** 电梯调度算法 571 | 572 | ##### F. *threadtest.cc* 与 *main.cc* 相关修改 573 | 574 | ###### *main.cc* 中作出的修改 575 | 576 | 类似于上一个实验,在 *main.cc* 里我们需要处理命令行调用 `./nachos` 时传入的参数。为此,我们定义了以下参数: 577 | 578 | | 参数标记 | 对应变量名 | 参数含义 | 579 | | ---- | ------------- | ---------------- | 580 | | -q | int testnum | 测例编号,用于进入不同的测试分支 | 581 | | -t | int threadNum | 需要创建的并行线程数量 | 582 | | -n | int floorNum | 电梯问题中大楼的楼层数 | 583 | 584 | + 运行时附加参数 `-d t` 可以打印出相关的调试信息。 585 | + 实验中电梯默认的容量是固定的,若要修改需要修改实验源码并重新编译。 586 | 587 | ###### *threadtest.cc* 中的修改 588 | 589 | 在本次实验中,我们提供了对事件栅栏原语、闹钟原语与电梯问题的测试。对应的测试编号见下表: 590 | 591 | | 测例类型 | 描述 | 测例编号 | 592 | | ------ | --------------------------------- | ------- | 593 | | 事件栅栏原语 | 演示多个并发线程在事件栅栏对象的作用下同步执行的情况 | ` -q 1` | 594 | | 闹钟原语 | 演示多个并发线程调用闹钟时的执行情况 | ` -q 2` | 595 | | 电梯问题 | 演示多个并发乘客线程在一座大楼、一部有限容量电梯的情况下的执行情况 | `-q 3` | 596 | 597 | + 以上测例的代码实现参见 *threadtest.cc* 与其他相关数据结构对应的源文件。 598 | 599 | ### 三、实验结果 600 | 601 | 因电梯问题依赖于事件栅栏原语与闹钟原语,下面仅演示电梯问题的实验结果。 602 | 603 | 为测试电梯问题,我们使用以下代码随机生成指定数目的乘客线程,其中`getRandNum(int)` 生成一个 1 到传入参数间的随机整数: 604 | 605 | ```C++ 606 | void 607 | riderAction(int which) { 608 | int from = getRandNum(floorNums), to = getRandNum(floorNums); 609 | alarms->Pause(getRandNum(20) * 100); 610 | printf("[PERS] No.%d %d->%d request\n",which,from,to); 611 | rider(which,from,to); 612 | printf("[PERS] No.%d %d->%d reach\n",which,from,to); 613 | } 614 | 615 | void 616 | mainThreadAction() 617 | { 618 | // run elevator 619 | building = new Building("office buildings", floorNums, 1); 620 | Thread *elev = new Thread("thread for elevator"); 621 | elev->Fork(elevatorAction, 1); 622 | // create riders 623 | Thread *t; 624 | for(int i = 0; i < threadNum; i++) { 625 | t = new Thread(getName(i + 1)); 626 | t->Fork(riderAction, i + 1); 627 | } 628 | printf("all rider has reached dest and exit\n"); 629 | } 630 | ``` 631 | 632 | > ***Code. 10*** 乘客生成算法 633 | 634 | ###### 容量有限情况 635 | 636 | ``` 637 | [PERS] No.1 1->2 request 638 | [BLDG] call up in 1 floor 639 | [BLDG] await up in 1 floor 640 | [PERS] No.2 1->5 request 641 | [BLDG] call up in 1 floor 642 | [BLDG] await up in 1 floor 643 | [PERS] No.3 1->5 request 644 | [BLDG] call up in 1 floor 645 | [PERS] No.4 1->6 request 646 | [BLDG] call up in 1 floor 647 | [BLDG] await up in 1 floor 648 | [PERS] No.5 1->2 request 649 | [BLDG] call up in 1 floor 650 | [BLDG] await up in 1 floor 651 | [ELEV] visit floor 1 652 | [BLDG] await up in 1 floor 653 | [ELEV] Desitination set to 1. 654 | [ELEV] visit floor 1 655 | [ELEV] Elevator 1 stopped at floor 1 656 | [ELEV] on floor 1 open door 657 | [PERS] some one enter on floor 1 658 | [PERS] some one enter on floor 1 659 | [PERS] some one enter on floor 1 660 | [BLDG] call up in 1 floor 661 | [BLDG] await up in 1 floor 662 | [ELEV] on floor 1 close door 663 | [PERS] request floor 2 664 | [PERS] request floor 6 665 | [PERS] request floor 5 666 | [ELEV] Desitination set to 6. 667 | [ELEV] visit floor 1 668 | [ELEV] Elevator 1 stopped at floor 1 669 | [ELEV] on floor 1 open door 670 | [BLDG] call up in 1 floor 671 | [BLDG] await up in 1 floor 672 | [BLDG] call up in 1 floor 673 | [BLDG] await up in 1 floor 674 | [ELEV] on floor 1 close door 675 | [ELEV] visit floor 1 676 | [ELEV] Elevator 1 stopped at floor 2 677 | [ELEV] on floor 2 open door 678 | [PERS] exit on floor 2 679 | [PERS] No.1 1->2 reach 680 | [ELEV] on floor 2 close door 681 | [ELEV] visit floor 2 682 | [ELEV] visit floor 3 683 | [ELEV] visit floor 4 684 | [ELEV] Elevator 1 stopped at floor 5 685 | [ELEV] on floor 5 open door 686 | [PERS] exit on floor 5 687 | [PERS] No.2 1->5 reach 688 | [ELEV] on floor 5 close door 689 | [ELEV] visit floor 5 690 | [ELEV] Elevator 1 stopped at floor 6 691 | [ELEV] on floor 6 open door 692 | [PERS] exit on floor 6 693 | [PERS] No.4 1->6 reach 694 | [ELEV] on floor 6 close door 695 | [ELEV] visit floor 6 696 | [ELEV] Desitination set to 1. 697 | [ELEV] visit floor 1 698 | [ELEV] Elevator 1 stopped at floor 1 699 | [ELEV] on floor 1 open door 700 | [PERS] some one enter on floor 1 701 | [PERS] request floor 5 702 | [PERS] some one enter on floor 1 703 | [PERS] request floor 2 704 | [ELEV] on floor 1 close door 705 | [ELEV] Desitination set to 5. 706 | [ELEV] visit floor 1 707 | [ELEV] visit floor 1 708 | [ELEV] Elevator 1 stopped at floor 2 709 | [ELEV] on floor 2 open door 710 | [PERS] exit on floor 2 711 | [PERS] No.5 1->2 reach 712 | [ELEV] on floor 2 close door 713 | [ELEV] visit floor 2 714 | [ELEV] visit floor 3 715 | [ELEV] visit floor 4 716 | [ELEV] Elevator 1 stopped at floor 5 717 | [ELEV] on floor 5 open door 718 | [PERS] exit on floor 5 719 | [PERS] No.3 1->5 reach 720 | [ELEV] on floor 5 close door 721 | 722 | Cleaning up... 723 | ``` 724 | 725 | > ***Fig. 1*** 3 容量电梯运载 5 人时的运行情况 726 | 727 | 可以看到有限容量(3人)的电梯在 1 楼同时有五人等待上行的情况下,只接受了 3 人入梯,完成运载后再返回 1 楼接起了其余两人并完成了运载。 728 | 729 | 随机生成乘客的情况可以采用形如 `./nachos -q 3 -t [乘客数] -n [楼高]` 的指令进行测试,因其输出过长,在此不再赘述。 730 | 731 | ### 四、实验中遇到的问题 732 | 733 | > 如何实现NachOS 中线程的随机切换? 734 | 735 | 在 `system.cc` 中设置 `randomYield = TRUE;` 后 NachOS 会默认启用随机切换。 736 | 737 | 同理,在执行时加上 `-rs` 参数也能达到同样的效果。 738 | 739 | > 如何控制电梯关门? 740 | 741 | 电梯的关门有两个条件,即:当电梯中的人都下去之后,在载客时电梯容量已满或是在该层等待的乘客都已进入时。 742 | 我们在电梯在开门之前统计一下该楼层可以进来乘客的数目,也就是取得阻塞在事件栅栏处的乘客数目和电梯容量剩余的人的数目之中的最小值,然后设置条件变量阻塞电梯线程,让最后一个进来的乘客去唤醒阻塞的电梯线程。 743 | 744 | ```c++ 745 | void 746 | Elevator::OpenDoors() { 747 | //let rider inside go out 748 | exit[currentfloor].Signal(); // 电梯中的人出去 749 | con_lock->Acquire(); 750 | //统计数目 751 | int waiters = b->getFloors()[currentfloor].e[direction].Waiters(); 752 | closeDoorNum = waiters > (capacity - occupancy)?(capacity - occupancy):waiters; 753 | con_lock->Release(); 754 | ..... 755 | } 756 | 757 | void 758 | Elevator::CloseDoors() { 759 | con_lock->Acquire(); 760 | while(closeDoorNum != 0){// 使用条件变量阻塞 761 | con_closeDoor->Wait(con_lock); 762 | } 763 | request[currentfloor] = false; 764 | con_lock->Release(); 765 | } 766 | 767 | void 768 | Elevator::RequestFloor(int floor) { 769 | request[floor] = true; 770 | con_lock->Acquire(); 771 | closeDoorNum--; 772 | if(closeDoorNum == 0){ //最后一个进来的乘客 唤醒等待关门的电梯 773 | con_closeDoor->Signal(con_lock); 774 | } 775 | con_lock->Release(); 776 | exit[floor].Wait(); 777 | } 778 | ``` 779 | 780 | > 什么时候设置指示当前楼层上/下行方向有乘客等待的信号灯(实验中的 `srcUp` 与 `srcDown` 布尔数组)的亮灭? 781 | 782 | 在电梯容量无限的情况下,我们将电梯对信号灯数组的灭设置放在 `closeDoor` 方法中,如下: 783 | 784 | ```c++ 785 | void 786 | Elevator::CloseDoors() { 787 | con_lock->Acquire(); 788 | while(closeDoorNum != 0){ 789 | con_closeDoor->Wait(con_lock); 790 | } 791 | b->getLock()->Acquire(); // 设置该楼层提出进电梯请求的信号 792 | if(direction == 1){ 793 | b->getSrcUp()[currentfloor] = false; 794 | } else { 795 | b->getSrcDown()[currentfloor] = false; 796 | } 797 | b->getLock()->Release(); 798 | request[currentfloor] = false; 799 | con_lock->Release(); 800 | } 801 | ``` 802 | 803 | 但是这样在容量有限的情况下会有问题:有可能乘客在因电梯已满没能进入电梯后,调用 `Building` 的 `CallUp/CallDown` 方法再次设置信号灯,然后阻塞在 `awaitUp/awaitDown` 方法中,之后电梯才执行了 `closeDoor` 方法,覆盖了没有进来的乘客的请求,于是造成 `srcUp` 与 `srcDown` 布尔数组丢失信号,电梯不会再次返回该层。 804 | 805 | 于是我们将这个设置放到了开门的时刻以解决此问题,代码如下: 806 | 807 | ```c++ 808 | void 809 | Elevator::OpenDoors() 810 | { 811 | ... 812 | //set src or srcdown , deal with problem that capacity limited 813 | b->getLock()->Acquire(); // Will there be any possible deadlock? 814 | if(direction == 1){ 815 | b->getSrcUp()[currentfloor] = false; 816 | }else{ 817 | b->getSrcDown()[currentfloor] = false; 818 | } 819 | b->getLock()->Release(); 820 | //接该楼层的人 821 | b->getFloors()[currentfloor].e[direction].Signal(); 822 | } 823 | ``` 824 | 825 | > 如何避免闹钟中尚有线程处在等待状态,但 NachOS 会因系统中没有活跃进程而停机的问题? 826 | 827 | 可以通过修改 NachOS 的机器定义或者创建一个守护线程实现。 828 | 829 | 守护线程可由闹钟原语在发现系统中已无其他活跃线程但闹钟中还有尚未到达唤醒时间的线程时创建。 830 | 831 | ### 五、实验收获与总结 832 | 833 | 本次实验为整个学期的操作系统实验课程划上了句号。通过本次实验,我们利用第二次实验中完成的锁机制与条件变量机制实现了更为强力的事件栅栏同步原语,并利用 NachOS 中模拟的 Timer 实现了闹钟原语,从而使得线程的调度控制成为可能。最后,我们利用了事件栅栏原语与闹钟原语成功模拟了一座大楼、一部有限容量电梯、若干乘客的情况下的电梯问题,顺利完成了整个实验。 834 | 835 | 在本次实验的过程中,我们深刻地体会到了有效的同步机制的设计与实现对于编写正确的并发程序的重要性。同步机制不仅提供了对多线程并行程序的共享资源的访问保护,更重要的是类似于事件栅栏这一强有力的同步原语大大减少了并发编程的困难度,也使得编程过程中逻辑错误出现的可能性大为降低。而闹钟原语则使得对线程的基本调度成为可能。 836 | 837 | 总的说来,这次实验可以看成前两次实验乃至操作系统课程整个学期大部分重难点知识的集大成者,通过这次实验我们对于操作系统中并发编程的同步问题、互斥问题、死锁问题、多任务的调度及中断机制都有了更为深刻的了解。同时,这次实验最大的意义之一在于实现了书本知识与编程实践之间的转化。虽然我们为本次实验提供的解法必然存在着诸多可以改良的地方,但随着实践经验的进一步积累,相信遇到类似的问题变体的时候我们会有更好的想法。 838 | 839 | ----- -------------------------------------------------------------------------------- /Exp_3/nachos03.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSPanda/NachOS/2c54352e936ab1ff7bbd455f355c212e1e4e47a4/Exp_3/nachos03.pdf -------------------------------------------------------------------------------- /Exp_3/system.cc: -------------------------------------------------------------------------------- 1 | // system.cc 2 | // Nachos initialization and cleanup routines. 3 | // 4 | // Copyright (c) 1992-1993 The Regents of the University of California. 5 | // All rights reserved. See copyright.h for copyright notice and limitation 6 | // of liability and disclaimer of warranty provisions. 7 | 8 | #include "copyright.h" 9 | #include "system.h" 10 | 11 | // This defines *all* of the global data structures used by Nachos. 12 | // These are all initialized and de-allocated by this file. 13 | 14 | Thread *currentThread; // the thread we are running now 15 | Thread *threadToBeDestroyed; // the thread that just finished 16 | Scheduler *scheduler; // the ready list 17 | Interrupt *interrupt; // interrupt status 18 | Statistics *stats; // performance metrics 19 | Timer *timer; // the hardware timer device, 20 | // for invoking context switches 21 | Alarm *alarms; 22 | 23 | #ifdef FILESYS_NEEDED 24 | FileSystem *fileSystem; 25 | #endif 26 | 27 | #ifdef FILESYS 28 | SynchDisk *synchDisk; 29 | #endif 30 | 31 | #ifdef USER_PROGRAM // requires either FILESYS or FILESYS_STUB 32 | Machine *machine; // user program memory and registers 33 | #endif 34 | 35 | #ifdef NETWORK 36 | PostOffice *postOffice; 37 | #endif 38 | 39 | 40 | // External definition, to allow us to take a pointer to this function 41 | extern void Cleanup(); 42 | 43 | 44 | //---------------------------------------------------------------------- 45 | // TimerInterruptHandler 46 | // Interrupt handler for the timer device. The timer device is 47 | // set up to interrupt the CPU periodically (once every TimerTicks). 48 | // This routine is called each time there is a timer interrupt, 49 | // with interrupts disabled. 50 | // 51 | // Note that instead of calling Yield() directly (which would 52 | // suspend the interrupt handler, not the interrupted thread 53 | // which is what we wanted to context switch), we set a flag 54 | // so that once the interrupt handler is done, it will appear as 55 | // if the interrupted thread called Yield at the point it is 56 | // was interrupted. 57 | // 58 | // "dummy" is because every interrupt handler takes one argument, 59 | // whether it needs it or not. 60 | //---------------------------------------------------------------------- 61 | static void 62 | TimerInterruptHandler(int dummy) 63 | { 64 | if (interrupt->getStatus() != IdleMode) 65 | interrupt->YieldOnReturn(); 66 | alarms->awake(); 67 | } 68 | 69 | //---------------------------------------------------------------------- 70 | // Initialize 71 | // Initialize Nachos global data structures. Interpret command 72 | // line arguments in order to determine flags for the initialization. 73 | // 74 | // "argc" is the number of command line arguments (including the name 75 | // of the command) -- ex: "nachos -d +" -> argc = 3 76 | // "argv" is an array of strings, one for each command line argument 77 | // ex: "nachos -d +" -> argv = {"nachos", "-d", "+"} 78 | //---------------------------------------------------------------------- 79 | void 80 | Initialize(int argc, char **argv) 81 | { 82 | int argCount; 83 | char* debugArgs = ""; 84 | bool randomYield = TRUE;// open timer whenever there has "-rs " 85 | 86 | #ifdef USER_PROGRAM 87 | bool debugUserProg = FALSE; // single step user program 88 | #endif 89 | #ifdef FILESYS_NEEDED 90 | bool format = FALSE; // format disk 91 | #endif 92 | #ifdef NETWORK 93 | double rely = 1; // network reliability 94 | int netname = 0; // UNIX socket name 95 | #endif 96 | 97 | for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount) { 98 | argCount = 1; 99 | if (!strcmp(*argv, "-d")) { 100 | if (argc == 1) 101 | debugArgs = "+"; // turn on all debug flags 102 | else { 103 | debugArgs = *(argv + 1); 104 | argCount = 2; 105 | } 106 | } else if (!strcmp(*argv, "-rs")) { 107 | ASSERT(argc > 1); 108 | RandomInit(atoi(*(argv + 1))); // initialize pseudo-random 109 | // number generator 110 | randomYield = TRUE; 111 | argCount = 2; 112 | } 113 | #ifdef USER_PROGRAM 114 | if (!strcmp(*argv, "-s")) 115 | debugUserProg = TRUE; 116 | #endif 117 | #ifdef FILESYS_NEEDED 118 | if (!strcmp(*argv, "-f")) 119 | format = TRUE; 120 | #endif 121 | #ifdef NETWORK 122 | if (!strcmp(*argv, "-l")) { 123 | ASSERT(argc > 1); 124 | rely = atof(*(argv + 1)); 125 | argCount = 2; 126 | } else if (!strcmp(*argv, "-m")) { 127 | ASSERT(argc > 1); 128 | netname = atoi(*(argv + 1)); 129 | argCount = 2; 130 | } 131 | #endif 132 | } 133 | 134 | DebugInit(debugArgs); // initialize DEBUG messages 135 | stats = new Statistics(); // collect statistics 136 | interrupt = new Interrupt; // start up interrupt handling 137 | scheduler = new Scheduler(); // initialize the ready queue 138 | alarms = new Alarm(); 139 | if (randomYield) // start the timer (if needed) 140 | timer = new Timer(TimerInterruptHandler, 0, randomYield); 141 | 142 | threadToBeDestroyed = NULL; 143 | 144 | // We didn't explicitly allocate the current thread we are running in. 145 | // But if it ever tries to give up the CPU, we better have a Thread 146 | // object to save its state. 147 | currentThread = new Thread("main"); 148 | currentThread->setStatus(RUNNING); 149 | 150 | interrupt->Enable(); 151 | CallOnUserAbort(Cleanup); // if user hits ctl-C 152 | 153 | #ifdef USER_PROGRAM 154 | machine = new Machine(debugUserProg); // this must come first 155 | #endif 156 | 157 | #ifdef FILESYS 158 | synchDisk = new SynchDisk("DISK"); 159 | #endif 160 | 161 | #ifdef FILESYS_NEEDED 162 | fileSystem = new FileSystem(format); 163 | #endif 164 | 165 | #ifdef NETWORK 166 | postOffice = new PostOffice(netname, rely, 10); 167 | #endif 168 | } 169 | 170 | //---------------------------------------------------------------------- 171 | // Cleanup 172 | // Nachos is halting. De-allocate global data structures. 173 | //---------------------------------------------------------------------- 174 | void 175 | Cleanup() 176 | { 177 | printf("\nCleaning up...\n"); 178 | #ifdef NETWORK 179 | delete postOffice; 180 | #endif 181 | 182 | #ifdef USER_PROGRAM 183 | delete machine; 184 | #endif 185 | 186 | #ifdef FILESYS_NEEDED 187 | delete fileSystem; 188 | #endif 189 | 190 | #ifdef FILESYS 191 | delete synchDisk; 192 | #endif 193 | 194 | delete timer; 195 | delete scheduler; 196 | delete interrupt; 197 | 198 | Exit(0); 199 | } 200 | 201 | -------------------------------------------------------------------------------- /Exp_3/system.h: -------------------------------------------------------------------------------- 1 | // system.h 2 | // All global variables used in Nachos are defined here. 3 | // 4 | // Copyright (c) 1992-1993 The Regents of the University of California. 5 | // All rights reserved. See copyright.h for copyright notice and limitation 6 | // of liability and disclaimer of warranty provisions. 7 | 8 | #ifndef SYSTEM_H 9 | #define SYSTEM_H 10 | 11 | #include "copyright.h" 12 | #include "utility.h" 13 | #include "thread.h" 14 | #include "scheduler.h" 15 | #include "interrupt.h" 16 | #include "stats.h" 17 | #include "timer.h" 18 | #include "Alarm.h" 19 | 20 | // Initialization and cleanup routines 21 | extern void Initialize(int argc, char **argv); // Initialization, 22 | // called before anything else 23 | extern void Cleanup(); // Cleanup, called when 24 | // Nachos is done. 25 | 26 | extern Thread *currentThread; // the thread holding the CPU 27 | extern Thread *threadToBeDestroyed; // the thread that just finished 28 | extern Scheduler *scheduler; // the ready list 29 | extern Interrupt *interrupt; // interrupt status 30 | extern Statistics *stats; // performance metrics 31 | extern Timer *timer; // the hardware alarm clock 32 | extern Alarm *alarms; // alarm for third experiment 33 | 34 | #ifdef USER_PROGRAM 35 | #include "machine.h" 36 | extern Machine* machine; // user program memory and registers 37 | #endif 38 | 39 | #ifdef FILESYS_NEEDED // FILESYS or FILESYS_STUB 40 | #include "filesys.h" 41 | extern FileSystem *fileSystem; 42 | #endif 43 | 44 | #ifdef FILESYS 45 | #include "synchdisk.h" 46 | extern SynchDisk *synchDisk; 47 | #endif 48 | 49 | #ifdef NETWORK 50 | #include "post.h" 51 | extern PostOffice* postOffice; 52 | #endif 53 | 54 | #endif // SYSTEM_H 55 | -------------------------------------------------------------------------------- /Exp_3/threadtest.cc: -------------------------------------------------------------------------------- 1 | // threadtest.cc 2 | // Simple test case for the threads assignment. 3 | // 4 | // Create two threads, and have them context switch 5 | // back and forth between themselves by calling Thread::Yield, 6 | // to illustratethe inner workings of the thread system. 7 | // 8 | // Copyright (c) 1992-1993 The Regents of the University of California. 9 | // All rights reserved. See copyright.h for copyright notice and limitation 10 | // of liability and disclaimer of warranty provisions. 11 | 12 | #include "copyright.h" 13 | #include "synch.h" 14 | #include "Elevator.h" 15 | #include 16 | #include 17 | #include "system.h" 18 | #include "dllist.h" 19 | #include "EventBarrier.h" 20 | 21 | int testnum = 1; 22 | int threadNum=1; 23 | int floorNums = 10; 24 | Building *building; 25 | extern int getRandNum(int range); 26 | Lock* dlistLock = new Lock("lock of dlist"); 27 | bool canYield = false; 28 | EventBarrier *e = new EventBarrier(); 29 | char * 30 | getName(int i) 31 | { 32 | switch(i) 33 | { 34 | case 0: return "forked thread 0"; 35 | case 1: return "forked thread 1"; 36 | case 2: return "forked thread 2"; 37 | case 3: return "forked thread 3"; 38 | case 4: return "forked thread 4"; 39 | case 5: return "forked thread 5"; 40 | case 6: return "forked thread 6"; 41 | case 7: return "forked thread 7"; 42 | case 8: return "forked thread 8"; 43 | case 9: return "forked thread 9"; 44 | case 10: return "forked thread 10"; 45 | default: 46 | return "forked thread 00"; 47 | } 48 | } 49 | 50 | //================test alarm =================== 51 | // ./nachos -q 1 -t t1(the num of test threads) 52 | void 53 | testAlarm(int which) 54 | { 55 | alarms->Pause(which*10000); 56 | printf("NO.%d thread finish its test\n",which); 57 | } 58 | 59 | //================= test barriers =============== 60 | 61 | void 62 | testBarrier(int which) 63 | { 64 | if(which % 2 == 0){ 65 | e->Wait(); 66 | printf("[BARI] No.%d pass 1 barrier\n",which); 67 | e->Complete(); 68 | e->Wait(); 69 | printf("[BARI] No.%d pass 2 barrier\n",which); 70 | e->Complete(); 71 | }else{ 72 | e->Wait(); 73 | printf("[BARI] No.%d pass 1 barrier\n",which); 74 | e->Complete(); 75 | } 76 | } 77 | 78 | 79 | 80 | void 81 | BarrieMain() 82 | { 83 | Thread *t; 84 | for(int i=0 ;i < threadNum ;i++) { 85 | t = new Thread(getName(i+1)); 86 | t->Fork(testBarrier,i+1); 87 | currentThread->Yield(); 88 | } 89 | printf("[BARI] open 1 barrier\n"); 90 | e->Signal(); 91 | currentThread->Yield(); 92 | printf("[BARI] open 2 barrier\n"); 93 | e->Signal(); 94 | } 95 | 96 | 97 | 98 | //============test elevator============== 99 | //-------------------------- 100 | // rider action 101 | //------------------ 102 | void rider(int id, int srcFloor, int dstFloor) { 103 | Elevator *e; 104 | 105 | if (srcFloor == dstFloor) 106 | return; 107 | 108 | DEBUG('t',"Rider %d travelling from %d to %d\n",id,srcFloor,dstFloor); 109 | do { 110 | if (srcFloor < dstFloor) { 111 | DEBUG('t', "Rider %d CallUp(%d)\n", id, srcFloor); 112 | building->CallUp(srcFloor); 113 | DEBUG('t', "Rider %d AwaitUp(%d)\n", id, srcFloor); 114 | e = building->AwaitUp(srcFloor); 115 | } else { 116 | DEBUG('t', "Rider %d CallDown(%d)\n", id, srcFloor); 117 | building->CallDown(srcFloor); 118 | DEBUG('t', "Rider %d AwaitDown(%d)\n", id, srcFloor); 119 | e = building->AwaitDown(srcFloor); 120 | } 121 | DEBUG('t', "Rider %d Enter()\n", id); 122 | } while (!e->Enter()); // elevator might be full! 123 | 124 | DEBUG('t', "Rider %d RequestFloor(%d)\n", id, dstFloor); 125 | e->RequestFloor(dstFloor); // doesn't return until arrival 126 | DEBUG('t', "Rider %d Exit()\n", id); 127 | e->Exit(); 128 | DEBUG('t', "Rider %d finished\n", id); 129 | } 130 | 131 | // elevator action 132 | void 133 | elevatorAction(int id) 134 | { 135 | building->RunElev(id); 136 | } 137 | 138 | void 139 | riderAction(int which) 140 | { 141 | int from = getRandNum(floorNums), to = getRandNum(floorNums); 142 | alarms->Pause( getRandNum(20) * 100); 143 | printf("[PERS] No.%d %d->%d request\n",which,from,to); 144 | rider(which,from,to); 145 | printf("[PERS] No.%d %d->%d reach\n",which,from,to); 146 | } 147 | 148 | 149 | void 150 | riderCapa(int which) 151 | { 152 | int from = 1, to = getRandNum(floorNums); 153 | alarms->Pause(1 * 100); 154 | printf("[PERS] No.%d %d->%d request\n",which,from,to); 155 | rider(which,from,to); 156 | printf("[PERS] No.%d %d->%d reach\n",which,from,to); 157 | } 158 | //--------------------- 159 | // main thread 160 | // command: ./nachos [-d t] -q 3 -t n1(the num of riders) -n n1(the num of floors) 161 | // you can set capacity on Elevator.cc file 162 | //--------------------- 163 | void 164 | mainThreadAction() 165 | { 166 | // run elevator 167 | building = new Building("office buildings",floorNums,1); 168 | Thread *elev = new Thread("thread for elevator"); 169 | elev->Fork(elevatorAction,1); 170 | // create riders 171 | Thread *t; 172 | for(int i=0 ;i < threadNum ;i++) { 173 | t = new Thread(getName(i+1)); 174 | t->Fork(riderAction,i+1); 175 | } 176 | printf("all rider has reached dest and exit\n"); 177 | } 178 | 179 | void 180 | capaTest() 181 | { 182 | // run elevator 183 | building = new Building("office buildings",floorNums,1); 184 | Thread *elev = new Thread("thread for elevator"); 185 | elev->Fork(elevatorAction,1); 186 | // create riders 187 | Thread *t; 188 | for(int i = 0; i < 5; i++) { 189 | t = new Thread(getName(i+1)); 190 | t->Fork(riderCapa, i+1); 191 | } 192 | printf("all rider has reached dest and exit\n"); 193 | } 194 | //---------------------------------------------------------------------- 195 | // ThreadTest 196 | // Invoke a test routine. 197 | //---------------------------------------------------------------------- 198 | void 199 | toDllistTest(VoidFunctionPtr func) 200 | { 201 | DEBUG('t', "Entering toDllistTest\n"); 202 | Thread *t; 203 | for(int i=0 ;i < threadNum ;i++) { 204 | t = new Thread(getName(i+1)); 205 | t->Fork(func,i+1); 206 | } 207 | } 208 | 209 | void 210 | ThreadTest() 211 | { 212 | printf("%d \n",testnum); 213 | switch (testnum) { 214 | case 1: 215 | //test barrier 216 | BarrieMain(); 217 | break; 218 | case 2: 219 | //test alarm 220 | toDllistTest(testAlarm); 221 | break; 222 | case 3: 223 | //test elevator 224 | mainThreadAction(); 225 | break; 226 | case 4: 227 | capaTest();break; 228 | default: 229 | printf("No test specified.\n"); 230 | break; 231 | } 232 | } 233 | 234 | --------------------------------------------------------------------------------