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