Framework for exporting system header to user space (#59)
[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 void
49 __dup_kernel_stack(struct thread* thread, ptr_t vm_mnt)
50 {
51     struct leaflet* leaflet;
52
53     ptr_t kstack_pn = pfn(current_thread->kstack);
54     kstack_pn -= pfn(KSTACK_SIZE);
55
56     // copy the kernel stack
57     pte_t* src_ptep = mkptep_pn(VMS_SELF, kstack_pn);
58     pte_t* dest_ptep = mkptep_pn(vm_mnt, kstack_pn);
59     for (size_t i = 0; i <= pfn(KSTACK_SIZE); i++) {
60         pte_t p = *src_ptep;
61
62         if (pte_isguardian(p)) {
63             set_pte(dest_ptep, guard_pte);
64         } else {
65             leaflet = dup_leaflet(pte_leaflet(p));
66             i += ptep_map_leaflet(dest_ptep, p, leaflet);
67         }
68
69         src_ptep++;
70         dest_ptep++;
71     }
72
73     struct proc_mm* mm = vmspace(thread->process);
74     tlb_flush_mm_range(mm, kstack_pn, leaf_count(KSTACK_SIZE));
75 }
76
77 /*
78     Duplicate the current active thread to the forked process's
79     main thread.
80
81     This is not the same as "fork a thread within the same 
82     process". In fact, it is impossible to do such "thread forking"
83     as the new forked thread's kernel and user stack must not
84     coincide with the original thread (because the same vm space)
85     thus all reference to the stack space are staled which could 
86     lead to undefined behaviour.
87
88 */
89
90 static struct thread*
91 dup_active_thread(ptr_t vm_mnt, struct proc_info* duped_pcb) 
92 {
93     struct thread* th = alloc_thread(duped_pcb);
94     if (!th) {
95         return NULL;
96     }
97
98     th->hstate = current_thread->hstate;
99     th->kstack = current_thread->kstack;
100
101     signal_dup_context(&th->sigctx);
102
103     /*
104      *  store the return value for forked process.
105      *  this will be implicit carried over after kernel stack is copied.
106      */
107     store_retval_to(th, 0);
108
109     __dup_kernel_stack(th, vm_mnt);
110
111     if (!current_thread->ustack) {
112         goto done;
113     }
114
115     struct mm_region* old_stack = current_thread->ustack;
116     struct mm_region *pos, *n;
117     llist_for_each(pos, n, vmregions(duped_pcb), head)
118     {
119         // remove stack of other threads.
120         if (!stack_region(pos)) {
121             continue;
122         }
123
124         if (!same_region(pos, old_stack)) {
125             mem_unmap_region(vm_mnt, pos);
126         }
127         else {
128             th->ustack = pos;
129         }
130     }
131
132     assert(th->ustack);
133
134 done:
135     return th;
136 }
137
138 pid_t
139 dup_proc()
140 {
141     no_preemption();
142     
143     struct proc_info* pcb = alloc_process();
144     if (!pcb) {
145         syscall_result(ENOMEM);
146         return -1;
147     }
148     
149     pcb->parent = __current;
150
151     // FIXME need a more elagent refactoring
152     if (__current->cmd) {
153         pcb->cmd_len = __current->cmd_len;
154         pcb->cmd = valloc(pcb->cmd_len);
155         memcpy(pcb->cmd, __current->cmd, pcb->cmd_len);
156     }
157
158     if (__current->cwd) {
159         pcb->cwd = __current->cwd;
160         vfs_ref_dnode(pcb->cwd);
161     }
162
163     fdtable_copy(pcb->fdtable, __current->fdtable);
164     uscope_copy(&pcb->uscope, current_user_scope());
165
166     struct proc_mm* mm = vmspace(pcb);
167     procvm_dupvms_mount(mm);
168
169     struct thread* main_thread = dup_active_thread(mm->vm_mnt, pcb);
170     if (!main_thread) {
171         syscall_result(ENOMEM);
172         procvm_unmount(mm);
173         delete_process(pcb);
174         return -1;
175     }
176
177     // 根据 mm_region 进一步配置页表
178     struct mm_region *pos, *n;
179     llist_for_each(pos, n, &pcb->mm->regions, head)
180     {
181         region_maybe_cow(pos);
182     }
183
184     procvm_unmount(mm);
185
186     commit_process(pcb);
187     commit_thread(main_thread);
188
189     return pcb->pid;
190 }
191
192 __DEFINE_LXSYSCALL(pid_t, fork)
193 {
194     return dup_proc();
195 }