目录
1.1 Fiber 在哪初始化?在哪注册?刚注册出来的时候是 Activate 还是 Deactivate?
1. Fiber
1.1 Fiber 在哪初始化?在哪注册?刚注册出来的时候是 Activate 还是 Deactivate?
(1)Fiber 在 Fiber 调度器中初始化;
(2)Fiber 在 Fiber 调度器初始化的过程中被注册;
(3)Fiber 刚注册出来的时候一般是 Deactivate 状态。
1.2 激活纤程的操作是如何实现的?
(1)通过设置活跃位图的相应位来实现的。
(2)在激活指定 Fiber 的过程中, 首先会判断当前是否有被激活的 Fiber。若是没有,则会输出一条调试日志,表示需要唤醒操作。
1.3 分区及分区中的 Fiber 数
(1)共定义了四个分区,分别是 HostPartition、CorePartition、MediaPartition 和 MediaLowerPartition。
(2)HostPartition 中定义了 10 种类型的Fiber,CorePartition 中定义了 23 种类型的Fiber,MediaPartition 中定义了 3 种类型的Fiber,MediaLowerPartition 中定义了 3 种类型的Fiber。其中,四个分区中均有 IpcFifoFiber。
2. mainCpu.c 文件
2.1 mainCpu0.c 文件
首先,对各个模块进行初始化操作,包括硬件抽象层、调试模块、队列管理器等,为后续程序执行做准备;
其次,检查是否支持 RAMDISK 功能。若不支持,则检查初始化引导模式是不是冷启动模式 (cInitBootCold):
1)若不支持 RAMDISK 功能,且是冷启动模式,则调用 cpu1_boot() 函数,启动CPU1;
2)若不支持 RAMDISK 功能,且不是冷启动模式,则获取CPU1的入口指针,输出调试日志,并判断CPU1的指针是否为 0。也就是说,在这种情况下,就需要设置CPU1的入口指针。
再次,利用 HostPartition_RegisterWithFiberScheduler() 函数将 HostPartition 注册到 FiberScheduler 中;
再次,利用 FiberScheduler_Initialize() 函数完成 FiberScheduler 的初始化工作;
再次,利用 HostPartition_Initialize() 函数完成 HostPartition 的初始化工作;
最后,利用 Kernel_Start() 函数启动内核。
2.2 mainCpu1.c 文件
首先,会获取初始化引导模式(initMode);
其次,会向 FiberScheduler 中注册 CorePartition、MediaPartition、MederLowerPartition;
再次,初始化 FiberScheduler;
最后,调用 ServiceLoop() 函数,进入服务循环,处理各种服务请求。
2.3 冷启动模式和热启动模式
(1)冷启动模式
冷启动指的是从系统完全关闭或应用程序尚未初始化的状态下启动系统或应用程序。在冷启动模式下,系统或应用程序需要进行一系列的初始化操作,包括加载必要的资源、建立环境、执行初始化代码等。这通常需要较长的时间,并且可能会对系统或应用程序的性能产生一些短期影响。冷启动模式更适用于系统或应用程序首次启动或在长时间停机后重新启动的情况。
(2)热启动模式
热启动指的是在系统或应用程序已经处于运行状态下重新启动。在热启动模式下,系统或应用程序可以利用已经初始化的状态和缓存的数据,快速重新启动而无需执行完整的初始化过程。热启动模式通常比冷启动模式更快,并且对系统或应用程序的性能影响较小。热启动模式适用于系统或应用程序需要进行周期性的重启或在运行中需要重新加载配置等情况。
3. 状态机 StateMachine
状态机的初始状态:当前步骤 curStep = 0,状态值 status = cStateMechineIdle(空闲状态)。
3.1 状态机状态 StateMechineStatus
StateMechineIdle:空闲状态,没有任务需要执行;
StateMechineContinueRun:继续执行当前状态;
StateMechineJumpRun:状态机跳转到指定的状态并执行;
StateMechinePending:状态机执行过程中遇到待处理任务,需要将任务推送到指定线程或预留线程进行处理;
StateMechineWaitCallBack:状态机正在等待回调函数的执行结果或外部事件的发生;
StateMechinePause:状态机暂停执行,等待进一步的命令或出发条件;
StateMechineQuit:状态机应退出执行,结束状态机的运行;
InvalidStateMachineStatus:无效的状态机状态,用于表示状态无效或错误。
3.2 状态机上下文对象
状态机上下文对象(StateMachineCtx):是个结构体,用于存储状态机的运行时上下文信息,包含执行状态机所需的各种数据和函数指针。通常,状态机上下文对象包含以下成员:
(1)指向状态机结构体的指针(pStateMachine):指向描述状态机行为的结构体,包括状态机的步骤序列和其他相关信息;
(2)当前步骤指针(pCurStep):指向当前执行的步骤在步骤序列中的位置;
(3)线程 ID(threadId):用于标识将状态机上下文对象推送到哪个线程进行等待和恢复;
(4)对象(Object):一个通用的对象或数据结构,用于传递给等待和恢复状态机执行的函数;
(5)输入函数指针(inFunc):指向一个函数,用于执行每个步骤之前执行特定的输入操作;
(6)输出函数指针(outFunction):指向一个函数,用于在执行每个步骤之后执行特定的输出操作。
状态机上下文对象的作用是在状态机的执行过程中存储和传递状态信息,以及进行输入和输出操作。它允许状态机在执行过程中与外部环境进行交互,并控制状态机的执行流程。
3.3 指定线程与预留线程
(1)指定线程是根据任务的需求或设计来明确指定的;而预留线程是为特定任务或操作而保留的,具有较高的优先级和响应性。
(2)指定线程可以是系统中的任何线程,如主线程、工作线程等;而预留线程通常是专门为关键任务而创建的线程。
(3)指定线程的使用更加灵活;而预留线程的使用主要针对特定的任务类型和需求。
4. IPC
IPC 是指不同进程之间进行数据交换和通信的机制和技术。在一个操作系统中,不同的进程可能需要相互通信和协作,以实现任务的协调和数据的交换。IPC 提供了一些通信机制,使进程之间能够传递消息、共享数据和同步操作。
IPC 路由是指用于确定消息传递路径的机制或组件,负责将消息从发送进程路由到接收进程。当进程发送 IPC 请求或消息时,需要指定目标进程或目标处理单元。IPC 路由负责将消息从发送进程路由接收到进程。它决定了消息从哪个发送者到达哪个接收者,并确保消息按照指定的路径传递。
IPC 路由标识是唯一标识一个 IPC 路由的值或标识符,用于区分不同的通信路径或目标进程。它可以是一个整数、字符串或其他类型的标识符,用于识别不同的 IPC 路由。
也可以说是,IPC 路由是指在系统中不同进程之间进行通信时,数据传输的路径或通道。每个 IPC 路由标识代表了系统中的一个特定的通信路径或通道。
IPC 路由的具体实现方式:共享内存、消息队列、管道(无名管道和有名管道两种)、套接字、信号量等等。
FIFO(有名管道) 是一种用于进程间通信的机制,基于先进先出的队列结构。它允许无亲缘关系的进程访问同一个 FIFO。它允许多个进程通过共享的 FIFO 缓冲区进行数据交换和通信。
FIFO 提供了一种可靠的消息传递机制,其中一个进程可以将消息写入到 FIFO 中,而另一个进程可以从 FIFO 中读取相应的消息。消息按照先进先出的顺序进行传递,保证了消息的顺序性。
FIFO 是半双工的,因此不能打开来既读又写。
4.1 IpcFifo.c 文件
1. IpcFifo.c 文件涉及到的两个函数 IpcFifoPush 和 IpcFifo_Insert
/**
*该函数用于将 IPC 请求对象(待处理对象)“推送”到IPC FIFO队列中进行处理。
*如果队列未满,直接将对象写入队列;如果队列已满,则将对象推送到特定线程进行后续处理。
*/
void IpcFifoPush(PendingObject_t* pObject)
{
IpcRequest_t* pIpcRequest = STR_PTR_FROM_MEMBER(pObject, IpcRequest_t, object);
IpcFifoRouteId_t qid = pIpcRequest->routeId;
CirBuf_t *pIpcFifo = &gIpcFifo[gIpcRoutes[qid].ipcPort];
if (!CbIsFull(pIpcFifo))
{
CbWrite(pIpcFifo, (void *)&pIpcRequest->object);
}
else
{
PendingScheduler_PushPendingObjectToThread(gIpcRoutes[qid].threadId, &pIpcRequest->object, (GenericPendingFptr_t)&IpcFifoPush, 0, 0);
}
}
/**
*代码的作用是将IPC请求对象“插入”到IPC FIFO队列中,以便后续处理或传递给相应的线程。
*根据不同的情况,请求对象可以直接写入IPC FIFO,或者通过待处理对象队列进行中转。
*/
void IpcFifo_Insert(IpcRequest_t *pIpcRequest)
{
ASSERT(pIpcRequest != NULL);
IpcFifoRouteId_t qid = pIpcRequest->routeId;
ASSERT_MESSAGE((gIpcRoutes[qid].threadId != 0), ("ipc route %d queue not register!!!\n", qid));
if (PendingScheduler_IsThreadInOrderReq(gIpcRoutes[qid].threadId, &pIpcRequest->object))
{
PendingScheduler_PushPendingObjectToThread(gIpcRoutes[qid].threadId, &pIpcRequest->object, (GenericPendingFptr_t)&IpcFifoPush, 0, 0);
}
else
{
CirBuf_t *pIpcFifo = &gIpcFifo[gIpcRoutes[qid].ipcPort];
if (!CbIsFull(pIpcFifo))
{
uint32_t objectAddr = &pIpcRequest->object;
CbWrite(pIpcFifo, (void *)(&objectAddr));
}
else
{
PendingScheduler_PushPendingObjectToThread(gIpcRoutes[qid].threadId, &pIpcRequest->object, (GenericPendingFptr_t)&IpcFifoPush, 0, 0);
}
}
}
上述两段代码有什么区别呢???
2. IpcFifoPush 函数和IpcFifo_Insert 函数的区别如下
(1)IpcFifoPush 函数
- 数据插入方式:直接将 pIpcRequest->object 的地址作为数据写入 FIFO 缓冲区。
- 处理逻辑:如果 FIFO 缓冲区未满,将数据直接写入缓冲区;如果 FIFO 缓冲区已满,将数据推送到特定线程的挂起对象队列中。
- 场景:适用于将数据优先插入到 FIFO 缓冲区中,而不考虑顺序要求。
该函数是一个辅助函数,它接受一个 PendingObject_t 类型的指针作为参数。该函数的作用是将待处理对象推送到 IPC FIFO 队列中。它首先根据 PendingObject_t 的地址计算出对应的 IpcRequest_t 结构体的指针,然后获取该请求对象的路由 ID。接下来,它检查对应的 IPC FIFO 是否已满,如果未满,则将请求对象的地址写入到 IPC FIFO 中。如果 IPC FIFO 已满,则将待处理对象推送到相应线程的待处理对象队列中,使用函数指针 IpcFifoPush 作为处理函数。
(2)IpcFifo_Insert 函数
- 数据插入方式:将 pIpcRequest->object 的地址存储在一个临时变量 objectAddr 中,然后将objectAddr 的地址写入 FIFO 缓冲区。
- 处理逻辑:首先检查是否满足特定线程的顺序要求,如果满足,则将数据推送到特定线程的挂起对象队列中;如果不满足,则将数据写入 FIFO 缓冲区。
- 场景:适用于需要根据顺序要求将数据插入到挂起对象队列或 FIFO 缓冲区中。如果数据需要按照特定顺序处理,且顺序要求由 PendingScheduler_IsThreadInOrderReq 函数确定,则使用 IpcFifo_Insert 函数。
该函数是将 IPC 请求对象插入到 IPC FIFO 队列中的函数。它接受一个 IpcRequest_t 类型的指针作为参数。该函数首先进行断言检查,确保传入的指针不为空。然后,根据请求对象的路由 ID 获取对应的线程 ID,并进行断言验证该线程是否已注册。接下来,它根据线程是否按顺序处理请求进行判断。如果按顺序处理,则将请求对象推送到对应线程的待处理对象队列中。如果不按顺序处理,则将请求对象写入到对应的 IPC FIFO 队列中。如果 IPC FIFO 已满,则将请求对象推送到相应线程的待处理对象队列中。
插入到FIFO缓冲区和推送到特定线程的挂起对象队列中是两种不同的数据处理方式,它们有以下区别:
-
FIFO缓冲区插入:将数据直接写入FIFO(First-In-First-Out)缓冲区中。FIFO是一种数据结构,按照先进先出的原则进行数据处理。当数据插入到FIFO缓冲区时,可以通过读取FIFO的头部来获取最早插入的数据,而最新插入的数据位于FIFO的尾部。这种方式适用于对数据的顺序不做特别要求,只需要存储和处理最新的数据。当FIFO缓冲区已满时,后续的数据插入操作可能需要等待或采取其他策略。
-
挂起对象队列推送:将数据推送到特定线程的挂起对象队列中。这种方式适用于需要按照特定的顺序要求处理数据的场景。挂起对象队列是一种数据结构,用于存储需要在特定线程中处理的数据对象。当数据推送到挂起对象队列时,可以保证按照特定的顺序要求进行处理,确保数据的有序性。推送到挂起对象队列中的数据可以在特定线程准备好并进行处理时被取出。
总结:插入到FIFO缓冲区是一种简单的插入操作,按照先进先出的原则处理数据。而推送到特定线程的挂起对象队列中则是一种按照特定顺序要求进行数据处理的方式,确保数据在特定线程中有序地处理。选择使用哪种方式取决于业务需求和数据处理的特定要求。
3. 使用 IpcFifoPush 函数的时候,如果 FIFO 缓冲区已满,那么将数据推送到特定线程的挂起对象队列中。那什么时候会处理这些在挂起对象队列中的数据呢???
当使用 IpcFifoPush 函数将数据推送到特定线程的挂起对象队列中时,处理这些在挂起对象队列中的数据通常发生在特定线程准备好并能够处理这些数据时。具体的处理时机可能取决于系统设计和业务需求。以下是一种可能的处理流程:
- 当使用IpcFifoPush函数发现FIFO缓冲区已满时,会将数据推送到特定线程的挂起对象队列中。
- 特定线程会定期检查自己的挂起对象队列,以确定是否有待处理的数据。
- 当特定线程准备好处理数据时,它会从挂起对象队列中取出数据进行处理。
- 特定线程根据业务逻辑对取出的数据进行相应的处理操作。
- 一旦数据处理完成,特定线程可以继续处理其他的挂起对象队列中的数据或者执行其他任务。
整个过程中,特定线程负责从挂起对象队列中取出数据并进行处理。处理时机可以根据实际需求进行调整,例如定期进行轮询、根据优先级或事件触发等方式。
4.2 IPC
(1)IpcRouteTable [NumberOfIpcRouteIds] 数组
存储的是 IPC 路由映射关系,数组的索引是 源路由,索引对应的值是 目标路由。
(2)GetIpcPortFrom2Cpu 函数
用于根据源 CPU 和目标 CPU 获取 IPC 端口。IPC 端口用于进程间通信的数据传输和通信。
(3)IpcRequest_t 结构体
实例化之后,得到 IPC 请求对象,IpcRequest。而 IpcRequest_t * pIpcRequest 代表指向该 IPC 请求对象的指针。通过这个指针,可以获取“该请求的 IPC 路由标识”。
(4)void IpcFifoPush(PendingObject_t* pObject)函数
用于将 IPC 请求对象(待处理对象)推送到 IPC FIFO 队列中进行处理。如果队列未满,直接将对象写入队列;如果队列已满,则将对象推送到特定线程进行后续处理。
(5)void IpcFifo_Insert(IpcRequest_t *pIpcRequest) 函数
用于将IPC请求对象插入到 IPC FIFO 队列中,以便后续处理或传递给相应的线程。根据不同的情况,请求对象可以直接写入 IPC FIFO,或者通过待处理对象队列进行中转。
(6)IpcRequest_t* IpcFifo_AllocRequest(void) 函数
用于在 IPC FIFO 中分配一个 IPC 请求(IpcRequest_t)结构体,并返回该结构体的指针。进行必要的初始化和管理操作,以便后续使用。
IPC 请求结构体用于封装 IPC 请求对象的数据,并提供统一的格式以便在 IPC FIFO 中进行传递。
把 IPC 请求对象推送到 IPC FIFO 中的时候,会把 IPC 请求对象的数据填充到 IPC 请求结构体中的相应字段中。这样可以将请求数据封装成一个统一的格式。
(7)void IpcFifo_FreeRequest(const IpcRequest_t *pIpcRequest) 函数
用于释放一个已使用的 IPC 请求结构体+,并将其标记为未使用状态,以便在后续的 IPC 通信中可以重新分配给其他 IPC 请求。
(8)IpcRequest_t* IpcFifo_Extract(IpcFifoRouteId_t qid) 函数
用于从指定的 IPC FIFO 队列中提取一个 IpcRequest_t 类型的数据对象,并进行相应的检查和处理,确保提取的数据对象符合要求并更新 FIFO 的读指针。
(9)void IpcFifo_HandleRequest(IpcRequest_t* pIpcRequest) 函数
根据给定的 IpcRequest_t 类型的数据对象的类型和属性,将其分配到相应的处理程序或回调函数进行处理。
(10)void IpcFifo_InsertBroadcast(uint32_t broadcastCount, ...) 函数
用于将多个 IpcRequest_t 类型的数据对象插入到 IPC FIFO 队列中进行广播。通过遍历每个要广播的数据对象,并将其插入到队列中,实现向多个目标路由ID广播数据的功能。在插入的时候,会调用 Ipc_Fifo() 函数将获取到的数据对象插入到 IPC FIFO 队列中,进行广播。
(11)void IpcFifo_RequestComplete(IpcRequest_t *pIpcRequest) 函数
根据给定的 IpcRequest_t 对象的类型和相关信息,进行相应的处理操作。
(12)void IpcFifo_RegisterQueue(IpcFifoRouteId_t qid, CpuId_t destCpuId) 函数
用于注册 IPC FIFO 队列,并将相关的信息存储在 IPC 路由映射表中的相应条目中。它包括注册 IPC 端口、线程属性,更新源 CPU 和目标 CPU 的标识,并记录相关信息的调试日志。
(13)void IpcFifo_RegisterHandler(IpcFifoRouteId_t qid, QueueHandlerFptr_t qhandler) 函数
通过调用 IpcFifo_RegisterHandler 函数,可以将特定的处理程序注册到 IPC FIFO 路由中。注册处理程序后,当 IPC FIFO 接收到特定路由的数据时,将调用相应的处理程序来处理这些数据。
(14)void IpcFifo_OneTimeInit(void) 函数
用于进行一次性的 IPC FIFO 初始化操作。这包括分配临时线程标识符、初始化循环缓冲区以及记录调试信息等操作。
一次性初始化操作,是指在程序执行过程中只需要执行一次的初始化步骤,通常在程序启动或特定阶段执行,以确保相关的数据结构、变量或资源处于正确的状态,以便后续的操作能够正常进行。
4.3 IPC 路由映射表
IPC 路由映射关系表的作用是记录进程间通信 (IPC) 的路由信息,以便在进程间进行正确的通信。在一个复杂的系统中,可能存在多个进程或线程需要进行 IPC 通信。IPC 路由映射关系表用于存储和管理这些通信的路由信息,以确保消息或数据能够正确地传递到目标进程。具体而言,IPC 路由映射关系表的作用包括:
(1)路由信息存储
IPC 路由映射关系表中存储了不同进程或线程之间的路由信息,例如源进程/线程、目标进程/线程、通信通道等。这些信息用于确定消息或数据的发送和接收方。
(2)路由查找
当一个进程或线程需要发送消息或数据时,它可以查询 IPC 路由映射关系表,根据目标进程/线程的标识找到相应的通信通道或目标地址。
(3)路由更新
当系统中的进程或线程发生变化时,例如新的进程加入或进程退出,IPC 路由映射关系表可以更新相应的路由信息,以保持与当前系统状态的一致性。
(4)错误处理
IPC 路由映射关系表也可以用于处理错误情况,例如当目标进程/线程不可达时,可以根据表中的路由信息采取相应的错误处理措施。