feat: nearly complete POSIX.1-2008 compliant terminal interface implementation
[lunaix-os.git] / lunaix-os / kernel / process / sched.c
1 #include <sys/abi.h>
2 #include <sys/interrupts.h>
3 #include <sys/mm/mempart.h>
4
5 #include <hal/intc.h>
6 #include <sys/cpu.h>
7
8 #include <lunaix/fs/taskfs.h>
9 #include <lunaix/mm/cake.h>
10 #include <lunaix/mm/mmap.h>
11 #include <lunaix/mm/pmm.h>
12 #include <lunaix/mm/valloc.h>
13 #include <lunaix/mm/vmm.h>
14 #include <lunaix/process.h>
15 #include <lunaix/sched.h>
16 #include <lunaix/signal.h>
17 #include <lunaix/spike.h>
18 #include <lunaix/status.h>
19 #include <lunaix/syscall.h>
20 #include <lunaix/syslog.h>
21
22 #include <klibc/string.h>
23
24 volatile struct proc_info* __current;
25
26 static struct proc_info dummy_proc;
27
28 struct proc_info dummy;
29
30 struct scheduler sched_ctx;
31
32 struct cake_pile* proc_pile;
33
34 LOG_MODULE("SCHED")
35
36 void
37 sched_init_dummy();
38
39 void
40 sched_init()
41 {
42     proc_pile = cake_new_pile("proc", sizeof(struct proc_info), 1, 0);
43     cake_set_constructor(proc_pile, cake_ctor_zeroing);
44
45     sched_ctx = (struct scheduler){
46         ._procs = vzalloc(PROC_TABLE_SIZE), .ptable_len = 0, .procs_index = 0};
47
48     // TODO initialize dummy_proc
49     sched_init_dummy();
50 }
51
52 #define DUMMY_STACK_SIZE 2048
53
54 void
55 sched_init_dummy()
56 {
57     // This surely need to be simplified or encapsulated!
58     // It is a living nightmare!
59
60     extern void my_dummy();
61     static char dummy_stack[DUMMY_STACK_SIZE] __attribute__((aligned(16)));
62
63     ptr_t stktop = (ptr_t)dummy_stack + DUMMY_STACK_SIZE;
64
65     dummy_proc = (struct proc_info){};
66
67     proc_init_transfer(&dummy_proc, stktop, (ptr_t)my_dummy, TRANSFER_IE);
68
69     dummy_proc.page_table = cpu_ldvmspace();
70     dummy_proc.state = PS_READY;
71     dummy_proc.parent = &dummy_proc;
72     dummy_proc.pid = KERNEL_PID;
73
74     __current = &dummy_proc;
75 }
76
77 void
78 run(struct proc_info* proc)
79 {
80     proc->state = PS_RUNNING;
81
82     intc_notify_eos(0);
83     switch_context(proc);
84 }
85
86 int
87 can_schedule(struct proc_info* proc)
88 {
89     if (!proc) {
90         return 0;
91     }
92
93     struct sighail* sh = &proc->sigctx;
94
95     if ((proc->state & PS_PAUSED)) {
96         return !!(sh->sig_pending & ~1);
97     }
98     if ((proc->state & PS_BLOCKED)) {
99         return sigset_test(sh->sig_pending, _SIGINT);
100     }
101
102     if (sigset_test(sh->sig_pending, _SIGCONT)) {
103         sigset_clear(sh->sig_pending, _SIGSTOP);
104     } else if (sigset_test(sh->sig_pending, _SIGSTOP)) {
105         // 如果进程受到SIGSTOP,则该进程不给予调度。
106         return 0;
107     }
108
109     return (proc->state == PS_READY);
110 }
111
112 void
113 check_sleepers()
114 {
115     struct proc_info* leader = sched_ctx._procs[0];
116     struct proc_info *pos, *n;
117     time_t now = clock_systime() / 1000;
118     llist_for_each(pos, n, &leader->sleep.sleepers, sleep.sleepers)
119     {
120         if (proc_terminated(pos)) {
121             goto del;
122         }
123
124         time_t wtime = pos->sleep.wakeup_time;
125         time_t atime = pos->sleep.alarm_time;
126
127         if (wtime && now >= wtime) {
128             pos->sleep.wakeup_time = 0;
129             pos->state = PS_READY;
130         }
131
132         if (atime && now >= atime) {
133             pos->sleep.alarm_time = 0;
134             proc_setsignal(pos, _SIGALRM);
135         }
136
137         if (!wtime && !atime) {
138         del:
139             llist_delete(&pos->sleep.sleepers);
140         }
141     }
142 }
143
144 void
145 schedule()
146 {
147     if (!sched_ctx.ptable_len) {
148         return;
149     }
150
151     // 上下文切换相当的敏感!我们不希望任何的中断打乱栈的顺序……
152     cpu_disable_interrupt();
153     struct proc_info* next;
154     int prev_ptr = sched_ctx.procs_index;
155     int ptr = prev_ptr;
156     int found = 0;
157
158     if (!(__current->state & ~PS_RUNNING)) {
159         __current->state = PS_READY;
160     }
161
162     check_sleepers();
163
164     // round-robin scheduler
165     do {
166         ptr = (ptr + 1) % sched_ctx.ptable_len;
167         next = sched_ctx._procs[ptr];
168
169         if (!(found = can_schedule(next))) {
170             if (ptr == prev_ptr) {
171                 next = &dummy_proc;
172                 goto done;
173             }
174         }
175     } while (!found);
176
177     sched_ctx.procs_index = ptr;
178
179 done:
180     run(next);
181 }
182
183 void
184 sched_yieldk()
185 {
186     cpu_enable_interrupt();
187     cpu_trap_sched();
188 }
189
190 __DEFINE_LXSYSCALL1(unsigned int, sleep, unsigned int, seconds)
191 {
192     if (!seconds) {
193         return 0;
194     }
195
196     time_t systime = clock_systime() / 1000;
197
198     if (__current->sleep.wakeup_time) {
199         return (__current->sleep.wakeup_time - systime);
200     }
201
202     struct proc_info* root_proc = sched_ctx._procs[0];
203     __current->sleep.wakeup_time = systime + seconds;
204
205     if (llist_empty(&__current->sleep.sleepers)) {
206         llist_append(&root_proc->sleep.sleepers, &__current->sleep.sleepers);
207     }
208
209     store_retval(seconds);
210
211     block_current();
212     schedule();
213
214     return 0;
215 }
216
217 __DEFINE_LXSYSCALL1(unsigned int, alarm, unsigned int, seconds)
218 {
219     time_t prev_ddl = __current->sleep.alarm_time;
220     time_t now = clock_systime() / 1000;
221
222     __current->sleep.alarm_time = seconds ? now + seconds : 0;
223
224     struct proc_info* root_proc = sched_ctx._procs[0];
225     if (llist_empty(&__current->sleep.sleepers)) {
226         llist_append(&root_proc->sleep.sleepers, &__current->sleep.sleepers);
227     }
228
229     return prev_ddl ? (prev_ddl - now) : 0;
230 }
231
232 __DEFINE_LXSYSCALL1(void, exit, int, status)
233 {
234     terminate_proc(status);
235     schedule();
236 }
237
238 __DEFINE_LXSYSCALL(void, yield)
239 {
240     schedule();
241 }
242
243 pid_t
244 _wait(pid_t wpid, int* status, int options);
245
246 __DEFINE_LXSYSCALL1(pid_t, wait, int*, status)
247 {
248     return _wait(-1, status, 0);
249 }
250
251 __DEFINE_LXSYSCALL3(pid_t, waitpid, pid_t, pid, int*, status, int, options)
252 {
253     return _wait(pid, status, options);
254 }
255
256 __DEFINE_LXSYSCALL(int, geterrno)
257 {
258     return __current->k_status;
259 }
260
261 pid_t
262 _wait(pid_t wpid, int* status, int options)
263 {
264     pid_t cur = __current->pid;
265     int status_flags = 0;
266     struct proc_info *proc, *n;
267     if (llist_empty(&__current->children)) {
268         return -1;
269     }
270
271     wpid = wpid ? wpid : -__current->pgid;
272 repeat:
273     llist_for_each(proc, n, &__current->children, siblings)
274     {
275         if (!~wpid || proc->pid == wpid || proc->pgid == -wpid) {
276             if (proc->state == PS_TERMNAT && !options) {
277                 status_flags |= PEXITTERM;
278                 goto done;
279             }
280             if (proc->state == PS_READY && (options & WUNTRACED)) {
281                 status_flags |= PEXITSTOP;
282                 goto done;
283             }
284         }
285     }
286     if ((options & WNOHANG)) {
287         return 0;
288     }
289     // 放弃当前的运行机会
290     sched_yieldk();
291     goto repeat;
292
293 done:
294     if (status) {
295         *status = proc->exit_code | status_flags;
296     }
297     return destroy_process(proc->pid);
298 }
299
300 struct proc_info*
301 alloc_process()
302 {
303     pid_t i = 0;
304     for (; i < sched_ctx.ptable_len && sched_ctx._procs[i]; i++)
305         ;
306
307     if (i == MAX_PROCESS) {
308         panick("Panic in Ponyville shimmer!");
309     }
310
311     if (i == sched_ctx.ptable_len) {
312         sched_ctx.ptable_len++;
313     }
314
315     struct proc_info* proc = cake_grab(proc_pile);
316
317     proc->state = PS_CREATED;
318     proc->pid = i;
319     proc->mm.pid = i;
320     proc->created = clock_systime();
321     proc->pgid = proc->pid;
322     proc->fdtable = vzalloc(sizeof(struct v_fdtable));
323
324     llist_init_head(&proc->mm.regions);
325     llist_init_head(&proc->tasks);
326     llist_init_head(&proc->children);
327     llist_init_head(&proc->grp_member);
328     llist_init_head(&proc->sleep.sleepers);
329
330     iopoll_init(&proc->pollctx);
331     waitq_init(&proc->waitqueue);
332
333     sched_ctx._procs[i] = proc;
334
335     return proc;
336 }
337
338 void
339 commit_process(struct proc_info* process)
340 {
341     assert(process == sched_ctx._procs[process->pid]);
342
343     if (process->state != PS_CREATED) {
344         __current->k_status = EINVAL;
345         return;
346     }
347
348     // every process is the child of first process (pid=1)
349     if (!process->parent) {
350         process->parent = sched_ctx._procs[1];
351     }
352
353     llist_append(&process->parent->children, &process->siblings);
354     llist_append(&sched_ctx._procs[0]->tasks, &process->tasks);
355
356     process->state = PS_READY;
357 }
358
359 // from <kernel/process.c>
360 extern void
361 __del_pagetable(pid_t pid, ptr_t mount_point);
362
363 pid_t
364 destroy_process(pid_t pid)
365 {
366     int index = pid;
367     if (index <= 0 || index > sched_ctx.ptable_len) {
368         __current->k_status = EINVAL;
369         return -1;
370     }
371
372     struct proc_info* proc = sched_ctx._procs[index];
373     sched_ctx._procs[index] = 0;
374
375     llist_delete(&proc->siblings);
376     llist_delete(&proc->grp_member);
377     llist_delete(&proc->tasks);
378     llist_delete(&proc->sleep.sleepers);
379
380     iopoll_free(pid, &proc->pollctx);
381
382     taskfs_invalidate(pid);
383
384     if (proc->cwd) {
385         vfs_unref_dnode(proc->cwd);
386     }
387
388     for (size_t i = 0; i < VFS_MAX_FD; i++) {
389         struct v_fd* fd = proc->fdtable->fds[i];
390         if (fd) {
391             vfs_pclose(fd->file, pid);
392             vfs_free_fd(fd);
393         }
394     }
395
396     vfree(proc->fdtable);
397
398     vmm_mount_pd(VMS_MOUNT_1, proc->page_table);
399
400     struct mm_region *pos, *n;
401     llist_for_each(pos, n, &proc->mm.regions, head)
402     {
403         mem_sync_pages(VMS_MOUNT_1, pos, pos->start, pos->end - pos->start, 0);
404         region_release(pos);
405     }
406
407     __del_pagetable(pid, VMS_MOUNT_1);
408
409     vmm_unmount_pd(VMS_MOUNT_1);
410
411     cake_release(proc_pile, proc);
412
413     return pid;
414 }
415
416 void
417 terminate_proc(int exit_code)
418 {
419     __current->state = PS_TERMNAT;
420     __current->exit_code = exit_code;
421
422     proc_setsignal(__current->parent, _SIGCHLD);
423 }
424
425 struct proc_info*
426 get_process(pid_t pid)
427 {
428     int index = pid;
429     if (index < 0 || index > sched_ctx.ptable_len) {
430         return NULL;
431     }
432     return sched_ctx._procs[index];
433 }
434
435 int
436 orphaned_proc(pid_t pid)
437 {
438     if (!pid)
439         return 0;
440     if (pid >= sched_ctx.ptable_len)
441         return 0;
442     struct proc_info* proc = sched_ctx._procs[pid];
443     struct proc_info* parent = proc->parent;
444
445     // 如果其父进程的状态是terminated 或 destroy中的一种
446     // 或者其父进程是在该进程之后创建的,那么该进程为孤儿进程
447     return proc_terminated(parent) || parent->created > proc->created;
448 }