-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathkernel.cc
More file actions
1784 lines (1560 loc) · 52 KB
/
Copy pathkernel.cc
File metadata and controls
1784 lines (1560 loc) · 52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "kernel.hh"
#include "k-ahci.hh"
#include "k-nic.hh"
#include "k-apic.hh"
#include "k-chkfs.hh"
#include "k-chkfsiter.hh"
#include "k-devices.hh"
#include "k-vmiter.hh"
#include "k-fs.hh"
#include "k-net.hh"
#include "obj/k-firstprocess.h"
// kernel.cc
//
// This is the kernel.
// # timer interrupts so far on CPU 0
std::atomic<unsigned long> ticks;
static void tick();
static void start_initial_process(pid_t pid, const char* program_name);
void run_init_task();
// kernel_start(command)
// Initialize the hardware and processes and start running. The `command`
// string is an optional string passed from the boot loader.
void kernel_start(const char* command) {
init_hardware();
consoletype = CONSOLE_NORMAL;
console_clear();
// set up process descriptors
for (pid_t i = 0; i < NTHREAD; i++) {
ptable[i] = nullptr;
}
for (pid_t i = 0; i < NPROC; i++) {
gtable[i] = nullptr;
}
// start `init` kernel process (pid 1)
proc* init = knew<proc>();
init->group_ = knew<thread_group>();
init->group_->threads_.push_front(init);
init->init_kernel(run_init_task);
init->id_ = 1;
init->group_->pid_ = 1;
init->group_->parent_id_ = 1;
ptable[init->id_] = init;
gtable[init->group_->pid_] = init->group_;
cpus[0].enqueue(init);
// Create network daemon kernel thread on the init process
if (eth_nic) {
proc* netd = knew<proc>();
netd->group_ = init->group_;
init->group_->threads_.push_back(netd);
netd->id_ = 2;
netd->init_kernel(run_packet_handler_task);
ptable[netd->id_] = netd;
cpus[ncpu-1].enqueue(netd);
}
// start first process
start_initial_process(2, CHICKADEE_FIRST_PROCESS);
// start running processes
cpus[0].schedule();
}
// start_initial_process(pid, name)
// Load application program `name` as process number `pid`.
// This loads the application's code and data into memory, sets its
// %rip and %rsp, gives it a stack page, and marks it as runnable.
// Only called at initial boot time.
void start_initial_process(pid_t pid, const char* name) {
// look up process image in initfs
spinlock_guard initfs_guard(memfile::initfs_lock);
int mindex = memfile::initfs_lookup(name, memfile::required);
x86_64_pagetable* pt = knew_pagetable();
assert(mindex >= 0 && pt);
// load code and data into pagetable
memfile_loader ld(mindex, pt);
int r = proc::load(ld);
assert(r >= 0);
initfs_guard.unlock();
pid_t tid = 1;
for (; tid < NTHREAD; ++tid) {
if (!ptable[tid]) {
break;
}
}
assert(tid < NTHREAD);
// allocate process, initialize registers
proc* p = knew<proc>();
p->group_ = knew<thread_group>();
p->group_->threads_.push_front(p);
p->id_ = tid;
p->group_->pid_ = pid;
p->group_->parent_id_ = 1; // Force init process ID as parent
p->init_user(pt);
p->regs_->reg_rip = ld.entry_rip_;
// initialize stack
void* stkpg = kalloc(PAGESIZE);
assert(stkpg);
vmiter(p, MEMSIZE_VIRTUAL - PAGESIZE).map(stkpg, PTE_PWU);
p->regs_->reg_rsp = MEMSIZE_VIRTUAL;
// map console
vmiter(p, ktext2pa(console)).map(console, PTE_PWU);
// create standard i/o file descriptors
file* stdio = knew<file>();
stdio->vnode_ = knew<stdio_vnode>();
stdio->vnode_->refcount_ = 1;
stdio->mode_ = OF_READ | OF_WRITE;
assert(!file_table[0]);
file_table[0] = stdio;
p->group_->file_descriptors_[0] = 0;
p->group_->file_descriptors_[1] = 0;
p->group_->file_descriptors_[2] = 0;
stdio->refcount_ += 3;
for (int i = 3; i < NFILE_PROC; ++i) {
p->group_->file_descriptors_[i] = -1;
}
// add to process table (requires lock in case another CPU is already
// running processes)
{
spinlock_guard guard(ptable_lock);
spinlock_guard hguard(gtable[1]->lock_);
assert(gtable[1]); // `init` process must exist
gtable[1]->children_.push_back(p->group_);
assert(!gtable[pid] && !ptable[tid]);
ptable[tid] = p;
gtable[pid] = p->group_;
hguard.unlock();
}
// add to run queue
cpus[pid % ncpu].enqueue(p);
}
// run_init_task()
// Function that runs the kernel task for the `init` process (pid 1)
void run_init_task() {
proc* init = ptable[1];
assert(init);
while (true) {
init->waitpid(0, W_NOHANG);
if (init->group_->children_.empty()) {
process_halt();
assert(false);
return;
}
init->yield();
}
}
// proc::exception(reg)
// Exception handler (for interrupts, traps, and faults).
//
// The register values from exception time are stored in `reg`.
// The processor responds to an exception by saving application state on
// the current CPU stack, then jumping to kernel assembly code (in
// k-exception.S). That code transfers the state to the current kernel
// task's stack, then calls proc::exception().
void proc::exception(regstate* regs) {
// It can be useful to log events using `log_printf`.
// Events logged this way are stored in the host's `log.txt` file.
//log_printf("proc %d: exception %d @%p\n", id_, regs->reg_intno, regs->reg_rip);
// Set stack canary
Canary stack_guard(stack_canary_);
// Record most recent user-mode %rip.
if ((regs->reg_cs & 3) != 0) {
recent_user_rip_ = regs->reg_rip;
}
// Show the current cursor location.
consolestate::get().cursor();
// Actually handle the exception.
switch (regs->reg_intno) {
case INT_IRQ + IRQ_TIMER: {
cpustate* cpu = this_cpu();
if (cpu->cpuindex_ == 0) {
tick();
}
lapicstate::get().ack();
regs_ = regs;
yield_noreturn();
break; /* will not be reached */
}
case INT_PF: { // pagefault exception
// Analyze faulting address and access type.
uintptr_t addr = rdcr2();
const char* operation = regs->reg_errcode & PFERR_WRITE
? "write" : "read";
const char* problem = regs->reg_errcode & PFERR_PRESENT
? "protection problem" : "missing page";
if ((regs->reg_cs & 3) == 0) {
panic_at(*regs, "Kernel page fault for %p (%s %s)!\n",
addr, operation, problem);
}
error_printf(CS_ERROR "Process %d page fault for %p (%s %s, rip=%p)!\n",
id_, addr, operation, problem, regs->reg_rip);
pstate_ = proc::ps_faulted;
yield();
break;
}
case INT_IRQ + IRQ_KEYBOARD:
keyboardstate::get().handle_interrupt();
break;
default:
if (sata_disk && regs->reg_intno == INT_IRQ + sata_disk->irq_) {
sata_disk->handle_interrupt();
} else if (eth_nic && regs->reg_intno == INT_IRQ + eth_nic->irq_) {
eth_nic->handle_interrupt();
} else {
panic_at(*regs, "Unexpected exception %d!\n", regs->reg_intno);
}
break; /* will not be reached */
}
// return to interrupted context
}
// proc::syscall(regs)
// System call handler.
//
// The register values from system call time are stored in `regs`.
// The return value from `proc::syscall()` is returned to the user
// process in `%rax`.
uintptr_t proc::syscall(regstate* regs) {
//log_printf("proc %d: syscall %ld @%p\n", id_, regs->reg_rax, regs->reg_rip);
// Record most recent user-mode %rip.
recent_user_rip_ = regs->reg_rip;
// Set stack canary
Canary stack_guard(stack_canary_);
switch (regs->reg_rax) {
case SYSCALL_CONSOLETYPE:
if (consoletype != (int) regs->reg_rdi) {
console_clear();
}
consoletype = regs->reg_rdi;
return 0;
case SYSCALL_PANIC:
panic_at(*regs, "process %d called sys_panic()", id_);
break; // will not be reached
case SYSCALL_EXIT:
syscall_exit(regs);
assert(false);
break; // will not be reached
case SYSCALL_TEXIT: {
thread_exit();
assert(false);
}
case SYSCALL_KTEST:
if (regs->reg_rdi == 1) {
return ktest_wait_queues();
}
return -1;
case SYSCALL_GETPID:
assert(group_);
return group_->pid_;
case SYSCALL_GETTID:
return id_;
case SYSCALL_YIELD:
yield();
return 0;
case SYSCALL_PAGE_ALLOC: {
uintptr_t addr = regs->reg_rdi;
if (addr >= VA_LOWEND || addr & 0xFFF) {
return -1;
}
void* pg = kalloc(PAGESIZE);
if (!pg) {
return -1;
}
spinlock_guard guard(group_->pt_lock_);
if (vmiter(this, addr).try_map(ka2pa(pg), PTE_PWU) < 0) {
guard.unlock();
kfree(pg);
return -1;
}
return 0;
}
case SYSCALL_PAUSE: {
sti();
for (uintptr_t delay = 0; delay < 1000000; ++delay) {
pause();
}
return 0;
}
case SYSCALL_FORK:
return syscall_fork(regs);
case SYSCALL_EXECV:
return syscall_execv(regs);
case SYSCALL_CLONE:
return syscall_clone(regs);
case SYSCALL_READ:
return syscall_read(regs);
case SYSCALL_WRITE:
return syscall_write(regs);
case SYSCALL_LSEEK:
return syscall_lseek(regs);
case SYSCALL_READDISKFILE:
return syscall_readdiskfile(regs);
case SYSCALL_SYNC: {
int drop = regs->reg_rdi;
// `drop > 1` asserts that no data blocks are referenced (except
// possibly superblock and FBB blocks). This can only be ensured on
// tests that run as the first process.
if (drop > 1 && strncmp(CHICKADEE_FIRST_PROCESS, "test", 4) != 0) {
drop = 1;
}
return bufcache::get().sync(drop);
}
case SYSCALL_GETUSAGE: {
if (!regs->reg_rdi ||
regs->reg_rdi % alignof(usage) != 0 ||
!vmiter(this, regs->reg_rdi).user()) {
return E_FAULT;
}
usage* u = reinterpret_cast<usage*>(regs->reg_rdi);
u->time = ticks;
u->allocated_pages = allocated_pages;
u->free_pages = total_pages - allocated_pages;
return 0;
}
case SYSCALL_CORRUPT: {
syscall_corrupt();
return 0;
}
case SYSCALL_KALLOC: {
return reinterpret_cast<uintptr_t>(kalloc(regs->reg_rdi));
}
case SYSCALL_KFREE: {
kfree(reinterpret_cast<void*>(regs->reg_rdi));
return 0;
}
case SYSCALL_PAGE_FREE: {
if (regs->reg_rdi == CONSOLE_ADDR || regs->reg_rdi >= HIGHMEM_BASE) {
return E_PERM;
}
vmiter it(pagetable_, regs->reg_rdi);
it.kfree_page();
it.invalidate();
return 0;
}
case SYSCALL_MSLEEP: {
unsigned msecs = round_up(regs->reg_rdi, 10)/10;
wake_timestamp_ = ticks + msecs;
assert(!runq_links_.is_linked());
spinlock_guard guard(group_->lock_);
group_->child_exited_ = false;
waiter().wait_until(sleep_queue, [&] () {
return group_->child_exited_ || (long(ticks - wake_timestamp_) >= 0);
}, guard);
if (group_->child_exited_) {
group_->child_exited_ = false;
return E_INTR;
}
return 0;
}
case SYSCALL_GETPPID: {
assert(group_);
return group_->parent_id_;
}
case SYSCALL_WAITPID: {
sti();
return waitpid(regs->reg_rdi, regs->reg_rsi);
}
case SYSCALL_DUP2: {
return syscall_dup2(regs);
}
case SYSCALL_OPEN: {
return syscall_open(regs);
}
case SYSCALL_CLOSE: {
return syscall_close(regs);
}
case SYSCALL_UNLINK: {
return syscall_unlink(regs);
}
case SYSCALL_PIPE: {
return syscall_pipe();
}
case SYSCALL_SETIPADDR: {
set_local_ipaddr(regs->reg_rdi & 0xffffffff);
return 0;
}
case SYSCALL_BIND: {
return syscall_bind(regs);
}
// For demo purposes only... and fun
case SYSCALL_TX_RAW_ETH: {
spinlock_guard guard(eth_nic->tx_lock_);
while (!eth_nic->transmit_raw(reinterpret_cast<void*>(regs->reg_rdi), regs->reg_rsi & 0xffffffff)) {
guard.unlock();
yield();
guard.lock();
}
return 0;
}
default:
// no such system call
log_printf("%d: no such system call %u\n", id_, regs->reg_rax);
return E_NOSYS;
}
}
[[noreturn]] void proc::thread_exit() {
cli();
ptable_lock.lock_noirq(); // Must hold this throughout most of the exit or else memviewer might try to lock something that doesn't exist
{
spinlock_guard guard(group_->lock_);
group_links_.erase();
}
// Once the group becomes empty, it should stay empty, so this check doesn't need to hold the group lock
if (group_->threads_.empty()) {
group_->pt_lock_.lock_noirq(); // Still necessary to stop conflicts with the memviewer
x86_64_pagetable* pt = pagetable_;
pagetable_ = early_pagetable;
group_->pt_lock_.unlock_noirq();
set_pagetable(early_pagetable);
// Freeing the pagetable can take time--enable interrupts for responsiveness
ptable_lock.unlock_noirq();
sti();
kfree_user_pagetable(pt);
cli();
// Need children lock because other processes might try to reparent to us or send an exit signal
group_->children_lock_.lock_noirq();
while (!group_->children_.empty()) {
// orphans become owned by the `init` process
assert(gtable[1]);
thread_group* c = group_->children_.pop_front();
c->parent_id_ = 1;
spinlock_guard init_guard(gtable[1]->children_lock_);
gtable[1]->children_.push_back(c);
}
assert(group_->children_.empty());
group_->children_lock_.unlock_noirq();
// Don't need to hold the group's fd lock because no other threads should be left to access it
file_table_lock.lock_noirq();
for (int i = 0; i < NFILE_PROC; ++i) {
int global_fd = group_->file_descriptors_[i];
if (global_fd == -1) continue;
assert(global_fd < NFILE_GLOBAL);
file* f = file_table[global_fd];
assert(f);
assert(f->refcount_ > 0);
--f->refcount_;
if (f->refcount_ == 0) {
delete f;
file_table[global_fd] = nullptr;
}
}
file_table_lock.unlock_noirq();
// Get the ptable lock again for the gtable and ptable operations
ptable_lock.lock_noirq();
assert(gtable[group_->parent_id_] && !gtable[group_->parent_id_]->exited_);
gtable[group_->parent_id_]->child_exited_ = true;
group_->exited_ = true;
waitpid_queue.notify_all();
}
// Release this tid for reuse and indicate that this thread's struct proc can be freed once back on the cpustack
pstate_ = ps_exited;
ptable[id_] = nullptr;
ptable_lock.unlock_noirq();
yield_noreturn();
}
// proc::syscall_exit()
// Handle exit system call
void proc::syscall_exit(regstate* regs) {
// Indicate that the other threads in this group should exit themselves as soon as possible
spinlock_guard guard(group_->lock_);
for (proc* t = group_->threads_.front(); t != nullptr; t = group_->threads_.next(t)) {
t->should_exit_ = true;
t->unblock();
}
// Set the exit code so that it can be retrieved once the rest of the threads finish exiting
group_->exit_code_ = regs->reg_rdi;
guard.unlock();
// Exit this thread--including cleanup tasks if this happens to be the last thread
thread_exit();
assert(false); // should never get here
}
// proc::syscall_corrupt()
// Handle corrupt system call
void proc::syscall_corrupt() {
char evil_array[PAGESIZE * 2];
memset(evil_array, -1, PAGESIZE*2);
}
// proc::syscall_fork(regs)
// Handle fork system call.
int proc::syscall_fork(regstate* regs) {
// This is a slow system call, so allow interrupts by default
sti();
// Allocate a new pagetable for the child and start crawling
// the parent's virtual address space to copy its mappings
x86_64_pagetable* pt = knew_pagetable();
if (!pt) {
return E_NOSYS;
}
{
spinlock_guard guard(group_->pt_lock_); // Prevent changes to current pagetable while copying
vmiter parent_it(this, 0);
vmiter child_it(pt, 0);
while (!parent_it.done() && parent_it.va() < HIGHMEM_BASE) {
if (parent_it.pa() != uintptr_t(-1) && parent_it.pa() != CONSOLE_ADDR) {
// If the parent has a mapping here that isn't the console, allocate
// a new page for it, then copy its data and mapping
void* new_page = kalloc(PAGESIZE);
if (!new_page) {
// Couldn't allocate enough space for the child
guard.unlock();
kfree_user_pagetable(pt);
return E_NOSYS;
}
memcpy(new_page, parent_it.kptr(), PAGESIZE);
if (child_it.find(parent_it.va()).try_map(new_page, parent_it.perm()) < 0) {
// Couldn't replicate the parent's mapping for this page
guard.unlock();
kfree(new_page);
kfree_user_pagetable(pt);
return E_NOSYS;
}
} else if (parent_it.pa() == CONSOLE_ADDR) {
if (child_it.find(CONSOLE_ADDR).try_map(CONSOLE_ADDR, parent_it.perm()) < 0) {
// Failed to map the console in the child's page table
guard.unlock();
kfree_user_pagetable(pt);
return E_NOSYS;
}
}
parent_it.next();
}
}
// Lock the process list and search for a free pid and tid >= 1
// cli(); // Holding a spinlock requires that interrupts are disabled
pid_t pid = 1;
pid_t tid = 1;
spinlock_guard guard(ptable_lock);
while (ptable[tid] && ptable[tid]->pstate_ != ps_blank) {
if (++tid >= NTHREAD) {
// No pids available
guard.unlock();
kfree_user_pagetable(pt);
return E_NOSYS;
}
}
while (gtable[pid]) {
if (++pid >= NPROC) {
guard.unlock();
kfree_user_pagetable(pt);
return E_NOSYS;
}
}
// There shouldn't be a proc in our slot
assert(!ptable[tid] || ptable[tid]->pstate_ == ps_blank);
assert(!gtable[pid]);
// Allocate a new proc and install its pagetable
proc* p = knew<proc>();
if (!p) {
// Failed to allocate a new proc
guard.unlock();
kfree_user_pagetable(pt);
return E_NOSYS;
}
thread_group* g = knew<thread_group>();
if (!g) {
// Failed to allocate a new thread group
guard.unlock();
kfree(p);
kfree_user_pagetable(pt);
return E_NOSYS;
}
p->id_ = tid;
p->group_ = g;
g->threads_.push_front(p);
g->pid_ = pid;
g->parent_id_ = group_->pid_;
p->init_user(pt);
// Copy parent's file descriptors to the child
spinlock_guard fd_guard(group_->fd_lock_);
for (int i = 0; i < NFILE_PROC; ++i) {
int global_fd = group_->file_descriptors_[i];
g->file_descriptors_[i] = global_fd;
if (global_fd != -1 && file_table[global_fd]) {
assert(file_table[global_fd]->refcount_ > 0);
++file_table[global_fd]->refcount_;
}
}
fd_guard.unlock();
{
spinlock_guard c_guard(group_->children_lock_);
group_->children_.push_back(p->group_);
}
// Copy the parent's current registers into the child's regstate
// and override the return value of this syscall to be 0
memcpy(p->regs_, regs, sizeof(regstate));
p->regs_->reg_rax = 0;
// Add the child to the process table and a run queue
ptable[tid] = p;
gtable[pid] = g;
cpus[pid % ncpu].enqueue(p);
return pid;
}
// validate_user_string(buf, page_table)
// Checks if a user-provided nul-terminated buffer is within accessible memory
// and returns its size (including the null termination)
int validate_user_string(const char* buf, x86_64_pagetable* pt) {
uint64_t wanted_perms = PTE_P | PTE_U;
int n = 0;
do {
uint64_t has_perms = vmiter(pt, reinterpret_cast<uintptr_t>(&buf[n])).perm();
if ((has_perms == uint64_t(-1)) || (has_perms & wanted_perms) != wanted_perms) {
return E_FAULT;
}
} while (buf[n++] != 0);
return n;
}
// proc::syscall_execv(regs)
// Handle execv system call
int proc::syscall_execv(regstate* regs) {
sti();
const char* pathname = reinterpret_cast<const char*>(regs->reg_rdi);
const char** argv = reinterpret_cast<const char**>(regs->reg_rsi);
int argc = regs->reg_rdx;
// Validate pathname
if (!pathname || !argv) {
return E_FAULT;
}
if (validate_user_string(pathname, pagetable_) < 0) {
return E_FAULT;
}
// Validate each argv element (including null termination)
size_t total_argv_size = 0;
int count = 0;
while (true) {
if (count > argc) {
return E_FAULT;
}
// Ensure valid location of argv[i]
uint64_t wanted_perms = PTE_P | PTE_U;
uint64_t has_perms = vmiter(pagetable_, reinterpret_cast<uintptr_t>(&argv[count])).perm();
if ((has_perms == uint64_t(-1)) || (has_perms & wanted_perms) != wanted_perms) {
return E_FAULT;
}
if (argv[count++] == nullptr) {
break;
}
// Ensure argv[i] is a valid string and keep track of total args length
int sz = validate_user_string(argv[count-1], pagetable_);
if (sz < 0) {
return E_FAULT;
}
// Includes null termination
total_argv_size += sz;
};
if (count <= argc) {
return E_FAULT;
}
assert(count == argc + 1);
// Locate program inode, allocate pagetable, and start loading program segments
chkfs_iref inode = chkfsstate::get().lookup_inode(pathname);
if (!inode) {
return E_NOENT;
}
x86_64_pagetable* pt = knew_pagetable();
if (!pt) {
return E_NOMEM;
}
inode->lock_read();
chkfs_fileiter it(inode.get());
chkfs_loader ld(&it, pt);
int r = load(ld);
inode->unlock_read();
if (r < 0) {
kfree_user_pagetable(pt);
return r;
}
// create and map stack + arguments list
// no one should let me write anything ever again
size_t argv_sz = (argc + 1) * 8;
if (argv_sz > PAGESIZE) {
kfree_user_pagetable(pt);
return E_NOSPC;
}
uintptr_t argv_va = 0;
void* current_argpg = kalloc(PAGESIZE);
uintptr_t current_argpg_va = MEMSIZE_VIRTUAL - PAGESIZE;
if (!current_argpg) {
kfree_user_pagetable(pt);
return E_NOSPC;
}
size_t current_argpg_sz = 0;
// Add 2 extra iterations to loop to handle allocating+mapping space for the argv
// array if necessary and allocating the stack page
// spinlock_guard guard(group_->lock_); // Not necessary--should only ever be called by single-threaded processes
for (int i = 0; i < argc + 2; ++i) {
size_t n = (i >= argc) ? argv_sz : strlen(argv[i]) + 1;
if ((i == argc + 1) || (current_argpg_sz + n > PAGESIZE)) {
if (vmiter(pt, current_argpg_va).try_map(kptr2pa(current_argpg), PTE_PWU) != 0) {
kfree_user_pagetable(pt);
return E_NOSPC;
}
current_argpg_sz = 0;
current_argpg = kalloc(PAGESIZE);
current_argpg_va -= PAGESIZE;
if (!current_argpg) {
kfree_user_pagetable(pt);
return E_NOSPC;
}
if (i == argc + 1) {
// should exit loop with all arg pages filled + mapped, the stack page in current_argpg, and the
// base va of the stack page in current_argpg_va
break;
}
}
assert(i <= argc);
const void* src = argv[i];
if (i == argc) {
src = argv;
}
memcpy(&reinterpret_cast<char*>(current_argpg)[current_argpg_sz], src, n);
if (i == argc) {
// current_argpg_sz should be offset of argv array
argv_va = current_argpg_va + current_argpg_sz;
continue;
}
argv[i] = reinterpret_cast<const char*>(current_argpg_va + current_argpg_sz);
current_argpg_sz += n;
}
assert(argv_va != 0);
void* stkpg = current_argpg;
if (vmiter(pt, current_argpg_va).try_map(kptr2pa(stkpg), PTE_PWU) != 0) {
kfree(stkpg);
kfree_user_pagetable(pt);
return E_NOSPC;
}
// map console
vmiter(pt, ktext2pa(console)).map(console, PTE_PWU);
spinlock_guard ptable_guard(ptable_lock);
// Save current pagetable now because init_user() updates pagetable_
x86_64_pagetable* old_pt = pagetable_;
init_user(pt);
// Set up registers to point to program entry, stack top, and main function arguments
regs_->reg_rip = ld.entry_rip_;
regs_->reg_rsp = current_argpg_va + PAGESIZE;
regs_->reg_rdi = argc;
regs_->reg_rsi = argv_va;
// Finally switch to new pagetable and free old one
set_pagetable(pt);
kfree_user_pagetable(old_pt);
ptable_guard.unlock();
// Disable interrupts and yield to scheduler
cli();
yield_noreturn();
}
// proc::syscall_clone(regs)
// Handle clone system call.
int proc::syscall_clone(regstate* regs) {
// This is a slow system call, so allow interrupts by default
sti();
// Lock the process list and search for a free tid >= 1
// cli(); // Holding a spinlock requires that interrupts are disabled
pid_t tid = 1;
spinlock_guard guard(ptable_lock);
while (ptable[tid] && ptable[tid]->pstate_ != ps_blank) {
if (++tid >= NTHREAD) {
// No pids available
return E_NOSYS;
}
}
// There shouldn't be a proc in our slot
assert(!ptable[tid] || ptable[tid]->pstate_ == ps_blank);
// Allocate a new proc and install its pagetable
proc* p = knew<proc>();
if (!p) {
// Failed to allocate a new proc
return E_NOSYS;
}
p->id_ = tid;
p->init_user(pagetable_);
// Add the thread to our group
{
spinlock_guard group_guard(group_->lock_);
p->group_ = group_;
group_->threads_.push_back(p);
p->should_exit_ = should_exit_; // In case the process is exited by another thread during the clone
}
// Copy the parent's current registers into the child's regstate
// and override the return value of this syscall to be 0
memcpy(p->regs_, regs, sizeof(regstate));
p->regs_->reg_rax = 0;
// Add the child to the process table and a run queue
ptable[tid] = p;
cpus[tid % ncpu].enqueue(p);
return tid;
}
// proc::waitpid()
// Internal waitpid implementation
uint64_t proc::waitpid(pid_t pid, int options) {
spinlock_guard guard(group_->children_lock_);
Canary stack_guard(stack_canary_);
if (group_->children_.empty()) {
return E_CHILD;
}
thread_group* child = group_->children_.front();
while (child) {
// Find the process we are looking for (or the first exited process if pid == 0)
assert(child->parent_id_ == group_->pid_);
if (child->pid_ == pid || (pid == 0 && child->exited_)) {
break;
}
child = group_->children_.next(child);
}
// If we were looking for a specific process but couldn't find it
if (!child && pid != 0) {
return E_CHILD;
}
// We need to return some result right away if called with NOHANG
if (options == W_NOHANG) {
if (!child) {
if (pid == 0) {
// When pid is wildcard, E_CHILD is only for having no children
return E_AGAIN;
}
return E_CHILD;
}
if (!child->exited_) {
return E_AGAIN;
}
// Finish destroying the child's proc and return the pid in the lower
// half of the return value and the exit code in the upper half
stack_guard.check();
int exit_status = reap_child(child);
guard.unlock();
return child->pid_ | (uint64_t(exit_status) << 32);
}
assert(options != W_NOHANG);
waiter().wait_until(waitpid_queue, [&]() {
stack_guard.check();
if (!child) {
// Still looking for an exited process (pid is wildcard)
assert(pid == 0);
child = this->group_->children_.front();
while (child && !child->exited_) {
child = this->group_->children_.next(child);
}
}
if (!child) {
// No exited children found
return false;
}
// Waiting on a specific child
return child->exited_;
}, guard);
// Finish destroying the child's proc and return the pid in the lower
// half of the return value and the exit code in the upper half
assert(child && child->threads_.empty());
stack_guard.check();
int exit_status = reap_child(child);
guard.unlock();
return child->pid_ | (uint64_t(exit_status) << 32);
}
// proc::reap_child(child)
// Finishes freeing the child's group struct and releases its PID for
// reuse before returning the child's exit status. The reaped process
// must be a child of the calling process and have actually exited.
// The parent's children_lock must be held before calling
int proc::reap_child(thread_group* p) {
spinlock_guard guard(p->lock_);
spinlock_guard c_guard(p->children_lock_);
assert(p->threads_.empty());
assert(p->children_.empty());
assert(p->parent_id_ == group_->pid_);
// This is okay because p->parent_links_ is part of the parent's
// children_ list, which we should be holding the lock for
assert(p->parent_links_.is_linked());
group_->children_.erase(p);
int exit_code = p->exit_code_;