Support to multi-threading and pthread interface (POSIX.1-2008) (#23)
[lunaix-os.git] / lunaix-os / kernel / process / thread.c
1 #include <lunaix/process.h>
2 #include <lunaix/sched.h>
3 #include <lunaix/syscall.h>
4 #include <lunaix/syscall_utils.h>
5 #include <lunaix/mm/mmap.h>
6 #include <lunaix/mm/page.h>
7 #include <lunaix/mm/vmm.h>
8 #include <lunaix/mm/pmm.h>
9 #include <lunaix/syslog.h>
10
11 #include <usr/lunaix/threads.h>
12
13 #include <sys/abi.h>
14 #include <sys/mm/mm_defs.h>
15
16 LOG_MODULE("THREAD")
17
18 static inline void 
19 inject_guardian_page(ptr_t vm_mnt, ptr_t va)
20 {
21     vmm_set_mapping(vm_mnt, PG_ALIGN(va), 0, 0, VMAP_GUARDPAGE);
22 }
23
24 static ptr_t
25 __alloc_user_thread_stack(struct proc_info* proc, struct mm_region** stack_region, ptr_t vm_mnt)
26 {
27     ptr_t th_stack_top = (proc->thread_count + 1) * USR_STACK_SIZE;
28     th_stack_top = ROUNDUP(USR_STACK_END - th_stack_top, MEM_PAGE);
29
30     struct mm_region* vmr;
31     struct proc_mm* mm = vmspace(proc);
32     struct mmap_param param = { .vms_mnt = vm_mnt,
33                                 .pvms = mm,
34                                 .mlen = USR_STACK_SIZE,
35                                 .proct = PROT_READ | PROT_WRITE,
36                                 .flags = MAP_ANON | MAP_PRIVATE,
37                                 .type = REGION_TYPE_STACK };
38
39     int errno = mmap_user((void**)&th_stack_top, &vmr, th_stack_top, NULL, &param);
40
41     if (errno) {
42         WARN("failed to create user thread stack: %d", errno);
43         return 0;
44     }
45
46     // Pre-allocate a page contains stack top, to avoid immediate trap to kernel
47     //  upon thread execution
48     ptr_t pa = pmm_alloc_page(0);
49     ptr_t stack_top = align_stack(th_stack_top + USR_STACK_SIZE - 1);
50     if (likely(pa)) {
51         vmm_set_mapping(vm_mnt, PG_ALIGN(stack_top), 
52                         pa, region_ptattr(vmr), 0);
53     }
54
55     inject_guardian_page(vm_mnt, vmr->start);
56
57     *stack_region = vmr;
58
59     return stack_top;
60 }
61
62 static ptr_t
63 __alloc_kernel_thread_stack(struct proc_info* proc, ptr_t vm_mnt)
64 {
65     v_mapping mapping;
66     ptr_t kstack = PG_ALIGN(KSTACK_AREA_END - KSTACK_SIZE);
67     while (kstack >= KSTACK_AREA) {
68         // first page in the kernel stack is guardian page
69         if (!vmm_lookupat(vm_mnt, kstack + MEM_PAGE, &mapping) 
70             || !PG_IS_PRESENT(mapping.flags)) 
71         {
72             break;
73         }
74
75         kstack -= KSTACK_SIZE;
76     }
77
78     if (kstack < KSTACK_AREA) {
79         WARN("failed to create kernel stack: max stack num reach\n");
80         return 0;
81     }
82
83     ptr_t pa = pmm_alloc_cpage(PN(KSTACK_SIZE) - 1, 0);
84
85     if (!pa) {
86         WARN("failed to create kernel stack: nomem\n");
87         return 0;
88     }
89
90     inject_guardian_page(vm_mnt, kstack);
91     for (size_t i = MEM_PAGE, j = 0; i < KSTACK_SIZE; i+=MEM_PAGE, j+=MEM_PAGE) {
92         vmm_set_mapping(vm_mnt, kstack + i, pa + j, PG_PREM_RW, 0);
93     }
94
95     return align_stack(kstack + KSTACK_SIZE - 1);
96 }
97
98 void
99 thread_release_mem(struct thread* thread, ptr_t vm_mnt)
100 {
101     for (size_t i = 0; i < KSTACK_SIZE; i+=MEM_PAGE) {
102         ptr_t stack_page = PG_ALIGN(thread->kstack - i);
103         vmm_del_mapping(vm_mnt, stack_page);
104     }
105     
106     if (thread->ustack) {
107         if ((thread->ustack->start & 0xfff)) {
108             fail("invalid ustack struct");
109         }
110         mem_unmap_region(vm_mnt, thread->ustack);
111     }
112 }
113
114 struct thread*
115 create_thread(struct proc_info* proc, ptr_t vm_mnt, bool with_ustack)
116 {
117     struct mm_region* ustack_region = NULL;
118     if (with_ustack && 
119         !(__alloc_user_thread_stack(proc, &ustack_region, vm_mnt))) 
120     {
121         return NULL;
122     }
123
124     ptr_t kstack = __alloc_kernel_thread_stack(proc, vm_mnt);
125     if (!kstack) {
126         mem_unmap_region(vm_mnt, ustack_region);
127         return NULL;
128     }
129
130     struct thread* th = alloc_thread(proc);
131     if (!th) {
132         return NULL;
133     }
134     
135     th->kstack = kstack;
136     th->ustack = ustack_region;
137
138     return th;
139 }
140
141 void
142 start_thread(struct thread* th, ptr_t vm_mnt, ptr_t entry)
143 {
144     assert(th && entry);
145     
146     struct transfer_context transfer;
147     if (!kernel_addr(entry)) {
148         assert(th->ustack);
149
150         ptr_t ustack_top = align_stack(th->ustack->end - 1);
151         ustack_top -= 16;   // pre_allocate a 16 byte for inject parameter
152         thread_create_user_transfer(&transfer, th->kstack, ustack_top, entry);
153
154         th->ustack_top = ustack_top;
155     } 
156     else {
157         thread_create_kernel_transfer(&transfer, th->kstack, entry);
158     }
159
160     inject_transfer_context(vm_mnt, &transfer);
161     th->intr_ctx = (isr_param*)transfer.inject;
162
163     commit_thread(th);
164 }
165
166 void 
167 exit_thread(void* val) {
168     terminate_current_thread((ptr_t)val);
169     schedule();
170 }
171
172 struct thread*
173 thread_find(struct proc_info* proc, tid_t tid)
174 {
175     struct thread *pos, *n;
176     llist_for_each(pos, n, &proc->threads, proc_sibs) {
177         if (pos->tid == tid) {
178             return pos;
179         }
180     }
181
182     return NULL;
183 }
184
185 __DEFINE_LXSYSCALL4(int, th_create, tid_t*, tid, struct uthread_info*, thinfo, 
186                                     void*, entry, void*, param)
187 {
188     struct thread* th = create_thread(__current, VMS_SELF, true);
189     if (!th) {
190         return EAGAIN;
191     }
192
193     start_thread(th, VMS_SELF, (ptr_t)entry);
194
195     ptr_t ustack_top = th->ustack_top;
196     *((void**)ustack_top) = param;
197
198     thinfo->th_stack_sz = region_size(th->ustack);
199     thinfo->th_stack_top = (void*)ustack_top;
200     
201     if (tid) {
202         *tid = th->tid;
203     }
204
205     return 0;
206 }
207
208 __DEFINE_LXSYSCALL(tid_t, th_self)
209 {
210     return current_thread->tid;
211 }
212
213 __DEFINE_LXSYSCALL1(void, th_exit, void*, val)
214 {
215     exit_thread(val);
216 }
217
218 __DEFINE_LXSYSCALL2(int, th_join, tid_t, tid, void**, val_ptr)
219 {
220     struct thread* th = thread_find(__current, tid);
221     if (!th) {
222         return EINVAL;
223     }
224
225     if (th == current_thread) {
226         return EDEADLK;
227     }
228
229     while (!proc_terminated(th)) {
230         sched_pass();
231     }
232
233     if (val_ptr) {
234         *val_ptr = (void*)th->exit_val;
235     }
236
237     destory_thread(VMS_SELF, th);
238
239     return 0;
240 }
241
242 __DEFINE_LXSYSCALL1(int, th_detach, tid_t, tid)
243 {
244     // can not detach the only thread
245     if (__current->thread_count == 1) {
246         return EINVAL;
247     }
248
249     struct thread* th = thread_find(__current, tid);
250     if (!th) {
251         return EINVAL;
252     }
253
254     detach_thread(th);
255     return 0;
256 }
257
258 __DEFINE_LXSYSCALL2(int, th_kill, tid_t, tid, int, signum)
259 {
260     struct thread* target = thread_find(__current, tid);
261     if (!target) {
262         return EINVAL;
263     }
264
265     if (signum > _SIG_NUM) {
266         return EINVAL;
267     }
268
269     if (signum) {
270         thread_setsignal(target, signum);
271     }
272     
273     return 0;
274 }