Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions simso/configuration/Configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,8 @@ def add_task(self, name, identifier, task_type="Periodic",
abort_on_miss=True, period=10, activation_date=0,
n_instr=0, mix=0.5, stack_file="", wcet=0, acet=0,
et_stddev=0, deadline=10, base_cpi=1.0, followed_by=None,
list_activation_dates=[], preemption_cost=0, data=None):
list_activation_dates=[], preemption_cost=0, data=None,
m=1, k=1):
"""
Helper method to create a TaskInfo and add it to the list of tasks.
"""
Expand All @@ -292,7 +293,7 @@ def add_task(self, name, identifier, task_type="Periodic",
activation_date, n_instr, mix,
(stack_file, self.cur_dir), wcet, acet, et_stddev,
deadline, base_cpi, followed_by, list_activation_dates,
preemption_cost, data)
preemption_cost, data, m=m, k=k)
self.task_info_list.append(task)
return task

Expand Down
9 changes: 8 additions & 1 deletion simso/core/Job.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from SimPy.Simulation import Process, hold, passivate
from simso.core.JobEvent import JobEvent
from math import ceil
from math import floor, ceil


class Job(Process):
Expand All @@ -29,6 +29,8 @@ def __init__(self, task, name, pred, monitor, etm, sim):
"""
Process.__init__(self, name=name, sim=sim)
self._task = task
self._m = task._task_info.m
self._k = task._task_info.k
self._pred = pred
self.instr_count = 0 # Updated by the cache model.
self._computation_time = 0
Expand All @@ -49,6 +51,11 @@ def __init__(self, task, name, pred, monitor, etm, sim):

self.context_ok = True # The context is ready to be loaded.

self.instance_num = int(name.split("_")[1])
self.cri = floor(ceil(self.instance_num * self._m/ self._k) * (self._k / self._m))
self.mandatory = self.instance_num == self.cri
self.optional = not self.mandatory

def is_active(self):
"""
Return True if the job is still active.
Expand Down
8 changes: 6 additions & 2 deletions simso/core/Task.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class TaskInfo(object):
def __init__(self, name, identifier, task_type, abort_on_miss, period,
activation_date, n_instr, mix, stack_file, wcet, acet,
et_stddev, deadline, base_cpi, followed_by,
list_activation_dates, preemption_cost, data):
list_activation_dates, preemption_cost, data,
m=1, k=1):
"""
:type name: str
:type identifier: int
Expand Down Expand Up @@ -63,6 +64,9 @@ def __init__(self, name, identifier, task_type, abort_on_miss, period,
self.data = data
self.preemption_cost = preemption_cost

self.m = m
self.k = k

@property
def csdp(self):
"""
Expand Down Expand Up @@ -272,7 +276,7 @@ def create_job(self, pred=None):
directly by a scheduler.
"""
self._job_count += 1
job = Job(self, "{}_{}".format(self.name, self._job_count), pred,
job = Job(self, "{}_{}".format(self.name, self._job_count - 1), pred, #-1 to be zero indexed
monitor=self._monitor, etm=self._etm, sim=self.sim)

if len(self._activations_fifo) == 0:
Expand Down
158 changes: 158 additions & 0 deletions simso/schedulers/MK_DBP.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
"""
@author Jonathan Tan (@jona1115 on GitHub)
@date 12/09/2025
"""

from collections import deque

from simso.core import Scheduler
from simso.schedulers import scheduler


@scheduler("simso.schedulers.MK_DBP")
class MK_DBP(Scheduler):
"""
Distance Based Priority scheduler for (m,k)-firm tasks.

Based on: Joël Goossens. (m, k)-firm constraints and DBP scheduling: impact of the initial
k-sequence and exact schedulability test. 16th International Conference on Real-Time and
Network Systems (RTNS 2008), Isabelle Puaut, Oct 2008, Rennes, France. ⟨inria-00336461⟩

This is coded to work on Uniprocessor simulations!
"""

def init(self):
self.ready_list = []
self._k_histories = {}
for task in self.task_list:
self._k_histories[task] = self._init_history(task)

def on_activate(self, job):
self.ready_list.append(job)
job.cpu.resched()

def on_terminated(self, job):
self._update_history(job)
if job in self.ready_list:
self.ready_list.remove(job)
else:
job.cpu.resched()

def _should_run(self, candidate, current):
if current is None:
return True
if current.mandatory and candidate.optional:
return False
return self._priority_tuple(candidate) < self._priority_tuple(current)

def _priority_tuple(self, job):
return (
job.optional, # mandatory > optional
self._compute_distance(job.task), # distance
job.absolute_deadline, # on tie use EDF
job.period # then RMS
)

def _priority_distance(self, job):
if job is None:
return float("inf")
return self._compute_distance(job.task)

def _compute_distance(self, task):
"""
Distance, as per Goossens 2008, is defined as starting from time now, the amount of
deadline we can affort to miss before we violate the (m,k)-firm requirement.

