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