void checkLocks(struct file *filp, osprd_info_t *d) { osprd_info_t *other; if ((other = file2osprd(filp)) != NULL) { if (pidInList(other->readLockingPids, current->pid) || pidInList(other->writeLockingPids, current->pid)) { d->holdOtherLocks = 1; return; } } }
// This function is called when a /dev/osprdX file is finally closed. // (If the file descriptor was dup2ed, this function is called only when the // last copy is closed.) static int osprd_close_last(struct inode *inode, struct file *filp) { if (filp) { osprd_info_t *d = file2osprd(filp); int filp_writable = filp->f_mode & FMODE_WRITE; // EXERCISE: If the user closes a ramdisk file that holds // a lock, release the lock. Also wake up blocked processes // as appropriate. // Your code here. osp_spin_lock(&(d->mutex)); if (!pidInList(d->writeLockingPids, current->pid) && !(pidInList(d->readLockingPids, current->pid))) { osp_spin_unlock(&(d->mutex)); return -EINVAL; } if (pidInList(d->writeLockingPids, current->pid)) { removeFromList(&(d->writeLockingPids), current->pid); } if (pidInList(d->readLockingPids, current->pid)) { removeFromList(&(d->readLockingPids), current->pid); } if (d->readLockingPids == NULL && d->writeLockingPids == NULL) { filp->f_flags &= !F_OSPRD_LOCKED; } osp_spin_unlock(&(d->mutex)); wake_up_all(&(d->blockq)); // This line avoids compiler warnings; you may remove it. (void) filp_writable, (void) d; } return 0; }
// This function is called when a /dev/osprdX file is finally closed. // (If the file descriptor was dup2ed, this function is called only when the // last copy is closed.) static int osprd_close_last(struct inode *inode, struct file *filp) { if (filp) { osprd_info_t *d = file2osprd(filp); if (d == NULL) return 1; osp_spin_lock(&(d->mutex)); // If the file hasn't locked the ramdisk, return -EINVAL if (!pidInList(d->writeLockingPids, current->pid) && !(pidInList(d->readLockingPids, current->pid))) { osp_spin_unlock(&(d->mutex)); return -EINVAL; } if (pidInList(d->writeLockingPids, current->pid)) { removeFromList(&d->writeLockingPids, current->pid); } if (pidInList(d->readLockingPids, current->pid)) { removeFromList(&d->readLockingPids, current->pid); } // Clear the lock from filp->f_flags if no processes (not just current process) hold any lock. if (d->readLockingPids == NULL && d->writeLockingPids == NULL) { filp->f_flags &= !F_OSPRD_LOCKED; } osp_spin_unlock(&(d->mutex)); wake_up_all(&(d->blockq)); } return 0; }
/* * osprd_ioctl(inode, filp, cmd, arg) * Called to perform an ioctl on the named file. */ int osprd_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { osprd_info_t *d = file2osprd(filp); // device info int r = 0; // return value: initially 0 unsigned myTicket; // is file open for writing? int filp_writable = (filp->f_mode & FMODE_WRITE) != 0; // This line avoids compiler warnings; you may remove it. (void) filp_writable, (void) d; // Set 'r' to the ioctl's return value: 0 on success, negative on error if (cmd == OSPRDIOCACQUIRE) { // EXERCISE: Lock the ramdisk. // // If *filp is open for writing (filp_writable), then attempt // to write-lock the ramdisk; otherwise attempt to read-lock // the ramdisk. // // This lock request must block using 'd->blockq' until: // 1) no other process holds a write lock; // 2) either the request is for a read lock, or no other process // holds a read lock; and // 3) lock requests should be serviced in order, so no process // that blocked earlier is still blocked waiting for the // lock. // // If a process acquires a lock, mark this fact by setting // 'filp-void checkLocks(struct file *filp, osprd_info_t *d)>f_flags |= F_OSPRD_LOCKED'. You also need to // keep track of how many read and write locks are held: // change the 'osprd_info_t' structure to do this. // // Also wake up processes waiting on 'd->blockq' as needed. // // If the lock request would cause a deadlock, return -EDEADLK. // If the lock request blocks and is awoken by a signal, then // return -ERESTARTSYS. // Otherwise, if we can grant the lock request, return 0. // 'd->ticket_head' and 'd->ticket_tail' should help you // service lock requests in order. These implement a ticket // order: 'ticket_tail' is the next ticket, and 'ticket_head' // is the ticket currently being served. You should set a local // variable to 'd->ticket_head' and increment 'd->ticket_head'. // Then, block at least until 'd->ticket_tail == local_ticket'. // (Some of these operations are in a critical section and must // be protected by a spinlock; which ones?) /* TUAN: Ticket is used to service lock requests in order. Each process maintains a unique local_ticket, starting at ticket_head. To obtain a unique local ticket, each process atomically set its local_ticket equal to ticket_head and then incremenet the ticket_head. Whichever process grasps the lock, it will get the next value of ticket_head and again atomically increments ticket_head. Eventually, we have a list of processes with ticket 0, 1, 2, 3. ticket_tail starts at 0. A process cannot obtain the lock on the RAM disk if its local ticket does not match ticket_tail. Since process 0 has local_ticket equal to ticket_tail which is 0, only process 0 can obtain the lock. Before process 0 releases the lock, it increments ticket_tail by 1 and release the lock. Since now process 1 has its local_ticket equal to ticket_tail which is now 1, it can grasb the lock... */ //requested a WRITE lock if (filp_writable) { //get a ticket osp_spin_lock(&(d->mutex)); myTicket = d->ticket_head; d->ticket_head++; //Check for deadlock - if I have previous read lock and will have to wait if (pidInList(d->readLockingPids, current->pid)) { osp_spin_unlock(&(d->mutex)); return -EDEADLK; } /* TUAN: It is considered deadlock to request the same write lock that you already hold in your current RAM disk. */ if (pidInList(d->writeLockingPids, current->pid)) { osp_spin_unlock(&(d->mutex)); return -EDEADLK; } osp_spin_unlock(&(d->mutex)); /* TUAN: wait_event_interruptible. The first argument is the wait queue. The second argument is the condition to wake up. The process wakes up when the condition is true or a signal is received. The function returns 0 if the condition is true. Return -ERESTARTSYS if a signal is received. */ //block until all conditions are met if (wait_event_interruptible(d->blockq, d->ticket_tail==myTicket && d->writeLockingPids == NULL && d->readLockingPids == NULL)) { //I encountered a signal, return error condition if (d->ticket_tail == myTicket) { grantTicketToNextAliveProcessInOrder(d); //Tuan define this } else { //mark my ticket as not usable before process exits addToTicketList(&(d->exitedTickets), myTicket); //TUAN: this is important because when other process grants the ticket //It makes sure it not grant the ticket to processes that already exited. //It do that by incrementing ticket_tail and make sure ticket_tail not //match the already exited ticket. } return -ERESTARTSYS; //TUAN: means your system call is restartable. The process is considered exited/died. } //if I arrive here, I have the ticket to proceed, and no one else holds a read or write lock osp_spin_lock(&(d->mutex)); //claim the lock officially filp->f_flags |= F_OSPRD_LOCKED; //TUAN: we keep track of the ID processes that are holding the write lock //Later on, to detect deadlock for the current process, we look up this list //to see if we already have the read or write lock there. addToList(&(d->writeLockingPids), current->pid); //TUAN: find next usable ticket number so that the next in-order alive process can use grantTicketToNextAliveProcessInOrder(d); osp_spin_unlock(&(d->mutex)); wake_up_all(&(d->blockq)); //TUAN: wait up all processes in the wait queue d->blockq and evaluate the condition //in wait_event_interruptable for those processes that go to sleep when invoking this function. return 0; } //requested a READ lock else { //get a ticket osp_spin_lock(&(d->mutex)); myTicket = d->ticket_head; d->ticket_head++; //Check for deadlock - if I have previous write lock and will have to wait if (pidInList(d->writeLockingPids, current->pid)) { osp_spin_unlock(&(d->mutex)); return -EDEADLK; } /* TUAN: It is considered deadlock to request the same read lock that you already hold in your current RAM disk. */ if (pidInList(d->readLockingPids, current->pid)) { osp_spin_unlock(&(d->mutex)); return -EDEADLK; } osp_spin_unlock(&(d->mutex)); //block until all conditions are met if (wait_event_interruptible(d->blockq, d->ticket_tail==myTicket && d->writeLockingPids == NULL)) { //I encountered a signal, return error condition if (d->ticket_tail == myTicket) { grantTicketToNextAliveProcessInOrder(d); } else { //add my ticket to non-usable ticket numbers addToTicketList(&(d->exitedTickets), myTicket); } return -ERESTARTSYS; } //if I arrive here, I have the ticket to proceed, and no one else holds a read or write lock osp_spin_lock(&(d->mutex)); //claim the lock officially filp->f_flags |= F_OSPRD_LOCKED; addToList(&(d->readLockingPids), current->pid); //TUAN: find next usable ticket number so that the next in-order alive process can use grantTicketToNextAliveProcessInOrder(d); osp_spin_unlock(&(d->mutex)); wake_up_all(&(d->blockq)); //TUAN: wake up all the processes that are waiting for the ticket //by setting the processes in the run queue to runnable state. return 0; } } else if (cmd == OSPRDIOCTRYACQUIRE) { // EXERCISE: ATTEMPT to lock the ramdisk. // // This is just like OSPRDIOCACQUIRE, except it should never // block. If OSPRDIOCACQUIRE would block or return deadlock, // OSPRDIOCTRYACQUIRE should return -EBUSY. // Otherwise, if we can grant the lock request, return 0. // Your code here (instead of the next two lines). // WRITE lock if (filp_writable) { // Get a ticket osp_spin_lock(&(d->mutex)); myTicket = d->ticket_head; d->ticket_head++; // Check for deadlock if (pidInList(d->readLockingPids, current->pid)) { grantTicketToNextAliveProcessInOrder(d); osp_spin_unlock(&(d->mutex)); return -EBUSY; } for_each_open_file(current, checkLocks, d); if (d->holdOtherLocks) { d->holdOtherLocks = 0; grantTicketToNextAliveProcessInOrder(d); osp_spin_unlock(&(d->mutex)); return -EBUSY; } if (pidInList(d->writeLockingPids, current->pid)) { grantTicketToNextAliveProcessInOrder(d); osp_spin_unlock(&(d->mutex)); return -EINVAL; } osp_spin_unlock(&(d->mutex)); if (d->writeLockingPids != NULL || d->readLockingPids != NULL) { grantTicketToNextAliveProcessInOrder(d); return -EBUSY; } osp_spin_lock(&(d->mutex)); filp->f_flags |= F_OSPRD_LOCKED; addToList(&(d->writeLockingPids), current->pid); grantTicketToNextAliveProcessInOrder(d); osp_spin_unlock(&(d->mutex)); wake_up_all(&(d->blockq)); return 0; } else { osp_spin_lock(&(d->mutex)); myTicket = d->ticket_head; d->ticket_head++; if (pidInList(d->writeLockingPids, current->pid)) { grantTicketToNextAliveProcessInOrder(d); osp_spin_unlock(&(d->mutex)); return -EBUSY; } for_each_open_file(current, checkLocks, d); if (d->holdOtherLocks) { d->holdOtherLocks = 0; grantTicketToNextAliveProcessInOrder(d); osp_spin_unlock(&(d->mutex)); return -EBUSY; } if (pidInList(d->readLockingPids, current->pid)) { grantTicketToNextAliveProcessInOrder(d); osp_spin_unlock(&(d->mutex)); return -EINVAL; } osp_spin_unlock(&(d->mutex)); if (d->writeLockingPids != NULL) { grantTicketToNextAliveProcessInOrder(d); return -EBUSY; } osp_spin_lock(&(d->mutex)); filp->f_flags |= F_OSPRD_LOCKED; addToList(&(d->readLockingPids), current->pid); grantTicketToNextAliveProcessInOrder(d); osp_spin_unlock(&(d->mutex)); wake_up_all(&(d->blockq)); return 0; } } else if (cmd == OSPRDIOCRELEASE) { // EXERCISE: Unlock the ramdisk. // // If the file hasn't locked the ramdisk, return -EINVAL. // Otherwise, clear the lock from filp->f_flags, wake up // the wait queue, perform any additional accounting steps // you need, and return 0. // Your code here (instead of the next line). osp_spin_lock(&(d->mutex)); // TUAN: If the file hasn't locked the ramdisk, return -EINVAL if (!pidInList(d->writeLockingPids, current->pid) && !(pidInList(d->readLockingPids, current->pid))) { osp_spin_unlock(&(d->mutex)); return -EINVAL; } if (pidInList(d->writeLockingPids, current->pid)) { removeFromList(&(d->writeLockingPids), current->pid); } if (pidInList(d->readLockingPids, current->pid)) { removeFromList(&(d->readLockingPids), current->pid); } // TUAN: Clear the lock from filp->f_flags if no processes (not just current process) hold any lock. if (d->readLockingPids == NULL && d->writeLockingPids == NULL) { filp->f_flags &= !F_OSPRD_LOCKED; } osp_spin_unlock(&(d->mutex)); wake_up_all(&(d->blockq)); return 0; } else r = -ENOTTY; /* unknown command */ return r; }