# Linux Kernel Module Cheat

###### 24.22.4.1.1. AtomicSimpleCPU initial events

Let’s have a closer look at the initial magically scheduled events of the simulation.

Most events come from other events, but at least one initial event must be scheduled somehow from elsewhere to kick things off.

The initial tick event:

    0: Event: AtomicSimpleCPU tick.wrapped_function_event: EventFunctionWrapped 39 scheduled @ 0

we’ll study by breaking at at the point that prints messages: `b Trace::OstreamLogger::logMessage()` to see where events are being scheduled from:

    Trace::OstreamLogger::logMessage() at trace.cc:149 0x5555593b3b1e
    void Trace::Logger::dprintf_flag<char const*, char const*, unsigned long>() at 0x55555949e603
    void Trace::Logger::dprintf<char const*, char const*, unsigned long>() at 0x55555949de58
    Event::trace() at eventq.cc:395 0x55555946d109
    EventQueue::schedule() at eventq_impl.hh:65 0x555557195441
    EventManager::schedule() at eventq.hh:746 0x555557194aa2
    AtomicSimpleCPU::activateContext() at atomic.cc:239 0x555559075531
    SimpleThread::activate() at simple_thread.cc:177 0x555559545a63
    Process::initState() at process.cc:283 0x555559484011
    ArmProcess64::initState() at process.cc:126 0x55555730827a
    ArmLinuxProcess64::initState() at process.cc:1,777 0x5555572d5e5e

The interesting call is at `AtomicSimpleCPU::activateContext`:

    schedule(tickEvent, clockEdge(Cycles(0)));

which calls `EventManager::schedule`.

`AtomicSimpleCPU` is an `EventManager` because `SimObject` inherits from it.

`tickEvent` is an `EventFunctionWrapper` which contains a `std::function<void(void)> callback;`, and is initialized in the constructor as:

    tickEvent([this]{ tick(); }, "AtomicSimpleCPU tick",
            false, Event::CPU_Tick_Pri),

The call stack above `ArmLinuxProcess64::initState` is pybind11 fuzziness, but if we grep a bit we find the Python call point:

src/python/m5/simulate.py

    def instantiate(ckpt_dir=None):

        ...

        # Create the C++ sim objects and connect ports
        for obj in root.descendants(): obj.createCCObject()
        for obj in root.descendants(): obj.connectPorts()

        # Do a second pass to finish initializing the sim objects
        for obj in root.descendants(): obj.init()

        ...

        # Restore checkpoint (if any)
        if ckpt_dir:
            ...
        else:
            for obj in root.descendants(): obj.initState()

and this gets called from the toplevel Python scripts e.g. se.py `configs/common/Simulation.py` does:

    m5.instantiate(checkpoint_dir)

As we can see, `initState` is just one stage of generic `SimObject` initialization. `root.descendants()` goes over the entire `SimObject` tree calling `initState()`.

Finally, we see that `initState` is part of the `SimObject` C++ API:

src/sim/sim_object.hh

    class SimObject : public EventManager, public Serializable, public Drainable,
                      public Stats::Group
    {

        ...

        /**
         * initState() is called on each SimObject when *not* restoring
         * from a checkpoint.  This provides a hook for state
         * initializations that are only required for a "cold start".
         */
        virtual void initState();

Finally, we see that `initState` is exposed to the Python API at:

build/ARM/python/_m5/param_SimObject.cc

    module_init(py::module &m_internal)
    {
        py::module m = m_internal.def_submodule("param_SimObject");
        py::class_<SimObjectParams, std::unique_ptr<SimObjectParams, py::nodelete>>(m, "SimObjectParams")
            .def_readwrite("name", &SimObjectParams::name)
            .def_readwrite("eventq_index", &SimObjectParams::eventq_index)
            ;

        py::class_<SimObject, Drainable, Serializable, Stats::Group, std::unique_ptr<SimObject, py::nodelete>>(m, "SimObject")
            .def("init", &SimObject::init)
            .def("initState", &SimObject::initState)
            .def("memInvalidate", &SimObject::memInvalidate)
            .def("memWriteback", &SimObject::memWriteback)
            .def("regProbePoints", &SimObject::regProbePoints)
            .def("regProbeListeners", &SimObject::regProbeListeners)
            .def("startup", &SimObject::startup)
            .def("loadState", &SimObject::loadState, py::arg("cp"))
            .def("getPort", &SimObject::getPort, pybind11::return_value_policy::reference, py::arg("if_name"), py::arg("idx"))
            ;

    }

which is more magical than the other param classes since `py::class_<SimObject` has non-trivial methods, those are auto-generated by the `cxx_exports` code generation mechanism:

    class SimObject(object):

        ...

        cxx_exports = [
            PyBindMethod("init"),
            PyBindMethod("initState"),
            PyBindMethod("memInvalidate"),
            PyBindMethod("memWriteback"),
            PyBindMethod("regProbePoints"),
            PyBindMethod("regProbeListeners"),
            PyBindMethod("startup"),
        ]

And the second magically scheduled event is the exit event:

    0: Event: Event_70: generic 70 scheduled @ 0
    0: Event: Event_70: generic 70 rescheduled @ 18446744073709551615

which is scheduled with backtrace:

    Trace::OstreamLogger::logMessage() at trace.cc:149 0x5555593b3b1e
    void Trace::Logger::dprintf_flag<char const*, char const*, unsigned long>() at 0x55555949e603
    void Trace::Logger::dprintf<char const*, char const*, unsigned long>() at 0x55555949de58
    Event::trace() at eventq.cc:395 0x55555946d109
    EventQueue::schedule() at eventq_impl.hh:65 0x555557195441
    BaseGlobalEvent::schedule() at global_event.cc:78 0x55555946d6f1
    GlobalEvent::GlobalEvent() at 0x55555949d177
    GlobalSimLoopExitEvent::GlobalSimLoopExitEvent() at sim_events.cc:61 0x555559474470
    simulate() at simulate.cc:104 0x555559476d6f

which comes at object creation inside `simulate()` through the `GlobalEvent()` constructor:

    simulate_limit_event =
        new GlobalSimLoopExitEvent(mainEventQueue[0]->getCurTick(),
                                    "simulate() limit reached", 0);

This event indicates that the simulation should finish by overriding `bool isExitEvent()` which gets checked in the main simulation at `EventQueue::serviceOne`:

    if (event->isExitEvent()) {
        assert(!event->flags.isSet(Event::Managed) ||
                !event->flags.isSet(Event::IsMainQueue)); // would be silly
        return event;

Tested in gem5 12c917de54145d2d50260035ba7fa614e25317a3.