refactor: vmm_set_map has option to ignore existed mapping.
[lunaix-os.git] / lunaix-os / kernel / process.c
1 #include <klibc/string.h>
2 #include <lunaix/clock.h>
3 #include <lunaix/common.h>
4 #include <lunaix/mm/pmm.h>
5 #include <lunaix/mm/region.h>
6 #include <lunaix/mm/vmm.h>
7 #include <lunaix/process.h>
8 #include <lunaix/spike.h>
9 #include <lunaix/status.h>
10 #include <lunaix/syscall.h>
11 #include <lunaix/syslog.h>
12
13 LOG_MODULE("PROC")
14
15 void*
16 __dup_pagetable(pid_t pid, uintptr_t mount_point)
17 {
18     void* ptd_pp = pmm_alloc_page(pid, PP_FGPERSIST);
19     vmm_set_mapping(PD_REFERENCED, PG_MOUNT_1, ptd_pp, PG_PREM_RW, VMAP_NULL);
20
21     x86_page_table* ptd = PG_MOUNT_1;
22     x86_page_table* pptd = (x86_page_table*)(mount_point | (0x3FF << 12));
23
24     for (size_t i = 0; i < PG_MAX_ENTRIES - 1; i++) {
25         x86_pte_t ptde = pptd->entry[i];
26         if (!ptde || !(ptde & PG_PRESENT)) {
27             ptd->entry[i] = ptde;
28             continue;
29         }
30
31         void* pt_pp = pmm_alloc_page(pid, PP_FGPERSIST);
32         vmm_set_mapping(
33           PD_REFERENCED, PG_MOUNT_2, pt_pp, PG_PREM_RW, VMAP_NULL);
34
35         x86_page_table* ppt = (x86_page_table*)(mount_point | (i << 12));
36         x86_page_table* pt = PG_MOUNT_2;
37
38         for (size_t j = 0; j < PG_MAX_ENTRIES; j++) {
39             x86_pte_t pte = ppt->entry[j];
40             pmm_ref_page(pid, PG_ENTRY_ADDR(pte));
41             pt->entry[j] = pte;
42         }
43
44         ptd->entry[i] = (uintptr_t)pt_pp | PG_PREM_RW;
45     }
46
47     ptd->entry[PG_MAX_ENTRIES - 1] = NEW_L1_ENTRY(T_SELF_REF_PERM, ptd_pp);
48
49     return ptd_pp;
50 }
51
52 void
53 __del_pagetable(pid_t pid, uintptr_t mount_point)
54 {
55     x86_page_table* pptd = (x86_page_table*)(mount_point | (0x3FF << 12));
56
57     for (size_t i = 0; i < PG_MAX_ENTRIES - 1; i++) {
58         x86_pte_t ptde = pptd->entry[i];
59         if (!ptde || !(ptde & PG_PRESENT)) {
60             continue;
61         }
62
63         x86_page_table* ppt = (x86_page_table*)(mount_point | (i << 12));
64
65         for (size_t j = 0; j < PG_MAX_ENTRIES; j++) {
66             x86_pte_t pte = ppt->entry[j];
67             // free the 4KB data page
68             if ((pte & PG_PRESENT)) {
69                 pmm_free_page(pid, PG_ENTRY_ADDR(pte));
70             }
71         }
72         // free the L2 page table
73         pmm_free_page(pid, PG_ENTRY_ADDR(ptde));
74     }
75     // free the L1 directory
76     pmm_free_page(pid, PG_ENTRY_ADDR(pptd->entry[PG_MAX_ENTRIES - 1]));
77 }
78
79 void*
80 dup_pagetable(pid_t pid)
81 {
82     return __dup_pagetable(pid, PD_REFERENCED);
83 }
84
85 __DEFINE_LXSYSCALL(pid_t, fork)
86 {
87     return dup_proc();
88 }
89
90 __DEFINE_LXSYSCALL(pid_t, getpid)
91 {
92     return __current->pid;
93 }
94
95 __DEFINE_LXSYSCALL(pid_t, getppid)
96 {
97     return __current->parent->pid;
98 }
99
100 __DEFINE_LXSYSCALL(pid_t, getpgid)
101 {
102     return __current->pgid;
103 }
104
105 __DEFINE_LXSYSCALL2(int, setpgid, pid_t, pid, pid_t, pgid)
106 {
107     struct proc_info* proc = pid ? get_process(pid) : __current;
108
109     if (!proc) {
110         __current->k_status = LXINVL;
111         return -1;
112     }
113
114     pgid = pgid ? pgid : proc->pid;
115
116     struct proc_info* gruppenfuhrer = get_process(pgid);
117
118     if (!gruppenfuhrer || proc->pgid == proc->pid) {
119         __current->k_status = LXINVL;
120         return -1;
121     }
122
123     llist_delete(&proc->grp_member);
124     llist_append(&gruppenfuhrer->grp_member, &proc->grp_member);
125
126     proc->pgid = pgid;
127     return 0;
128 }
129
130 void
131 init_proc(struct proc_info* pcb)
132 {
133     memset(pcb, 0, sizeof(*pcb));
134
135     pcb->pid = alloc_pid();
136     pcb->created = clock_systime();
137     pcb->state = PROC_CREATED;
138     pcb->pgid = pcb->pid;
139 }
140
141 pid_t
142 dup_proc()
143 {
144     struct proc_info pcb;
145     init_proc(&pcb);
146     pcb.mm = __current->mm;
147     pcb.intr_ctx = __current->intr_ctx;
148     pcb.parent = __current;
149
150 #ifdef USE_KERNEL_PG
151     setup_proc_mem(&pcb, PD_MOUNT_1); //挂载点#1是当前进程的页表
152 #else
153     setup_proc_mem(&pcb, PD_REFERENCED);
154 #endif
155
156     // 根据 mm_region 进一步配置页表
157     if (!__current->mm.regions) {
158         goto not_copy;
159     }
160
161     llist_init_head(&pcb.mm.regions);
162     struct mm_region *pos, *n;
163     llist_for_each(pos, n, &__current->mm.regions->head, head)
164     {
165         region_add(&pcb, pos->start, pos->end, pos->attr);
166
167         // 如果写共享,则不作处理。
168         if ((pos->attr & REGION_WSHARED)) {
169             continue;
170         }
171
172         uintptr_t start_vpn = PG_ALIGN(pos->start) >> 12;
173         uintptr_t end_vpn = PG_ALIGN(pos->end) >> 12;
174         for (size_t i = start_vpn; i < end_vpn; i++) {
175             x86_pte_t* curproc = &PTE_MOUNTED(PD_MOUNT_1, i);
176             x86_pte_t* newproc = &PTE_MOUNTED(PD_MOUNT_2, i);
177             cpu_invplg(newproc);
178
179             if (pos->attr == REGION_RSHARED) {
180                 // 如果读共享,则将两者的都标注为只读,那么任何写入都将会应用COW策略。
181                 cpu_invplg(curproc);
182                 *curproc = *curproc & ~PG_WRITE;
183                 *newproc = *newproc & ~PG_WRITE;
184             } else {
185                 // 如果是私有页,则将该页从新进程中移除。
186                 *newproc = 0;
187             }
188         }
189     }
190
191 not_copy:
192     vmm_unmount_pd(PD_MOUNT_2);
193
194     // 正如同fork,返回两次。
195     pcb.intr_ctx.registers.eax = 0;
196
197     push_process(&pcb);
198
199     return pcb.pid;
200 }
201
202 extern void __kernel_end;
203
204 void
205 setup_proc_mem(struct proc_info* proc, uintptr_t usedMnt)
206 {
207     // copy the entire kernel page table
208     pid_t pid = proc->pid;
209     void* pt_copy = __dup_pagetable(pid, usedMnt);
210
211     vmm_mount_pd(PD_MOUNT_2, pt_copy); // 将新进程的页表挂载到挂载点#2
212
213     // copy the kernel stack
214     for (size_t i = KSTACK_START >> 12; i <= KSTACK_TOP >> 12; i++) {
215         volatile x86_pte_t* ppte = &PTE_MOUNTED(PD_MOUNT_2, i);
216
217         /*
218             This is a fucking nightmare, the TLB caching keep the rewrite to PTE
219            from updating. Even the Nightmare Moon the Evil is far less nasty
220            than this. It took me hours of debugging to figure this out.
221
222             In the name of Celestia our glorious goddess, I will fucking HATE
223            the TLB for the rest of my LIFE!
224         */
225         cpu_invplg(ppte);
226
227         x86_pte_t p = *ppte;
228         void* ppa = vmm_dup_page(pid, PG_ENTRY_ADDR(p));
229         *ppte = (p & 0xfff) | (uintptr_t)ppa;
230     }
231
232     // 我们不需要分配内核的区域,因为所有的内核代码和数据段只能通过系统调用来访问,任何非法的访问
233     // 都会导致eip落在区域外面,从而segmentation fault.
234
235     // 定义用户栈区域,但是不分配实际的物理页。我们会在Page fault
236     // handler里面实现动态分配物理页的逻辑。(虚拟内存的好处!)
237     // FIXME: 这里应该放到spawn_proc里面。
238     // region_add(proc, USTACK_END, USTACK_SIZE, REGION_PRIVATE | REGION_RW);
239
240     // 至于其他的区域我们暂时没有办法知道,因为那需要知道用户程序的信息。我们留到之后在处理。
241
242     proc->page_table = pt_copy;
243 }