Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test thread #204

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Binary file added Images/after.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Images/before.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ OBJS = \
uart.o\
vectors.o\
vm.o\


# Cross-compiling (e.g., on Mac OS X)
# TOOLPREFIX = i386-jos-elf
Expand Down Expand Up @@ -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 $@ $^
Expand All @@ -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
Expand Down Expand Up @@ -181,6 +182,8 @@ UPROGS=\
_usertests\
_wc\
_zombie\
_Test_Thread\
_Test_Thread2\

fs.img: mkfs README $(UPROGS)
./mkfs fs.img README $(UPROGS)
Expand Down Expand Up @@ -250,7 +253,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 Test_Thread2.c\
README dot-bochsrc *.pl toc.* runoff runoff1 runoff.list\
.gdbinit.tmpl gdbutil\

Expand Down
Binary file added Pdf_Document/Kernel_Threads_Xv6.pdf
Binary file not shown.
51 changes: 0 additions & 51 deletions README

This file was deleted.

58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.

26 changes: 26 additions & 0 deletions Test_Thread.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include"types.h"
#include"user.h"
#include"stat.h"
Lock My_Lock;
void function(void* arg1,void* arg2){
int* X=(int*)arg2;
Lock_Acquire(&My_Lock);
printf(2,"Thread %d Finished with value =%d\n",(*X),2*(*X)+1);
Lock_Release(&My_Lock);
exit();
}
int main(){
int l=3;
int* size=&l;
int list[3];
printf(0,"***This Program will calculate 2x+1 for 3 threads where x is the tid passed to thread as its 2nd arg***\n");
Lock_Init(&My_Lock);
for(int i=0;i<3;i++){
list[i]=i+1;
thread_create(&function,(void*)size,(void*)&list[i]);
}
for(int i=1;i<=3;i++){
join(i);
}
exit();
}
32 changes: 32 additions & 0 deletions Test_Thread2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include"types.h"
#include"user.h"
#include"stat.h"
Lock P2_Perm,P1_Perm;
void p1(void* arg1,void* arg2){
printf(0,"A\n");
Lock_Release(&P2_Perm);
Lock_Acquire(&P1_Perm);
printf(0,"C\n");
printf(0,"B\n");
exit();
}

void p2(void* arg1,void* arg2){
Lock_Acquire(&P2_Perm);
printf(0,"F\n");
Lock_Release(&P1_Perm);
printf(0,"E\n");
printf(0,"G\n");
exit();
}
int main(){
Lock_Init(&P2_Perm);
Lock_Init(&P1_Perm);
Lock_Acquire(&P2_Perm);
Lock_Acquire(&P1_Perm);
thread_create(&p1,(void*)0,(void*)0);
thread_create(&p2,(void*)0,(void*)0);
join(1);
join(2);
exit();
}
4 changes: 4 additions & 0 deletions defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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*);
Expand Down
135 changes: 132 additions & 3 deletions proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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().
Expand Down Expand Up @@ -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.
Expand Down
Loading