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