diff --git a/Images/after.png b/Images/after.png new file mode 100644 index 0000000000..4555724caa Binary files /dev/null and b/Images/after.png differ diff --git a/Images/before.png b/Images/before.png new file mode 100644 index 0000000000..1893b79136 Binary files /dev/null and b/Images/before.png differ diff --git a/Makefile b/Makefile index 09d790cf63..0eaddd8b70 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ OBJS = \ uart.o\ vectors.o\ vm.o\ + # Cross-compiling (e.g., on Mac OS X) # TOOLPREFIX = i386-jos-elf @@ -143,7 +144,7 @@ tags: $(OBJS) entryother.S _init vectors.S: vectors.pl ./vectors.pl > vectors.S -ULIB = ulib.o usys.o printf.o umalloc.o +ULIB = ulib.o usys.o printf.o umalloc.o _%: %.o $(ULIB) $(LD) $(LDFLAGS) -N -e main -Ttext 0 -o $@ $^ @@ -153,7 +154,7 @@ _%: %.o $(ULIB) _forktest: forktest.o $(ULIB) # forktest has less library code linked in - needs to be small # in order to be able to max out the proc table. - $(LD) $(LDFLAGS) -N -e main -Ttext 0 -o _forktest forktest.o ulib.o usys.o + $(LD) $(LDFLAGS) -N -e main -Ttext 0 -o _forktest forktest.o ulib.o usys.o umalloc.o $(OBJDUMP) -S _forktest > forktest.asm mkfs: mkfs.c fs.h @@ -181,6 +182,7 @@ UPROGS=\ _usertests\ _wc\ _zombie\ + _Test_Thread\ fs.img: mkfs README $(UPROGS) ./mkfs fs.img README $(UPROGS) @@ -250,7 +252,7 @@ qemu-nox-gdb: fs.img xv6.img .gdbinit EXTRA=\ mkfs.c ulib.c user.h cat.c echo.c forktest.c grep.c kill.c\ ln.c ls.c mkdir.c rm.c stressfs.c usertests.c wc.c zombie.c\ - printf.c umalloc.c\ + printf.c umalloc.c Test_Thread.c\ README dot-bochsrc *.pl toc.* runoff runoff1 runoff.list\ .gdbinit.tmpl gdbutil\ diff --git a/Pdf_Document/Kernel_Threads_Xv6.pdf b/Pdf_Document/Kernel_Threads_Xv6.pdf new file mode 100644 index 0000000000..a72d57581e Binary files /dev/null and b/Pdf_Document/Kernel_Threads_Xv6.pdf differ diff --git a/README b/README deleted file mode 100644 index 923e0a4821..0000000000 --- a/README +++ /dev/null @@ -1,51 +0,0 @@ -NOTE: we have stopped maintaining the x86 version of xv6, and switched -our efforts to the RISC-V version -(https://github.com/mit-pdos/xv6-riscv.git) - -xv6 is a re-implementation of Dennis Ritchie's and Ken Thompson's Unix -Version 6 (v6). xv6 loosely follows the structure and style of v6, -but is implemented for a modern x86-based multiprocessor using ANSI C. - -ACKNOWLEDGMENTS - -xv6 is inspired by John Lions's Commentary on UNIX 6th Edition (Peer -to Peer Communications; ISBN: 1-57398-013-7; 1st edition (June 14, -2000)). See also https://pdos.csail.mit.edu/6.828/, which -provides pointers to on-line resources for v6. - -xv6 borrows code from the following sources: - JOS (asm.h, elf.h, mmu.h, bootasm.S, ide.c, console.c, and others) - Plan 9 (entryother.S, mp.h, mp.c, lapic.c) - FreeBSD (ioapic.c) - NetBSD (console.c) - -The following people have made contributions: Russ Cox (context switching, -locking), Cliff Frey (MP), Xiao Yu (MP), Nickolai Zeldovich, and Austin -Clements. - -We are also grateful for the bug reports and patches contributed by Silas -Boyd-Wickizer, Anton Burtsev, Cody Cutler, Mike CAT, Tej Chajed, eyalz800, -Nelson Elhage, Saar Ettinger, Alice Ferrazzi, Nathaniel Filardo, Peter -Froehlich, Yakir Goaron,Shivam Handa, Bryan Henry, Jim Huang, Alexander -Kapshuk, Anders Kaseorg, kehao95, Wolfgang Keller, Eddie Kohler, Austin -Liew, Imbar Marinescu, Yandong Mao, Matan Shabtay, Hitoshi Mitake, Carmi -Merimovich, Mark Morrissey, mtasm, Joel Nider, Greg Price, Ayan Shafqat, -Eldar Sehayek, Yongming Shen, Cam Tenny, tyfkda, Rafael Ubal, Warren -Toomey, Stephen Tu, Pablo Ventura, Xi Wang, Keiichi Watanabe, Nicolas -Wolovick, wxdao, Grant Wu, Jindong Zhang, Icenowy Zheng, and Zou Chang Wei. - -The code in the files that constitute xv6 is -Copyright 2006-2018 Frans Kaashoek, Robert Morris, and Russ Cox. - -ERROR REPORTS - -We don't process error reports (see note on top of this file). - -BUILDING AND RUNNING XV6 - -To build xv6 on an x86 ELF machine (like Linux or FreeBSD), run -"make". On non-x86 or non-ELF machines (like OS X, even on x86), you -will need to install a cross-compiler gcc suite capable of producing -x86 ELF binaries (see https://pdos.csail.mit.edu/6.828/). -Then run "make TOOLPREFIX=i386-jos-elf-". Now install the QEMU PC -simulator and run "make qemu". \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000000..ed7d6dff34 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# Threading Support in Xv6 + +**Authors:** Ali Momen, Mobin Barfi + +## Introduction +### What are threads? +Within a program, a Thread is a separate execution path. It is a lightweight process that the operating system can schedule and run concurrently with other threads. The operating system creates and manages threads, and they share the same memory and resources as the program that created them. This enables multiple threads to collaborate and work efficiently within a single program. Each such thread has its own CPU state and stack, but they share the address space of the process and the environment. + +### Project Overview +The aim of this project was to add threading support in xv6 via adding kernel threads. The core idea is to add 2 main system calls `Clone` and `Join`. After that, we had to implement Locks to ensure the safety of the execution of our threads. Finally, we programmed a file called `Test-Thread` to test the functionality of our program. + +## System Calls +### Clone +This call creates a new kernel thread which shares the calling process's address space. File descriptors are copied as in `fork()`. The new process uses stack as its user stack, which is passed two arguments (`arg1` and `arg2`) and uses a fake return PC (0xffffffff); a proper thread will simply call `exit()` when it is done (and not return). The stack should be one page in size and page-aligned. In our implementation of this system call, the parent would allocate 4096 bytes (1 page) of dynamic memory using `malloc` and set the pointer pointing at the child's stack to this block of memory. + +New Members added to `struct proc`: +- `int tid`: New Thread Id +- `char *tstack`: Thread stack +- `int Is-Thread`: Set to 1 if it is a thread +- `int Thread-Num`: The number of threads of a process + +### Join +The other new system call is `int join(int tid)`. This call waits for a child thread that shares the address space with the calling process to exit. It returns 1 upon success and 0 if failed. After the `join` system call, the user stack dynamically allocated by the parent will be freed. It is really good to mention that the kernel stack allocated for this thread will be freed when `exit()` is called. + +### Wrappers and a few details +In our implementation, there are 2 user functions called: +1. `int thread-create(void (*worker)(int*, int*), int* arg1, int* arg2)` +2. `int thread-join(int thread-id)` + +These 2 functions serve as wrappers to the `clone` and `join` system calls. + +Now I would like to show the changes before and after creating a thread in the virtual address space of the parent process: + +![Virtual address space before clone](./Images/before.png) +*Figure 1: Virtual address space before clone* + +![Virtual address space after clone](./Images/after.png) +*Figure 2: Virtual address space after clone* + +## Lock system +### Lock-init +For the safety of our program and conserving the logic of our code, we need to implement a lock system (spin lock). There should be a type lock that one uses to declare a lock, and two routines (`lock-acquire` and `lock-release`). One last routine, `void lock-init(lock *)`, is used to initialize the lock as need be (it should only be called by one thread). +It will set the Is_Locked member of our struct to 1 + +### Lock-acquire +The `lock-acquire(lock*)` is the function responsible for taking the lock if available if not it will loop over and over until the lock is released.Mainly this is possible using xchgl instruction written in x86.h file. + +### Lock-release +The `lock-release(lock*)` is the function responsible for releasing the lock. +It would set Is_Locked member to zero atomically using movl assembly instruction. + +## Test-Program +In our implementation, we added 2 new C files that use the user-space functions `thread-create` and `thread-join` to test the multithreaded functionality. +### Test-Thread.c +This C code is a simple multi-threaded program that calculates the expression 2x + 1 for three different threads, where x is the thread ID (tid) passed to each thread as its second argument. The program uses a simple locking mechanism to ensure that threads don't interfere with each other while accessing shared resources. +### Test-Thread2.c +This C code is a simple program that uses two locks (P2_Perm and P1_Perm) to control the order of execution between two threads (p1 and p2). The program prints a sequence of letters in a specific order by coordinating the execution of the two threads using locks. + diff --git a/Test_Thread.c b/Test_Thread.c new file mode 100644 index 0000000000..b2b2615fa5 --- /dev/null +++ b/Test_Thread.c @@ -0,0 +1,23 @@ +#include"types.h" +#include"user.h" +#include"stat.h" +void Print(void* arg1,void* arg2){ + int* X=(int*)arg2; + printf(1,"***Thread****%d\n",*X); + exit(); +} +int main(){ + int l=3; + int* size=&l; + int tid,Stat; + int list[3]; + for(int i=0;i<3;i++){ + list[i]=i+1; + tid=thread_create(&Print,(void*)size,(void*)&list[i]); + } + for(int i=1;i<=3;i++){ + Stat=thread_join(i); + } + printf(2,"%d %d",tid,Stat); + exit(); +} \ No newline at end of file diff --git a/defs.h b/defs.h index 82fb982837..d5a761d83c 100644 --- a/defs.h +++ b/defs.h @@ -120,6 +120,10 @@ void userinit(void); int wait(void); void wakeup(void*); void yield(void); +int clone(void (*)(void*,void*),void*,void*,void*); +int join(int); +//We will add clone & join syscall prototypes here + // swtch.S void swtch(struct context**, struct context*); diff --git a/proc.c b/proc.c index 806b1b184b..1c8309858f 100644 --- a/proc.c +++ b/proc.c @@ -111,7 +111,10 @@ allocproc(void) p->context = (struct context*)sp; memset(p->context, 0, sizeof *p->context); p->context->eip = (uint)forkret; - + p->Is_Thread=0; + p->Thread_Num=0; + p->tstack=0; + p->tid=0; return p; } @@ -209,7 +212,7 @@ fork(void) np->cwd = idup(curproc->cwd); safestrcpy(np->name, curproc->name, sizeof(curproc->name)); - + //np->tid=-1; pid = np->pid; acquire(&ptable.lock); @@ -246,7 +249,9 @@ exit(void) iput(curproc->cwd); end_op(); curproc->cwd = 0; - + if(curproc->tid == 0 && curproc->Thread_Num!=0) { + panic("Parent cannot exit before its children"); + } acquire(&ptable.lock); // Parent might be sleeping in wait(). @@ -495,6 +500,130 @@ kill(int pid) release(&ptable.lock); return -1; } +int clone(void (*worker)(void*,void*),void* arg1,void* arg2,void* stack) +{ + //int i, pid; + struct proc *New_Thread; + struct proc *curproc = myproc(); + uint sp,HandCrafted_Stack[3]; + // Allocate process. + if((New_Thread = allocproc()) == 0){ + return -1; + } + if(curproc->tid!=0){ + kfree(New_Thread->kstack); + New_Thread->kstack = 0; + New_Thread->state = UNUSED; + cprintf("Clone called by a thread\n"); + return -1; + } + //The new thread parent would be curproc + New_Thread->pid=curproc->pid; + New_Thread->sz=curproc->sz; + + //The tid of the thread will be determined by Number of current threads + //of a process + curproc->Thread_Num++; + New_Thread->tid=curproc->Thread_Num; + New_Thread->Is_Thread=1; + + //The parent of thread will be the process calling clone + New_Thread->parent=curproc; + + //Sharing the same virtual address space + New_Thread->pgdir=curproc->pgdir; + if(!stack){ + kfree(New_Thread->kstack); + New_Thread->kstack = 0; + New_Thread->state = UNUSED; + curproc->Thread_Num--; + New_Thread->tid=0; + New_Thread->Is_Thread=0; + cprintf("Child process wasn't allocated a stack\n"); + } + //Assuming that child_stack has been allocated by malloc + New_Thread->tstack=(char*)stack; + //Thread has the same trapframe as its parent + *New_Thread->tf=*curproc->tf; + + HandCrafted_Stack[0]=(uint)0xfffeefff; + HandCrafted_Stack[1]=(uint)arg1; + HandCrafted_Stack[2]=(uint)arg2; + + sp=(uint)New_Thread->tstack; + sp-=3*4; + if(copyout(New_Thread->pgdir, sp,HandCrafted_Stack, 3 * sizeof(uint)) == -1){ + kfree(New_Thread->kstack); + New_Thread->kstack = 0; + New_Thread->state = UNUSED; + curproc->Thread_Num--; + New_Thread->tid=0; + New_Thread->Is_Thread=0; + return -1; + } + New_Thread->tf->esp=sp; + New_Thread->tf->eip=(uint)worker; + //Duplicate all the file descriptors for the new thread + for(uint i = 0; i < NOFILE; i++){ + if(curproc->ofile[i]) + New_Thread->ofile[i] = filedup(curproc->ofile[i]); + } + New_Thread->cwd = idup(curproc->cwd); + safestrcpy(New_Thread->name, curproc->name, sizeof(curproc->name)); + acquire(&ptable.lock); + New_Thread->state=RUNNABLE; + release(&ptable.lock); + //cprintf("process running Clone has %d threads\n",curproc->Thread_Num); + return New_Thread->tid; +} +int join(int Thread_id) +{ + struct proc *p,*curproc=myproc(); + int Join_Thread_Exit=0,jtid; + if(Thread_id==0) + return -1; + for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ + if(p->tid == Thread_id && p->parent == curproc) { + Join_Thread_Exit=1; + break; + } + } + if(!Join_Thread_Exit || curproc->killed){ + //cprintf("Herere"); + return -1; + } + acquire(&ptable.lock); + for(;;){ + // thread is killed by some other thread in group + //cprintf("I am waiting\n"); + if(curproc->killed){ + release(&ptable.lock); + return -1; + } + if(p->state == ZOMBIE){ + // Found the thread + curproc->Thread_Num--; + jtid = p->tid; + kfree(p->kstack); + p->kstack = 0; + p->pgdir = 0; + p->pid = 0; + p->tid = 0; + p->tstack = 0; + p->parent = 0; + p->name[0] = 0; + p->killed = 0; + p->state = UNUSED; + release(&ptable.lock); + //cprintf("Parent has %d threads\n",curproc->Thread_Num); + return jtid; + } + + sleep(curproc, &ptable.lock); + } + //curproc->Thread_Num--; + return 0; +} //PAGEBREAK: 36 // Print a process listing to console. For debugging. diff --git a/proc.h b/proc.h index 1647114179..5ddf53ae8c 100644 --- a/proc.h +++ b/proc.h @@ -41,6 +41,10 @@ struct proc { char *kstack; // Bottom of kernel stack for this process enum procstate state; // Process state int pid; // Process ID + int tid; // Thread Id + char* tstack; // Thread stack + int Is_Thread; // if 0 --->Parent o.w child + int Thread_Num; // Keeping track of the number of threads of a process struct proc *parent; // Parent process struct trapframe *tf; // Trap frame for current syscall struct context *context; // swtch() here to run process diff --git a/string.c b/string.c index a7cc61fa66..486ed45197 100644 --- a/string.c +++ b/string.c @@ -1,6 +1,5 @@ #include "types.h" #include "x86.h" - void* memset(void *dst, int c, uint n) { diff --git a/syscall.c b/syscall.c index ee85261602..bb9805927d 100644 --- a/syscall.c +++ b/syscall.c @@ -103,7 +103,8 @@ extern int sys_unlink(void); extern int sys_wait(void); extern int sys_write(void); extern int sys_uptime(void); - +extern int sys_clone(void); +extern int sys_join(void); static int (*syscalls[])(void) = { [SYS_fork] sys_fork, [SYS_exit] sys_exit, @@ -126,6 +127,8 @@ static int (*syscalls[])(void) = { [SYS_link] sys_link, [SYS_mkdir] sys_mkdir, [SYS_close] sys_close, +[SYS_clone] sys_clone, +[SYS_join] sys_join, }; void diff --git a/syscall.h b/syscall.h index bc5f35651c..6b757d1340 100644 --- a/syscall.h +++ b/syscall.h @@ -20,3 +20,5 @@ #define SYS_link 19 #define SYS_mkdir 20 #define SYS_close 21 +#define SYS_clone 22 +#define SYS_join 23 diff --git a/sysproc.c b/sysproc.c index 0686d295b6..b0f8235eca 100644 --- a/sysproc.c +++ b/sysproc.c @@ -7,6 +7,49 @@ #include "mmu.h" #include "proc.h" +int sys_clone(void) +{ + + void (*func)(void *,void *); + char *child_stack, *arg1,*arg2; + + if(argptr(0, (char **)&func, 0) == -1) { + return -1; + } + + if(argptr(1, &arg1, 0) == -1) { + return -1; + } + + if(argptr(2, &arg2, 0) == -1) { + return -1; + } + if(argint(3, (int *)&child_stack) == -1) { + return -1; + } + + + if(myproc()->sz < (uint)arg1 + (uint)arg2) { + return -1; + } + + + if(myproc()->sz <= (uint)child_stack && + myproc()->sz < (uint)child_stack - PGSIZE) { + return -1; + } + + + return clone(func,(void*)arg1,(void*)arg2,(void *)child_stack); + +} +int sys_join(void) +{ + int Thread_id; + if(argint(0,&Thread_id)<0) + return -1; + return join(Thread_id); +} int sys_fork(void) { diff --git a/ulib.c b/ulib.c index 8e1e1a24c7..4290d0e75c 100644 --- a/ulib.c +++ b/ulib.c @@ -4,6 +4,36 @@ #include "user.h" #include "x86.h" +void Lock_Init(Lock* mutex) +{ + mutex->Is_Locked = 0; +} + +void Lock_Acquire(Lock* mutex) +{ + __sync_synchronize(); + while(xchg(&(mutex->Is_Locked), 1) != 0); + + return; +} + +void Lock_Release(Lock* mutex) +{ + asm volatile("movl $0, %0" : "+m" (mutex->Is_Locked) : ); +} + +int +thread_create(void (*worker)(void*,void*),void* arg1,void* arg2) +{ + void* Child_Stack=malloc(4096); + int Thread_id=clone(worker,arg1,arg2,Child_Stack); + return Thread_id; +} +int thread_join(int thread_id) +{ + return join(thread_id); +} + char* strcpy(char *s, const char *t) { @@ -104,3 +134,4 @@ memmove(void *vdst, const void *vsrc, int n) *dst++ = *src++; return vdst; } + diff --git a/user.h b/user.h index 4f99c52ba6..4c98b6bdd7 100644 --- a/user.h +++ b/user.h @@ -23,7 +23,8 @@ int getpid(void); char* sbrk(int); int sleep(int); int uptime(void); - +int clone(void (*)(void*,void*),void*,void*,void*); +int join(int); // ulib.c int stat(const char*, struct stat*); char* strcpy(char*, const char*); @@ -37,3 +38,18 @@ void* memset(void*, int, uint); void* malloc(uint); void free(void*); int atoi(const char*); + +//Thread library +int thread_create(void (*)(void*,void*),void*,void*); +int thread_join(int thread_id); + +//Lock System +typedef struct Lock +{ + uint Is_Locked; +} Lock; + +//Lock Functions +void Lock_Init(Lock* mutex); +void Lock_Acquire(Lock* mutex); +void Lock_Release(Lock* mutex); \ No newline at end of file diff --git a/usys.S b/usys.S index 8bfd8a1bc4..77fac402ed 100644 --- a/usys.S +++ b/usys.S @@ -29,3 +29,5 @@ SYSCALL(getpid) SYSCALL(sbrk) SYSCALL(sleep) SYSCALL(uptime) +SYSCALL(clone) +SYSCALL(join)