440 likes | 729 Views
Real Time Operating System RTLinux – time module. u9 01631 陳碩璜. Real Time Operating System. able to execute all of its tasks without violating specific timing constraints . RTLinux version2 : 15us in worst case Linux : unpredictable ( level of ms )
E N D
Real Time Operating System RTLinux – time module u901631 陳碩璜
Real Time Operating System • able to execute all of its tasks without violating specific timing constraints . RTLinux version2 : 15us in worst case Linux : unpredictable ( level of ms ) • Time when the task will execute can be predicted deterministically on the basis of knowledge about the system`s hardwre and software.
Why is Linux unsuitable • Delay is unpredictable ex: file system . page fault . disable interrupt … • Resolution of time is not good • generate timer interrupt every 1~10ms • delay function , But …
Con. • Dynamic timer : for kernel • Interval timer : for user processes • Linux has • Timer handler executed by deferrable function • Critical • Noncritical • Noncritical deferrable
Timing in RTLinux • Programmable interval timer inhibit regular timer interrupts • RTLinux still requires low resolution to do normal periodic timer , so the interrupt emulator simulates this by generating periodical 1~10ms interrupt for Linux .
Time Facility • RTLinux provides several timer objects of different clocks as the low level interface for the scheduler . obtaining timestamps … • Here is the general timing APIs • int clock_gettime(clockid_t clock_id, struct timespec *tp) • hrtime_t clock_gethrtime (clockid_t clock_id , ) • for POSIX standard • hrtime_t t = clock_id->gethrtime(clock_id); • struct timespec{ time_t tv_sec ; //second • long tv_nsec ; // nano second} • typedef long long hrtime_t;
Supported Clocks (Version 3.2) • Programmable Interval Timer (PIC) • Time Stamp Counter (TSC) • Local APIC Timer
Programmable Interval Timer • 8254 CMOS chip using 0x40~0x43 I/O ports • 16 bits down counter • Support several modes • Command register : 0x43 • 3 16-bits counters : 0x40~0x42 • Driven by 1193182 HZ oscillator • One shot • periodical • …
ex : outb(0xb6 , 0x43) 0xb6 = 1011 0110 From Intel document
Time Stamp Counter • 64-bits counter driven by CPU clocks • 1 nsec resolution for 1GHZ CPU • High Resolution • Drawbacks • Frequency varies • Power management changes CPU clock
Local APIC Timer • 32-bit down-counter driven by bus clock signal • Send the interrupt to its processor only • (PIC generates the global interrupt) • Three registers From Intel document
Trace RTLinux Source Code rtl_time.c
struct rtl_clock { int (*init) (struct rtl_clock *); void (*uninit) (struct rtl_clock *); hrtime_t (*gethrtime)(struct rtl_clock *); int (*sethrtime)(struct rtl_clock *, hrtime_t t); int (*settimer)(struct rtl_clock *, hrtime_t interval); int (*settimermode)(struct rtl_clock *, int mode); clock_irq_handler_t handler; int mode; hrtime_t resolution; hrtime_t value; hrtime_t delta; pthread_spinlock_t lock; struct rtl_clock_arch arch; }; time from initialization of rtl_clock data about Linux
int init_module (void) {rtl_spin_lock_init (&lock8254); rtl_spin_lock_init (&lock_linuxtime); init_hrtime(); rtl_create_clock_8254(); #ifdef CONFIG_X86_LOCAL_APIC { int i; for (i = 0; i < rtl_num_cpus(); i++) { int cpu = cpu_logical_map (i); rtl_create_clock_apic(cpu); } }
#ifdef CONFIG_RTL_CLOCK_GPOS if (smp_found_config) { rtl_irqstate_t flags; rtl_no_interrupts(flags); _8254_init(&_i8254_clock); _i8254_clock. settimermode (&_i8254_clock, RTL_CLOCK_MODE_PERIODIC); _i8254_clock.settimer (&_i8254_clock, LATCH_NS); rtl_restore_interrupts(flags);} #endif #endif rtl_init_standard_clocks(); #ifdef CONFIG_RTL_CLOCK_GPOS rtl_clock_gpos_init(); #endif return 0; } initialize struct rtl_clock clock_ust
For what unsigned long scaler_8254_to_hrtime; unsigned long scaler_hrtime_to_8254; static unsigned long scaler_pentium_to_hrtime; static unsigned long scaler_hrtime_to_apic; static hrtime_t (*rtl_do_get_time)(void); From TSC or from PIC 8254 TSC local APIC nano sec nextreturn
CLOCK_TICK_RATE=1193182 HRTICKS_PER_SEC=1000000000 muldiv(a,mul,div)=(a*mul)/div static void init_hrtime (void) { int flags; #if HRTICKS_PER_SEC != CLOCK_TICK_RATE scaler_8254_to_hrtime = muldiv (HRTICKS_PER_SEC, 1 << 22, CLOCK_TICK_RATE); scaler_hrtime_to_8254 = muldiv (CLOCK_TICK_RATE, 1 << 31, HRTICKS_PER_SEC / 2); LATCH_NS = muldiv (LATCH, HRTICKS_PER_SEC, CLOCK_TICK_RATE); #else LATCH_NS = LATCH; #endif ns / LATCH ==CLOCK_TICK_RATE / HZ HZ=100 nextreturn
MAX_LATCH_ONESHOT = LATCH_NS * 3 / 4; rtl_no_interrupts(flags); if (cpu_has_tsc && !notsc) { can_change_latch2 = 1; do_calibration(1); rtl_do_get_time = pent_gettime; hrtime_resolution = 32;} == (TSC register) * scaler_pentium_to_hrtime nextreturn
} else { // if no TSC do_calibration(0); can_change_latch2 = 0; outb(0xb6, 0x43); /* binary, mode 3, LSB/MSB, ch 2 */ outb(LATCH2 & 0xff, 0x42); outb((LATCH2 >> 8) & 0xff, 0x42); outb((inb(0x61) & 0xfd) | 1, 0x61); /* shut up the speaker and enable counting */ LATCH_CNT2(); READ_CNT2(last_c2); offset_time = 0; base_time = 0; rtl_do_get_time = global_8254_gettime; hrtime_resolution = HRTICKS_PER_SEC / CLOCK_TICK_RATE; } // end of init_hrtime return ==32*1024 used to modify base_time used to keep time from now
static void do_calibration(int do_tsc) { #ifdef CONFIG_X86_LOCAL_APIC long temp = 0 , a1 = 0 , a2 = 0 , save_apic = 0 , result_apic; #endif long long t1 = 0 , t2 = 0 ; long pps; int j; long result = 0; rtl_irqstate_t flags; rtl_no_interrupts(flags); #ifdef CONFIG_X86_LOCAL_APIC if (smp_found_config) { save_apic = apic_read(APIC_TMICT); apic_write(APIC_TMICT, 1000000000/APIC_DIVISOR); } #endif next unsigned long Read initial count register of local APIC timer ==16 write to initial count register of local APIC timer
outb((inb(0x61) & ~0x02) | 0x01, 0x61); outb(0xb6, 0x43); /* binary, mode 3, LSB/MSB, ch 2 */ outb(CLATCH & 0xff, 0x42); outb(CLATCH >> 8, 0x42); wait_cycle(); if (do_tsc) rdtscll(t1); #ifdef CONFIG_X86_LOCAL_APIC if (smp_found_config) { a1 = apic_read(APIC_TMCCT);} #endif next apic_read(APIC_TMCCP): read the current count register of local APIC rdtscll(x): read TSC register ==1024*32 ==50 for (j = 0; j < NLOOPS; j++) { wait_cycle();} if (do_tsc) rdtscll(t2); #ifdef CONFIG_X86_LOCAL_APIC if (smp_found_config) { a2 = apic_read(APIC_TMCCT); } #define wait_value(x) do {; } while ((inb(0x61) & 0x20) != (x)) #define wait_cycle() do { wait_value(0); wait_value(0x20); } while (0)
result_apic = a1 - a2; <= duration of local APIC timer #endif if (do_tsc) result = t2 - t1; <= duration of TSC #ifdef CONFIG_X86_LOCAL_APIC if (smp_found_config) { restore the original value temp = apic_read(APIC_TMICT); apic_write(APIC_TMICT, save_apic);} #endif rtl_restore_interrupts(flags); if (do_tsc) { ==count of TSC during one sec pps = muldiv (result, CLOCK_TICK_RATE, CLATCH * NLOOPS); #if HRTICKS_PER_SEC == NSECS_PER_SEC Pentium TSC scaler_pentium_to_hrtime = muldiv (1 << 27, HRTICKS_PER_SEC, pps); #else scaler_pentium_to_hrtime = muldiv (1 << 31, HRTICKS_PER_SEC * 2, pps); #endif next
} else { scaler_pentium_to_hrtime = 0; } #ifdef CONFIG_X86_LOCAL_APIC if (smp_found_config) { temp = ==count of local APIC during one sec muldiv (result_apic, CLOCK_TICK_RATE, CLATCH * NLOOPS); #if HRTICKS_PER_SEC == NSECS_PER_SEC scaler_hrtime_to_apic = muldiv (temp, 1 << 31, HRTICKS_PER_SEC / 2); #else scaler_hrtime_to_apic = muldiv (temp, 1 << (31 - 10), HRTICKS_PER_SEC / 2); #endif apic_ticks_per_sec = temp;} #endif } // end of do_calibration return
Read-Back Command of 8254 #define LATCH_CNT2() outb(0xd8, 0x43); 0xd8 == 1101 1000 #define READ_CNT2(var) \ do { var = inb(0x42); var |= (inb(0x42) << 8); } while (0) return
hrtime_t global_8254_gettime (void) { register unsigned int c2; int flags; long t; rtl_spin_lock_irqsave (&lock8254, flags); LATCH_CNT2(); READ_CNT2(c2); offset_time += ((c2 < last_c2) ? (last_c2 - c2) / 2 : (last_c2 - c2 + LATCH2) / 2); last_c2 = c2; if (offset_time >= CLOCK_TICK_RATE) { offset_time -= CLOCK_TICK_RATE; base_time += HRTICKS_PER_SEC; } nextreturn
#if HRTICKS_PER_SEC != CLOCK_TICK_RATE __asm__("shl $10, %%eax\n\t" "mul %%ebx\n\t" :"=d" (t) : "b" (scaler_8254_to_hrtime), "a" (offset_time)); #else t = offset_time; #endif last_8254_time = base_time + t; rtl_spin_unlock_irqrestore (&lock8254, flags); return last_8254_time; } //end of global_8254_gettime return
int rtl_create_clock_8254(void) { _i8254_clock = RTL_CLOCK_DEFAULTS; _i8254_clock.init = _8254_init; _i8254_clock.uninit = _8254_uninit; _i8254_clock.settimermode = _8254_settimermode; return 0; } return
Default clock constructor #define RTL_CLOCK_INITIALIZER { \ definit, \ defuninit, \ defgethrtime, \ defsethrtime, \ defsettimer, \ defsettimermode, \ default_handler, \ RTL_CLOCK_MODE_UNINITIALIZED, \ 0, \ 0, \ 0, \ PTHREAD_SPINLOCK_INITIALIZER, \ RTL_CLOCK_ARCH_INITIALIZER}; struct rtl_clock RTL_CLOCK_DEFAULTS = RTL_CLOCK_INITIALIZER; return
static int _8254_init (clockid_t clock) { int flags; rtl_no_interrupts (flags); save_do_gettimeoffset = do_gettimeoffset; do_gettimeoffset = do_rt_gettimeoffset; save_use_tsc = use_tsc; use_tsc = 0; rtl_save_jiffies = jiffies; ==return rtl_do_get_time(); _i8254_clock.arch.linux_time = gethrtime() + LATCH_NS; register real time 8254 timer interrupt handler “_8254_irq” rtl_request_global_irq(0, _8254_irq); _8254_settimermode (clock, RTL_CLOCK_MODE_ONESHOT); _i8254_clock.settimer (clock, HRTIME_INFINITY); rtl_restore_interrupts (flags); return 0; } return
int _8254_settimermode (struct rtl_clock *c, int mode) { if (mode == _i8254_clock.mode) { return 0;} if (mode == RTL_CLOCK_MODE_PERIODIC) { outb(0x34, 0x43); 0x34=0011 0100 /* binary, mode 2, LSB/MSB, ch 0 */ _i8254_clock.mode = mode; _i8254_clock.gethrtime = periodic_gethrtime; _i8254_clock.settimer = _8254_setperiodic; _i8254_clock.arch.count_irqs = 0; periodic static hrtime_t periodic_gethrtime (struct rtl_clock *c) { return c->value; } Updated by _8254_irq nextreturn
} else if (mode == RTL_CLOCK_MODE_ONESHOT) { outb(0x30, 0x43); 0x30 == 0011 0000 /* 8254, channel 0, mode 0, lsb+msb */ _i8254_clock.mode = mode; _i8254_clock.gethrtime = oneshot_gethrtime; _i8254_clock.settimer = _8254_setoneshot; _i8254_clock.resolution = HRTICKS_PER_SEC / CLOCK_TICK_RATE; } else { return -EINVAL;} return 0; } One shot return static hrtime_t oneshot_gethrtime (struct rtl_clock *c) { return gethrtime(); }
static int _8254_setperiodic (clockid_t c, hrtime_t interval) { int flags; long t; rtl_spin_lock_irqsave (&lock8254, flags); t = RTIME_to_8254_ticks (interval) + 1; if (t < 10) { t = LATCH; rtl_printf("RTLinux 8254 periodic settimer set too low!\n");} if (t > LATCH) { t = LATCH; rtl_printf("RTLinux 8254 periodic settimer set too high!\n");} WRITE_COUNTER_ZERO16 (t); _i8254_clock.value = gethrtime(); _i8254_clock.resolution = interval; _i8254_clock.arch.istimerset = 1; rtl_spin_unlock_irqrestore(&lock8254, flags); return 0; } return
static inline long RTIME_to_8254_ticks(long t) { #if HRTICKS_PER_SEC != CLOCK_TICK_RATE int dummy; __asm__("mull %2" :"=a" (dummy), "=d" (t) :"g" (scaler_hrtime_to_8254), "0" (t) ); #endif return (t); } #define WRITE_COUNTER_ZERO16(x) do { \ outb(x&0xff,0x40); outb((x>>8)&0xff,0x40);\ clock_counter =x; \ } while (0) return
static int _8254_setoneshot (clockid_t c, hrtime_t interval) { rtl_irqstate_t flags; long t; rtl_spin_lock_irqsave (&lock8254, flags); if (interval > MAX_LATCH_ONESHOT) { interval = MAX_LATCH_ONESHOT;} t = RTIME_to_8254_ticks (interval); /* - _8254_latency); */ if (t < 1) {t = 1;} == WRITE_COUNTER_ZERO16(x) WRITE_COUNTER_ZERO_ONESHOT(t); _i8254_clock.arch.istimerset = 1; rtl_spin_unlock_irqrestore(&lock8254, flags); return 0; } return
unsigned long muldiv(unsigned long a, unsigned long mul, unsigned long div) { int temp; __asm__("mull %2 ; divl %3" :"=a" (a), "=d" (temp) :"1" (mul), "c" (div), "0" (a) ); return a; } return
Example #if HRTICKS_PER_SEC != CLOCK_TICK_RATE __asm__("shl $10, %%eax\n\t" "mul %%ebx\n\t" :"=d" (t) : "b" (scaler_8254_to_hrtime), "a" (offset_time)); scaler_8254_to_hrtime= (HRTICKS_PER_SEC * 2^ 22) / CLOCK_TICK_RATE offset_time * 2^10 * scaler_8254_to_hrtime = offset_time * 2^10 * ((HRTICKS_PER_SEC * 2^ 22) / CLOCK_TICK_RATE) = (offset_time * HRTICKS_PER_SEC / CLOCK_TICK_RATE) * (2^32) ==t return
static void rtl_clock_gpos_init(void) { rtl_clock_gpos_irq = rtl_get_soft_irq (clock_irq_handler, "RTLinux CLOCK_GPOS"); clock_gpos = RTL_CLOCK_DEFAULTS; clock_gpos.gethrtime = &gpos_gethrtime; clock_gpos.resolution = gethrtimeres(); clock_gpos.delta = gpos_get_delta(); } global time – execution time of system retur static hrtime_t gpos_gethrtime(struct rtl_clock *c) { hrtime_t t; pthread_spin_lock(&c->lock); t = gethrtime() + c->delta; pthread_spin_unlock(&c->lock); return t; } from start of computer
static unsigned int _8254_irq(unsigned int irq, struct pt_regs *regs) { int flags; rtl_spin_lock_irqsave (&lock8254, flags); if (_i8254_clock.mode == RTL_CLOCK_MODE_PERIODIC) { if (test_and_set_bit(0, &_i8254_clock.arch.count_irqs)) { _i8254_clock.value += _i8254_clock.resolution;}} else {_i8254_clock.arch.istimerset = 0;} rtl_hard_enable_irq(0); rtl_spin_unlock_irqrestore (&lock8254, flags); _i8254_clock.handler(regs); if (rtl_rt_system_is_idle()) { rtl_allow_interrupts(); //rtl_hard_sti()} _8254_checklinuxirq(); rtl_stop_interrupts(); //rtl_hard_cli() return 0;} return
void _8254_checklinuxirq (void) { rtl_irqstate_t flags; hrtime_t t; rtl_no_interrupts(flags); rtl_spin_lock(&lock_linuxtime); t = gethrtime(); if (t > _i8254_clock.arch.linux_time && !rtl_global_ispending_irq(0)) { _i8254_clock.arch.linux_time += LATCH_NS; rtl_global_pend_irq (0); do {rtl_save_jiffies = jiffies; } while (rtl_save_jiffies != jiffies); rtl_clock_gpos_update(); } rtl_spin_unlock(&lock_linuxtime); if (!_i8254_clock.arch.istimerset) { _i8254_clock.settimer (&_i8254_clock, MAX_LATCH_ONESHOT);} rtl_restore_interrupts(flags);} return