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