6dbe737206c8e2d4f8a698795bfd9430625c7ad3
[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
12 #include <sys/abi.h>
13 #include <sys/mm/mm_defs.h>
14
15 #include <klibc/string.h>
16
17 LOG_MODULE("FORK")
18
19 static void
20 region_maybe_cow(struct mm_region* region)
21 {
22     int attr = region->attr;
23     if ((attr & REGION_WSHARED)) {
24         return;
25     }
26
27     pfn_t start_pn = pfn(region->start);
28     pfn_t end_pn = pfn(region->end);
29     
30     for (size_t i = start_pn; i <= end_pn; i++) {
31         pte_t* self = mkptep_pn(VMS_SELF, i);
32         pte_t* guest = mkptep_pn(VMS_MOUNT_1, i);
33         ptr_t va = page_addr(ptep_pfn(self));
34
35         if ((attr & REGION_MODE_MASK) == REGION_RSHARED) {
36             set_pte(self, pte_mkwprotect(*self));
37             set_pte(guest, pte_mkwprotect(*guest));
38         } else {
39             // 如果是私有页,则将该页从新进程中移除。
40             set_pte(guest, null_pte);
41         }
42     }
43
44     tlb_flush_vmr_all(region);
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     struct leaflet* leaflet;
63
64     ptr_t kstack_pn = pfn(current_thread->kstack);
65     kstack_pn -= pfn(KSTACK_SIZE) - 1;
66
67     // copy the kernel stack
68     pte_t* src_ptep = mkptep_pn(VMS_SELF, kstack_pn);
69     pte_t* dest_ptep = mkptep_pn(vm_mnt, kstack_pn);
70     for (size_t i = 0; i < pfn(KSTACK_SIZE); i++) {
71         pte_t p = *src_ptep;
72
73         if (pte_isguardian(p)) {
74             set_pte(dest_ptep, guard_pte);
75         } else {
76             leaflet = dup_leaflet(pte_leaflet(p));
77             i += ptep_map_leaflet(dest_ptep, p, leaflet);
78         }
79
80         src_ptep++;
81         dest_ptep++;
82     }
83
84     struct proc_mm* mm = vmspace(thread->process);
85     tlb_flush_mm_range(mm, kstack_pn, leaf_count(KSTACK_SIZE));
86 }
87
88 /*
89     Duplicate the current active thread to the forked process's
90     main thread.
91
92     This is not the same as "fork a thread within the same 
93     process". In fact, it is impossible to do such "thread forking"
94     as the new forked thread's kernel and user stack must not
95     coincide with the original thread (because the same vm space)
96     thus all reference to the stack space are staled which could 
97     lead to undefined behaviour.
98
99 */
100
101 static struct thread*
102 dup_active_thread(ptr_t vm_mnt, struct proc_info* duped_pcb) 
103 {
104     struct thread* th = alloc_thread(duped_pcb);
105     if (!th) {
106         return NULL;
107     }
108
109     th->hstate = current_thread->hstate;
110     th->kstack = current_thread->kstack;
111
112     signal_dup_context(&th->sigctx);
113
114     /*
115      *  store the return value for forked process.
116      *  this will be implicit carried over after kernel stack is copied.
117      */
118     store_retval_to(th, 0);
119
120     __dup_kernel_stack(th, vm_mnt);
121
122     if (!current_thread->ustack) {
123         goto done;
124     }
125
126     struct mm_region* old_stack = current_thread->ustack;
127     struct mm_region *pos, *n;
128     llist_for_each(pos, n, vmregions(duped_pcb), head)
129     {
130         // remove stack of other threads.
131         if (!stack_region(pos)) {
132             continue;
133         }
134
135         if (!same_region(pos, old_stack)) {
136             mem_unmap_region(vm_mnt, pos);
137         }
138         else {
139             th->ustack = pos;
140         }
141     }
142
143     assert(th->ustack);
144
145 done:
146     return th;
147 }
148
149 pid_t
150 dup_proc()
151 {
152     // FIXME need investigate: issue with fork, as well as pthread
153     //       especially when involving frequent alloc and dealloc ops
154     //       (could be issue in allocator's segregated free list)
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 }