-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathc4ke.c
3210 lines (2940 loc) · 106 KB
/
c4ke.c
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
///
// C4 Kernel Experiment (C4KE)
//
// A multitasking kernel with a primitive emergency shell.
//
// Invocation: make run # Simple invocation (runs under c4m)
// make run-alt # Run with -a flag to c4m, fixes a timing issue
// make run-c4 # Run under c4 (slow)
// make c4rs && ./c4m load-c4r.c -- c4ke.c4r # Use compiled .c4r file
// ./c4 c4m.c load-c4r.c c4ke.c # Run from c4 (slow) without loader
// ./c4 c4m.c load-c4r.c -- c4ke # Run from c4 (slow) loader and compiled .c4r file
// ./c4m load-c4r.c c4ke.c # Run from c4m without using loader
// ./c4m load-c4r.c -- c4ke # Run from c4m using loader and compiled .c4r file
//
// You can specificy a number of different command-line options. Try --help for a listing.
//
// The kernel can be run inside itself:
//
// eshell>
// toggle psf # Enable ps listing while running below commands
// c4ke # Run as a normal process (takes over from running kernel)
// c4m load-c4r.c -- c4ke # Run under a c4m interpreter
// c4 c4m.c load-c4r.c -- c4ke # Run under a c4m interpreter within c4
//
// This kernel has some optional extras if compiled with them:
// - c4ke_ipc.c - InterProcess Communication
// - c4ke_plus.c - Support for advanced features of c4plus
//
// See u0.c for the user mode interface to the kernel. u0.c is used for most programs compiled to
// run under C4KE.
//
// This kernel focuses on being as simple as possible. Features such as filesystems are implemented
// in microservices (see c4ke.vfs.c) rather than being part of the kernel. There are two main reasons:
// - Keep the kernel simple and small.
// - Keep it compatible with c4m. C4CC supports many more features than c4m, and services can take
// advantage of these improvements.
//
// The kernel supports (TODO but has not implemented) message passing and waiting (with timeouts.)
//
// This program compiles but does nothing useful under GCC.
// This GCC-compatibility is provided so that IDE's can syntax check the file.
// The resulting file can be run, but cannot task schedule so the kernel immediately shuts down.
//
// Task switching is accomplished in two ways:
// * Co-operative: schedule(), using a custom opcode that invokes a
// trap handler, which saves registers and returnpc to the task, loads up the
// new values from the target task, and "returns" to the newly loaded task.
// * A cycle-based interrupt that calls an interrupt handler, which switches task automatically.
//
// Tasks contain, among many other things, the register values needed to return to
// the task later. There is no virtual machine or other abstraction, each task is
// C4 code running in C4m. By having access to the registers during a trap, we can
// change what code is running in C4m.
//
// This means that tasks cannot exit by themselves, as their stack is still
// being used at the point of call to exit() or returning from main.
// Instead, the idle task (task_idle) cleans up tasks that have finished, as
// their stack is no longer active.
//
// Programs are compiled to .c4r files, the "C4 Relocatable" executable.
// These executables are not interpreted, merely loaded and called directly
// from the code that loaded it, as any other C function would be.
//
// Note that C4 only supports one keyboard entry method - a very primitive
// readline supported by libc that allows text to be entered. Arrow keys
// and other navigation are not supported, and the entire kernel is stopped
// until the user presses enter, which disables task switching while awaiting
// input. It does not seem feasible to implement any other method without
// significantly altering C4 itself.
//
// The shell can run programs in the background using the & symbol, just like
// linux. Otherwise, a program will run in the foreground until it is done.
// When backgrounding a task in this way, control will switch back to the shell
// and as above this will pause the entire kernel. Hitting enter will call
// schedule() and allow other tasks to run, until they call schedule() or are
// pre-emptively interrupted by the cycle interrupt.
//
//
// Job control:
// - The command 'fg' returns focus to the last started command, if possible.
// TODO: You cannot currently use 'fg x' to foreground job x.
// - The command 'kill x' sends a SIGTERM signal to the given process id.
// TODO: Not implemented.
//
//
// Shell notes:
// Any .c4r file can be run, and you do not need to specify the .c4r part of the
// file, it will be appended automatically.
// Example:
// c4ke eshell>
// c4 -s c4m.c &
//
// Note that buffers cannot be flushed, so the shell must present the input
// prompt on a new line.
//
// The kernel shuts down once the init process exits.
//
// TODO:
// - Implement stack trace
// -> can simulate LEV then check if TLEV, simulate it if necessary
// -> can use tasks c4r structure, then fall back to loaded modules (TODO) or
// using the kernels (TODO)
// Not doing:
// - Load balance: use a percentage of the cycle interrupt interval based on number
// of used process slots.
// - Find cause of random segfault. Sometimes the kernel will crash, especially
// under high load (eg, 100 processes running.)
// - is c4m trap overwriting currently pushed arguments? it must be, as top is causing a crash
// when interrupted by the cycle interrupt.
// - TRAP_SEGV and TRAP_OPV were added to try to narrow down the cause of the crash.
// -> c4m has specific checks that slow down execution to provide these additional traps.
// -> Sometimes the segfault happens outside of these checks and the kernel crashes.
// - OpenRISC 1000: segfaulting due to bad argc value (several million!)
// - The best the kernel can do right now is terminate the faulting process, but
// stack traces would help narrow down where the issue is.
// - Implement relative functions as a kernel module provided set of extended
// opcodes. (Undecided, would dramatically reduce performance.)
// - Update c4rlink to change all patches into relative instructions
//
// 2024 / 10 / 04 - Implemented kernel extensions.
// 2024 / 09 / 09 - Fixed kernel idle sleep time, uses correct value. Also fixed
// c4m to actually use usleep().
// 2024 / 07 / 07 - Removed ethereal mode, kernel now waits on init process pid.
// 2024 / 07 / 06 - Reworked kernel to use init process, shutdown once complete.
// 2024 / 07 / 05 - Added support for waiting on a pid.
// 2024 / 07 / 04 - Reformat .c4r to place symbols at very end; don't load symbols
// by default.
// 2024 / 04 / 01 - Added signal handling.
// 2023 / 11 / 20 - Added critical path functions to avoid interrupts when
// updating kernel structures.
// 2023 / 11 / 19 - Added sleep, STATE_WAITING for sleep.
// 2023 / 11 / 18 - Added timekeeping via /proc/uptime or proper time calls.
// 2023 / 11 / 12 - Added cycle timekeeping.
#include "c4.h"
#include "c4m.h"
#define NO_LOADC4R_MAIN
#include "load-c4r.c"
///
// Kernel configuration
///
/// Feel free to play around with the values in this enum
enum { // Main configuration section
KERN_TASK_COUNT = 128, // Fixed count until updated to use linked list
TASK_STACK_SIZE = 0x1000, // How much stack memory to allocate to tasks.
SIGNAL_MAX = 64, // How many signals are supported
// Minimum acceptable cycles between cycle-based interrupt.
// Values below this may crash the kernel.
// 900 seems to crash fairly consistently, 901 only sometimes.
KERNEL_CYCLES_MIN = 1000,
// Maximum pre-emptive interrupts per second to aim for.
IH_MAX_CYCLES_PER_SECOND = 10,
KERNEL_EXTENSIONS_MAX = 32,
// Give tasks 10 seconds to comply with SIGTERM during shutdown
KERNEL_FINISH_WAIT_TIME = 10000,
};
///
// Kernel definitions
///
/// These shouldn't need to be changed.
/// Kernel verbosity
enum {
VERB_NONE = 0, // Be as quiet as possible
VERB_MIN = 10, // Minimal information
VERB_MED = 50, // Medial information
VERB_MAX = 100, // Maximal information
VERB_DEFAULT = 50
};
// These settings control how long we measure performance for during boot.
enum { KERNEL_MEASURE_QUICK = 200, KERNEL_MEASURE_SLOW = 1000 };
enum { KERNEL_TFACTOR_QUICK = 5, KERNEL_TFACTOR_SLOW = 1 };
enum { KERNEL_IDLE_SLEEP_TIME = 10000 }; // used with usleep, so in microseconds
// Details for managing the opcode to function vector
enum { CO_BASE = 128, CO_MAX = 128 };
// start_errno values
enum {
START_NONE, // No error, success
START_NOFREE, // No free task slot
START_NOSTACK, // Unable to allocate stack
START_NOSIG, // Unable to allocate signal handlers
START_ARGV, // Failed to allocate argv
};
// At some stage this will be a symbol available under c4m
// Trap codes
enum {
// Illegal opcode, allows custom opcodes to be implemented using
// install_trap_handler()
TRAP_ILLOP,
// Hard IRQ generated by c4m under some condition
TRAP_HARD_IRQ,
// Soft IRQ generated by user code
TRAP_SOFT_IRQ,
// A POSIX signal was received
TRAP_SIGNAL,
// Invalid memory read or write
TRAP_SEGV,
// Invalid opcode value (specifically with OPCD)
TRAP_OPV,
};
// Configure codes, for use with CSYS/__c4_configure
enum { C4KE_CONF_CYCLE_INTERRUPT_INTERVAL, C4KE_CONF_CYCLE_INTERRUPT_HANDLER };
static char *VERSION() { return "0.66"; }
// Task state, keep up to date in u0.c
enum {
STATE_UNLOADED = 0x0,
STATE_LOADED = 0x1,
STATE_RUNNING = 0x2,
STATE_WAITING = 0x4,
STATE_TRAPPED = 0x8, // TODO: Unused
STATE_ZOMBIE = 0x20, // Waiting to die
STATE_NOTRUN = 0x30
};
// Task privileges
enum {
PRIV_NONE, // TODO: Useful?
PRIV_USER, // Usermode acess
PRIV_KERNEL // Kernel mode access
};
// Wait states
enum {
WSTATE_TIME, // Waiting for a time target. WAITARG is target timestamp
WSTATE_PID, // Waiting for a process to terminate. WAITARG is the pid.
WSTATE_MESSAGE, // Waiting for a message. WAITARG is the "give up" timestamp
};
// Task structure
enum {
// Since it's the first element, state can be checked with one
// less array lookup if it is at the start of the array.
TASK_STATE, // int, see STATE_
TASK_ID, // int, task id
TASK_NICE, // int, priority counter
TASK_NICE_BASE, // int, priority base level
TASK_PARENT, // int, parent task id
TASK_WAITSTATE, // int, see WSTATE_
TASK_WAITARG, // int, depends on WSTATE_
TASK_BASE, // int *, Task BP/SP base address
TASK_CODE, // int *, Code address, not used by builtin tasks
TASK_DATA, // char *, Data address, not used by builtin tasks
TASK_REG_A, // int, saved register A value
TASK_REG_BP, // int *, saved register BP value
TASK_REG_SP, // int *, saved register SP value
TASK_REG_PC, // int *, saved register PC value
TASK_ENTRY, // int *, initial PC register value
TASK_PRIVS, // int, see PRIV_
TASK_NAME, // char *, copied from source and freed at clean
TASK_NAMELEN, // int, length of name
TASK_EXIT_CODE, // int, exit code for task
TASK_ARGC, // int, arg count
TASK_ARGV, // char **, pointer to allocated argv
TASK_ARGV_DATA, // char **, TASK_ARGV points to here
TASK_CYCLES, // int, how many cycles task has run
TASK_TIMEMS, // int, time running in milliseconds
TASK_TRAPS, // int, how many traps the task has caused
TASK_C4R, // int *, ptr to C4R structure
TASK_SIGHANDLERS, // int *, ptr to SIGH_ structure
TASK_SIGPENDING, // int, number of pending signals total
TASK_MBOX, // int *, see MBOX_
TASK_MBOX_SZ, // int,
TASK_MBOX_COUNT, // int,
TASK_EXCLUSIVE, // int,
TASK_EXTDATA, // int *, task extension data
TASK__Sz // task structure size
};
// Kernel task information, the superstructure for kernel task information
// returned via kern_tasks_export() in u0.c
enum {
KTI_COUNT,
KTI_USED,
KTI_LIST, // see KTE_*
KTI__Sz
};
// Kernel task element - keep up to date with u0.c
enum {
// First element, so can be referenced as *kte instead of kte[KTE_TASK_STATE]
KTE_TASK_STATE, // int
KTE_TASK_ID, // int
KTE_TASK_PARENT, // int
KTE_TASK_NAME, // char *
KTE_TASK_NAMELEN,// int
KTE_TASK_PRIORITY, // int
KTE_TASK_PRIVS, // int, see PRIV_*
KTE_TASK_NICE, // int
KTE_TASK_CYCLES, // int
KTE_TASK_TIMEMS, // int
KTE_TASK_TRAPS, // int
KTE__Sz
};
// Custom opcodes
// TODO: Move to a syscall interface instead of string based opcode lookup.
// All of these are checked via memcmp in request_symbol()
// TODO: These could be global ints instead, and offset when detecting we're running
// within C4KE already. That way multiple kernels could run at once. At the moment,
// each child kernel disables the parent kernel.
enum {
// Request a symbols opcode
// (char *symbol) -> integer value of requested symbol
OP_REQUEST_SYMBOL = 128,
// Request system flags
// () -> get system flags
OP_C4INFO,
// Switch task manually
// () -> success (1) or no task to switch to (0)
OP_SCHEDULE,
// Wait for a message or timeout
// (int timeout) -> 0 TODO: only timeout is implemented, messages not
OP_AWAIT_MESSAGE,
// Wait for a process to exit
// (int pid) -> pid exit code
OP_AWAIT_PID,
// Finish a task - not used by user code, put directly onto the stack
// of a task so that returning from main calls it to cleanly finish.
OP_TASK_FINISH,
// Exit a task - similar to OP_TASK_FINISH, but called by the user.
// (int exit_code) -> does not return
OP_TASK_EXIT,
// Indicate which task is currently focused
// (int pid)
OP_TASK_FOCUS,
// Get the number of cycles the task has run
OP_TASK_CYCLES,
// TODO: Not implemented, currently the only way to shutdown is for
// the init process to exit.
// Request kernel shutdown
OP_SHUTDOWN,
// Request a halt.
// TODO: does nothing.
OP_HALT,
// Request a timestamp, return value in milliseconds.
// TODO: this is much slower than just using __time(), not really useful
OP_TIME,
// Test opcode that returns the value of the BP register
OP_PEEK_BP,
// Test opcode that returns the value of the SP register
OP_PEEK_SP,
// TODO: not used at all
//OP_KERN_TASK_START_BUILTIN,
// TODO: replaced by u0 functions that work better
//OP_KERN_PRINT_TASK_STATE,
//OP_KERN_GETLEN_TASK_STATE,
// Get the current task ID
OP_KERN_TASK_CURRENT_ID,
// Check if a task is running
// (int task_id) -> 1 if running, 0 if not
OP_KERN_TASK_RUNNING,
// Request number of allocated tasks
OP_KERN_TASK_COUNT,
// Request maximum number of tasks
OP_KERN_TASKS_MAX,
// Request a task listing. Returns a KTI_* struct.
// () -> int *
OP_KERN_TASKS_EXPORT,
// Update a task listing with the latest data from the kernel.
// (int *kti) -> void
OP_KERN_TASKS_EXPORT_UPDATE,
// Release a task listing and all data
OP_KERN_TASKS_EXPORT_FREE,
// Get the number of running tasks
OP_KERN_TASKS_RUNNING,
// Debug command: print a stacktrace of the current program
OP_DEBUG_PRINTSTACK,
// Debug command: print a debug kernel state
OP_DEBUG_KERNELSTATE,
// Start a .c4r file. Uses task_loadc4r.
// int __user_start_c4r(int argc, char **argv);
// TODO: Move to a system request interface. This is temporary.
// Probably not very secure, since kernel will copy the given argv
// based on argc.
OP_USER_START_C4R,
// Sleep the specified number of milliseconds
// int sleep(int ms)
OP_USER_SLEEP,
// Get the process id of the current task
OP_USER_PID,
// Get the parent process id of the current task
OP_USER_PARENT,
// Setup a signal handler
OP_USER_SIGNAL,
// Send a signal to a process
OP_USER_KILL,
// Update the name of the current task. Badly named?
OP_CURRENTTASK_UPDATE_NAME,
// Debugging functions to disable/enable the cycle based interrupt
OP_KERN_REQUEST_EXCLUSIVE,
OP_KERN_RELEASE_EXCLUSIVE,
OP_EXTENSIONS_START, // Not used directly, kernel extensions will start from this
// number when registering opcodes.
};
static int request_symbol (char *symbol) {
if (!memcmp(symbol, "OP_HALT", 7)) return OP_HALT;
if (!memcmp(symbol, "OP_TIME", 7)) return OP_TIME;
if (!memcmp(symbol, "OP_C4INFO", 9)) return OP_C4INFO;
if (!memcmp(symbol, "OP_PEEK_BP", 10)) return OP_PEEK_BP;
if (!memcmp(symbol, "OP_PEEK_SP", 10)) return OP_PEEK_SP;
if (!memcmp(symbol, "OP_SCHEDULE", 11)) return OP_SCHEDULE;
if (!memcmp(symbol, "OP_USER_PID", 11)) return OP_USER_PID;
if (!memcmp(symbol, "OP_USER_KILL", 12)) return OP_USER_KILL;
if (!memcmp(symbol, "OP_AWAIT_PID", 12)) return OP_AWAIT_PID;
if (!memcmp(symbol, "OP_TASK_EXIT", 12)) return OP_TASK_EXIT;
if (!memcmp(symbol, "OP_TASK_FOCUS", 13)) return OP_TASK_FOCUS;
if (!memcmp(symbol, "OP_USER_SLEEP", 13)) return OP_USER_SLEEP;
if (!memcmp(symbol, "OP_TASK_CYCLES", 14)) return OP_TASK_CYCLES;
if (!memcmp(symbol, "OP_USER_SIGNAL", 14)) return OP_USER_SIGNAL;
if (!memcmp(symbol, "OP_TASK_FINISH", 14)) return OP_TASK_FINISH;
if (!memcmp(symbol, "OP_USER_PARENT", 14)) return OP_USER_PARENT;
if (!memcmp(symbol, "OP_AWAIT_MESSAGE", 16)) return OP_AWAIT_MESSAGE;
if (!memcmp(symbol, "OP_REQUEST_SYMBOL", 17)) return OP_REQUEST_SYMBOL;
if (!memcmp(symbol, "OP_USER_START_C4R", 17)) return OP_USER_START_C4R;
if (!memcmp(symbol, "OP_KERN_TASKS_MAX", 17)) return OP_KERN_TASKS_MAX;
if (!memcmp(symbol, "OP_KERN_TASK_COUNT", 18)) return OP_KERN_TASK_COUNT;
if (!memcmp(symbol, "OP_DEBUG_PRINTSTACK", 19)) return OP_DEBUG_PRINTSTACK;
if (!memcmp(symbol, "OP_DEBUG_KERNELSTATE", 20)) return OP_DEBUG_KERNELSTATE;
if (!memcmp(symbol, "OP_KERN_TASK_RUNNING", 20)) return OP_KERN_TASK_RUNNING;
if (!memcmp(symbol, "OP_KERN_TASKS_RUNNING", 21)) return OP_KERN_TASKS_RUNNING;
if (!memcmp(symbol, "OP_KERN_TASK_CURRENT_ID", 23)) return OP_KERN_TASK_CURRENT_ID;
//if (!memcmp(symbol, "OP_KERN_PRINT_TASK_STATE", 24)) return OP_KERN_PRINT_TASK_STATE;
if (!memcmp(symbol, "OP_KERN_REQUEST_EXCLUSIVE", 25)) return OP_KERN_REQUEST_EXCLUSIVE;
if (!memcmp(symbol, "OP_KERN_RELEASE_EXCLUSIVE", 25)) return OP_KERN_RELEASE_EXCLUSIVE;
//if (!memcmp(symbol, "OP_KERN_GETLEN_TASK_STATE", 25)) return OP_KERN_GETLEN_TASK_STATE;
if (!memcmp(symbol, "OP_KERN_TASKS_EXPORT_FREE", 25)) return OP_KERN_TASKS_EXPORT_FREE;
if (!memcmp(symbol, "OP_KERN_TASKS_EXPORT_UPDATE", 27)) return OP_KERN_TASKS_EXPORT_UPDATE;
if (!memcmp(symbol, "OP_KERN_TASKS_EXPORT", 20)) return OP_KERN_TASKS_EXPORT;
//if (!memcmp(symbol, "OP_KERN_TASK_START_BUILTIN", 26)) return OP_KERN_TASK_START_BUILTIN;
if (!memcmp(symbol, "OP_CURRENTTASK_UPDATE_NAME", 26)) return OP_CURRENTTASK_UPDATE_NAME;
printf("c4ke: symbol '%s' not found\n", symbol);
return 0;
}
// C4INFO state - must match that in c4m.c
enum {
C4I_NONE = 0x0, // No C4 info
C4I_C4 = 0x1, // Ultimately running under C4
C4I_C4M = 0x2, // Running under c4m (directly or C4)
C4I_C4P = 0x4, // Running under c4plus
C4I_HRT = 0x10, // High resolution timer
C4I_SIG = 0x20, // Signals supported
C4I_C4KE = 0x40, // C4KE is running
};
static int c4_info;
static void print_c4_info () {
if (c4_info == C4I_NONE) printf("(unknown)");
if (c4_info & C4I_C4) printf("C4+");
if (c4_info & C4I_C4M) printf("C4M");
if (c4_info & C4I_C4P) printf("C4Plus");
if (c4_info & C4I_HRT) printf("+HighResTimer");
if (c4_info & C4I_SIG) printf("+Signals");
if (c4_info & C4I_C4KE) printf(" under C4KE");
printf(" %dbit", sizeof(int) * 8);
}
///
/// Signals
///
// Signal handlers structure
enum {
SIGH_PENDING, //
SIGH_BLOCKED,
SIGH_ADDRESS, // C4 code address of handler
SIGH__Sz
};
//enum {
// SIGHUP = 1, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT,
// // Default caught but can be overridden
// SIGTERM,
// // Last chance for task to quit nicely
// SIGKILL,
// SIGSEGV = 11
//};
// Stop regular compilers from caring about these signals
#undef SIGHUP
#undef SIGINT
#undef SIGQUIT
#undef SIGILL
#undef SIGTRAP
#undef SIGABRT
#undef SIGBUS
#undef SIGFPE
#undef SIGKILL
#undef SIGUSR1
#undef SIGSEGV
#undef SIGUSR2
#undef SIGPIPE
#undef SIGALRM
#undef SIGTERM
#undef SIGSTKFLT
#undef SIGCHLD
#undef SIGCONT
#undef SIGSTOP
#undef SIGTSTP
#undef SIGTTIN
#undef SIGTTOU
#undef SIGURG
#undef SIGXCPU
#undef SIGXFSZ
#undef SIGVTALRM
#undef SIGPROF
#undef SIGWINCH
#undef SIGIO
#undef SIGPWR
#undef SIGSYS
#undef SIGRTMIN
#undef SIGRTMAX
enum {
SIGHUP = 1 ,SIGINT ,SIGQUIT ,SIGILL ,SIGTRAP,
SIGABRT ,SIGBUS ,SIGFPE ,SIGKILL ,SIGUSR1,
SIGSEGV ,SIGUSR2 ,SIGPIPE ,SIGALRM ,SIGTERM,
SIGSTKFLT ,SIGCHLD ,SIGCONT ,SIGSTOP ,SIGTSTP,
SIGTTIN ,SIGTTOU ,SIGURG ,SIGXCPU ,SIGXFSZ,
SIGVTALRM ,SIGPROF ,SIGWINCH ,SIGIO ,SIGPWR,
SIGSYS ,
SIGRTMIN = 32, // ...
SIGRTMAX = 63, SIGMAX = 64 // Should match SIGNAL_MAX
};
// Kernel extensions
enum {
KEXT_STATE, // int, See KXS_*
KEXT_NAME, // char *
KEXT_INIT, // int *, ptr to init function if any
KEXT_START, // int *, ptr to start (called just before tasks execute) if any
KEXT_SHUTDOWN, // int *, ptr to shutdown function if any
KEXT__Sz
};
// KEXT_STATE values
enum {
KXS_NONE, // Not in use
KXS_REGISTERED = 0x1, // Present and registered
KXS_ERROR = 0x2 // Error during init
};
// Kernel extension callback function return values
enum {
KXERR_NONE = 0, // No error
KXERR_FAIL = 1, // Error of some sort (see kernel_ext_errno)
};
///
// Kernel main data
///
static int *kernel_tasks; // struct TASK*
static int *kernel_task_current; // struct TASK*
static int *kernel_task_idle; // struct TASK*
static int kernel_task_id_counter;
static int kernel_max_slot;
static int kernel_task_extdata_size;// For TASK_EXTDATA member of TASK
static int *kernel_task_focus; // Interactive focus, hacky
static int kernel_verbosity;
static int kernel_loadc4r_mode; // defaults to not loading symbols unless -g is given
static int kernel_shutdown;
static char*kernel_init,
*kernel_default_init;
static int kernel_init_argc,
*kernel_init_task;
static char **kernel_init_argv;
static int kernel_running;
static int *kernel_c4r;
static int kernel_start_time;
static int kernel_schedule_time;
static int kernel_shutdown_time;
static int *kernel_extensions, kernel_ext_count, kernel_ext_errno;
static int kernel_ext_initialized;
// Testing modes
static int enable_measurement; // perform speed measurement at startup
static int enable_test_tasks; // launch internal test tasks
static int kernel_cycles_count, kernel_cycles_base;
static int kernel_cycles_force;
// Track task counts
static int kernel_tasks_unloaded, kernel_tasks_loaded;
static int kernel_tasks_running, kernel_tasks_waiting;
static int kernel_tasks_trapped;
static int kernel_tasks_zombie;
static int kernel_ips; // Rough measure of instructions per second
// Preserve old cycle interrupt details in these variables.
static int old_ih_cycle_interval, *old_ih_cycle_handler;
// And signal handlers
static int *old_sig_int;//, *old_sig_segv;
static int *custom_opcodes;
static int start_errno; // see START_*
static int kernel_task_find_iterator;
// TODO: removed
//static int kernel_hlt_count; // Tracks tasks call to halt, except for the idle task
static int last_trap_handler;
// Set in main()
static char *readable_int_table;
static int readable_int_max;
static int kernel_last_cycle;
static int kernel_last_time;
// Initialized to the C4m opcode for TLEV in main(), used only in process_trap.
static int opcode_TLEV;
static int opcode_PSH;
// TODO: no longer used
static int critical_path_value;
///
// Utility functions
///
static int _strlen (char *s) { char *t; t = s; while(*t) ++t; return t - s; }
static int _strcmp (char *s1, char *s2) { while(*s1 && (*s1 == *s2)) { ++s1; ++s2; } return *s1 - *s2; }
static int _atoi (char *str, int radix) {
int v, sign;
v = 0;
sign = 1;
if(*str == '-') {
sign = -1;
++str;
}
while (
(*str >= 'A' && *str <= 'Z') ||
(*str >= 'a' && *str <= 'z') ||
(*str >= '0' && *str <= '9')) {
v = v * radix + ((*str > '9') ? (*str & ~0x20) - 'A' + 10 : (*str - '0'));
++str;
}
return v * sign;
}
// Old implementation
// static void print_int_readable (int n) {
// int table_pos;
// int rem;
// table_pos = rem = 0;
// while(table_pos < readable_int_max && n / 1000 > 0) {
// rem = n % 1000;
// n = n / 1000;
// ++table_pos;
// }
// printf("%ld", n);
// if (rem) printf(".%03d", rem);
// printf("%c", readable_int_table[table_pos]);
// }
static void print_int_readable (int n) {
int table_pos;
int x, rem;
char c;
table_pos = rem = 0;
while(table_pos < readable_int_max && (x = n / 1000) > 0) {
rem = n % 1000;
n = x;
++table_pos;
}
printf("%ld.%03d %c", n, rem, readable_int_table[table_pos]);
}
static void print_int_readable_using (int n, char *table, int table_max) {
int table_pos;
int rem;
char c;
table_pos = rem = 0;
while(table_pos < table_max && n / 1000 > 0) {
rem = n % 1000;
n = n / 1000;
++table_pos;
}
printf("%ld", n);
if (rem) printf(".%03d", rem);
if ((c = table[table_pos]) && c != ' ')
printf("%c", c);
}
static void print_time_readable (int ms) {
print_int_readable_using(ms, "m ", 2);
}
// strcpy with allocation
static char *k_strcpy_alloc (char *source) {
int len, *s;
char *dest, *t;
//if (source == 0) return 0; // allow nuls
if (source == 0) {
printf("c4ke: k_strcpy_alloc - source is null\n");
source = "(k_strcpy_alloc)";
}
// Find length
len = 0; t = source;
while (*t++) ++len;
// Allocate buffer
if (!(dest = malloc(len + 1))) {
printf("c4ke: memory allocation failure in k_strcpy_alloc\n");
return 0;
}
// Copy
t = dest;
while (*source)
*dest++ = *source++;
*dest++ = 0;
return t;
}
// Print the readable form of a task state
static void kernel_print_task_state (int s) {
if(s & STATE_ZOMBIE) printf("Z");
else if(s & STATE_WAITING) printf("W");
else if(s & STATE_TRAPPED) printf("T");
else if(s & STATE_RUNNING) printf("R");
else printf("U");
}
// Get the string length for the readable form of a task state
static int kernel_getlen_task_state (int s) {
return 1;
}
// Print task state information
static void kernel_print_task (int *t) {
printf("c4ke: task '%s' at 0x%lx:\n", t[TASK_NAME], t);
printf(" Id: %d State: 0x%x ", t[TASK_ID], t[TASK_STATE]);
kernel_print_task_state(t[TASK_STATE]);
printf("\n Registers: A=0x%lx BP=0x%lx SP=0x%lx PC=0x%lx (entry - 0x%lx)\n",
t[TASK_REG_A], t[TASK_REG_BP], t[TASK_REG_SP], t[TASK_REG_PC], t[TASK_REG_PC] - t[TASK_ENTRY]);
//if (t[TASK_STATE]
// printf("\n Exit code: %d\n", t[TASK_EXIT_CODE]);
}
// TODO: load balancing removed
enum { MAX_BALANCE_RANGE = 2 };
//static void kernel_load_balance () {
// // Multiply kernel_cycles_base based on number of loaded tasks
// int new_cycles;
// // doesn't play well on slow systems
// return;
//
// new_cycles = kernel_cycles_base + (kernel_cycles_base * (MAX_BALANCE_RANGE * (100 - ((kernel_tasks_loaded - kernel_tasks_waiting) * 100 / KERN_TASK_COUNT * 100) / 100) / 100));
// if (new_cycles != kernel_cycles_count) {
// if (kernel_verbosity >= VERB_MED)
// printf("c4ke: load balance adjusting cycles to %ld\n", new_cycles);
// __c4_configure(C4KE_CONF_CYCLE_INTERRUPT_INTERVAL, (kernel_cycles_count = new_cycles));
// }
//}
// Cycle count helper
static int cycles_difference () {
int c, d;
c = __c4_cycles();
d = c - kernel_last_cycle;
kernel_last_cycle = c;
return d;
}
///
// Kernel-callable functions
///
// kernel-callable function to invoke await_pid op
static int await_pid (int pid) {
return __c4_opcode(pid, OP_AWAIT_PID);
}
static int pid () { return kernel_task_current[TASK_ID]; }
//
// Trap emulator
//
// Kernel function that handles the trap and forwards the result onto the signal handler.
// This is so that signal handlers can be passed some signal information instead of what
// is usually on the stack during a trap handle.
// TODO: unused, signal handlers in user processes get access to the full trap handler stack.
static void process_trap_handler (int *handler, int signal) {
// TODO
#define handler(x) ((int (*)(int))handler)(x)
handler(signal);
}
#undef handler
// Takes two extra arguments that are pushed onto the stack.
// Cause a trap on a task.
// NOTE: Cannot be done if the task is currently executing.
// Works the same as c4m's trap function, writing temporary values to the stack
// to be popped by TLEV to return to the previous code position.
//
static void process_trap (int *task, int type, int parameter, int *handler) {
int *temp, i;
int *sp, *bp, *pc, a;
if (kernel_task_current == task) {
printf("c4ke: process_trap() BUG current task would be modified!\n");
return;
}
// Copy the segfault fix from c4m
if (!handler) {
printf("c4ke: process_trap() BUG handler not set!\n");
printf("c4ke: Trap type %d, offending instruction at 0x%X, sp=0x%X\n", type, pc - 1, sp);
return;
}
sp = (int *)task[TASK_REG_SP];
bp = (int *)task[TASK_REG_BP];
pc = (int *)task[TASK_REG_PC];
a = task[TASK_REG_A];
temp = sp;
// Push details used by TLEV
// Push instruction and trap number
*--sp = type; // printf("*sp(0x%X) = trap type %d\n", sp, *sp);
*--sp = parameter; // printf("*sp(0x%X) = parameter %d (at 0x%X)\n", sp, *sp, pc - 1);
// Push the registers. These can be updated, they are restored by TLEV.
*--sp = (int)a; // printf("*sp(0x%X) = a %d\n", sp, a);
*--sp = (int)bp; // printf("*sp(0x%X) = bp 0x%X\n", sp, *sp);
*--sp = (int)temp; // printf("*sp(0x%X) = sp 0x%X\n", sp, *sp);
*--sp = (int)pc; // printf("*sp(0x%X) = returnpc 0x%X\n", sp, *sp);
*--sp = (int)&opcode_TLEV; // set LEV returnpc to TLEV instruction address
*--sp; // Next position is our handler bp
// Update BP as if we entered a function, allowing arguments to be referenced.
// We peek at the ENT x that was skipped to read the number of local variables.
bp = sp; // Update handler bp
*sp = (int)bp; // Set LEV bp to handler bp
// Adjust stack to account for local variables
sp = sp - *(handler + 1); // printf("adjusted stack using ENT %d\n", *(handler - 1));
// printf("Arguments pushed: %d, sp=0x%X\n", t - sp, sp);
// Update passed in registers and set pc trap handler after ENT x opcode.
task[TASK_REG_SP] = (int)sp;
task[TASK_REG_BP] = (int)bp;
task[TASK_REG_PC] = (int)(handler + 2); // +2 to skip ENT x
}
//
// Critical path functions
//
// Trap handlers may be interrupted by c4m, at present by the cycle interrupt.
// These function disable the cycle interrupt during critical paths where an
// interrupt could leave the kernel in an inconsistent state.
//
static void critical_path_start () {
//if (!critical_path_value)
__c4_configure(C4KE_CONF_CYCLE_INTERRUPT_INTERVAL, 0);
//++critical_path_value;
//printf("c4ke: critical path value now at %d\n", critical_path_value);
}
static void critical_path_end() {
//if (critical_path_value && !--critical_path_value) {
__c4_configure(C4KE_CONF_CYCLE_INTERRUPT_INTERVAL, kernel_cycles_count);
// critical_path_value = 0;
//}
//printf("c4ke: critical path value now at %d\n", critical_path_value);
}
//
// Timekeeping helpers
//
static int time_difference () {
int t, d;
t = __time();
d = (t - kernel_last_time);
kernel_last_time = t;
return d;
}
// Update the current task's timekeeping details.
static void current_task_timekeeping () {
kernel_task_current[TASK_CYCLES] = kernel_task_current[TASK_CYCLES] + cycles_difference();
kernel_task_current[TASK_TIMEMS] = kernel_task_current[TASK_TIMEMS] + time_difference();
++kernel_task_current[TASK_TRAPS];
}
// Update the kernel tasks' timekeeping details.
static void kernel_task_timekeeping () {
kernel_tasks[TASK_CYCLES] = kernel_tasks[TASK_CYCLES] + cycles_difference();
kernel_tasks[TASK_TIMEMS] = kernel_tasks[TASK_TIMEMS] + time_difference();
}
static void trap_enter() {
critical_path_start();
current_task_timekeeping();
}
// Called by all traps to restore interrupts and task state
static void trap_exit() {
// Assign task keeping
kernel_task_timekeeping();
critical_path_end();
}
///
// Scheduling and task manipulation
///
// Perform scheduling and switch to another active task.
// @return 1 on successful switch, 0 on no other task to switch to
int schedule () {
// TODO: trap_exit() here?
//trap_exit();
return __c4_opcode(OP_SCHEDULE);
}
// TODO: make me a macro!
static void kernel_task_wake (int *task) {
// No checking done to see if task is valid
task[TASK_STATE] = task[TASK_STATE] & ~(STATE_WAITING);
--kernel_tasks_waiting;
// TODO: this line causes more problems than its worth
// task[TASK_WAITSTATE] = task[TASK_WAITARG] = 0;
}
static int kernel_is_task_running (int *task) {
return task[TASK_STATE] & STATE_RUNNING;
}
// Find a task to run.
// Also handles certain wait states like sleeping.
// This function is probably inefficient
// TODO: re-enable find_mask if appropriate
// TODO: the logic could be much simplified, but attempts to do so keep breaking task switching.
static int *kernel_task_find () {
int c, i, *t, ts, ws, wa, *result, ms, n, nb;
//int cycles;
int *backup_task, backup_task_nice, backup_task_iterator;
int tsw;
i = kernel_task_find_iterator;
result = backup_task = (int *)(c = 0);
ms = kernel_max_slot + 1;
// cycles = __c4_cycles();
backup_task_nice = 100;
// backup_task_iterator = 0;
//while (!result && ++c < KERN_TASK_COUNT) {
// i = (i + 1) % KERN_TASK_COUNT;
while (++c < ms) {
i = (i + 1) % ms;
t = kernel_tasks + (TASK__Sz * i);
//ts = t[TASK_STATE];
// printf("c4ke: check task 0x%lx, id %ld, state 0x%x\n", t, t[TASK_ID], t[TASK_STATE]);
// TASK_STATE is the first item in the structure
if (!(ts = *t) || ts & STATE_ZOMBIE) {
// Skip empty/zombie tasks
//} else
//} else if (t == kernel_task_current) {
// Skip current task
//} else if (ts & STATE_ZOMBIE) {
// Skip zombies
// } else if (ts & STATE_NOTRUN) {
// Skip ethereal and zombie tasks
//} else if (ts) { // & find_mask) {
//} else if ((tsw = ts & STATE_WAITING)) {
} else if (ts & STATE_WAITING) {
//ws = t[TASK_WAITSTATE];
wa = t[TASK_WAITARG];
if ((ws = t[TASK_WAITSTATE]) == WSTATE_TIME) {
// kernel_last_time will have been updated recently enough that we
// need not call __time() but use the last saved value.
if (kernel_last_time >= wa)
result = t;
}
else if (ws == WSTATE_MESSAGE) {
// Control returns to op_await_message
// TODO: message waiting only supports timeout, never delivers messages.
//if (t[TASK_MBOX_COUNT] > 0) {
// printf("c4ke: have %d messages for pid %d\n", t[TASK_MBOX_COUNT], t[TASK_ID]);
// result = t;
//} else
if (kernel_last_time >= wa) {
// printf("c4ke: message timeout for pid %d\n", t[TASK_ID]);
result = t;
}
}
// WSTATE_PID is not checked here, but in kernel_task_finish
if (result) {
kernel_task_find_iterator = i;
t[TASK_NICE] = t[TASK_NICE_BASE];
// Wake up
kernel_task_wake(t);
//if (c4_info & C4I_C4)
// printf("c4ke: woke up task %d after %d cycles (crit path: %d)\n", t[TASK_ID], __c4_cycles() - cycles, critical_path_value);
return t;
}
// } else if (!tsw && n > 0) {
} else if ((n = t[TASK_NICE]) > 0) {
// TODO: this section feels wrong.
// Skip if nice counter not met,
// but allow if nothing else is found
if ((t[TASK_NICE] = --n) < backup_task_nice) {
backup_task = t;
backup_task_nice = n;
backup_task_iterator = i;
}
} else {