【FreeRTOS】FreeRTOS 源码学习笔记 (5) 任务调度器 + vTaskStartScheduler、xPortPendSVHandler、xPortSysTickHandler

2023-05-16

1. 引言

FreeRTOS的任务调度是个大头,也是一个操作系统的核心。
其实个人理解,FreeRTOS调度规则很好理解,原则就是“优先级高抢占”,因为FreeRTOS是一个抢占式实时内核,一定会保证就绪态的高优先级任务可以先运行。
所有的调度都是为了实现这个目的来做的。
一些个人思考可以看4.1节。

2. 原理分析

2.1 任务切换是怎么进行的

任务切换是一个明确的操作,就是通过pendSV中断,把当前任务的现场全部保存在自己任务的栈帧里,然后,把SP和PC等切换到新的任务上去。

上下文切换是在pendSV中做的。
如何进入pendSV中断?
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
在这里插入图片描述
或者是调用 portYIELD
在这里插入图片描述
可见,系统是通过直接配置portNVIC_INT_CTRL_REG 寄存器或者调用portYIELD接口,进而进而PendSV中断来进行上下文切换的。
那PendSV是如何进行切换的?可以看2.2.2节的源码分析。

2.2 什么情况会任务切换

个人理解,
只有当一个更高优先级的任务进入到就绪列表时,需要进行抢占时,才会进行任务切换。
或者是同优先级的任务交替时间片运行。
FreeRTOS的任务的状态迁移如下图:
在这里插入图片描述

那在不考虑同优先级的情况,什么时候会产生高优先级任务进入ready态呢

  • 创建任务
  • 任务从其他状态切换过来
  • 其他?我暂时没想到
    这些情况下,FreeRTOS就会调用portTIELD或者直接给portNVIC_INT_CTRL_REG 置位来做任务切换了。
  • 在这里插入图片描述

2. 源码分析

2.1 任务调度器开启

freeRTOS在创建任务后,需要手动开启任务调度器,即 vTaskStartScheduler 函数。
代码如下:


