Multiuser, Capabilities and Access Controls (#54)
[lunaix-os.git] / lunaix-os / kernel / process / fork.c
1 #include <lunaix/mm/region.h>
2 #include <lunaix/mm/valloc.h>
3 #include <lunaix/mm/page.h>
4 #include <lunaix/mm/mmap.h>
5 #include <lunaix/process.h>
6 #include <lunaix/spike.h>
7 #include <lunaix/status.h>
8 #include <lunaix/syscall.h>
9 #include <lunaix/syslog.h>
10 #include <lunaix/signal.h>
11 #include <lunaix/kpreempt.h>
12
13 #include <asm/abi.h>
14 #include <asm/mm_defs.h>
15
16 #include <klibc/string.h>
17
18 LOG_MODULE("FORK")
19
20 static void
21 region_maybe_cow(struct mm_region* region)
22 {
23     int attr = region->attr;
24     if ((attr & REGION_WSHARED)) {
25         return;
26     }
27
28     pfn_t start_pn = pfn(region->start);
29     pfn_t end_pn = pfn(region->end);
30     
31     for (size_t i = start_pn; i <= end_pn; i++) {
32         pte_t* self = mkptep_pn(VMS_SELF, i);
33         pte_t* guest = mkptep_pn(VMS_MOUNT_1, i);
34         ptr_t va = page_addr(ptep_pfn(self));
35
36         if ((attr & REGION_MODE_MASK) == REGION_RSHARED) {
37             set_pte(self, pte_mkwprotect(*self));
38             set_pte(guest, pte_mkwprotect(*guest));
39         } else {
40             // 如果是私有页,则将该页从新进程中移除。
41             set_pte(guest, null_pte);
42         }
43     }
44
45     tlb_flush_vmr_all(region);
46 }
47
48 static inline void
49 __dup_fdtable(struct proc_info* pcb)
50 {
51     for (size_t i = 0; i < VFS_MAX_FD; i++) {
52         struct v_fd* fd = __current->fdtable->fds[i];
53         if (!fd)
54             continue;
55         vfs_dup_fd(fd, &pcb->fdtable->fds[i]);
56     }
57 }
58
59
60 static void
61 __dup_kernel_stack(struct thread* thread, ptr_t vm_mnt)
62 {
63     struct leaflet* leaflet;
64
65     ptr_t kstack_pn = pfn(current_thread->kstack);
66     kstack_pn -= pfn(KSTACK_SIZE);
67
68     // copy the kernel stack
69     pte_t* src_ptep = mkptep_pn(VMS_SELF, kstack_pn);
70     pte_t* dest_ptep = mkptep_pn(vm_mnt, kstack_pn);
71     for (size_t i = 0; i <= pfn(KSTACK_SIZE); i++) {
72         pte_t p = *src_ptep;
73
74         if (pte_isguardian(p)) {
75             set_pte(dest_ptep, guard_pte);
76         } else {
77             leaflet = dup_leaflet(pte_leaflet(p));
78             i += ptep_map_leaflet(dest_ptep, p, leaflet);
79         }
80
81         src_ptep++;
82         dest_ptep++;
83     }
84
85     struct proc_mm* mm = vmspace(thread->process);
86     tlb_flush_mm_range(mm, kstack_pn, leaf_count(KSTACK_SIZE));
87 }
88
89 /*
90     Duplicate the current active thread to the forked process's
91     main thread.
92
93     This is not the same as "fork a thread within the same 
94     process". In fact, it is impossible to do such "thread forking"
95     as the new forked thread's kernel and user stack must not
96     coincide with the original thread (because the same vm space)
97     thus all reference to the stack space are staled which could 
98     lead to undefined behaviour.
99
100 */
101
102 static struct thread*
103 dup_active_thread(ptr_t vm_mnt, struct proc_info* duped_pcb) 
104 {
105     struct thread* th = alloc_thread(duped_pcb);
106     if (!th) {
107         return NULL;
108     }
109
110     th->hstate = current_thread->hstate;
111     th->kstack = current_thread->kstack;
112
113     signal_dup_context(&th->sigctx);
114
115     /*
116      *  store the return value for forked process.
117      *  this will be implicit carried over after kernel stack is copied.
118      */
119     store_retval_to(th, 0);
120
121     __dup_kernel_stack(th, vm_mnt);
122
123     if (!current_thread->ustack) {
124         goto done;
125     }
126
127     struct mm_region* old_stack = current_thread->ustack;
128     struct mm_region *pos, *n;
129     llist_for_each(pos, n, vmregions(duped_pcb), head)
130     {
131         // remove stack of other threads.
132         if (!stack_region(pos)) {
133             continue;
134         }
135
136         if (!same_region(pos, old_stack)) {
137             mem_unmap_region(vm_mnt, pos);
138         }
139         else {
140             th->ustack = pos;
141         }
142     }
143
144     assert(th->ustack);
145
146 done:
147     return th;
148 }
149
150 pid_t
151 dup_proc()
152 {
153     no_preemption();
154     
155     struct proc_info* pcb = alloc_process();
156     if (!pcb) {
157         syscall_result(ENOMEM);
158         return -1;
159     }
160     
161     pcb->parent = __current;
162
163     // FIXME need a more elagent refactoring
164     if (__current->cmd) {
165         pcb->cmd_len = __current->cmd_len;
166         pcb->cmd = valloc(pcb->cmd_len);
167         memcpy(pcb->cmd, __current->cmd, pcb->cmd_len);
168     }
169
170     if (__current->cwd) {
171         pcb->cwd = __current->cwd;
172         vfs_ref_dnode(pcb->cwd);
173     }
174
175     __dup_fdtable(pcb);
176     uscope_copy(&pcb->uscope, current_user_scope());
177
178     struct proc_mm* mm = vmspace(pcb);
179     procvm_dupvms_mount(mm);
180
181     struct thread* main_thread = dup_active_thread(mm->vm_mnt, pcb);
182     if (!main_thread) {
183         syscall_result(ENOMEM);
184         procvm_unmount(mm);
185         delete_process(pcb);
186         return -1;
187     }
188
189     // 根据 mm_region 进一步配置页表
190     struct mm_region *pos, *n;
191     llist_for_each(pos, n, &pcb->mm->regions, head)
192     {
193         region_maybe_cow(pos);
194     }
195
196     procvm_unmount(mm);
197
198     commit_process(pcb);
199     commit_thread(main_thread);
200
201     return pcb->pid;
202 }
203
204 __DEFINE_LXSYSCALL(pid_t, fork)
205 {
206     return dup_proc();
207 }