diff --git a/course/08-Task_management.md b/course/08-Task_management.md
new file mode 100644
index 0000000000000000000000000000000000000000..7e835f0f762fd4cfca79ad53bb615b76b904f6fa
--- /dev/null
+++ b/course/08-Task_management.md
@@ -0,0 +1,517 @@
+---
+author: Florent Gluck - Florent.Gluck@hesge.ch
+
+title: Task Management
+
+date: \vspace{.5cm} \footnotesize \today
+
+pandoc-latex-fontsize:
+  - classes: [tiny]
+    size: tiny
+  - classes: [verysmall]
+    size: scriptsize
+  - classes: [small]
+    size: footnotesize
+  - classes: [huge, important]
+    size: huge
+---
+
+[//]: # ----------------------------------------------------------------
+## User tasks and task isolation
+
+- We would like:
+  - tasks to have their own address space, isolated from other tasks
+  - tasks to be forbidden to access any kernel code or data
+  - tasks to have access to specific memory areas, e.g.:
+    - shared pages between tasks
+    - framebuffer
+    - ...
+
+- How to provide task isolation with the above requirements?
+
+[//]: # ----------------------------------------------------------------
+## Task isolation: what we wish for
+
+\centering
+![](images/tasks_and_kernel_addr_spaces_1.png){ height=80% }
+
+[//]: # ----------------------------------------------------------------
+## Task isolation: a more realistic view (1/2)
+
+\centering
+![](images/tasks_and_kernel_addr_spaces_2.png){ height=100% }
+
+[//]: # ----------------------------------------------------------------
+## Task isolation: a more realistic view (2/2)
+
+\centering
+![](images/tasks_and_kernel_addr_spaces_2.png){ height=70% }
+
+\vfill
+
+**Why** map the RAM and kernel in each task's address space?
+
+[//]: # ----------------------------------------------------------------
+## Structure of a task
+
+- The IA-32 architecture implements hardware task switching
+  - CPU automatically saves and restores a task's context during a task switch
+
+- A hardware task is composed of two parts:
+  1) The task **memory space**
+     - defined by the task's page directory, stored in RAM
+  1) The task **state** or task **context**
+     - defined by the Task-State Segment (TSS), stored in RAM
+
+[//]: # ----------------------------------------------------------------
+## Task-State Segment (TSS)
+
+- A TSS defines a task's state or context:
+  - the content of all CPU registers
+    - the instruction pointer (`eip`)
+    - the stack pointer (`esp`)
+    - the page directory (`cr3`) which defines the task's adress space
+    - etc.
+  - ports (PMIO I/O) permissions for the task
+  - a link to the previous task
+  - stack pointers to each privilege level (ring 0, 1, 2, 3)
+- The TSS is a data structure that resides in RAM
+
+[//]: # ----------------------------------------------------------------
+## TSS structure
+
+\centering
+![](images/tss.png){ width=60% }
+
+[//]: # ----------------------------------------------------------------
+## Current task
+
+- The CPU has a task register named `tr`
+- The `tr` register tells the CPU which is the current task
+  - `tr` must be programmed to "point" to a TSS
+  - used to save the CPU current context into the TSS before switching to another task
+
+[//]: # ----------------------------------------------------------------
+## Task handling
+
+- How to create a task?
+- How to switch to a task?
+- What happens during a task switch?
+
+[//]: # ----------------------------------------------------------------
+## Creating a new task
+
+To create a task, we must:
+
+- Define a TSS structure for it:
+  - **explicitly** used to setup the initial state of the task, before running it for the first time
+  - **implicitly** used by the CPU to save/restore the task' state during each task switch
+
+- \textcolor{myred}{Fields of the task's TSS structure must be initialized to \textbf{correct and sensible} values!}
+
+[//]: # ----------------------------------------------------------------
+## TSS structure initialization
+
+\footnotesize
+
+--------- ------------------------------------------------------------------
+`eip`     points to the virtual address of the task's first instruction
+`esp`     points to the virtual address of the task's top of the stack
+`eflags`  0, except for bit 9 enabled (hardware interrupts unmasked)
+`cs`      sets to the user code selector (`GDT_USER_CODE_SELECTOR`)
+`ds,es`   sets to the user data selector (`GDT_USER_DATA_SELECTOR`)
+`fs,gs`   sets to the user data selector (usually never used)
+`ss0`     sets to the kernel data selector (`GDT_KERNEL_DATA_SELECTOR`)
+`esp0`    points to the top of the task's kernel stack
+`cr3`     points to the page directory that defines the task's address space
+          **important**: the address **must** be aligned to a page (4KB)!
+`...`     all other fields should be set to 0
+--------- ------------------------------------------------------------------
+
+[//]: # ----------------------------------------------------------------
+## Switching to a task (1/2)
+
+How to switch to a task?
+
+Several possible ways to switch to a task:
+
+(1) \textcolor{myblue}{Explicit task switch, to a \textbf{nested} task, using the} `call far` \textcolor{myblue}{instruction}
+(1) Explicit task switch using the `jmp far` instruction
+(1) Implicit task switch using the `iret` instruction (when `NT` flag (NT = Nested Task) from EFLAGS is 1)
+(1) Implicit task switch when an interrupt is triggered via a **call gate**
+
+[//]: # ----------------------------------------------------------------
+## Switching to a task (2/2)
+
+- Here, we will describe how to switch to a nested task by calling the `call far` instruction
+- Very simple: one simply executes the `call far` instruction with the TSS selector of the task to switch to in argument:
+
+  ```{.assembler .small}
+  ; switch to the task referenced by
+  ; the specified tss_selector
+  call far <TSS selector>
+  ```
+
+[//]: # ----------------------------------------------------------------
+## Task switching: steps (simplified)
+
+\small
+
+The CPU automatically peforms these operations during a task switch to a \textcolor{mygreen}{new task}:
+
+1) Reads the `tr` register to obtain the \textcolor{myorange}{current task}'s TSS
+1) Saves the current CPU context into the \textcolor{myorange}{current task}'s TSS
+1) Using the \textcolor{mygreen}{new task}'s TSS selector, loads the `tr` register to point to the \textcolor{mygreen}{new task}'s TSS
+1) Loads the \textcolor{mygreen}{new task}'s context (from its TSS) into the CPU
+   - includes `cr3` = \textcolor{mygreen}{new task}'s address space (= memory mapping)
+   - `cr3` points to the \textcolor{mygreen}{new task}'s page directory
+1) Resumes the execution of the \textcolor{mygreen}{new task} (from the restored `eip`)
+
+[//]: # ----------------------------------------------------------------
+## Switching back to the previous task
+
+\footnotesize
+
+Switching back to the \textcolor{mypurple}{previous task} is trivial:
+
+- Execute the `iret` assembly instruction
+
+What happens (simplified) when the CPU executes `iret`?
+
+1) Reads the `tr` register to obtain the \textcolor{mygreen}{current task}'s TSS
+1) Saves the current CPU context into the \textcolor{mygreen}{current task}'s TSS
+1) Loads the `tr` register with the value of the "Previous Task Link" field of the \textcolor{mygreen}{current task}'s TSS
+1) Loads the \textcolor{mypurple}{previous task}'s context (from its TSS) into the CPU
+   - includes `cr3` = \textcolor{mypurple}{previous task}'s address space
+1) Resumes the execution of the \textcolor{mypurple}{previous task}
+
+[//]: # ----------------------------------------------------------------
+## Stack switching
+
+- A stack switch occurs during a task switch from a **lesser privilege level to a higher** privilege level
+  - E.g. \textcolor{mygreen}{ring 3} $\rightarrow$ \textcolor{myred}{ring 0}: `ss` and `esp` are loaded from the current task's `TSS.ss0` and `TSS.esp0` values
+- Typically happens when:
+  - a task (user mode) performs a system call (\textcolor{mygreen}{ring 3} $\rightarrow$ \textcolor{myred}{ring 0})
+  - user code (\textcolor{mygreen}{ring 3}) is interrupted by a hardware interrupt or a processor exception
+
+[//]: # ----------------------------------------------------------------
+## Back to task switching
+
+- As previously seen, the CPU can switch to a task using a TSS **selector**:
+  ```{.assembler .small}
+  call far <TSS selector>
+  ```
+- What is a TSS **selector**?
+
+[//]: # ----------------------------------------------------------------
+## Global Descriptor Table (GDT)
+
+- The Global Descriptor Table (GDT) is a CPU data structure that resides in RAM, similar to the IDT for interrupts
+- The GDT contains 64-bit entries called **segment descriptors**
+- Each segment descriptor defines a contiguous memory segment, its type and privilege:
+  - **base**: defines where the memory segment starts in physical memory
+  - **limit**: the length of the segment
+  - type: the segment type (TSS, data, code, etc.)
+  - privilege: what ring level can access it
+- In other words, a descriptor tells the CPU where to find a segment in RAM and perform a size and security check
+
+[//]: # ----------------------------------------------------------------
+## Segment descriptors
+
+- The CPU supports several types of segment descriptors:
+  - data segment descriptor
+  - code segment descriptor
+  - **task state segment (TSS) descriptor**
+  - etc.
+
+[//]: # ----------------------------------------------------------------
+## Tasks, GDT, descriptors and selectors
+
+\small
+
+- As previously seen, every task requires a TSS stored in RAM
+- The CPU must know where to find a task's TSS and control its access (security)
+- Solution: each TSS is **referenced** in the GDT through the use of a **TSS segment descriptor**
+  - this descriptor defines where the task's TSS resides
+  - this descriptor defines the task's access control (security)
+- The reference to the TSS segment descriptor is called a **selector**
+- This **TSS selector** is what is used to switch to a task
+- \textcolor{myred}{The \textbf{selector for a given TSS descriptor} in the GDT, is its offset in bytes in the GDT}
+  - given a descriptor is 64 bits (8 bytes), the selector is the descriptor's index (in the GDT) multiplied by 8
+
+[//]: # ----------------------------------------------------------------
+## GDT example
+
+Example of a GDT defining 3 tasks:
+
+\vfill
+
+\centering
+![](images/GDT.png){ height=65% }
+
+[//]: # ----------------------------------------------------------------
+## Task structures overview
+
+\setlength{\parindent}{-.9cm}
+![](images/tasks_overview.png){ width=118% }
+
+
+[//]: # ----------------------------------------------------------------
+## Task switching revisited: in-depth steps
+
+\footnotesize
+
+The CPU automatically peforms these operations during a task switch:
+
+1) Reads the `tr` register to obtain the current task's TSS
+1) \textcolor{mygreen}{Checks that the current task is authorized to switch to the new task}
+1) \textcolor{mygreen}{Checks the TSS descriptor of the new task is present and has a valid limit}
+1) Saves the current CPU context into the current task's TSS
+1) Using the new task's TSS selector, loads the `tr` register to point to the new task's TSS
+1) \textcolor{mygreen}{Writes the calling task's TSS selector into the "Previous Task Link" field of the new task's TSS}
+1) Loads the new task's context (from its TSS) into the CPU
+   - includes `cr3` = new task's address space (= memory mapping)
+   - `cr3` points to the new task's page directory
+1) Resumes the execution of the new task (from the restored `eip`)
+
+[//]: # ----------------------------------------------------------------
+## Task switching: important notes
+
+- The task register, `tr`, is loaded using the `ltr` instruction
+  - requires a TSS selector as operand
+- **Before** switching to the first task, an **initial TSS must be set!**
+  - \textcolor{myred}{required to save the current CPU context}
+- The GDT **must** have been loaded **before** loading the `tr` register
+  - `ltr` requires a TSS selector which implies a properly initialized and loaded GDT
+
+[//]: # ----------------------------------------------------------------
+## Task initialization, static data, done once
+
+\footnotesize
+
+1. Reserve a GDT entry (TSS descriptors) for the initial TSS 
+   - used to save the CPU context **before** switching to the first task
+   - to be done **once**
+1. Initialize the initial TSS
+1. Obtain the TSS selector from the initial TSS
+1. Loads the task register with the initial TSS selector
+1. Reserve a GDT entry (TSS descriptors) for task T
+1. Allocate a data structures for task T:
+   - \scriptsize page directory
+   - \scriptsize TSS structure and TSS selector
+   - \scriptsize kernel stack
+   - \scriptsize anything else that might be useful
+1. Initialize T's page directory with desired memory mappings, typically:
+   - \scriptsize framebuffer (identity, user privileged)
+   - \scriptsize RAM (identity, kernel privileged)
+
+[//]: # ----------------------------------------------------------------
+## Task load workflow (1/3)
+
+- User application code and data are compiled into flat binary files loaded by GRUB as multiboot modules
+- How to "load" and prepare a task (app) for execution?
+
+[//]: # ----------------------------------------------------------------
+## Task load workflow (2/3)
+
+\small
+
+1. Initialize task T's data structures:
+   - \footnotesize initialize T's TSS descriptor (so it points to T's TSS)
+   - \footnotesize compute T's TSS selector
+   - \footnotesize initialize T's context (TSS structure)
+1. Initialize T's page directory with memory mapping where app (code + data) will be loaded:
+   - \footnotesize allocate frames to store app + stack
+   - \footnotesize contiguously map these frames at the desired virtual address
+1. \textcolor{myorange}{Copy the app (multiboot module) into T's allocated content}
+
+[//]: # ----------------------------------------------------------------
+## Task load workflow (3/3)
+
+\textcolor{myorange}{Copy the app (multiboot module) into T's allocated content}
+
+\footnotesize
+
+- In kernel address space, the task's allocated content might be scattered into **non-contiguous** frames
+
+**How** to easily copy the app content into these non-contiguous frames?
+
+- Use the task memory mapping as it **ensures a contiguous** memory space!
+
+**How?**
+
+- Save the current memory mapping (= page dir) as we'll modify it
+- Load the new task's address space (page dir) to allow the current task to write the app content into the new task's address space
+- Copy the app content into the new task's address space
+- Restore the previously saved memory mapping (= page dir)
+
+[//]: # ----------------------------------------------------------------
+## Task address space creation (1/7)
+
+![](images/task_create1.png){ width=100% }
+
+[//]: # ----------------------------------------------------------------
+## Task address space creation (2/7)
+
+![](images/task_create2.png){ width=100% }
+
+[//]: # ----------------------------------------------------------------
+## Task address space creation (3/7)
+
+![](images/task_create3.png){ width=100% }
+
+[//]: # ----------------------------------------------------------------
+## Task address space creation (4/7)
+
+![](images/task_create4.png){ width=100% }
+
+[//]: # ----------------------------------------------------------------
+## Task address space creation (5/7)
+
+![](images/task_create5.png){ width=100% }
+
+[//]: # ----------------------------------------------------------------
+## Task address space creation (6/7)
+
+![](images/task_create6.png){ width=100% }
+
+[//]: # ----------------------------------------------------------------
+## Task address space creation (7/7)
+
+![](images/task_create7.png){ width=100% }
+
+[//]: # ----------------------------------------------------------------
+## Task execution workflow
+
+1. The kernel switches to a new task by using the new task's TSS selector
+1. A task is completed once it executes the `iret` instruction
+  - the CPU then resumes the execution of the parent task
+1. Upon task completion, the kernel must free all frame allocated by the completed task
+
+[//]: # ----------------------------------------------------------------
+## Simplified example: task setup and task switch (1/3)
+
+\fontsize{7}{7}
+```{.c}
+// Virtual address (1GB) where user task code/data is mapped
+// (i.e. application entry point)
+#define TASK_VIRT_ADDR 0x40000000
+#define TASK_SIZE 4000
+
+// A task is made of:
+// - A page directory
+// - A TSS structure to save its state (context)
+// - A TSS selector which references a TSS descriptor in the GDT
+// - A kernel stack
+typedef struct {
+	PDE_t pagedir[PAGETABLES_IN_PD] __attribute__((aligned(4096)));
+	tss_t tss;
+	uint16_t tss_selector;
+	uint8_t kernel_stack[65536];
+} task_t;
+
+// Task's code (simply writes some white pixels).
+static void task_code() {
+    uint16_t *fb = (uint16_t *)0xFE000000;
+    for (int i = 0; i < 640*240; i++) {
+        fb[i] = 0xFFFF;  // white
+    }
+    iret();
+}
+```
+
+[//]: # ----------------------------------------------------------------
+## Simplified example: task setup and task switch (2/3)
+
+\fontsize{6}{6}
+```{.c}
+static tss_t initial_tss;
+static uint8_t initial_tss_kernel_stack[65536];
+
+static void task_setup_initial_tss() {
+    extern gdt_entry_t *gdt_initial_tss;
+
+    // Entry for initial kernel TSS (CPU state of first task saved there)
+    *gdt_initial_tss = gdt_make_tss(&initial_tss, DPL_KERNEL);
+    memsetb(&initial_tss, 0, sizeof(tss_t));
+    initial_tss.ss0 = GDT_KERNEL_DATA_SELECTOR;
+    initial_tss.esp0 = ((uint32_t)initial_tss_kernel_stack) + sizeof(initial_tss_kernel_stack);
+    initial_tss.cr3 = (uint32_t)paging_get_current_pagedir();
+
+    // Loads the task register to point to the initial TSS selector.
+    // The GDT must already be loaded before loading the task register!
+    task_ltr(gdt_entry_to_selector(gdt_initial_tss));  // Implemented in task_asm.s
+}
+
+void task_example() {
+    task_setup_initial_tss();
+
+    static task_t task;
+    extern gdt_entry_t *gdt_first_free_entry;
+    task_create(&task, gdt_first_free_entry);
+
+    task_switch(task.tss_selector);  // Implemented in task_asm.s
+}
+```
+
+[//]: # ----------------------------------------------------------------
+## Simplified example: task setup and task switch (3/3)
+
+\fontsize{5}{5}
+```{.c}
+// t: output argument where the created task is returned.
+// gdt_task_tss: output argument filled with the task's TSS descriptor.
+static void task_create(task_t* t, gdt_entry_t *gdt_task_tss) {
+    memset(t, 0, sizeof(task_t));  // Clears the whole task_t structure
+
+    // Adds the task's TSS to the GDT and stores its selector
+    int gdt_tss_sel = gdt_entry_to_selector(gdt_task_tss);
+    *gdt_task_tss = gdt_make_tss(&t->tss, DPL_KERNEL);
+    t->tss_selector = gdt_tss_sel;
+
+    // Sets up the task's initial context
+    tss_t *tss = &t->tss;
+    tss->cs = GDT_USER_CODE_SELECTOR;  // user code segment
+    tss->ds = tss->es = tss->fs = tss->gs = tss->ss = GDT_USER_DATA_SELECTOR;
+    tss->cr3 = (uint32_t)t->pagedir;  // task's page directory
+    tss->eflags = (1 << 9);  // enables hardware interrupts (bit 9)
+    tss->ss0 = GDT_KERNEL_DATA_SELECTOR;  // task's kernel stack segment
+    tss->esp0 = (uint32_t)(t->kernel_stack)+sizeof(t->kernel_stack); // task's kernel stack ptr
+    tss->eip = TASK_VIRT_ADDR;  // task's instruction ptr
+    tss->esp = tss->ebp = TASK_VIRT_ADDR + TASK_SIZE;  // task's stack and stack frame ptr
+
+    // Identity maps available RAM so kernel code can access its own code/data
+    paging_mmap(t->pagedir, 0, 0, RAM_in_KB()*1024, PRIVILEGE_KERNEL, ACCESS_READWRITE);
+
+    // Identity maps framebuffer so task code can access it
+    paging_mmap(t->pagedir, (uint32_t)framebuffer->addr, (uint32_t)framebuffer->addr, framebuffer->size, PRIVILEGE_USER, ACCESS_READWRITE);
+
+    // Allocates task address space (content is zeroed) and copies its content into it.
+    // Task's virtual address space is located at TASK_VIRT_ADDR.
+    void *task_code_frame = frame_alloc();
+    memcpyb(task_code_frame, task_code, 1024);  // (over) copies content of task_code()
+    paging_mmap(t->pagedir, TASK_VIRT_ADDR, (uint_t)task_code_frame, PAGE_SIZE, PRIVILEGE_USER, ACCESS_READWRITE);
+}
+```
+
+[//]: # ----------------------------------------------------------------
+## Resources
+
+\small
+
+- Operating Systems: Three Easy Pieces, Remzi H. and Andrea C. Arpaci-Dusseau. Arpaci-Dusseau Books\
+\footnotesize [\textcolor{myblue}{http://pages.cs.wisc.edu/~remzi/OSTEP/}](http://pages.cs.wisc.edu/~remzi/OSTEP/)
+
+\small
+
+- Intel IA32-Task Management\
+\footnotesize [\textcolor{myblue}{https://www.cs.umd.edu/users/hollings/cs412/s02/proj1/ia32ch7.pdf}](https://www.cs.umd.edu/users/hollings/cs412/s02/proj1/ia32ch7.pdf)
+
+\small
+
+- "Managing Tasks on x86 Processors" by Jim Turley\
+\footnotesize [\textcolor{myblue}{https://www.embedded.com/managing-tasks-on-x86-processors/}](https://www.embedded.com/managing-tasks-on-x86-processors/)
+
+\small
+
+- Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 3: System Programming Guide, in the course's git repository's `resources` folder
diff --git a/course/08-Task_management.pdf b/course/08-Task_management.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..008062e6af4b7e03a7c47627e338bc6d0f18c2c4
Binary files /dev/null and b/course/08-Task_management.pdf differ
diff --git a/course/images/tasks_and_kernel_addr_spaces_1.odg b/course/images/tasks_and_kernel_addr_spaces_1.odg
index f0fde77aa5d5c3e330613abb4f650f17b1c8f58b..212e2e13e0eef7ccaadc389f5b14ba486604eed3 100644
Binary files a/course/images/tasks_and_kernel_addr_spaces_1.odg and b/course/images/tasks_and_kernel_addr_spaces_1.odg differ
diff --git a/course/images/tasks_and_kernel_addr_spaces_1.png b/course/images/tasks_and_kernel_addr_spaces_1.png
index 86b385beb41d1724c5f66be0339d864aa6ad24c9..8c77f079d9f0e74d810766168697c2748bbd5db5 100644
Binary files a/course/images/tasks_and_kernel_addr_spaces_1.png and b/course/images/tasks_and_kernel_addr_spaces_1.png differ
diff --git a/course/images/tasks_and_kernel_addr_spaces_2.odg b/course/images/tasks_and_kernel_addr_spaces_2.odg
index aa0455f7cf4be9b08d2f6f632a084174a88c2a5d..b761ea3c2f8e66e61ba071ecf0814a5b5ccad17b 100644
Binary files a/course/images/tasks_and_kernel_addr_spaces_2.odg and b/course/images/tasks_and_kernel_addr_spaces_2.odg differ
diff --git a/course/images/tasks_and_kernel_addr_spaces_2.png b/course/images/tasks_and_kernel_addr_spaces_2.png
index 1722bd4a60881bb9b7475db36341de9ccca75cbb..311f6bcd4e1175a166943946e0314a271847957e 100644
Binary files a/course/images/tasks_and_kernel_addr_spaces_2.png and b/course/images/tasks_and_kernel_addr_spaces_2.png differ
diff --git a/labs/lab4-tasks_syscalls/Makefile b/labs/lab4-tasks_syscalls/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3c124efda95948aa584656a983f908d2c27a23f6
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/Makefile
@@ -0,0 +1 @@
+include ../lab.mk
diff --git a/labs/lab4-tasks_syscalls/images/tasks_and_kernel_addr_spaces_2.png b/labs/lab4-tasks_syscalls/images/tasks_and_kernel_addr_spaces_2.png
new file mode 100644
index 0000000000000000000000000000000000000000..311f6bcd4e1175a166943946e0314a271847957e
Binary files /dev/null and b/labs/lab4-tasks_syscalls/images/tasks_and_kernel_addr_spaces_2.png differ
diff --git a/labs/lab4-tasks_syscalls/lab4-tasks_syscalls.md b/labs/lab4-tasks_syscalls/lab4-tasks_syscalls.md
new file mode 100644
index 0000000000000000000000000000000000000000..f93d0a7e7b0c9b5af8179d6c41ef55926b611d72
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/lab4-tasks_syscalls.md
@@ -0,0 +1,380 @@
+---
+title       : Programmation Système Avancée
+author      :
+     - "Professeur : Florent Gluck"
+date        : \today
+papersize   : A4
+geometry    : "left=2.5cm,right=2cm,top=1.5cm,bottom=2cm"
+colorlinks  : urlcolor
+fontsize    : 11pt
+lang        : fr-CH
+---
+
+# Tâches utilisateur et appels systèmes
+
+## Introduction
+
+A ce stade du développement, votre noyau, bien que très basique, est capable de gérer la mémoire, manipuler l'affichage, gérer le timer et d'interagir avec l’utilisateur grâce au clavier. Une brique extrêmement importante pour former un "vrai" système d’exploitation, mais manquante, est la gestion des tâches utilisateur, et par conséquent, le support des appels systèmes.
+
+Pour exécuter des applications utilisateur, il est nécessaire que le noyau (privilégié - ring 0) gère la création et l’exécution de tâches. Par soucis de sécurité, celles-ci doivent fonctionner en mode limité (non privélégié - ring 3) et posséder un espace d’adressage propre sans possibilité de corrompre le noyau ou d'autres tâches. Pour que les tâches puissent réaliser quelque chose d’utile, elles nécessitent d’accéder aux périphériques et aux services offerts par le système d’exploitation. C’est là le but des appels systèmes\ : ils forment un "pont" entre le mode noyau et le mode utilisateur.
+
+## Objectifs
+
+Le but de ce travail pratique est d'aboutir à un mini système d'exploitation interactif monotâche capable d’exécuter des applications utilisateur pouvant exploiter les services du noyau via les appels système. Il est important que le noyau isole correctement les applications entre-elles et du noyau lui-même, afin qu'une tâche boggée ne puisse ni affecter les autres tâches, ni compromettre le bon fonctionnement du noyau, donc du système.
+
+Les objectifs de ce travail pratique sont les suivants\ :
+
+1) Au niveau noyau :
+   - Support de tâches utilisateur imbriquées
+   - Support des appels systèmes
+   - Terminaison des tâches boggées
+
+1) Au niveau utilisateur :
+   - Mini librairie système implémentant les fonctionalités de base
+   - Application simple "hello world" réalisant un affichage quelconque
+   - Applications boggées provoquant volontairement des exceptions afin de valider que le noyau les "tue" correctement
+   - Application mesurant l'overhead introduit par les appels système
+   - Mini-shell interactif
+   - Et bien sûre toutes autres demo ou jeux à faire saliver vos collègues de jalousie\ !
+
+Une image ISO compressée disponible dans le répertoire `validation` illustre le comportement attendu du noyau et des applications utilisateur.
+
+## Cahier des charges 
+
+Les différents points à réaliser dans le cadre de ce travail pratique sont résumés ci-dessous. Chaque point est détaillé dans les sections qui suivent.
+
+1) Code noyau (répertoire `kernel`) :
+   - Support d'un nombre fixe (maximal) de tâches imbricables s'exécutant en mode non privilégié (ring 3)\ ; fichiers `task.c` et `task.h`\ :
+     - chaque tâche utilisateur est un module multiboot, **compilé indépendamment** du noyau
+     - le code et les données de chaque tâche est chargé par GRUB
+     - à l'exécution d'une tâche, le noyau la "charge", puis l'exécute\ : l'espace d'adressage de la tâche est créé, son espace mémoire est alloué dynamiquement, le contenu de la tâche y est copié depuis le module en RAM et le noyau commute vers la nouvelle tâche
+   - Support des appels système grâce au mécanisme d'interruption logicielle\ ; fichier `idt.c`.
+   - Un ensemble minimum d’appels systèmes est à implémenter\ ; fichier `syscall.c`.
+   - Le noyau doit forcer la terminaison de toute tâche boggée qui génère une exception (ie. la "tuer")\ ; fichier `idt.c`.
+
+1) Code utilisateur (répertoire `user`) :
+   - Une mini librairie système (i.e. librairie C) utilisée par les applications\ ; fichier `ulibc.c`.
+   - Une tâche qui génère une exception **page fault**
+   - Une tâche qui génère une exception **general protection fault**
+   - Une tâche qui mesure l'overhead lié aux appels système ; fichier `pix.c`.
+   - Un mini-shell interactif   
+
+## Noyau : gestion des tâches
+
+Le code lié à la gestion des tâches se trouve dans `kernel/task`.
+
+Avant de commencer, il est nécessaire de bien comprendre la configuration mémoire du noyau et des tâches utilisateur. On se base sur la configuration de l'espace d'adressage des tâches présentée en cours. Pour chaque tâche, le noyau doit créer un espace d'adressage aux caractéristiques illustrées en Figure 1\ :
+
+![Configuration de l'espace mémoire - Exemple pour deux tâches. On peut observer à droite, les frames allouées physiquement par les deux tâches dans l'espace d'adressage identité utilisé par le noyau.](images/tasks_and_kernel_addr_spaces_2.png){ width=100% }
+
+Le premier objectif de votre noyau est la possibilité de charger des programmes (tâches) en mémoire afin de les exécuter. Pour des raisons de simplicité, on considère que le nombre de tâches maximum supporté par le noyau est fixe (8 tâches maximum). Les tâches sont simplement stockées dans un tableau statique de la taille du nombre de tâches maximum désirées. Chaque entrée dans ce tableau est une structure  de tâche `task_t` (cf. structure dans `task.h`).
+
+Chaque tâche est un programme en cours d'exécution. Chaque programme est compilé **séparément et indépendamment** du noyau, ce qui signifie que le répertoire `kernel` n'est pas nécessaire pour compiler les différents programmes utilisateur. Nous utilisons GRUB pour charger chaque programme en RAM via l'utilisation de modules. Ainsi, une tâche est donc un module multiboot.
+
+Au niveau du code fourni, la gestion des tâches est à implémenter dans `task/task.c`. Une petite partie du code vous est fournie, mais vous devez implémenter l'essentiel des fonctionalités. Les fonctions à implémenter sont décrites ci-dessous.
+
+**`static task_t *task_create(char *name, uint_t addr_space_size)`**
+\vspace{-2mm}
+
+Cette fonction créée une nouvelle tâche et la retourne (ou NULL si il n'y a plus de tâche disponible). Comme le nombre de tâches maximum est limité, la fonction doit vérifier qu'il y a encore au moins une tâche de disponible (dans le tableau de tâches). Ensuite, les structures associées à la tâche doivent être initialisées et l'espace d'adressage virtuel de la tâche doit être créé, en n'oubliant pas d'allouer dynamiquement, à l'aide de frames, l'espace mémoire nécessaire pour le code, les données et la pile (taille pile fixée à `TASK_STACK_SIZE_MB` en MB). Cet espace mémoire sera typiquement alloué dynamiquement avec la fonction `paging_alloc` du module `paging.h`. Cette fonction en plus d'allouer les frames pour l'espace mémoire nécessaire, réalise également le mapping à l'adresse virtuelle spécifiée en argument et modifie le répertoire de pages passé en argument en conséquence. A noter que vous devrez libérer les frames allouées lorsque la tâche sera terminée dans la fonction `task_free`.
+
+Le structure TSS de la tache à exécuter, c'est à dire son contexte, doit être initialisée comme indiqué en cours. Attention donc à ce que les registres possèdent les bonnes valeurs en gardant en tête l'espace d'adressage virtuel de la tâche : celle-ci commence à l'adresse `TASK_VIRT_ADDR`, donc c'est à cette adresse que devra se trouver le début du code du programme. Cela signifie que le fichier binaire de toute application utilisateur doit commencer par le code. C'est le rôle du fichier linker `app.ld` fourni (répertoire `user`) et qui est utilisé pour lier toute application utilisateur (cf. `Makefile` dans `user`). Il est important que le pointeur d'instruction (`eip`) de la structure TSS pointe sur la première instruction du programme. La pile de la tâche sera typiquement située tout à la fin de la zone mémoire allouée pour la tâche (car la pile grandit vers le bas) et le pointeur de pile `esp` devra donc pointer à la fin de cet espace mémoire.
+
+En ce qui concerne l'espace d'adressage virtuel de la tâche, il est important que les mappings suivants soient mis en place\ :
+
+- Mapping identité de toute la RAM. Ce mapping doit être **privilégié** (`PRIVILEGE_KERNEL`) car on veut que seul le noyau y ait accès. Ce mapping est nécessaire lors des appels système, car le noyau doit pouvoir accéder à ses données et à son code, à toute la RAM, ainsi qu'aux données de la tâche (p.ex. arguments des appels système).
+- Mapping identité du *framebuffer*. Ce mapping doit être **non privilégié** (`PRIVILEGE_USER`) car on veut, pour des raisons de performance, que la tâche puisse accéder au *framebuffer* sans avoir à faire d'appels système.
+
+En plus de ces deux mappings, le noyau doit allouer dynamiquement le nombre de frames nécessaires (en incluant la pile) en fonction de la taille du programme à charger et créer un mapping supplémentaire : le mapping de la zone mémoire [1GB, 1GB + taille tâche + taille pile] vers les frames allouées, comme expliqué ci-dessus avec la fonction `paging_alloc`. Ce mapping doit être **non privilégié** (`PRIVILEGE_USER`) car il s'agit du code, données et pile de la tâche. Le registre `cr3` du TSS de la tâche doit donc pointer vers le répertoire de page de la tâche.
+
+**`static task_t *task_load(char *filename)`**
+\vspace{-2mm}
+
+Cette fonction "charge" la tâche dont le nom est passé en argument (ou renvoie `NULL` en cas d'erreur). Lorsque l'image ISO du système est créée, chaque application est insérée dans celle-ci sous forme d'un module multiboot où le nom du programme est passé en argument.
+
+La fonction `task_load` doit notamment utiliser la fonction `task_create` pour créer une nouvelle tâche. Une fois la nouvelle tâche créée et ses structures de données initialisées, la fonction `task_load` copie le contenu du programme (module) dans l'espace mémoire de la nouvelle tâche mappé à l'adresse virtuelle 1GB (`TASK_VIRT_ADDR`). Cependant, durant l'exécution de `task_load`, l'espace d'adressage de la nouvelle tâche **n'est pas mappé à 1GB**. Il n'est donc pas possible de copier le contenu du module à cette adresse\ ! La solution pour résoudre ce problème est donc de \: 
+
+- sauvegarder le répertoire de pages courant avec la fonction `paging_get_current_pagedir()`
+- charger le répertoire de pages de la nouvelle tâche avec la fonction `paging_load_pagedir`
+- effectuer la copie du contenu du programme dans l'espace mémoire de la nouvelle tâche à 1GB
+- restaurer le répertoire de pages du point 1 avec la fonction `paging_load_pagedir`
+
+Un dernier point particulièrement dangereux et subtile subsiste toutefois\ : `task_load` prend un fichier en argument comme nom de programme à charger. Il s'agit d'un pointeur vers un `char`, donc une adresse se trouvant dans la tâche courante. Hors, à partir du point 2 ci-dessus, le mapping mémoire actif est celui de la nouvelle tâche et en conséquence, le contenu pointé par le pointeur (données se trouvant dans la tâche courante) n'est donc plus accessible\ ! Assurez-vous donc d'accéder au nom du programme à charger **avant** de charger le répertoire de pages de la nouvelle tâche.
+
+**`bool task_exec(char *filename)`**
+\vspace{-2mm}
+
+Cette fonction s'occupe d'exécuter une tâche dont le nom est spécifié en argument. Cette fonction doit typiquement faire appel à la fonction `task_load`. Celle-ci retourne un booléen indiquant si l'exécution s'est déroulée correctement (`true`) ou pas (`false`). Une fois l'exécution de la tâche terminée, l'espace mémoire alloué pour la tâche doit être libéré, typiquement en appelant la fonction `task_free` décrit ci-dessous.
+
+**`static void task_free(task_t *t)`**
+\vspace{-2mm}
+
+Cette fonction s'occupe de libérer la mémoire utilisée par la tâche passée en argument.
+Pensez à libérer toutes les frames allouées. Pour rappel, chaque page mappée implique une frame allouée. Chaque table de pages implique également une frame allouée.
+
+## Noyau : appels systèmes
+
+Le code lié à la gestion des appels système se trouve dans `kernel/syscall`.
+
+La gestion des appels système est réalisée grâce à l'interruption logicielle 48. Il vous faut ajouter l'entrée correspondante dans la table IDT de votre noyau. Pour rappel, le code lié à l'IDT se trouve dans le fichier `idt.c`.
+
+En terme des fonctionnalités mises à disposition des applications utilisateur, votre noyau doit au minimum implémenter les appels système suivants (les noms peuvent être changés bien sûre\ !)\ :
+
+\footnotesize
+
+Description                                            Nom fonction            Arguments
+------------------------------------------------------ ----------------------- -------------------------
+Affiche une chaîne de caractères ; appelle la fonction `syscall_term_puts`     arg1: chaîne à afficher
+`term_puts` du noyau
+\phantom{x}
+Récupère une touche pressée au clavier ; appelle la    `syscall_keyb_get_key`  arg1: touche lue
+fonction `keyb_get_key` du noyau                                               (argument de sortie,
+                                                                               de type `int *`)
+\phantom{x}
+Récupère fréquence et nombre de ticks du timer         `syscall_timer_info`    arg1: fréquence
+                                                                               arg2: ticks
+                                                                               arg1 et arg2 sont de
+                                                                               type `uint_t *` et sont
+                                                                               des arguments de sortie
+\phantom{x}
+Attends le nombre de millisecondes spécifié ; appelle  `syscall_timer_sleep`   arg1: temps en ms
+la fonction `timer_sleep` du noyau.
+\phantom{x}
+Affiche un pixel ; appelle la fonction `setpixel`      `syscall_setpixel`      arg1: position x du pixel
+(ou similaire) du noyau                                                        arg2: position y du pixel
+                                                                               arg3: couleur du pixel
+\phantom{x}
+Charge et exécute le programme dont le nom est passé   `syscall_task_exec`     arg1: nom de la tâche à
+en argument ; appelle la fonction `task_exec` du noyau                         exécuter (type `char *`)
+------------------------------------------------------ ----------------------- -------------------------
+
+\normalsize
+
+Vous remarquerez que la plupart de ces appels systèmes ne sont rien d'autre que des *wrappers* de fonctions déjà existantes dans votre noyau.
+
+**Attention** à prêtez un soin particulier à la gestion des erreurs dans les appels systèmes car ceux-ci peuvent potentiellement échouer (lorsque c'est applicable).
+
+Bien que vous soyez libres d’implémenter des appels systèmes supplémentaires, posez-vous la question si cela est réellement nécessaire. En effet, une fonctionnalité implémentée purement en *user space* offre de meilleures perfomances car il n’y a pas de changement de contexte *user space* $\leftrightarrow$ *kernel space*. Cela a aussi l’avantage de limiter la complexité du noyau et ainsi sa fiabilité (moins de ligne de code où peuvent se trouver des bugs). Vous pourrez constater l'overhead lié aux appels système lors de la réalisation de la partie *user space* du labo décrite un peu plus loin.
+
+## Noyau : terminaison de tâche boggée
+
+Toute tâche décidant d'exécuter un instruction privilégiée (`int` (autre que 48), `cli`, `sti`, `lgdt`, `ltr`, etc.) ou référençant une zone mémoire en dehors de son espace d'adressage, provoquera une exception du type *general protection fault*. Il se peut aussi qu'une application génère une autre exception (p.ex. division par zéro) mais dans tous les cas, ces exceptions sont interceptées par le code noyau dans le gestionnaire d'exceptions processeur qui stoppera alors le CPU avec la fonction `halt`.
+
+L'objectif dans le labo n'est plus de stopper l'exécution du noyau avec `halt`, mais de "tuer" la tâche offensive afin que le noyau puisse continuer à s'exécuter. C'est qui le *boss* après tout\ ?
+
+Première information importante\ : si la routine du gestionnaire d'exception se termine (celle-ci se termine par l'instruction `iret`), l'exécution reprendra dans le code de la tâche ayant levé l'exception à l'adresse pointée par le pointeur d'instruction `regs->eip`. La fonction `task_get_current_sel()` retourne le sélecteur de TSS de la tâche courante, celle ayant provoqué l'exception. Vous pouvez donc identifier la tâche boggée.
+
+Un moyen de terminer la tâche boggée est de faire en sorte qu'à la sortie du code du gestionnaire d'exception, la tâche exécute l'instruction `iret`. Cette instruction termine alors la tâche courante et l'exécution reprends dans la tâche parente. Comment peut-on réaliser ceci\ ? En modifiant l'instruction se trouvant à l'adresse du pointeur d'instruction de la tâche. Rappelez-vous que celui-ci est sauvegardé sur la pile. Hors, le contenu de la pile de la tâche est accessible par le noyau, notamment grâce à la structure `regs` passée an argument au handler d'exception. Le pointeur d'instruction sauvegardé est donc `regs->eip`. Indice : quel est l'opcode (code machine) de l'instruction `iret` ?
+
+Enfin, attention toutefois à ne tuer la tâche ayant provoqué l'exception que lorsqu'il s'agit effectivement d'une tâche. Si c'est le noyau qui a provoqué l'exception, alors vous afficherez simplement un message "PANIC" et stopperez l'exécution du noyau avec la fonction `halt`.
+
+## Noyau : comportement
+
+Après initialisation des différents sous-systèmes (affichage, GDT, paging, IDT, etc.), le noyau exécutera simplement le shell décrit dans la section qui suit : "Partie applications utilisateur". A noter que l’utilisateur peut quitter le shell courant avec la commande "exit". Au cas où l'utilisateur se trouverait dans le shell initialement exécuté par le noyau au démarrage (i.e. pas dans un sous-shell imbriqué), un appel à la commande "exit" affichera un message d’arrêt du système, suivi de l’arrêt du noyau via la fonction `halt`.
+
+## Applications utilisateur : librairie système
+
+Le code lié aux applications utilisateur se trouve dans le répertoire `user`.
+
+Il est important de comprendre que les applications utilisateur sont compilées complètement séparément et indépendamment du noyau. Les applications n'ont aucunes dépendences envers le noyau, **sauf** les numéros d'appels systèmes et quelques autres constantes ou structures (codes de touches spéciales, etc.). En d’autres termes, la compilation de tout le code se trouvant dans le répertoire `user` ne doit pas dépendre du répertoire `kernel` (ou un de ses sous-répertoires). Le code `user` dépends par contre du code se trouvant dans le répertoire `common`. Si vous trouvez du code dans votre noyau qui pourrait également être utile à la partie *user space*, pensez alors à le placer dans `common`. Ceci devrait typiquement être le cas pour les numéros d'appels systèmes, car le noyau doit les connaître, mais également la librairie système utilisateur.
+
+Un autre point important à réaliser est que les applications utilisateur ne sont pas des fichiers ELF. Les fichiers générés sont des fichiers binaires "plats" (*flat binary*), sans format, où le code se trouve au début de l'exécutable, suivi des données initialisées. Les données non-initialisées (segment `.bss`), ne sont pas présentes dans l'exécutable. **Cela signifie, et ceci est extrêmement important, qu'aucun espace n'est alloué pour les données non-initialisées\ !** Pour rappel, voici ce qui est présent dans le segment `.bss` d'un exécutable\ :
+
+- variables globales non initialisées
+- variables globales non initialisées déclarées `static`
+- variables locales non initialisées déclarées `static`
+
+Si vous devez déclarer des variables tel que dans les cas présentés ci-dessus, alors préfixez leurs déclarations avec la macro `SECTION_DATA` (définie dans `ld.h`). Celle-ci force la variable ainsi déclarée à être stockée dans la section `data`, ce qui force le linker à allouer l'espace nécessaire (avec des 0).
+
+Afin d'éviter que chaque application réinvente la roue, on désire implémenter une librairie système utilisable par toute application utilisateur. Celle-ci agit comme une mini librairie C offrant aux applications utilisateur les fonctionnalités de base du système. La librairie C offre également une couche d'abstraction au dessus des appels systèmes mis à disposition par le noyau. Par soucis de compatibilité, les applications uilisteur doivent uniquement utiliser les fonctionnalités mises à disposition par la librairie système et donc éviter d'appeler les appels systèmes directement (via la fonction `syscall`).
+
+Etant donné que les applications dépendent de la librairie système, il est judicieux de commencer par le développement de celle-ci. Le code de la mini librairie système se trouve dans `ulibc.h` et `ulibc.c` et voici les prototypes des fonctions minimales souhaitées\ :
+
+- `bool task_exec(char *filename);`
+- `void timer_info(uint_t *freq, uint_t *ticks);`
+- `void setpixel(int x, int y, uint16_t color);`
+- `void setpixel_syscall(int x, int y, uint16_t color);`
+- `int getc();`
+- `void printf(char *fmt, ...);`
+- `void sleep(uint_t ms);`
+- `void exit();`
+
+Les prototypes de fonctions sont suffisamment explicites pour ne pas nécessiter d'explications. A savoir que vous êtes libres d’ajouter toutes autres fonctions souhaitées si le besoin s’en fait sentir.
+
+Indice\ : la fonction `exit` nécessitera probablement d'être implémentée en assembleur. Celle-ci doit terminer la tâche courante afin que la tâche parente puissent continuer son exécution.
+
+## Applications utilisateur : mini-shell
+
+La possibilité d'exécuter des programmes utilisateur depuis le noyau est utile, mais peu pratique d'un point de vue utilisateur. En effet, dans le cadre d'un système d'exploitation interactif, il est beaucoup plus utile de pouvoir charger des programmes dynamiquement à la demande. Ces fonctionnalités de base ne sont rien d'autre que celles proposées par un shell, certes très primitif. Implémentez donc un mini-shell permettant d'interagir avec le système à l'aide, au minimum, des commandes ci-dessous\ :
+
+\small
+```
+PROG      : execute program PROG
+exit      : exit this shell
+help      : display this help
+sleep N   : sleep for N milliseconds
+ticks     : show the current ticks value and timer frequency
+```
+\normalsize
+
+## Applications utilisateur : mesure de l'overhead des appels système
+
+On vous a probablement dit par le passé (cours système de 2ème année) qu'il faut éviter d'écrire du code réalisant des appels système très fréquents et de manière répétée.
+
+Le but ici est de déterminer à quel point les appels système impactent donc les performances. Un moyen pour déterminer ceci est de créer une application qui réalise une opération particulière de deux manière\ :
+
+- une première fois, à l'aide de nombreux appels système
+- une deuxième fois, sans appels système
+
+Ensuite, il suffit de mesurer le temps écoulé pour chaque scénario.
+
+A vous d'inventer un scénario qui fait sens, puis implémentez une application qui réalise exactement ceci en utilisant les fonctions `setpixel_syscall` et `setpixel`. Ses fonctions permettent d'afficher un pixel à l'écran, avec, et sans réaliser d'appel système. Trouvez également un moyen de mesurer les performances de chaque version.
+
+Pensez à compiler votre code en activant les optimisations du compilateur avec `make clean && make run DEBUG=0` afin d'obtenir des résultats plus représentatifs de conditions réelles.
+
+Une fois terminé, quels sont les temps obtenus pour chaque version\ ? Quel est le gain de vitesse obtenu pour la version sans appels système par rapport à la version avec appels système\ ?
+
+## Bonus : page partagée pour éviter certains appels système
+
+Dans le but d'améliorer les performances des applications, le noyau peut mettre en place un mécanisme évitant aux applications de réaliser certains appels système. L'idée est que le noyau écrives certaines informations dans l'espace d'adressage des tâches, évitant ainsi à celle-ci de réaliser des appels système pour les obtenir.
+
+Ce mécanisme peut typiquement être utilisé dans le cadre de votre noyau pour l'appel système `syscall_timer_info`. Cet appel système renvoie deux valeurs interne au noyau, la fréquence du timer et le nombre de ticks. Au moment de la création d'une tâche, le noyau peut ajouter une page dans l'espace d'adressage de celle-ci et y placer ces deux valeurs. Si la tâche connait l'adresse de ces informations, alors elle peut simplement les lire, sans avoir à réaliser d'appels système.
+
+Essayez donc d'ajouter ceci dans votre noyau et votre code utilisateur\ ! A savoir que Linux implémente un mécanisme similaire pour éviter certains appels système.
+
+## Makefile racine
+
+Le Makefile racine nécessite d'être modifié afin que la génération des applications utilisateur fasse partie des cibles disponibles. Voici donc les modifications à apporter.
+
+- Cible `help`, ajouter :
+  \scriptsize
+  ```
+  @echo "user     build the user space executables only"
+  ```
+  \normalsize
+- Cible `$(ISO_NAME)`, ajouter la dépendence `user` et ajouter la copie des application à l'image ISO dans les actions de production
+- Nouvelle cible `user` à ajouter :
+  \scriptsize
+  ```
+  user:
+      $(MAKE) -C $@ CC_DEFINES=$(CC_DEFINES) CC_FLAGS="$(CC_FLAGS)" LD_FLAGS="$(LD_FLAGS)"
+  ```
+  \normalsize
+- Ajouter cette action de production à la cible `clean` :
+  \scriptsize
+  ```
+  $(MAKE) -C user clean
+  ```
+  \normalsize
+
+<!--
+**Makefile**\
+Tout comme pour le répertoire `kernel`, le répertoire `user` doit contenir son propre fichier `Makefile` permettant de compiler la librairie C et les applications utilisateur (dont le shell).
+
+<!--
+## Fichiers fournis
+
+Afin de vous faciliter la tâche, plusieurs fichiers vous sont fournis sur le git dans le répertoire lié au travail pratique. Ces fichiers contiennent du code d’exemple sur lequel vous pouvez baser votre implémentation. L'arborescence est listée ci-dessous et chaque fichier est décrit dans les sections qui suivent.
+
+\footnotesize
+```
+|-- common
+|   `-- syscall_nb.h
+|-- kernel
+|   |-- syscall
+|   |   |-- syscall_asm.s
+|   |   |-- syscall.c
+|   |   `-- syscall.h
+|   `-- task
+|       |-- task_asm.s
+|       |-- task.c
+|       |-- task.h
+|       `-- tss.h
+`-- user
+    |-- app.ld
+    |-- entrypoint_asm.s
+    |-- Makefile
+    |-- syscall_asm.s
+    |-- syscall.h
+    |-- ulibc.c
+    `-- ulibc.h
+```
+\normalsize
+
+### Fichiers communs
+\vspace{-3mm}
+
+Ces fichiers se trouvent dans le répertoire `common`.
+
+**`syscall_nb.h`**\
+Le type énuméré `syscall_t` définissant les numéros des appels systèmes implémentés par le noyau.
+
+### Fichiers côté noyau
+\vspace{-3mm}
+
+Ces fichiers se trouvent dans le répertoire `kernel`.
+
+**`task/task.h`**\
+Ebauche d'une structure de tâche ainsi que la déclaration des fonctions externes `task_ltr` et `task_switch` implémentées dans `task_asm.s`.
+
+**`task/task.c`**\
+Code pour réaliser le setup du TSS noyau initial ainsi qu'une ébauche de création de tâche.
+
+**`task/tss.h`**\
+Le type `tss_t` décrivant la structure TSS associée à une tâche.
+
+**`task/task_asm.s`**\
+Deux fonctions assembleur :
+
+- `task_ltr` : charge le task register avec le sélecteur de TSS passé en argument.
+- `task_switch` : commute vers la tâche dont le sélecteur de TSS est passé en argument.
+
+**`syscall/syscall_asm.s`**\
+La fonction assembleur `_syscall_handler`. Il s'agit de la partie "basse" du gestionnaire d'appels systèmes. Cette fonction appelle ensuite la fonction C ("haute"), `syscall_handler` en prenant soin de lui passer le numéro d'appel système, les arguments et le sélecteur TSS de la tâche ayant réalisé l'appel système.
+
+**`syscall/syscall.c`**\
+Une ébauche du *handler* d'appels systèmes ainsi qu'un tableau de fonctions pour les différents appels systèmes.
+
+**`syscall/syscall.h`**\
+Le prototype de la fonction `syscall_handler` implémentée dans `syscall.c`.
+
+### Fichiers côté applications utilisateur
+\vspace{-3mm}
+
+Ces fichiers se trouvent dans le répertoire `user`.
+
+**`app.ld`**\
+Similairement au fichier de configuration `kernel.ld` décrivant le mapping des sections du noyau lors de l’édition des liens (section `.text` pour le code, section `.bss` pour les données non initalisées, etc.), ce fichier défini pour les applications utilisateur où les différentes sections doivent être mappées au moment de l'édition des liens, ainsi que le format de fichier binaire généré. Comme nous n’avons pas implémenté de parseur ELF, nous générons simplement un fichier binaire "plat" (*flat*). Ce fichier spécifie aussi que la section `.entrypoint` se trouve à l’adresse 0, ce qui garanti que le code de cette section figurera en début du fichier binaire généré. Ainsi, pour exécuter l'application, le noyau aura simplement à charger le binaire à l'adresse de début du segment de code de l'application, initialiser le pointeur d'instruction (eip) du contexte de la tâche à 0 et de commuter vers celle-ci. 
+
+**`entrypoint_asm.s`**\
+Ce code doit impérativement être lié à toute application utilisateur car il défini la section où se trouve le point d’entrée de l’application. Le code de la section `.entrypoint` fait appel à la fonction `main` qui est le point d’entrée de l’application. Une fois la fonction `main` terminée, l'instruction assembler `iret` permet de revenir à la tâche appelante (soit le noyau, soit une autre tâche s'exécutant en ring 3).
+
+**`shell.c`**\
+Implémentation presque complète du mini shell.
+
+**`syscall_asm.s`**\
+La fonction assembleur `syscall` qui permet d'exécuter un appel système vers le noyau. Cette fonction requiert cinq arguments : le numéro d'appel système, suivi de quatre arguments. A vous de définir les arguments nécessaires (quatre au maximum) pour chaque appel système.
+
+**`syscall.h`**\
+Prototype de la fonction `syscall` implémentée dans `syscall_asm.s`.
+
+**`ulibc.c`**\
+Ebauche d'un début de mini librairie système. Implémente deux fonctions utilisée dans le mini shell: `trim` pour nettoyer un string avec des espaces en début et fin de chaîne et `read_string` qui lit des caractères entrés au clavier.
+
+**`ulibc.h`**\
+Le prototype des deux fonctions implémentées dans `ulibc.c`.
+
+-->
+<!--
+## Remarques importantes
+                         
+Similairement à ce qui est réalisé pour le répertoire `kernel`, le `Makefile` global situé à la racine du système d'exploitation doit faire appel au `Makefile` se trouvant dans le répertoire `user`, afin de compiler les applications utilisateur.
+
+Le `Makefile` global doit aussi se charger d'ajouter (sous forme de modules GRUB) le shell et les applications créés dans le répertoire `user` à l'image ISO de votre système d'exploitation. Si ce n'est pas le cas, le noyau ne pourra jamais lire, puis exécuter le shell. Au cas où cela se produirait, le noyau doit bien-sûre s’arrêter et afficher un message d'erreur.
+
+Il faut savoir que QEMU ne réalise pas une émulation 100% exacte de l'architecture IA-32. En effet, il apparaît que lors des accès mémoire, la limite des segments n'est pas testée systématiquement. Ce comportement semble dépendre de la manière dont QEMU est exécuté (avec/sans kvm, options, etc.). A noter qu'il est aussi possible de tester votre système sur les hyperviseurs VirtualBox ou VMWare.
+-->
+
+## Remarque importante
+
+Pensez à bien étudier et comprendre le code qui vous est fourni. En effet, le but n'est pas seulement que vous implémentier les parties manquantes, mais aussi que vous compreniez le fonctionnent du système de part en part. Il est important que vous possédiez une compréhension globale du système dans son entier.
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/common/syscall_nb.h b/labs/lab4-tasks_syscalls/skeleton/yoctos/common/syscall_nb.h
new file mode 100644
index 0000000000000000000000000000000000000000..07727324760f625fc88dd2e4c8ff1ec67d41979e
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/common/syscall_nb.h
@@ -0,0 +1,9 @@
+#ifndef _SYSCALL_NB_COMMON_H_
+#define _SYSCALL_NB_COMMON_H_
+
+typedef enum {
+    SYSCALL_xxx = 1,
+    __SYSCALL_END__
+} syscall_t;
+
+#endif
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/mem/paging.c b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/mem/paging.c
new file mode 100644
index 0000000000000000000000000000000000000000..775265ad4096236c107953098c99f6659a0fe65c
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/mem/paging.c
@@ -0,0 +1,40 @@
+// Allocates size bytes of physical memory (as frames, so not necessarily contiguous), then maps these
+// frames at virtual address virt_addr. The area at virt_addr is guaranteed to be contiguous.
+// The mapping is performed in the specified page directory.
+// This function dynamically allocates the necessary frames to store the page tables and stores
+// their addresses into page_tables so that they can be subsequently freed.
+// If NULL is passed in page_tables, then the allocated frames' addresses won't be stored.
+// IMPORTANT: virt_addr and phys_addr must be aligned to a page size (4KB).
+// Returns the number of frames allocated (including frames used to store page tables).
+
+uint_t paging_alloc(PDE_t *pagedir, PTE_t *page_tables[PAGETABLES_IN_PD], uint32_t virt_addr, uint32_t size, enum privilege_t privilege) {
+    if (virt_addr % PAGE_SIZE || phys_addr % PAGE_SIZE) {
+        // Virtual address must be aligned to PAGE_SIZE!
+        halt();
+    }
+
+    // Skip entries of already allocated pagetables since we'll need to store newly allocated page tables
+    uint_t pt_offset = 0;
+    while (page_tables && page_tables[pt_offset]) {
+        pt_offset++;
+    }
+
+    int pt_count = 0;
+
+    // How many frames do we need?
+    uint_t frame_count = PAGE_COUNT(size);
+    if (frame_count == 0)
+        return 0;
+
+    for (uint_t i = 0; i < frame_count; i++) {
+        uint32_t frame_addr = (uint32_t)frame_alloc();
+        PTE_t *page_table = mmap_page(pagedir, virt_addr, frame_addr, privilege, ACCESS_READWRITE);
+        // A new page table was just allocated, store it into the input page_tables array of pointer to page tables
+        if (page_tables && page_table) {
+            page_tables[pt_offset+pt_count++] = page_table;
+        }
+        virt_addr += PAGE_SIZE;
+    }
+
+    return pt_count+frame_count;
+}
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/syscall/syscall.c b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/syscall/syscall.c
new file mode 100644
index 0000000000000000000000000000000000000000..bfb3ae8a587e8a4aa7bc8a9428b6c581ffdba3c8
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/syscall/syscall.c
@@ -0,0 +1,17 @@
+#include "common/types.h"
+#include "syscall.h"
+
+// Dummy syscall
+static int syscall_dummy(uint32_t arg1, uint32_t arg2, uint32_t arg3, uint32_t arg4) {
+    UNUSED(arg1);
+    UNUSED(arg2);
+    UNUSED(arg3);
+    UNUSED(arg4);
+    return 0;
+}
+
+// Called by the assembly function: _syscall_handler
+int syscall_handler(syscall_t nb, uint32_t arg1, uint32_t arg2, uint32_t arg3, uint32_t arg4) {
+    // Call the syscall function based on nb.
+    // Make sure to return an error in case of an invalid syscall number.
+}
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/syscall/syscall.h b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/syscall/syscall.h
new file mode 100644
index 0000000000000000000000000000000000000000..3fb537dfa80fed7e5a6d25ad6f774d3358bc7961
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/syscall/syscall.h
@@ -0,0 +1,11 @@
+#ifndef _SYSCALL_H_
+#define _SYSCALL_H_
+
+#include "common/syscall_nb.h"
+
+int syscall_handler(syscall_t nb, uint32_t arg1, uint32_t arg2, uint32_t arg3, uint32_t arg4);
+
+// Declared in syscall_asm.s
+void _syscall_handler();
+
+#endif
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/syscall/syscall_asm.s b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/syscall/syscall_asm.s
new file mode 100644
index 0000000000000000000000000000000000000000..cc243d161691c0f0c36e29a20a37e6cf2e43f71b
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/syscall/syscall_asm.s
@@ -0,0 +1,49 @@
+%include "const.inc"
+
+section .text                      ; start of the text (code) section
+align 4                            ; the code must be 4 byte aligned
+
+extern syscall_handler
+
+global _syscall_handler
+
+_syscall_handler:
+    ; Save segments registers as they are modified to point to the kernel's data
+    push    ds
+    push    es
+    push    fs
+    push    gs
+    
+    ; Load kernel data descriptor into all segments
+    push    eax
+    mov     ax,GDT_KERNEL_DATA_SELECTOR
+    mov     ds,ax
+    mov     es,ax
+    mov     fs,ax
+    mov     gs,ax
+    pop     eax
+
+    ; Pass the 5 arguments (nb, arg1, ..., arg4) to the syscall_handler
+    ; They are in reverse order to match the cdecl IA-32 ABI.
+    push    esi
+    push    edx
+    push    ecx
+    push    ebx
+    push    eax
+
+    call    syscall_handler
+
+    ; These 5 "pop eax" instructions are only here to balance the pushes
+    ; above used to pass the arguments to the syscall_handler function
+    pop     ebx
+    pop     ebx
+    pop     ebx
+    pop     ebx
+    pop     ebx
+
+    ; Restore segment registers
+    pop     gs
+    pop     fs
+    pop     es
+    pop     ds
+    iret
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/task/task.c b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/task/task.c
new file mode 100644
index 0000000000000000000000000000000000000000..8c7cb59bc57f30fb64718b311b03540f91aa62dc
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/task/task.c
@@ -0,0 +1,53 @@
+#include "task.h"
+
+#define TASK_STACK_SIZE_MB 2
+
+static task_t tasks[MAX_TASK_COUNT];
+static tss_t initial_tss;
+static uint8_t initial_tss_kernel_stack[65536];
+
+// Initializes the task subsystem.
+void tasks_init() {
+    memset(tasks, 0, sizeof(tasks));
+
+    // Allocates and initializes the initial TSS: it is where the CPU state
+    // will be saved before switching from the kernel to the very first user task.
+    extern gdt_entry_t *gdt_initial_tss;
+    *gdt_initial_tss = gdt_make_tss(&initial_tss, DPL_KERNEL);
+    memset(&initial_tss, 0, sizeof(tss_t));
+    initial_tss.ss0 = GDT_KERNEL_DATA_SELECTOR;
+    initial_tss.esp0 = ((uint32_t)initial_tss_kernel_stack) + sizeof(initial_tss_kernel_stack);
+    initial_tss.cr3 = (uint32_t)paging_get_current_pagedir();
+
+    // Loads the task register to point to the initial TSS selector.
+    // IMPORTANT: The GDT must already be loaded before loading the task register!
+    task_ltr(gdt_entry_to_selector(gdt_initial_tss));
+}
+
+// Returns a pointer to the current task.
+// If NULL is returned it means we are in the kernel.
+task_t *task_get_current() {
+    return NULL;
+}
+
+// Creates and returns a task from the fixed pool of tasks.
+// This function dynamically allocates the address space of size "size" (in bytes) for the task.
+// Returns NULL if failed.
+static task_t *task_create(char *name, uint_t addr_space_size) {
+}
+
+// Frees a task previously created with task_create().
+// This function frees the task's page frames.
+static void task_free(task_t *t) {
+}
+
+// Creates a new task with the content of the specified binary application.
+// Once loaded, the task is ready to be executed.
+// Returns NULL if failed (ie. reached max number of tasks).
+static task_t *task_load(char *filename) {
+}
+
+// Loads a task and executes it.
+// Returns false if failed.
+bool task_exec(char *filename) {
+}
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/task/task.h b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/task/task.h
new file mode 100644
index 0000000000000000000000000000000000000000..51700bf3ce426a1a5aca146bc39dea1dec96a374
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/task/task.h
@@ -0,0 +1,37 @@
+#ifndef _TASK_H_
+#define _TASK_H_
+
+#include "common/types.h"
+#include "mem/paging.h"
+#include "tss.h"
+
+#define MAX_TASK_COUNT  8
+
+// Virtual address (1GB) where task user code/data is mapped (i.e. application entry point)
+#define TASK_VIRT_ADDR 0x40000000
+
+// Structure of a task.
+typedef struct {
+    PDE_t pagedir[PAGETABLES_IN_PD] __attribute__((aligned(4096)));  // Task page directory
+    tss_t tss __attribute__((aligned(4096)));   // Context of the task (must not cross a page boundary!)
+    PTE_t *page_tables[PAGES_IN_PT];            // Save pointers to page tables in order to deallocate
+                                                // previously allocated frames at task termination
+    uint16_t tss_selector;                      // selector required when switching to the task
+    uint8_t kernel_stack[65536];                // kernel stack
+    uint32_t virt_addr;                         // Start of the task's virtual address space
+    uint32_t addr_space_size;                   // Size of the task's address space in bytes
+    // TODO: add whathever you'd like...
+} task_t;
+
+void tasks_init();
+
+task_t *task_get_current();
+
+bool task_exec(char *filename);
+
+// Implemented in task_asm.s
+void task_ltr(uint16_t tss_selector);
+void task_switch(uint16_t tss_selector);
+uint16_t task_get_current_sel();
+
+#endif
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/task/task_asm.s b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/task/task_asm.s
new file mode 100644
index 0000000000000000000000000000000000000000..de7f3fb74432a68f7dd4f86546aa63a6510603fa
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/task/task_asm.s
@@ -0,0 +1,40 @@
+global task_ltr
+global task_switch
+global task_get_current_sel
+
+section .data
+tss_sel_offs dd 0  ; must always be 0
+tss_sel_seg  dw 0  ; overwritten by the task_switch function
+
+section .text:                     ; start of the text (code) section
+align 4                            ; the code must be 4 byte aligned
+
+; Load the task register with the TSS selector passed in argument.
+; The TSS selector represents the offset of the TSS descriptor in the GDT table.
+; NOTE: The GDT must be loaded before issuing the ltr instruction!
+;
+; void task_ltr(uint16_t tss_selector);
+task_ltr:
+    mov     eax,[esp+4]
+    ltr     ax
+    ret
+
+; Call the task specified by the tss selector in argument.
+; When the CPU switches to the new task, it automatically loads the task register
+; with the new task (ltr instruction) and the LDT from the tss.ldt_selector field.
+;
+; void task_switch(uint16_t tss_selector)
+task_switch:
+    mov     ax,[esp+4]  ; get the TSS selector passed in argument (16 bits)
+    ; rewrite the segment to jump to with the tss selector passed in argument
+	mov		ecx,tss_sel_seg
+    mov     [ecx],ax
+    call    far [ecx-4]
+    ret
+
+; Return the current task's TSS selector
+;
+; uint16_t task_get_current_sel()
+task_get_current_sel:
+    str     ax  ; store the segment selector from the task register (TR) in ax
+    ret
\ No newline at end of file
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/task/tss.h b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/task/tss.h
new file mode 100644
index 0000000000000000000000000000000000000000..391f0d7d6a8d04810bc99ecf1daeba48dcaef300
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/kernel/task/tss.h
@@ -0,0 +1,36 @@
+#ifndef _TSS_H_
+#define _TSS_H_
+
+#include "common/types.h"
+
+// IMPORTANT: by default (if IOMAP is 0) all ports are accessibles from ring 3!
+// Set to 1 if iomap is needed (forbid or allow access to IO ports from user mode).
+// It requires an extra 8KB in the TSS to store the ports bitmap.
+#define IOMAP 1
+
+// Task-State Segment (TSS) structure.
+typedef struct {
+    uint16_t previous_task_link, reserved0;
+    uint32_t esp0;
+    uint16_t ss0, reserved1;
+    uint32_t esp1;
+    uint16_t ss1, reserved2;
+    uint32_t esp2;
+    uint16_t ss2, reserved3;
+    uint32_t cr3;
+    uint32_t eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;
+    uint16_t es, reserved4;
+    uint16_t cs, reserved5;
+    uint16_t ss, reserved6;
+    uint16_t ds, reserved7;
+    uint16_t fs, reserved8;
+    uint16_t gs, reserved9;
+    uint16_t ldt_selector, reserved10;
+    uint16_t reserved11;
+    uint16_t iomap_base_addr;  // adress (relative to byte 0 of the TSS) of the IO permission bitmap
+#if IOMAP
+    uint8_t iomap[8192];       // IO permission bitmap for ports 0 to 0xFFFF
+#endif
+} __attribute__ ((packed)) tss_t;
+
+#endif
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/user/Makefile b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..e56a8cf1979c434a9538dbc02ee81b3042d21281
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/Makefile
@@ -0,0 +1,39 @@
+include ../common.mk
+
+override LD_FLAGS+=-Tapp.ld $(BAREMETAL_FLAGS) -lgcc
+
+USER_C_SRC=$(shell find . -iname "*.c")
+USER_ASM_SRC=$(shell find . -iname "*.s")
+USER_C_OBJ=$(USER_C_SRC:.c=.o)
+USER_C_DEP=$(USER_C_SRC:.c=.d)
+USER_ASM_OBJ=$(USER_ASM_SRC:.s=.o)
+
+DEP=$(USER_C_OBJ:%.o=%.d)
+
+APP_DEP=entrypoint_asm.o syscall_asm.o ulibc.o $(COMMON_OBJ)
+
+APPS=hello.exe
+
+all: $(APPS)
+
+# Since these obj files are not deps of anything (intermediary files), make deletes them.
+# <HACK>Here we force them to be a dep of something</HACK>
+# There is probably a better way of doing this...
+UNUSED: $(USER_C_OBJ)
+
+%.o: %.c
+	$(CC) $(CC_FLAGS) $(CC_DEFINES) -I.. $< -o $@
+
+%.o: %.s
+	nasm -f elf32 $< -o $@
+
+%.exe: %.o $(APP_DEP)
+	$(LD) $^ -o $@ $(LD_FLAGS)
+
+clean:
+	$(MAKE) -C $(COMMON_DIR) clean
+	/bin/rm -f $(APPS) $(USER_C_OBJ) $(USER_C_DEP) $(USER_ASM_OBJ)
+
+.PHONY: clean
+
+-include $(DEP)
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/user/app.ld b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/app.ld
new file mode 100644
index 0000000000000000000000000000000000000000..e0e939865b693ca157521983b370cef2df3d25b8
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/app.ld
@@ -0,0 +1,32 @@
+OUTPUT_FORMAT("binary")
+OUTPUT_ARCH(i386)
+
+SECTIONS {
+	. = 0x40000000;         /* first section located at 1GB */
+
+	.entrypoint ALIGN(4):   /* entry point: must be located at 0 */
+	{
+		*(.entrypoint)
+	}
+
+	.text ALIGN(4) :        /* code */
+	{
+		*(.text*)
+	}
+
+	.rodata ALIGN(4) :      /* read-only data */
+	{
+		*(.rodata*)          
+	}
+
+	.data ALIGN(4) :        /* initialized data */
+	{
+		*(.data*)
+	}
+
+	.bss ALIGN(4) :         /* unitialized data */
+	{
+		*(COMMON)
+		*(.bss*)
+	}
+}
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/user/entrypoint_asm.s b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/entrypoint_asm.s
new file mode 100644
index 0000000000000000000000000000000000000000..033e93e58baa7093e4dd700d7c52784483366c68
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/entrypoint_asm.s
@@ -0,0 +1,7 @@
+extern main
+
+section .entrypoint
+    call  main
+    jmp   exit
+
+section .text
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/user/hello.c b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/hello.c
new file mode 100644
index 0000000000000000000000000000000000000000..d73a5692431ea93d3f7c2f73fe773feb7bfb0b93
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/hello.c
@@ -0,0 +1,4 @@
+#include "ulibc.h"
+
+void main() {
+}
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/user/ld.h b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/ld.h
new file mode 100644
index 0000000000000000000000000000000000000000..7ebb0bfcc11835bdab88c55aea441e6285af716d
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/ld.h
@@ -0,0 +1,6 @@
+#ifndef _LD_H_
+#define _LD_H_
+
+#define SECTION_DATA __attribute__((section(".data")))
+
+#endif
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/user/syscall.h b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/syscall.h
new file mode 100644
index 0000000000000000000000000000000000000000..c811d8decaa255b3a7591be324ea3b450ff6ad25
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/syscall.h
@@ -0,0 +1,9 @@
+#ifndef _SYSCALL_H_
+#define _SYSCALL_H_
+
+#include "common/types.h"
+#include "common/syscall_nb.h"
+
+int syscall(syscall_t nb, uint32_t arg1, uint32_t arg2, uint32_t arg3, uint32_t arg4);
+
+#endif
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/user/syscall_asm.s b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/syscall_asm.s
new file mode 100644
index 0000000000000000000000000000000000000000..aa3627af69b9894cce3b6ece7a2923bf8476e62b
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/syscall_asm.s
@@ -0,0 +1,40 @@
+global syscall
+
+section .text                      ; start of the text (code) section
+align 4                            ; the code must be 4 byte aligned
+
+; int syscall(uint32_t nb, uint32_t arg1, uint32_t arg2, uint32_t arg3, uint32_t arg4);
+syscall:
+    ; parameters cannot be passed into the stack because the trap/interrupt gate
+    ; performs a stack switch (from user stack to kernel stack). By the time we're
+    ; in the syscall handler we're accessing the kernel stack (tss.ss/tss.esp).
+    push    ebp
+    mov     ebp,esp
+
+	; save general registers since we modify them below
+	; eax is not saved as it's used to store the syscall's return value 
+    push    ebx
+    push    ecx
+    push    edx
+    push    esi
+    push    edi
+
+    mov     eax,[ebp+8]
+    mov     ebx,[ebp+12]
+    mov     ecx,[ebp+16]
+    mov     edx,[ebp+20]
+    mov     esi,[ebp+24]
+    ; edi could be used to pass an additional argument
+
+    int     48
+   
+	; restore general registers 
+    pop     edi
+    pop     esi
+    pop     edx
+    pop     ecx
+    pop     ebx
+
+    mov     esp,ebp
+    pop     ebp
+    ret
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/user/ulibc.c b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/ulibc.c
new file mode 100644
index 0000000000000000000000000000000000000000..b82bac6712ea069acff175c035251fe6b7db5fb6
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/ulibc.c
@@ -0,0 +1 @@
+#include "ulibc.h"
diff --git a/labs/lab4-tasks_syscalls/skeleton/yoctos/user/ulibc.h b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/ulibc.h
new file mode 100644
index 0000000000000000000000000000000000000000..22929f58fbba9c24bed0da02c1f262185aeba409
--- /dev/null
+++ b/labs/lab4-tasks_syscalls/skeleton/yoctos/user/ulibc.h
@@ -0,0 +1,8 @@
+#ifndef _ULIBC_H_
+#define _ULIBC_H_
+
+#include "common/types.h"
+#include "common/colors.h"
+#include "common/string.h"
+
+#endif