void vTaskStartScheduler( void )
{
BaseType_t xReturn;

	/* Add the idle task at the lowest priority. */
	#if( configSUPPORT_STATIC_ALLOCATION == 1 )
	{
		StaticTask_t *pxIdleTaskTCBBuffer = NULL;
		StackType_t *pxIdleTaskStackBuffer = NULL;
		uint32_t ulIdleTaskStackSize;

		/* The Idle task is created using user provided RAM - obtain the
		address of the RAM then create the idle task. */
		vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
		xIdleTaskHandle = xTaskCreateStatic(	prvIdleTask,
												configIDLE_TASK_NAME,
												ulIdleTaskStackSize,
												( void * ) NULL, /*lint !e961.  The cast is not redundant for all compilers. */
												portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
												pxIdleTaskStackBuffer,
												pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */

		if( xIdleTaskHandle != NULL )
		{
			xReturn = pdPASS;
		}
		else
		{
			xReturn = pdFAIL;
		}
	}
	#else
	{
		/* The Idle task is being created using dynamically allocated RAM. */
		xReturn = xTaskCreate(	prvIdleTask,
								configIDLE_TASK_NAME,
								configMINIMAL_STACK_SIZE,
								( void * ) NULL,
								portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
								&xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
	}
	#endif /* configSUPPORT_STATIC_ALLOCATION */

	#if ( configUSE_TIMERS == 1 )
	{
		if( xReturn == pdPASS )
		{
			xReturn = xTimerCreateTimerTask();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif /* configUSE_TIMERS */

	if( xReturn == pdPASS )
	{
		/* freertos_tasks_c_additions_init() should only be called if the user
		definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is
		the only macro called by the function. */
		#ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
		{
			freertos_tasks_c_additions_init();
		}
		#endif

		/* Interrupts are turned off here, to ensure a tick does not occur
		before or during the call to xPortStartScheduler().  The stacks of
		the created tasks contain a status word with interrupts switched on
		so interrupts will automatically get re-enabled when the first task
		starts to run. */
		portDISABLE_INTERRUPTS();

		#if ( configUSE_NEWLIB_REENTRANT == 1 )
		{
			/* Switch Newlib's _impure_ptr variable to point to the _reent
			structure specific to the task that will run first.
			See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
			for additional information. */
			_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
		}
		#endif /* configUSE_NEWLIB_REENTRANT */

		xNextTaskUnblockTime = portMAX_DELAY;
		xSchedulerRunning = pdTRUE;
		xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;

		/* If configGENERATE_RUN_TIME_STATS is defined then the following
		macro must be defined to configure the timer/counter used to generate
		the run time counter time base.   NOTE:  If configGENERATE_RUN_TIME_STATS
		is set to 0 and the following line fails to build then ensure you do not
		have portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() defined in your
		FreeRTOSConfig.h file. */
		portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

		traceTASK_SWITCHED_IN();

		/* Setting up the timer tick is hardware specific and thus in the
		portable interface. */
		if( xPortStartScheduler() != pdFALSE )
		{
			/* Should not reach here as if the scheduler is running the
			function will not return. */
		}
		else
		{
			/* Should only reach here if a task calls xTaskEndScheduler(). */
		}
	}
	else
	{
		/* This line will only be reached if the kernel could not be started,
		because there was not enough FreeRTOS heap to create the idle task
		or the timer task. */
		configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
	}

	/* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
	meaning xIdleTaskHandle is not used anywhere else. */
	( void ) xIdleTaskHandle;
}
/*-----------------------------------------------------------*/

这段代码也很重要,我们来分析一下流程

  1. 首先进行一些全局变量的初始化,
  2. 之后对中断进行一些配置,
  3. 最后进入汇编,配置一些寄存器。
    在这里插入图片描述

2.2 三个中断

我们在代码会遇到SVC、pendSV、滴答这三个中断,他们的实现都是在port.c中。

2.2.1 SVC

vPortSVCHandler,只在第一次启动任务调度的时候用。

__asm void vPortSVCHandler( void )
{
	PRESERVE8

	ldr	r3, =pxCurrentTCB	/* Restore the context. */
	ldr r1, [r3]			/* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
	ldr r0, [r1]			/* The first item in pxCurrentTCB is the task top of stack. */
	ldmia r0!, {r4-r11}		/* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
	msr psp, r0				/* Restore the task stack pointer. */
	isb
	mov r0, #0
	msr	basepri, r0
	orr r14, #0xd
	bx r14
}

这里要注意一下LDR的使用。LDR有指令和伪指令两种用法,一定要区分开。

区别一
ldr r3, = 变量
ldr r3, = 标号
ldr r3, = 立即数

区别二
ldr r3, = 立即数
ldr r3, 立即数 , 就是把立即数这个地址中的值存放到r0中

步骤:

  1. pxCurrentTCB 是一个指针变量,指向当前运行任务的TCB。
    首先将pxCurrentTCB 的 地址赋给r3,即 r3 = & pxCurrentTCB ;
  2. 然后把pxCurrentTCB 的值赋值给r1,即r1 = pxCurrentTCB 。
  3. 最后pxCurrentTCB所指的TCB的第一个成员变量(任务堆栈地址)赋给r0,即r1 = [r3] = *pxCurrentTCB= pxCurrentTCB->pxTopOfStack
  4. 把人为入栈的寄存器r4 - r11手动出栈,剩下的 xPSR、PC、LR、R12、R3 - R0会自动出栈。
  5. 把出栈完成之后的栈顶地址赋给psp,供任务使用。

这里要注意的就是第一句,是把pxCurrentTCB 的地址赋值给r3,而不是把他的值赋值r3。
要分清 LDR的汇编指令和伪指令的用法
引用在论坛里看到的一段话:

C代码中的pxCurrentTCB是变量。
而汇编中的pxCurrentTCB是一个label,可认为就是一个地址,它对应C中的pxCurrentTCB
ldr r3,=pxCurrentTCB # 读取<pxCurrentTCB变量的地址>到 r3
ldr r1,[r3] # 读取<pxCurrentTCB变量>到r1,即获取当前任务TCB地址
ldr r0,[r1] # 从<当前任务TCB>读取一个WORD大小到 r0,TCB首元素即任务栈顶

ldr rn, =label 是ldr伪指令,可以理解将label作为立即数加载到rn
ldr rn, label 不是伪指令,它通过间接寻址,把label作为地址,从这个地址中加载值到rn
你可能会想,按这个说法
既然汇编中的pxCurrentTCB是C变量地址,为啥不直接
ldr r1,pxCurrentTCB
正好是间接寻址,拿到TCB指针。
这是因为ldr rn, label 这个指令是基于PC寻址,是有范围要求的,
对于C中的变量,由于是在链接时才能确定变量地址的,所以有可能会超了范围。
所以使用ldr rn,[rm] 。这个是基于rm的,不超过rm大小都可以。

2.2.2 pendSV

pendSV 中断为 xPortPendSVHandler。
用于任务切换。

__asm void xPortPendSVHandler( void )
{
	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;

	PRESERVE8

	mrs r0, psp
	isb

	ldr	r3, =pxCurrentTCB		/* Get the location of the current TCB. */
	ldr	r2, [r3]

	stmdb r0!, {r4-r11}			/* Save the remaining registers. */
	str r0, [r2]				/* Save the new top of stack into the first member of the TCB. */

	stmdb sp!, {r3, r14}
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri, r0
	dsb
	isb
	bl vTaskSwitchContext
	mov r0, #0
	msr basepri, r0
	ldmia sp!, {r3, r14}

	ldr r1, [r3]
	ldr r0, [r1]				/* The first item in pxCurrentTCB is the task top of stack. */
	ldmia r0!, {r4-r11}			/* Pop the registers and the critical nesting count. */
	msr psp, r0
	isb
	bx r14
	nop
}

步骤如下:

  1. 系统进入pendSV异常,硬件自动压栈了PSR、PC、LR、R12、R3~R0的寄存器(使用PSP指针,压入任务堆栈)
  2. 然后 mrs r0, psp,把PSP的指针给到r0,一会手动压栈用
  3. 接着ldr r3, =pxCurrentTCB和 ldr r2, [r3],把变量pxCurrentTCB的地址赋值给r3,把pxCurrentTCB的值赋值给r2,即r2为当前任务TCB的地址。
  4. 接着从r0地址手动压栈r4 - r11。
  5. 接着临时入栈r3 和 r14,为了调用vTaskSwitchContext准备
  6. 通过调用 vTaskSwitchContext ,找到优先级最高的task,把pxCurrentTCB指过去。
  7. 出栈r3 和 r14
  8. r3存的是变量pxCurrentTCB的地址,通过ldr r1, [r3] 和 ldr r0, [r1],可以获得r1为指向新的TCB的pxCurrentTCB指针,r0为新的TCB第一个成员(栈顶指针)
  9. 然后从r0的地址手动出栈r4-r11
  10. 把更新完的r0的值给到psp,用于一会自动出栈 PSR、PC、LR、R12、R3~R0
  11. bx r14 /* 异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回,然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。*/

这样可以看出一个任务task的堆栈分配,抄朱工一张图来理解(朱工链接可以见文末参考链接)
在这里插入图片描述

2.2.2 SysTick

xPortSysTickHandler是系统滴答定时器的中断。


void xPortSysTickHandler( void )
{
	/* The SysTick runs at the lowest interrupt priority, so when this interrupt
	executes all interrupts must be unmasked.  There is therefore no need to
	save and then restore the interrupt mask value as its value is already
	known - therefore the slightly faster vPortRaiseBASEPRI() function is used
	in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
	vPortRaiseBASEPRI();
	{
		/* Increment the RTOS tick. */
		if( xTaskIncrementTick() != pdFALSE )
		{
			/* A context switch is required.  Context switching is performed in
			the PendSV interrupt.  Pend the PendSV interrupt. */
			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
		}
	}
	vPortClearBASEPRIFromISR();
}

可以看出这个中断函数主要就是做了xTaskIncrementTick,如果xTaskIncrementTick导致有优先级更高的任务进入就绪,就portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT调用pendSV来进行任务切换。
这个主要要分析的是xTaskIncrementTick。

3.3 N个状态链表

/* Lists for ready and blocked tasks. --------------------
xDelayedTaskList1 and xDelayedTaskList2 could be move to function scople but
doing so breaks some kernel aware debuggers and debuggers that rely on removing
the static qualifier. */
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*< Prioritised ready tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList1;						/*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2;						/*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;				/*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;		/*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList;						/*< Tasks that have been readied while the scheduler was suspended.  They will be moved to the ready list when the scheduler is resumed. */
PRIVILEGED_DATA static List_t xSuspendedTaskList;					/*< Tasks that are currently suspended. */

可以看出,这里系统一共维护了pxReadyTasksLists[configMAX_PRIORITIES ]、pxDelayedTaskList 、pxOverflowDelayedTaskList 、xPendingReadyList 、xSuspendedTaskList这么几个链表。

  1. Ready态对应 pxReadyTasksLists[ configMAX_PRIORITIES ]
    每个优先级都有一个pxReadyTasksLists来管理当前优先级的所有就绪态的任务TCB,在pendSV中断中,会调用taskSELECT_HIGHEST_PRIORITY_TASK接口来找最高优先级的就绪态的下一个任务。

  2. block态对应 pxDelayedTaskList 或 pxOverflowDelayedTaskList , 这两个指针指的就是 xDelayedTaskList1 和 xDelayedTaskList2。
    在滴答定时器中断中,首先会根据定时器是否溢出做一次指针的切换。
    然后会把pxDelayedTaskList中的任务的xItemValue值做一次更新。
    如果任务到期,把任务从pxDelayedTaskList链表中删除,加入到ready链表,如果这个任务的优先级更高,就返回pdTRUE,函数出去之后去置位pendSV中断寄存器。

  3. xPendingReadyList 要注意一下,这个是在调度器被暂停时,新的任务进入了ready状态,因此先保存起来,在xTaskResumeAll时再加入到相应的ready list中。
    引申几个问题:

  • 为什么不直接加入到ready态,而建立了一个特殊的中间状态?
    网上找了一段:

那么为什么要来一个中间步骤而不直接放进就绪任务列表呢?
这是因为任务从挂起到恢复可能出现优先级大于当前运行任务,高优先级任务要抢占低优先级任务,由于之前调度器被挂起,所以无法执行抢占操作。等调度器恢复后,再将xPendingReadyList里的任务一一取出来,判定是否有抢占操作发生或任务延时到期。

可见,在xTaskResumeAll中会把所有的xPendingReadyList加入到就绪列表。

  • 什么时候会加入xPendingReadyList?
    当任务从事件列表中删除的时候,即xTaskRemoveFromEventList中,如果调度器没挂起,就会加到Ready列表,如果挂起了,就会加到xPendingReadyList中。
  • 挂起调度器是什么意思
    代码中就是uxSchedulerSuspended的值改变了。执行vTaskSuspendAll的时候会++uxSchedulerSuspended,xTaskResumeAll的时候会 --uxSchedulerSuspended;当uxSchedulerSuspended不为0的时候就便是被挂起了,会挂起会发生什么?
    在pendSV中断中,会调用vTaskSwitchContext,如果被挂起,vTaskSwitchContext是不会调用taskSELECT_HIGHEST_PRIORITY_TASK去就绪列表里找下一个任务的,

综上所述,xPendingReadyList的作用就是,保证操作系统的实时性,保证高优先级任务抢占能正常进行。
因为如果高优先级task从其他状态退出要进入就绪态时,应当立刻进行任务切换,同时状态进入就绪列表。但是,因为调度器挂起,无法立刻切换任务,如果直接把任务塞入就绪列表,当调度器恢复时,就无法实现“实时”抢占,要等下一个时间片查找高优先级任务时才能切换。因为只有状态变化的这个事件,会导致进行任务切换,如果当时没有成功切换,而且状态又已经切换成了就绪,就没有一个新的状态变化的事件再来触发一次任务切换了。
所以这里设置一个新的状态列表xPendingReadyList,当调度器恢复时,这些xPendingReadyList里的任务还要进行一次状态切换,利用这次状态变换的事件来触发任务切换。

  1. suspend态对应 xSuspendedTaskList,没啥说的,就是任务挂起的时候用。

4. 一些以前的错误思考

4.1 任务切换的原则

在认真阅读源码之前,其实之前的心里一直感觉是,任务调度是基于时间片的调度,每个时间周期(eg:1ms),作一次任务切换,切PC、栈之类的。
所以需要一个稳定的滴答定时器来做定时切换。

后来发现不是这样的,其实任务切换是基于状态来做的。
因为是抢占式的,只有更高优先级的任务进入ready状态才需要切状态,
状态的切换才会导致一次任务切换(pendSV)
滴答定时器只是做一个简单的工作,就是给计时数加1,(函数名字写的也很明白了,就是xTaskIncrementTick),如果因为加一,导致有些任务的状态发生变化,比如堵塞到期等,而导致改变了状态链表,产生了一个触发,这样才会进行pendSV的调度器。
可见滴答定时器也没有之前想象的那么重要,他也不过是产生了一个触发。
和其他事件的触发是一样。
比如入队出队操作,导致系统任务的状态发生变化,有优先级更高的任务放了出来,进入了ready,也是触发中断,进pendSV做上下文切换。

所以滴答和pendSV完全是两个东西,做的是两件事情。
所以放在两个中断里。

我见过有朋友问,为什么任务切换是pendSV,按理说,任何一个中断,设置到这个优先级上,都可以来做这个事,为什么是pendSV有这个“殊荣",如此独特。
是因为pendSV设计出来就是用来做切换的,pendSV可以挂起。
这部分可以见结尾参考链接"Cortex-M PendSV 应用"。

4.2 为什么滴答,pendSV的优先级要最低

之前一直以为滴答中断的优先级要高,这样能保证实时性,计时不会乱。
后来发现压根不是这回事。
在 xPortStartScheduler 中,
在这里插入图片描述

滴答和pendSV的中断优先级最低是为了保证其他中断都能正常运行, 保证系统会话切换不会阻塞系统其他中断的响应。计时是硬件计时器做的,及时进入中断不及时,也不会乱掉。

即我们不能允许下图这样的情况出现,会导致中断处理的时间不可控。
在这里插入图片描述

4.3 为什么正常的函数调用不需要手动压栈r4-r11

网上找的图,原文见参考链接。
在这里插入图片描述
中断调用,系统自动帮压栈R0-R3、R12、LR、PSR。中断程序也是优先用这些,如果不够,需要别的寄存器,编译器帮做压栈,所以不用手动写。

5. 参考链接

  • FreeRTOS高级篇3—FreeRTOS调度器启动过程分析
  • Cortex-M PendSV 应用
  • 关于FreeRTOS任务栈的那点事儿
  • 嵌入式操作系统学习(3)FreeRTOS的任务调度机制
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【FreeRTOS】FreeRTOS 源码学习笔记 (5) 任务调度器 + vTaskStartScheduler、xPortPendSVHandler、xPortSysTickHandler 的相关文章

随机推荐

  • 双轴驱动步进电机云台二自由度单片机控制程序PTU57

    高精度云台由两个电机驱动 xff0c 可控制方位角和高度角 xff0c 具有两自由度的机械电子设备 可用于机器视觉 摄影摄像 监控安防 天文观测 雷达扫描 DIY雕刻机 转盘转台 智能机械手臂 双轴跟踪太阳能定日镜等各类应用高精度云台的场合
  • php使用curl获取需要认证的https请求

    lt php php使用curl获取需要认证的https请求的方法 url 61 34 XXXXXX 34 arr header 61 34 Accept application json 34 arr header 61 34 Autho
  • i-vector本质剖析

    1 i vector的由来 基于因子分析理论 xff0c 句子h的超向量可以描述成 其中为ubm模型的均值超向量 xff0c 即为i vector 2 i vector的计算 2 1 T矩阵的估计 为句子h的观察特征 xff0c 可以对应于
  • C++程序设计基础实验-实验七 多态性

    实验七多态性 一 实验目的 掌握运算符重载的方法 xff1b 掌握使用虚函数的继承实现动态多态性 掌握纯虚函数及抽象类的使用 二 实验内容 设计复数类Complex xff08 请参照教材例题8 1的设计 xff09 xff0c 实现运算符
  • g2o_a_general_framework_for_graph_optimaization

    g2o A General Framework for Graph Optimization NONLINEAR GRAPH OPTIMIZATION USING LEAST SQUARES 机器人和计算机视觉中的许多问题都可以用下列方程的
  • P5644 [PKUWC2018]猎人杀

    P5644 PKUWC2018 猎人杀 题目大意 一开始有 n n n 个猎人 xff0c 第 i i i 个猎人有仇恨度
  • 【Linux基础系列之】platform虚拟总线

    linux当中大多数的设备都是以paltform虚拟总线挂载上去的 xff0c 这里以kernel drivers net dm9000 c为例子分析一下 xff0c platform设备挂在过程 xff1b xff08 1 xff09 d
  • ARMv8-AArch64简述

    ARMv8是ARM版本升级以来最大的一次改变 xff0c ARMv8的架构继承以往ARMv7与之前处理器技术的基础 xff0c 除了现有的16 32bit的Thumb2指令支持外 xff0c 也向前兼容现有的A32 ARM 32bit 指令
  • ARMv8-AArch64寄存器和指令集

    xff08 一 xff09 简述 AArch拥有31个通用寄存器 xff0c 系统运行在64位状态下的时候名字叫Xn xff0c 运行在32位的时候就叫Wn xff1b AArch32与AArch64寄存器对应关系 xff1a xff08
  • ION框架学习(一)

    第一章介绍 xff1a ION的框架和buffer的分配 xff1b 第二章介绍 xff1a 如何使用ION buffer xff1b ION是google在Android4 0 为了解决内存碎片管理而引入的通用内存管理器 用来支持不同的内
  • 高通Camera 驱动调试要点(一)

    本文主要介绍QCOM camera调试的重要参数 xff1b xff08 1 xff09 Lane assign 和lane mask 现在摄像头基本都是mipi接口类型 xff0c 因为前后摄都对应到平台这边不同的mipi接口 xff0c
  • 高通Camera 驱动调试要点(二)

    这篇文章主要介绍数据流这边Camera ISP这块所遇到的问题 xff0c 主要介绍bus overflow和sof freeze xff1b xff08 一 xff09 bus overflow 摄像头传感器时钟通道 即 MIPI DDR
  • c/c++代码性能效率

    一 尽量减少值传递 xff0c 多用引用来传递参数 boolCompare xff08 span class hljs keyword string span s1 span class hljs keyword string span s
  • 6.Docker定制镜像

    当我们从docker镜像仓库中下载的镜像不能满足我们的需求时 xff0c 我们可以通过以下两种方式对镜像进行更改 1 从已经创建的容器中更新镜像 xff0c 并且提交这个镜像 2 使用 Dockerfile 指令来创建一个新的镜像 Dock
  • 全球最大成人网站公布年度榜单!原来lsp最爱看的是这种片……

    前几天 xff0c 那个号称全球最大的成人网站P hub xff0c 发布了 2022年度报告 别惊讶 xff0c 这已经是P某发布年度报告的第9个年头了 正所谓 xff0c 知己知彼百战不殆 不发年度报告 xff0c 怎么总结过去 xff
  • ROS学习篇(三)ROS系统的串口数据读取和解析(组合导航系统)

    一 Ubuntu下的串口助手cutecom 下载 xff1a sudo apt get install cutecom 打开 xff1a sudo cutecom 查看电脑链接的串口信息 xff08 名称 xff09 xff1a dmesg
  • VScode 结合clangd 构建linux源代码阅读环境

    1 背景介绍 上一篇文章 xff1a VScode 结合Global构建linux源代码阅读环境 xff0c 介绍了在VS Code工具中通过remote ssh远程登陆到Linux远程服务器 xff0c 使用Global构建linux源代
  • 信号量 PK 自旋锁

    信号量可能允许有多个持有者 xff0c 而自旋锁在任何时候只能允许一个持有者 xff0c 当然也有信号量叫互斥信号量 xff08 只能有一个持有者 xff09 xff0c 允许有多个持有者的信号量叫 计数信号量 信号量适合于保持时间较长的情
  • 【FreeRTOS】FreeRTOS 源码学习笔记 (4) 任务创建xTaskCreate + 常用结构体TCB、xLIST

    1 引言 经过第一节的移植 xff0c 我们已经拿到了一个可以用的工程 经过第二三节的基础知识 xff0c 我们对基本的数据结构 xff0c 列表 队列这些也有了一个了解 接下来就可以单步跟踪了 xff0c 看一下系统是怎么运行的 使用Fr
  • 【FreeRTOS】FreeRTOS 源码学习笔记 (5) 任务调度器 + vTaskStartScheduler、xPortPendSVHandler、xPortSysTickHandler

    1 引言 FreeRTOS的任务调度是个大头 xff0c 也是一个操作系统的核心 其实个人理解 xff0c FreeRTOS调度规则很好理解 xff0c 原则就是 优先级高抢占 xff0c 因为FreeRTOS是一个抢占式实时内核 xff0