In practice, we achieve this but keeping track of the history of the task as a list.
if successes is < m the distance is 0 because it is already violated (m,k)-firm
requirement. Else, it append a hypothetical miss into the list, then it runs the check
again.
"""
history = self._k_histories.get(task)
if history is None:
history = self._init_history(task)
self._k_histories[task] = history
m = getattr(task._task_info, "m", 1)
if m <= 0:
return float("inf")
sequence = list(history)
successes = sum(sequence)
if successes < m:
return 0
allowed = 0
current = sequence[:]
k = len(current)
while allowed < k:
current = current[1:] + [0]
allowed += 1
if sum(current) < m:
return allowed - 1
return allowed

def _init_history(self, task):
k = max(1, getattr(task._task_info, "k", 1))
initial = None
if task.data and isinstance(task.data, dict):
initial = task.data.get("initial_k_sequence")
values = self._normalize_init_sequence(initial)
history = deque(maxlen=k)
slice_start = max(0, len(values) - k)
for value in values[slice_start:]:
history.append(value)
while len(history) < k:
history.appendleft(1)
return history

@staticmethod
def _normalize_init_sequence(initial):
if initial is None:
return [1]
result = []
if isinstance(initial, str):
for char in initial:
if char == "1":
result.append(1)
elif char == "0":
result.append(0)
elif isinstance(initial, (list, tuple, deque)):
for value in initial:
result.append(1 if value else 0)
elif isinstance(initial, bool):
result.append(1 if initial else 0)
elif isinstance(initial, (int, float)):
result.append(1 if initial else 0)
else:
result.append(1)
if not result:
result.append(1)
return result

def _update_history(self, job):
task = job.task
history = self._k_histories.get(task)
if history is None:
history = self._init_history(task)
self._k_histories[task] = history
history.append(0 if job.exceeded_deadline else 1)

def schedule(self, cpu):
if not self.ready_list:
return None

key = lambda x: (
0 if x.running is None else 1,
-self._priority_distance(x.running) if x.running else 0,
0 if x is cpu else 1,
)
cpu_min = min(self.processors, key=key)

job = min(self.ready_list, key=self._priority_tuple)

if self._should_run(job, cpu_min.running):
self.ready_list.remove(job)
if cpu_min.running:
self.ready_list.append(cpu_min.running)
print(self.sim.now() / 1000000, job.name, cpu_min.name)
return (job, cpu_min)

return None
49 changes: 49 additions & 0 deletions simso/schedulers/MK_EDF.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""
@author Jonathan Tan (@jona1115 on GitHub)
@date 12/09/2025
"""

from simso.core import Scheduler
from simso.schedulers import scheduler

@scheduler("simso.schedulers.MK_EDF")
class MK_EDF(Scheduler):
"""Earliest Deadline First (EDF) with (m,k)-firm requirements"""
def on_activate(self, job):
job.cpu.resched()

def on_terminated(self, job):
job.cpu.resched()

def schedule(self, cpu):
# List of ready jobs not currently running:
ready_jobs = [t.job for t in self.task_list
if t.is_active() and not t.job.is_running()]

if ready_jobs:
# Select a free processor or, if none,
# the one with the greatest deadline (self in case of equality):
key = lambda x: (
1 if not x.running else 0,
x.running.absolute_deadline if x.running else 0,
1 if x is cpu else 0
)
cpu_min = max(self.processors, key=key)


# Select the job with the least priority:
job = min(ready_jobs, key=lambda x: (x.optional, x.absolute_deadline, x.period))

if ((# If there is nothing running rn, or
(cpu_min.running is None) or
# if the current running is optional and the job is mandatory, also if the current running deadline < job deadline, or
((cpu_min.running is not None and job is not None) and (cpu_min.running.optional and job.mandatory) and (cpu_min.running.absolute_deadline < job.absolute_deadline)) or
# if the current running deadline is > the job's deadline
(cpu_min.running.absolute_deadline > job.absolute_deadline)
) and
# finally we make sure a mandatory task don't get preempted by a optional task
not ((cpu_min.running is not None and cpu_min.running.mandatory) and job.optional)
):

print(self.sim.now()/1000000, job.name, cpu_min.name)
return (job, cpu_min)
50 changes: 50 additions & 0 deletions simso/schedulers/MK_RMS.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
@author Jonathan Tan (@jona1115 on GitHub)
@date 12/09/2025
"""

from simso.core import Scheduler
from simso.schedulers import scheduler

@scheduler("simso.schedulers.MK_RMS")
class MK_RMS(Scheduler):
""" Rate monotonic (RMS) with (m,k)-firm requirements """
def init(self):
self.ready_list = []

def on_activate(self, job):
self.ready_list.append(job)
job.cpu.resched()

def on_terminated(self, job):
if job in self.ready_list:
self.ready_list.remove(job)
else:
job.cpu.resched()

def schedule(self, cpu):
decision = None
if self.ready_list:
############### As far as I can tell this chunk is just selecting a CPU, so for uniprocessor, this chunk is junk ###############
# Get a free processor or a processor running a low priority job.
key = lambda x: ( # this lambda func aparently returns a 3 element tuple
0 if x.running is None else 1,
-x.running.period if x.running else 0,
0 if x is cpu else 1
)
cpu_min = min(self.processors, key=key) # with one cpu, cpu min will just be the only cpu
####################################################### end junk chunk #######################################################

# Job with highest priority.
job = min(self.ready_list, key=lambda x: (x.optional, x.period))

if (((cpu_min.running is None) or (cpu_min.running.period > job.period)) and
not ((cpu_min.running is not None and cpu_min.running.mandatory) and job.optional)): # this check is to prevent a optional job preempting a mandatory job

self.ready_list.remove(job)
if cpu_min.running:
self.ready_list.append(cpu_min.running) # if it got preempted, add it back to the que
decision = (job, cpu_min)
print(self.sim.now()/1000000, job.name, cpu_min.name)

return decision