ReturnInfinity / BareMetal-OS-legacy

BareMetal is a 64-bit OS for x86-64 based computers. The OS is written entirely in Assembly while applications can be written in Assembly, C/C++, and Rust.
1.74k stars 303 forks source link

Implement process time functionality #121

Closed vilhelmgray closed 7 years ago

vilhelmgray commented 7 years ago

This patch adds functionality to provide a simple working version of the times function. The current timer counter value is saved at the start of a new process (right before the main function is called). The elapsed process time can then be computed by subtracting the current timer counter value from the process start time (the timer counter value saved previously).

Two new syscalls are introduced: os_get_proc_time and os_set_proc_start_time. The os_set_proc_start_time syscall saves the current timer counter value to a new sysvar called os_ProcessStartTime. The os_get_proc_time syscall computes the elapsed process time by taking the current os_ProcessStartTime value and subtracting it from the current timer counter value.

The times function implementation is not perfect quite yet: since BareMetal OS lacks a way to differentiate between user and system time, the elapsed process time is assumed to be entirely user time; system time is hardcoded to a value of 0. A future patch should be made to implement functionality to differentiate and keep track of user and system time.

IanSeyler commented 7 years ago

This pull request has been accepted. I will most likely move the os_get_proc_time and os_set_proc_start_time functions into os_system_misc just to keep the call table tidy.

Great work!

vilhelmgray commented 7 years ago

Ah yes, os_system_misc would have be a far more apt place to put those routines.

By the way, I suspect a naive solution to break down the process time between user time and system time may be rather quick to code. One solution I've thought of is to use an additional sysvar variable to keep track of system time: current time is saved at the start of a syscall, then elapsed time is added to the respective sysvar variable at the end of syscall. The sysvar variable would accumulate the total system time, while the total user time can be computed by subtracting total system time from the total elapsed process time.

IanSeyler commented 7 years ago

How precise does this need to be overall? Currently os_ClockCounter is incremented 8 times per second.

vilhelmgray commented 7 years ago

The C standard leaves the granularity of clock_t up to the implementation; but the existence of CLOCKS_PER_SECOND does imply an update frequency of at least 1 Hz.

Still, however, 8 Hz is a rather slow frequency for CPU clock counter in general. A good future milestone to consider would be to reimplement the updates using the APIC timer so that the frequency can be much higher.

IanSeyler commented 7 years ago

Agreed on the APIC timer. I've couldn't get it to work before but it will required for precision.

vilhelmgray commented 7 years ago

I discovered another option available which I would like to share: the Time Stamp Counter (TSC). Modern Intel processors feature a built-in clock counter which may be read via the RDTSC instruction. For example, to grab the current TSC value for the current CPU and return it in register RAX, you could do something like this:

push rdx
rdtsc
shl rdx, 32
or rax, rdx
pop rdx

Note that the RDTSC instruction loads the high-order 32 bits in RDX while the low-order 32 bits are loaded in RAX.

While historically the TSC was unreliable and variable, modern 64-bit Intel processors have an invariant TSC in each which counts monotonically and is automatically reset to 0 when the respective core is reset. Since the counts are automatically maintained by the CPU, we don't need to use a timer IRQ to keep track of time.

vilhelmgray commented 7 years ago

Two caveats to keep in mind when working with the Intel processors' RTCs: RDTSC is not a serializing instruction (i.e. it may read the counter out of order), and the TSC value on each core is independent. Therefore, it's recommended to use the RDTSCP instruction instead, which is a serializing instruction and also loads the CPUID in register ECX as well to identify which TSC was grabbed.

IanSeyler commented 7 years ago

HPET (https://en.wikipedia.org/wiki/High_Precision_Event_Timer) may be another option.