Аннотация: В этой статье вы познакомитесь с основными понятиями стека задач и контекста задачи легкого ядра Hongmeng, а также проанализируете код для инициализации стека задач.
Эта статья опубликована в сообществе HUAWEI CLOUD.«Hongmeng Light Kernel M Core, серия анализа исходного кода, семь задач и планирование задач (1) стек задач», оригинальный автор: zhushy.
Мы начинаем эту статью с анализа задачи и модуля планирования задач. Во-первых, мы вводим базовую концепцию стека задач. Стек задач представляет собой нисходящий стек, растущий от старшего адреса к младшему, а указатель стека указывает на расположение элемента, который нужно поместить в стек. Инициализированное содержимое неиспользуемого пространства стека после инициализации представляет собой значение 0xCACACACA, представленное макросом OS_TASK_STACK_INIT, а вершина стека инициализируется значением 0xCCCCCCCC, представленным макросом OS_TASK_MAGIC_WORD. Схематическая диаграмма стека задач выглядит следующим образом, где нижний указатель — это самый большой адрес памяти стека, верхний указатель — это самый маленький адрес памяти стека, а указатель стека растет от нижней части стека к вершине. стека.
Контекст задачи (Task Context) — еще одно важное понятие модуля планирования задач и задач, которое относится к среде, в которой выполняются задачи, например счетчикам программ, указателям стека, общим регистрам и т. д. В многозадачном планировании переключение контекста задачи (переключение контекста задачи) относится к основному содержимому и является основой для выполнения нескольких задач на одном ядре ЦП. Во время планирования задачи информация регистра, используемая задачей, выходящей из состояния выполнения, сохраняется в стеке задач, а контекстная информация также считывается из стека задачи, входящей в состояние выполнения, для восстановления информации регистра.
Далее мы анализируем исходный код стека задач и инициализацию стека задач.Если задействована плата разработки, возьмем в качестве примера цели проекта платы разработки\cortex-m7_nucleo_f767zi_gcc\ для анализа исходного кода. Во-первых, посмотрите на структуру контекста задачи.
1. Определение структуры контекста TaskContext
В файле kernel\arch\arm\cortex-m7\gcc\los_arch_context.h структура определяемого контекста следующая, в основном это регистры с плавающей запятой и регистры общего назначения.
typedef struct TagTskContext {
#if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
(defined(__FPU_USED) && (__FPU_USED == 1U)))
UINT32 S16;
UINT32 S17;
UINT32 S18;
UINT32 S19;
UINT32 S20;
UINT32 S21;
UINT32 S22;
UINT32 S23;
UINT32 S24;
UINT32 S25;
UINT32 S26;
UINT32 S27;
UINT32 S28;
UINT32 S29;
UINT32 S30;
UINT32 S31;
#endif
UINT32 uwR4;
UINT32 uwR5;
UINT32 uwR6;
UINT32 uwR7;
UINT32 uwR8;
UINT32 uwR9;
UINT32 uwR10;
UINT32 uwR11;
UINT32 uwPriMask;
UINT32 uwR0;
UINT32 uwR1;
UINT32 uwR2;
UINT32 uwR3;
UINT32 uwR12;
UINT32 uwLR;
UINT32 uwPC;
UINT32 uwxPSR;
#if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
(defined(__FPU_USED) && (__FPU_USED == 1U)))
UINT32 S0;
UINT32 S1;
UINT32 S2;
UINT32 S3;
UINT32 S4;
UINT32 S5;
UINT32 S6;
UINT32 S7;
UINT32 S8;
UINT32 S9;
UINT32 S10;
UINT32 S11;
UINT32 S12;
UINT32 S13;
UINT32 S14;
UINT32 S15;
UINT32 FPSCR;
UINT32 NO_NAME;
#endif
} TaskContext;
2. Функции, связанные со стеком задач
2.1 Функция инициализации стека задач
Функция инициализации стека задач VOID*HalTskStackInit(t() определена в файле kernel\arch\arm\cortex-m7\gcc\los_context.c. Эта функция вызывается функцией UINT32OsNewTaskInit() в файле kernel\src\ los_task.c Завершите инициализацию задачи, а затем вызовите ее в функции создания задачи UINT32 LOS_TaskCreateOnly(), чтобы завершить инициализацию стека задач вновь созданной задачи.
Эта функция использует 3 параметра: один — номер задачи UINT32 ID задачи, другой — размер инициализированного стека UINT32stackSize, а третий параметр — указатель вершины стека VOID *topStack. Код в (1) инициализирует содержимое стека OS_TASK_STACK_INIT, а (2) инициализирует вершину стека OS_TASK_MAGIC_WORD.
Код в (3) получает адрес указателя контекста задачи TaskContext *context. Для вновь созданных задач, начиная с нижней части стека, пространство стека sizeof(TaskContext) хранит данные контекста. ⑷ Если вычисление чисел с плавающей запятой поддерживается, необходимо инициализировать регистры, связанные с числами с плавающей запятой. ⑸Инициализировать общие регистры, среди которых .uwLR инициализируется как (UINT32)(UINTPTR)HalSysExit. .uwPC инициализируется как (UINT32)(UINTPTR)OsTaskEntry, что является расположением первой инструкции, которую выполняет ЦП при первом выполнении задачи. Эти две функции будут проанализированы ниже.
Возвращаемое значение в ⑹ — это указатель (VOID*) taskContext, который является указателем стека после инициализации задачи. Обратите внимание, что он не начинается с нижней части стека. Нижняя часть стека хранит контекст, а стек указатель должен быть вычтен из размера стека, занимаемого контекстом. В стеке из TaskContext *context
Направление, в котором увеличивается указатель, сохраняет первый элемент и второй элемент структуры контекста в свою очередь ... Кроме того, при инициализации стека, за исключением нескольких специальных регистров, хотя начальные значения разных регистров являются бессмысленно, есть некоторые правила инициализации., Например, реестр R2 инициализируется до 0x02020202L, а R12 Regive инициализируется до 0x121212121. Инициализированный контент связан с номером регистра, а остальные аналогичны.
LITE_OS_SEC_TEXT_INIT VOID *HalTskStackInit(UINT32 taskID, UINT32 stackSize, VOID *topStack)
{
TaskContext *context = NULL;
errno_t result;
/* initialize the task stack, write magic num to stack top */
⑴ result = memset_s(topStack, stackSize, (INT32)(OS_TASK_STACK_INIT & 0xFF), stackSize);
if (result != EOK) {
printf("memset_s is failed:%s[%d]\r\n", __FUNCTION__, __LINE__);
}
⑵ *((UINT32 *)(topStack)) = OS_TASK_MAGIC_WORD;
⑶ context = (TaskContext *)(((UINTPTR)topStack + stackSize) - sizeof(TaskContext));
#if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
(defined(__FPU_USED) && (__FPU_USED == 1U)))
⑷ context->S16 = 0xAA000010;
context->S17 = 0xAA000011;
context->S18 = 0xAA000012;
context->S19 = 0xAA000013;
context->S20 = 0xAA000014;
context->S21 = 0xAA000015;
context->S22 = 0xAA000016;
context->S23 = 0xAA000017;
context->S24 = 0xAA000018;
context->S25 = 0xAA000019;
context->S26 = 0xAA00001A;
context->S27 = 0xAA00001B;
context->S28 = 0xAA00001C;
context->S29 = 0xAA00001D;
context->S30 = 0xAA00001E;
context->S31 = 0xAA00001F;
context->S0 = 0xAA000000;
context->S1 = 0xAA000001;
context->S2 = 0xAA000002;
context->S3 = 0xAA000003;
context->S4 = 0xAA000004;
context->S5 = 0xAA000005;
context->S6 = 0xAA000006;
context->S7 = 0xAA000007;
context->S8 = 0xAA000008;
context->S9 = 0xAA000009;
context->S10 = 0xAA00000A;
context->S11 = 0xAA00000B;
context->S12 = 0xAA00000C;
context->S13 = 0xAA00000D;
context->S14 = 0xAA00000E;
context->S15 = 0xAA00000F;
context->FPSCR = 0x00000000;
context->NO_NAME = 0xAA000011;
#endif
⑸ context->uwR4 = 0x04040404L;
context->uwR5 = 0x05050505L;
context->uwR6 = 0x06060606L;
context->uwR7 = 0x07070707L;
context->uwR8 = 0x08080808L;
context->uwR9 = 0x09090909L;
context->uwR10 = 0x10101010L;
context->uwR11 = 0x11111111L;
context->uwPriMask = 0;
context->uwR0 = taskID;
context->uwR1 = 0x01010101L;
context->uwR2 = 0x02020202L;
context->uwR3 = 0x03030303L;
context->uwR12 = 0x12121212L;
context->uwLR = (UINT32)(UINTPTR)HalSysExit;
context->uwPC = (UINT32)(UINTPTR)OsTaskEntry;
context->uwxPSR = 0x01000000L;
⑹ return (VOID *)context;
}
2.2 Получите функцию ватерлинии стека задач
Поскольку стек задач помещается в стек и извлекается из него, текущий размер стека не обязательно является максимальным значением. Эта функция определена в файле kernel\src\los_task.c, для нее требуется 1 параметр, а именно номер задачи UINT32 taskID, а возвращаемое значение UINT32peakUsed представляет собой полученное значение водяного знака, то есть максимальное значение, используемое стеком задачи.
Рассмотрим код подробнее.Код в (1) указывает на то, что если вершина стека равна заданному магическому слову, это означает, что стек не уничтожается при переполнении.Часть стека, которая заполняется макросом OS_TASK_STACK_INIT с вершины стека — это неиспользуемое пространство стека. Используйте временный указатель стека stackPtr, чтобы увеличить переменную указателя до нижней части стека, чтобы определить, использовался ли стек.Цикл while заканчивается, и указатель стека stackPtr указывает на наибольший неиспользуемый адрес стека. (2) Код получает наибольший размер используемого пространства стека, то есть требуемую ватерлинию. (3) Если вершина стека переполнится, будет возвращено недопустимое значение OS_NULL_INT.
Эта функция вызывается функцией LOS_TaskInfoGet(UINT32taskId, TSK_INFO_S *taskInfo) в kernel\base\los_task.c для получения информации о задаче. Информация о приходе или стеке также используется в модулях оболочки.
UINT32 OsStackWaterLineGet(const UINTPTR *stackBottom, const UINTPTR *stackTop, UINT32 *peakUsed)
{
UINT32 size;
const UINTPTR *tmp = NULL;
⑴ if (*stackTop == OS_STACK_MAGIC_WORD) {
tmp = stackTop + 1;
while ((tmp < stackBottom) && (*tmp == OS_STACK_INIT)) {
tmp++;
}
⑵ size = (UINT32)((UINTPTR)stackBottom - (UINTPTR)tmp);
*peakUsed = (size == 0) ? size : (size + sizeof(CHAR *));
return LOS_OK;
} else {
*peakUsed = OS_INVALID_WATERLINE;
return LOS_NOK;
}
}
UINT32 OsGetTaskWaterLine(UINT32 taskID)
{
UINT32 *stackPtr = NULL;
UINT32 peakUsed;
⑴ if (*(UINT32 *)(UINTPTR)OS_TCB_FROM_TID(taskID)->topOfStack == OS_TASK_MAGIC_WORD) {
stackPtr = (UINT32 *)(UINTPTR)(OS_TCB_FROM_TID(taskID)->topOfStack + OS_TASK_STACK_TOP_OFFSET);
while ((stackPtr < (UINT32 *)(OS_TCB_FROM_TID(taskID)->stackPointer)) && (*stackPtr == OS_TASK_STACK_INIT)) {
stackPtr += 1;
}
⑵ peakUsed = OS_TCB_FROM_TID(taskID)->stackSize -
((UINT32)(UINTPTR)stackPtr - OS_TCB_FROM_TID(taskID)->topOfStack);
} else {
⑶ PRINT_ERR("CURRENT task %s stack overflow!\n", OS_TCB_FROM_TID(taskID)->taskName);
peakUsed = OS_NULL_INT;
}
return peakUsed;
}
3. Функция входа и выхода задачи
3.1 Функция выхода из задачи
При инициализации контекста в регистре связи устанавливается функция (UINT32)(UINTPTR)HalSysExit, которая определена в файле kernel\src\los_task.c. Вызовите LOS_IntLock() в коде функции, чтобы отключить прерывание, а затем войдите в бесконечный цикл. При обычном планировании задачи эта функция теоретически выполняться не будет. Когда система ненормальна, эта функция также вызывается при активном вызове LOS_Panic()c для запуска исключения.
LITE_OS_SEC_TEXT_MINOR VOID HalSysExit(VOID)
{
LOS_IntLock();
while (1) {
}
}
3.2. Функция ввода задачи
При инициализации контекста в регистр ПК устанавливается функция VOIDOsTaskEntry(UINT32 taskId), которая определена в файле kernel\base\los_task.c, давайте проанализируем исходный код, (1) код получает taskCB, а затем выполняет (2) вызывает функцию ввода задачи. После выполнения задачи выполните (3), чтобы удалить задачу. Обычно функция выполнения записи задачи представляет собой цикл while.Когда задача не выполняется, она будет запланирована для других задач или простаивающих задач и не будет выполняться до этапа удаления задачи.
LITE_OS_SEC_TEXT_INIT VOID OsTaskEntry(UINT32 taskID)
{
UINT32 retVal;
⑴ LosTaskCB *taskCB = OS_TCB_FROM_TID(taskID);
⑵ (VOID)taskCB->taskEntry(taskCB->arg);
⑶ retVal = LOS_TaskDelete(taskCB->taskID);
if (retVal != LOS_OK) {
PRINT_ERR("Delete Task[TID: %d] Failed!\n", taskCB->taskID);
}
}
резюме
В этой статье вы познакомитесь с основными понятиями стека задач и контекста задачи легкого ядра Hongmeng, а также проанализируете код для инициализации стека задач. В будущем будет выпущено больше статей для обмена. Пожалуйста, с нетерпением ожидайте их. Вы также можете поделиться своим опытом в изучении и использовании ядра Harmony Light. Если у вас есть какие-либо вопросы или предложения, вы можете оставить нам сообщение:git ee.com/open гармония….为了更容易找到鸿蒙轻内核代码仓,建议访问git ee.com/open гармония…, подпишитесь на Watch, как Star и Fork на свой аккаунт, спасибо.
Нажмите «Подписаться», чтобы впервые узнать о новых технологиях HUAWEI CLOUD~