optimize the menuconfig redrawing
[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 <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         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
177     struct proc_mm* mm = vmspace(pcb);
178     procvm_dupvms_mount(mm);
179
180     struct thread* main_thread = dup_active_thread(mm->vm_mnt, pcb);
181     if (!main_thread) {
182         syscall_result(ENOMEM);
183         procvm_unmount(mm);
184         delete_process(pcb);
185         return -1;
186     }
187
188     // 根据 mm_region 进一步配置页表
189     struct mm_region *pos, *n;
190     llist_for_each(pos, n, &pcb->mm->regions, head)
191     {
192         region_maybe_cow(pos);
193     }
194
195     procvm_unmount(mm);
196
197     commit_process(pcb);
198     commit_thread(main_thread);
199
200     return pcb->pid;
201 }
202
203 __DEFINE_LXSYSCALL(pid_t, fork)
204 {
205     return dup_proc();
206 }