# 2022q1 Homework5 (quiz6) contrubuted by <yaohwang99> > [problem link](https://hackmd.io/@sysprog/linux2022-quiz6) ## Problem 1 ### Spin lock c typedef struct { volatile int lock; unsigned int locker; } spin_t;  Set lock and locker to 0 c int spin_init(spin_t *l) { volatile int out; volatile int *lck = &(l->lock); asm("movl $0x0,(%1);" : "=r"(out) : "r"(lck)); l->locker = 0; return 0; }  --- If lock is 0, swap the value of lock with the address of itself, then return. If lock is not 0, then the value is the address of itself, swap with the address of itself until the value is changed by other thread calling spin_release to set lck to 0. c int spin_acquire(spin_t *l) { int out; volatile int *lck = &(l->lock); asm("whileloop:" "xchg %%al, (%1);" "test %%al,%%al;" "jne whileloop;" : "=r"(out) : "r"(lck)); return 0; }  The instuction xchg swaps %al with (%rax), which means swap the last byte of %rax with *lck. where %rax stores the value of lck.  spin_acquire: pushq %rbp movq %rsp, %rbp movq %rdi, -24(%rbp) movq -24(%rbp), %rax movq %rax, -8(%rbp) movq -8(%rbp), %rax whileloop:xchg %al, (%rax);test %al,%al;jne whileloop; movl %eax, -12(%rbp) movl$0, %eax popq %rbp ret  --- c static inline int spin_release(spin_t *l) { int out; volatile int *lck = &(l->lock); asm("movl $0x0,(%1);" : "=r"(out) : "r"(lck)); l->locker = 0; return 0; }  :::warning By my understanding, the lock works by swaping tha least significant byte of it's address, which is not 0x00. I also don't understand why locker exists. ::: --- ### Mutex lock Mutex lock is almost the same as spin lock except that it uses system call and [futex](https://man7.org/linux/man-pages/man2/futex.2.html) to sleep (line 12) until FUTEX_WAKE (line 28). c= static __attribute__((noinline)) int mutex_acquire(mutex_t *m) { volatile int out; volatile int *lck = &(m->lock); asm("mutexloop:" "mov$1, %%eax;" "xchg %%al, (%%rdi);" "test %%al,%%al;" "je end" : "=r"(out) : "r"(lck)); syscall(SYS_futex, m, FUTEX_WAIT, 1, NULL, NULL, 0); asm("jmp mutexloop"); asm("end:"); return 0; } /** * @brief Release the lock object atomically and wake up waiting threads * @param lock Mutex Lock object */ static inline int mutex_release(mutex_t *m) { volatile int out; volatile int *lck = &(m->lock); asm("movl $0x0,(%1);" : "=r"(out) : "r"(lck)); m->locker = 0; syscall(SYS_futex, m, FUTEX_WAKE, 1, NULL, NULL, 0); return 0; }  --- ### Create thread c= /** * @brief Create a One One mapped thread * @param t Reference to the thread * @param routine Function associated with the thread * @param arg Arguments to the routine */ int thread_create(thread_t *t, void *routine, void *arg) { spin_acquire(&global_lock); static bool init_state = false; if (!t || !routine) { spin_release(&global_lock); return EINVAL; } if (!init_state) { init_state = true; init(); } node_t *node = list_insert(&tid_list, 0); if (!node) { printf("Thread address not found\n"); spin_release(&global_lock); return -1; } funcargs_t *fa = malloc(sizeof(funcargs_t)); if (!fa) { printf("Malloc failed\n"); spin_release(&global_lock); return -1; } fa->f = routine; fa->arg = arg; void *thread_stack = alloc_stack(STACK_SZ, GUARD_SZ); if (!thread_stack) { perror("thread create"); spin_release(&global_lock); free(fa); return errno; } fa->stack = thread_stack; thread_t tid = clone(wrap, (char *) thread_stack + STACK_SZ + GUARD_SZ, CLONE_FLAGS, fa, &(EXP1), NULL, &(EXP2)); node->tid_copy = tid; node->fa = fa; if (tid == -1) { perror("thread create"); free(thread_stack); spin_release(&global_lock); return errno; } *t = tid; spin_release(&global_lock); return 0; }  At line17, init() will only be called by the first cloned thread because static variables will ony be initialized once. ### clone flag c #define CLONE_FLAGS \ (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | \ CLONE_SYSVSEM | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID)  CLONE_VM: the calling process and the child process run in the same memory space. CLONE_FS: the caller and the child process share the same filesystem information. CLONE_FILES: the calling process and the child process share the same file descriptor table. CLONE_SIGHAND: the calling process and the child process share the same table of signal handlers. CLONE_THREAD: the child is placed in the same thread group as the calling process. CLONE_SYSVSEM: the child and the calling process share a single list of System V semaphore adjustment (semadj) values (see semop(2)). CLONE_CHILD_CLEARTID: Clear (zero) the child thread ID at the location pointed to by child_tid (clone()) or cl_args.child_tid (clone3()) in child memory when the child exits, and do a wakeup on the futex at that address. The address involved may be changed by the set_tid_address(2) system call. This is used by threading libraries. CLONE_PARENT_SETTID: Store the child thread ID at the location pointed to by parent_tid (clone()) or cl_args.parent_tid (clone3()) in the parent's memory. thread_create$\to$wrap$\to\$ routine c thread_t tid = clone(wrap, (char *) thread_stack + STACK_SZ + GUARD_SZ, CLONE_FLAGS, fa, &(node->tid), NULL, &(node->tid)); node->tid_copy = tid;  The above function will create a new thread with a new tid, where tid, node->tid, and node->tid_copy has the same value. :::danger Because the flag include CLONE_CHILD_CLEARTID and CLONE_PARENT_SETTID, the function should clear node->tid to 0 and set node->tid to tid and return tid. ::: --- When there are multiple reader entering the critical section, rwlock is acquired by the first reader and released by the last reader, preventing dead lock. c= static void f1(void) { mutex_acquire(&lock); if (++n_readers == 1) mutex_acquire(&rwlock); mutex_release(&lock); safe_printf(&print_lock, "Reader process in\n"); atomic_fetch_add(&n_readers_in, 1); mutex_acquire(&lock); if (--n_readers == 0) mutex_release(&rwlock); mutex_release(&lock); atomic_fetch_sub(&n_readers_in, 1); safe_printf(&print_lock, "Reader process out\n"); }  ## Problem 2 KKKK: &prev->next QQQQ: &map->bucket[] ZZZZ: &prev->next