FreeRTOS启动第一个任务和任务的切换(Cortex-M4)

2023-05-16

 调度器是FreeRTOS操作系统的核心,主要负责任务切换,即在就绪任务中找出最高优先级的任务,并使之获得CPU运行权。调度器并非自动运行的,需要人为启动它。

      API函数vTaskStartScheduler()用于启动调度器,它会创建一个空闲任务、初始化一些静态变量,最主要的,它会初始化系统节拍定时器并把SVC中断和Pendsv中断设置为最低优先级,在prvStartFirstTask函数里开全局中断及触发SVC异常,接下来进入SVC中断服务函数vPortSVCHandler,这个函数只要是把pxCurrentTCB 这个结构体指向的任务栈信息加载到CPU的寄存器中,{r4-r11,r14}需要手动加载,其他的栈的信息回自动加载到cpu的其他寄存器中,在这里第一个要运行的任务已经准备好了,此时会进入systick中断函数判断是否需要切换任务,如果需要则触发Pendsv异常,接着进入Pendsv中断服务函数,如果只创建了一个任务,在Pendsv服务程序就是执行这个任务,如果创建了多个任务则会Pendsv服务程序里选择在启动第就绪态最高优先级的任务来执行。本文以Cortex-M4架构为例。

先来看启动调度器的API函数vTaskStartScheduler(),源码精简后如下所示:

void vTaskStartScheduler( void )
{
    BaseType_t xReturn;
    StaticTask_t *pxIdleTaskTCBBuffer= NULL;
    StackType_t *pxIdleTaskStackBuffer= NULL;
    uint16_t usIdleTaskStackSize =tskIDLE_STACK_SIZE;
 
    /*如果使用静态内存分配任务堆栈和任务TCB,则需要为空闲任务预先定义好任务内存和任务TCB空间*/
    #if(configSUPPORT_STATIC_ALLOCATION == 1 )
    {
       vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &usIdleTaskStackSize);
    }
    #endif /*configSUPPORT_STATIC_ALLOCATION */
 
    /* 创建空闲任务,使用最低优先级*/
    xReturn =xTaskGenericCreate( prvIdleTask, "IDLE",usIdleTaskStackSize, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT), &xIdleTaskHandle,pxIdleTaskStackBuffer,pxIdleTaskTCBBuffer, NULL );
 
    if( xReturn == pdPASS )
    {
        /* 先关闭中断,确保节拍定时器中断不会在调用xPortStartScheduler()时或之前发生.当第一个任务启动时,会重新启动中断*/
       portDISABLE_INTERRUPTS();
       
        /* 初始化静态变量 */
       xNextTaskUnblockTime = portMAX_DELAY;
       xSchedulerRunning = pdTRUE;
        xTickCount = ( TickType_t ) 0U;
 
        /* 如果宏configGENERATE_RUN_TIME_STATS被定义,表示使用运行时间统计功能,则下面这个宏必须被定义,用于初始化一个基础定时器/计数器.*/
       portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
 
        /* 设置系统节拍定时器,这与硬件特性相关,因此被放在了移植层.*/
        if(xPortStartScheduler() != pdFALSE )
        {
            /* 如果调度器正确运行,则不会执行到这里,函数也不会返回*/
        }
        else
        {
            /* 仅当任务调用API函数xTaskEndScheduler()后,会执行到这里.*/
        }
    }
    else
    {
        /* 执行到这里表示内核没有启动,可能因为堆栈空间不够 */
       configASSERT( xReturn );
    }
 
    /* 预防编译器警告*/
    ( void ) xIdleTaskHandle;
}

      这个API函数首先创建一个空闲任务,空闲任务使用最低优先级(0级),空闲任务的任务句柄存放在静态变量xIdleTaskHandle中,可以调用API函数xTaskGetIdleTaskHandle()获得空闲任务句柄。

      如果任务创建成功,则关闭中断portDISABLE_INTERRUPTS();(调度器启动结束时会再次使能中断的),初始化一些静态变量,然后调用函数xPortStartScheduler()来启动系统节拍定时器并启动第一个任务。

     注意:

             portDISABLE_INTERRUPTS();这个函数在不同的架构中需要修改,主要就是关闭中断,具体要关闭那些中断可以根据项目的具体需求,我这里是关闭了除nmi和复位的其他所有中断。

             因为设置系统节拍定时器涉及到硬件特性,因此函数xPortStartScheduler()由移植层提供,不同的硬件架构,这个函数的代码也不相同。但是大体的思路是一样的。

对于Cortex-M4架构,函数xPortStartScheduler()的实现如下所示:

BaseType_t xPortStartScheduler( void )
{
	/* configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to 0.
	See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html */
	configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );

	/* This port can be used on all revisions of the Cortex-M7 core other than
	the r0p1 parts.  r0p1 parts should use the port from the
	/source/portable/GCC/ARM_CM7/r0p1 directory. */
	configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
	configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );

	#if( configASSERT_DEFINED == 1 )
	{
		volatile uint32_t ulOriginalPriority;
		volatile uint8_t * const pucFirstUserPriorityRegister = ( volatile uint8_t * const ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
		volatile uint8_t ucMaxPriorityValue;

		/* Determine the maximum priority from which ISR safe FreeRTOS API
		functions can be called.  ISR safe functions are those that end in
		"FromISR".  FreeRTOS maintains separate thread and ISR API functions to
		ensure interrupt entry is as fast and simple as possible.

		Save the interrupt priority value that is about to be clobbered. */
		ulOriginalPriority = *pucFirstUserPriorityRegister;

		/* Determine the number of priority bits available.  First write to all
		possible bits. */
		*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;

		/* Read the value back to see how many bits stuck. */
		ucMaxPriorityValue = *pucFirstUserPriorityRegister;

		/* Use the same mask on the maximum system call priority. */
		ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;

		/* Calculate the maximum acceptable priority group value for the number
		of bits read back. */
		ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
		while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
		{
			ulMaxPRIGROUPValue--;
			ucMaxPriorityValue <<= ( uint8_t ) 0x01;
		}

		#ifdef __NVIC_PRIO_BITS
		{
			/* Check the CMSIS configuration that defines the number of
			priority bits matches the number of priority bits actually queried
			from the hardware. */
			configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == __NVIC_PRIO_BITS );
		}
		#endif

		#ifdef configPRIO_BITS
		{
			/* Check the FreeRTOS configuration that defines the number of
			priority bits matches the number of priority bits actually queried
			from the hardware. */
			configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == configPRIO_BITS );
		}
		#endif

		/* Shift the priority group value back to its position within the AIRCR
		register. */
		ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
		ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;

		/* Restore the clobbered interrupt priority register to its original
		value. */
		*pucFirstUserPriorityRegister = ulOriginalPriority;
	}
	#endif /* conifgASSERT_DEFINED */

	/* 将PendSV和SysTick中断设置为最低优先级*/
	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
	portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;

    /* 启动系统节拍定时器,即SysTick定时器,初始化中断周期并使能定时器*/
	vPortSetupTimerInterrupt();

	/* Initialise the critical nesting count ready for the first task. */
	uxCriticalNesting = 0;

	/* Ensure the VFP is enabled - it should be anyway. */
	vPortEnableVFP();

	/* Lazy save always. */
	*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;

	/* Start the first task. */
	prvPortStartFirstTask();

	/* Should never get here as the tasks will now be executing!  Call the task
	exit error function to prevent compiler warnings about a static function
	not being called in the case that the application writer overrides this
	functionality by defining configTASK_RETURN_ADDRESS.  Call
	vTaskSwitchContext() so link time optimisation does not remove the
	symbol. */
	vTaskSwitchContext();
	prvTaskExitError();

	/* Should not get here! */
	return 0;
}

在Cortex-M4架构中,FreeRTOS为了任务启动和任务切换使用了三个异常:SVC、PendSV和SysTick。SVC(系统服务调用)用于任务启动(即第一个任务的准备工作),有些操作系统不允许应用程序直接访问硬件,而是通过提供一些系统服务函数,通过SVC来调用;PendSV(可挂起系统调用)用于完成任务切换,它的最大特性是如果当前有优先级比它高的中断在运行,PendSV会推迟执行,直到高优先级中断执行完毕;SysTick用于产生系统节拍时钟(中断的时间要控制好),提供一个时间片,如果多个任务共享同一个优先级,则每次SysTick中断,下一个任务将获得一个时间片。关于详细的SVC、PendSV异常描述,推荐《Cortex-M3权威指南》一书的“异常”部分。

      这里将PendSV和SysTick异常优先级设置为最低,这样任务切换不会打断某个中断服务程序,中断服务程序也不会被延迟,这样简化了设计,有利于系统稳定。

      接下来调用函数vPortSetupTimerInterrupt()设置SysTick定时器中断周期并使能定时器运行这个函数比较简单,就是设置SysTick硬件的相应寄存器。不同的架构有所不同。

     接下来有一个关键的函数是prvStartFirstTask(),这个函数用来启动第一个任务。我们先看一下源码:我这是在ubuntu上用GCC 编译的,在keil上编译的话编写格式不一样。

static void prvPortStartFirstTask( void )
{
	/* Start the first task.  This also clears the bit that indicates the FPU is
	in use in case the FPU was used before the scheduler was started - which
	would otherwise result in the unnecessary leaving of space in the SVC stack
	for lazy saving of FPU registers. */
	__asm volatile(
                    /* Cortext-M4硬件中,0xE000ED08地址处为VTOR(向量表偏移量)寄存器,存储向量表起始地址*/
					" ldr r0, =0xE000ED08 	\n" /* Use the NVIC offset register to locate the stack. */
					" ldr r0, [r0] 			\n"
					" ldr r0, [r0] 			\n"
					" msr msp, r0			\n" /* Set the msp back to the start of the stack. */

                    /* 将堆栈地址存入主堆栈指针 */
					" mov r0, #0			\n" /* Clear the bit that indicates the FPU is in use, see comment above. */
					" msr control, r0		\n"

                     /* 使能全局中断*/
					" cpsie i				\n" /* Globally enable interrupts. */
					" cpsie f				\n"
					" dsb					\n"
					" isb					\n"

                     /* 调用SVC启动第一个任务 */
					" svc 0					\n" /* System call to start first task. */
					" nop					\n"
				);
}
/*-----------------------------------------------------------*/

首先复位主堆栈指针MSP的值,表示从此以后MSP指针被FreeRTOS接管,需要注意的是,Cortex-M3硬件的中断也使用MSP指针。之后使能中断,使用汇编指令svc 0触发SVC中断,完成启动第一个任务的工作。我们看一下SVC中断服务函数:

void vPortSVCHandler( void )
{
	__asm volatile (
					"	ldr	r3, =pxCurrentTCB		    \n" /*   (1) Restore the context. */
					"	ldr r1, [r3]					\n" /*   (2)Use pxCurrentTCBConst to get the pxCurrentTCB address. */
					"	ldr r0, [r1]					\n" /*   (3)The first item in pxCurrentTCB is the task top of stack. */
					"	ldmia r0!, {r4-r11, r14}		\n" /*   (4) Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
					"	msr psp, r0						\n" /*   (5)Restore the task stack pointer. */
					"	isb								\n" //   (5)
					"	mov r0, #0 						\n" //   (6)
					"	msr	basepri, r0					\n" //   (7)
					"	bx r14							\n" //   (8)
					"									\n"
					"	.align 4						\n" //   (9)

				);
}

(1) pxCurrentTCB是一个全局变量指针,用来指向当前正在运行或者即将运行任务的任务控制块。 加载pxCurrentTCB任务控制块的地址到R3中;注意这个pxCurrentTCB的地址永远不会变,但是在在任务切换的时候会改变里的值,用来指向不同的任务块。
(2) 载pxCurrentTCB到R1
(3) 加载pxCurrentTCB指向的任务控制块到R0中,因为任务控制块中的第一个成员变量就是任务的栈顶指针: pxTopOfStack。
(4) 以r0为基地址(指针先加后操作)将栈中的8个字的数据加载到CPU中的R4-R11中。 

(5) 将操作5后新的栈顶指针R0更新到PSP中(注意:在执行异常的时候,SP以MSP为栈指针,在执行任务时SP以PSP为栈指针);
(6) 执行7之前清流水线,确保操作6指令执行完毕。清除R0;
(7) 设置BASEPR寄存器为0 即打开所有中断;
(8) 两个操作具体解释如下
当r14为0xFFFFFFFX,执行是中断返回指令,
cortext-m3的做法,X的bit0为1表示返回thumb状态,bit1和bit2分别表示返回后sp用msp还是psp、以及返回到特权模式还是用户模式;
异常返回,这个时候栈中的剩下内容将会自动加载到CPU寄存器:xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)同时PSP的值也将更新,即指向任务栈的栈顶 。

0x0D 表示返回后为thumb状态,且指定SP出栈指针使用PSP作为出栈指针,这里的栈指针指向的准备运行的栈,所有在执行BX LR后,在恢复现场时会以当前的PSP为基地址,把栈中的剩下内容将会自动加载到CPU寄存器: xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)中,又因为跳转是BX 无链接,所以跳转后的R15 PC会被从新的PC从堆栈中恢复到R15(PC)寄存器中,所以程序会从该新任务入口函数开始执行。
(9) 4个字节对齐

 

在这里第一个任务的准备工作已经准备完了,接下来就是等待systick中断的到来触发Pendsv异常,在Pendsv里进行任务切换。

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. */
	portDISABLE_INTERRUPTS();//关闭中断。不同架构关闭中断的方式不一样,在移植在RSIC-V的架构中这里个函数是需要改变的
	{
		/* 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; //触发pendsv异常
		}
	}
	portENABLE_INTERRUPTS();//打开中断
}

从上面可以看到xPortSysTickHandler函数主要就是关中断,判断是否有任务需要切换,有就触发Pendsv异常,最后打开i中断。

任务切换

 在FreeROTS任务的切换实际由xPortPendSVHandler函数完成,主要完成上下文切换即保存上文,切换下文两个主要目的操作;
切换前我们需要了解上文需要保存什么,下文切换的又是什么;
上文中保存的主要内容是:
(1).寄存器中的R4-R11数据(其余的进入异常前CPU自动保存);在cortex-m4在还包括R14寄存器
(2).当前任务的栈顶指针;
(3).全局TCB地址;
下文切换需要切换的内容主要是:
(1).最新任务的TCB;
(2).最新任务中栈中的r4-r11数据到CPU R4-R11;在cortex-m4在还包括R14寄存器
(3)新任务栈顶存入PSP,用来出栈根据PSP调用新任务;

主要流程如下图所示:在cortex-m4在还包括R14寄存器

在这里插入图片描述

xPortPendSVHandler函数的代码如下:

void xPortPendSVHandler( void )
{
	/* This is a naked function. */

	__asm volatile
	(
	"	mrs r0, psp							\n"  //(1)
	"	isb									\n"  //(2)
	"										\n"
	"	ldr	r3, pxCurrentTCBConst			\n"  //(3) /* Get the location of the current TCB. */
	"	ldr	r2, [r3]						\n"  //(4)
	"										\n"

    //如果你的Cortex-M4架构支持FPU 就是打开FPU的编译选项
	//"	tst r14, #0x10						\n" /* Is the task using the FPU context?  If so, push high vfp registers. */
	//"	it eq								\n"
	//"	vstmdbeq r0!, {s16-s31}				\n"
	"										\n"
	"	stmdb r0!, {r4-r11, r14}			\n"  //(5) /* Save the core registers. */
	"	str r0, [r2]						\n"  //(6) /* Save the new top of stack into the first member of the TCB. */
	"										\n"
	"	stmdb sp!, {r0, r3}					\n"  //(7)
	"	mov r0, %0 							\n"  //(8)
	"	msr basepri, r0						\n"  //(9)
	"	dsb									\n"  //(10)
	"	isb									\n"  //(11)
	"	bl vTaskSwitchContext				\n"  //(12)
	"	mov r0, #0							\n"  //(13)
	"	msr basepri, r0						\n"  //(14)
	"	ldmia sp!, {r0, r3}					\n"  //(15)
	"										\n"
	"	ldr r1, [r3]						\n"   //(16)/* The first item in pxCurrentTCB is the task top of stack. */
	"	ldr r0, [r1]						\n"  //(17)
	"										\n"  //(18)
	"	ldmia r0!, {r4-r11, r14}			\n"   //(18)/* Pop the core registers. */
	"										\n"

    //如果你的Cortex-M4架构支持FPU 就是打开FPU的编译选项
	//"	tst r14, #0x10						\n" /* Is the task using the FPU context?  If so, pop the high vfp registers too. */
	//"	it eq								\n"
	//"	vldmiaeq r0!, {s16-s31}				\n"
	"										\n"
	"	msr psp, r0							\n"  //(19)
	"	isb									\n"  //(20)
	"										\n"
	#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata workaround. */
		#if WORKAROUND_PMU_CM001 == 1
	"			push { r14 }				\n"
	"			pop { pc }					\n"
		#endif
	#endif
	"										\n"
	"	bx r14								\n"  //(21)
	"										\n"
	"	.align 4							\n"  //(22)
	"pxCurrentTCBConst: .word pxCurrentTCB	\n"
	::"i"(configMAX_SYSCALL_INTERRUPT_PRIORITY)
	);
}

 说明:

上文的保存,指的是自动入栈的CPU寄存器

注意:在进入该函数前,系统(CPU)会自动的将上一个任务的运行的环境即: xPSR,PC(任务入口地址),R12,R3,R2,R1,R0(任务的形参)这些寄存器的值会存储到任务的栈中,是自动完成的。
(1) 进入到xPortPendSVHandler函数中第一步要做的是将剩下的 R4~R11,R14手动保存入栈,同时PSP 会自动更新(在更新之前 PSP 指向任务栈的栈顶)。
(3)将pxCurrentTCB的地址加载到R3中;pxCurrentTCB的地址的值存放的是任务块的指针,这个值是会改变的,通常任务切换到其他的任务就是改这个值,指向需要切换的任务块的指针。
(4)将R3中指向的内容加载到R2中R2 =pxCurrentTCB ;
(5)先将R0指向地址递减在以新R0为及地址将CPU中的R4-R11,R14手动入栈,即保存到当前任务栈中,把R14(LR)寄存器入栈保存,是因为调用vTaskSwitchContext函数返回时,CPU会把返回地址自动保存到LR寄存器中,R14(LR)寄存器的值被覆盖故需要进入入栈保护处理。

(6)将R0的值存储到R2指向的内容,因为R2等于pxCurrentTCB,而pxCurrentTCB的第一个成员为pxTopOfStack,所以就是将R0存储到的上一个任务的pxTopOfStack任务栈顶指针中。完成了上文的保存;

(7)把R3一并入栈保存是因为下面要调用vTaskSwitchContext函数来切换任务控制块(实际就是把需要切换的任务块的结构体指针存放在pxCurrentTCB的地址里),可能会用到R3,把R3的值覆盖,而我们还需要把原来的R3来当作新的pxCurrentTCB,所以保险起见一并把R3也保存了起来;至于为什么要把R0入栈,还不知到原因。后面出栈的时候会被覆盖

上下文切换;

   (8)把0值存到R0中,用来屏蔽BASEPRI的值;
(9)关闭部分高优先级中断;

(10)(11)清流水线
(12)调用vTaskSwitchContext函数用来更新下一个需要运行任务的任务控制块pxCurrentTCB;

  (13)把0值存到R0中,用来屏蔽BASEPRI的值;
(14)退出临界段,开中断,直接往 BASEPRI写 0;

(15)从堆栈中恢复R3,R0的值,
(16)将将要运行任务的任务控制块pxCurrentTCB的地址指向的内容即任务控制块本身加载到R1中;
(17)加载R1中指向的内容即将要运行的任务的栈顶指针到R0中;
(18)把R0为基地址,将下一个将要运行任务的栈加载到R4-R11,R14寄存器中,此时的R0的指向已经变化;
(19)将R0的值更新到PSP中,用来退出异常后以PSP为及地址,将新任务栈中剩下的内容加载到CPU寄存器中,然后清流水线,保证此操作完成后在执行下一条指令。

(20)清流水线
(21) 异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回, 然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,当新任务的运行地址及参数r0,r1,r2,r3,r12,r15,xpsr自动加载到R0,R1,R2,R3,R12,R15(PC)以及xPSR寄存器后,CPU根据被出栈到PC寄存器的值,执行新的任务指令。

(22) 4字节对齐

 

到此任务切换就已经准备完毕,等这个函数返回时,CPU已经指向新的任务了,

仅供个人笔记和学习参考使用

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS启动第一个任务和任务的切换(Cortex-M4) 的相关文章

  • [px4仿真]px4的STIL仿真中添加向下的摄像头

    后面发现这样改有问题 xff0c 正确的修改方法参考这条提交记录 xff1a https github com TokyoClod sitl gazebo commit e61e6e46a665804f072474b2b1b085fb701
  • VISP库IBVS仿真

    示例程序1 tutorial ibvs 4pts cpp span class hljs comment example tutorial ibvs 4pts cpp span span class hljs preprocessor in
  • blender中UV贴图及导出dae文件

    设置单位meter 设置大小 按 N调出属性面板 设置 依次选择编辑模式 线框 面选择 xff1b 进入UV贴图模式 右击选中物体上表面 xff0c 按U 展开 xff1b 上方选择UV Editing模式 贴图 左下底部选择 图像 打开图
  • AprilTag视觉定位系统

    AprilTag是一个视觉基准库 xff0c 在AR xff0c 机器人 xff0c 相机校准领域广泛使用 通过特定的标志 xff08 与二维码相似 xff0c 但是降低了复杂度以满足实时性要求 xff09 xff0c 可以快速地检测标志
  • keras 多输入多输出网络

    keras中的多输入多输出网络 多输入多输出网络搭建的官网介绍 xff1a http keras cn readthedocs io en latest getting started functional API Demo span cl
  • lodash源码分析之compact中的遍历

    小时候 xff0c 乡愁是一枚小小的邮票 xff0c 我在这头 xff0c 母亲在那头 长大后 xff0c 乡愁是一张窄窄的船票 xff0c 我在这头 xff0c 新娘在那头 后来啊 xff0c 乡愁是一方矮矮的坟墓 xff0c 我在外头
  • A-Softmax的keras实现-《SphereFace: Deep Hypersphere Embedding for Face Recognition》

    A Softmax的keras实现 参考文档 xff1a https www cnblogs com heguanyou p 7503025 html 注 xff1a 主体完成 xff0c 调试中 xff0c 先行记录 xff0c 待续 已
  • AM-Softmax的keras实现: 《Additive Margin Softmax for Face Verification》

    原答案在对输入进行归一化时有错误 xff0c 另外m应该是一个固定的超参数不需要训练 xff0c 已改正 论文地址 xff1a Additive Margin Softmax for Face Verification 和L Softmax
  • 最小跳跃次数

    1 最小跳跃次数 1 最小跳跃次数 1 1 题目描述 xff1a 1 2 解题思路1 3 实现代码 出自华为实习机试第二题 xff1a 1 1 题目描述 xff1a 先输入一个数字代表数字总数 然后依次输入几个数字 xff0c 代表当前位置
  • moby、docker-ce与docker-ee的区别

    近期研究docker相关技术 xff0c 发现官网分为moby docker ce与docker ee不同板块 xff0c ce和ee版本好理解 xff0c 但2017年开始又多出个Moby xff0c 开始有点凌乱 xff0c Googl
  • AutoRun与NoDriveTypeAutoRun键值

    Autorun inf 与注册表NoDriveTypeAutoRun键值的一些说明 二进制位数 8 7 6 5 4 3 2 1 Type 1 RAMDISK CDROM REMOTE FIXED REMOVABLE NO ROOT DIR
  • Windows下编译qt-material

    Windows下编译qml material 公司新项目里选择用 QML 来做界面 xff0c 这段时间一直在学习 QML 的语法和基础组件 xff0c 限于 QML 目前不太成熟 xff0c 没有十分丰富的控件 xff0c 加上自己水平有
  • SpringSecurity是如何实现账号密码的验证登录的

    个人理解 xff1a 1 首先在 配置类中定表单登录的URL和账号密码 2 jsp表单中的url和账号秘密要与指定的名称一致 3 创建 SecurityAdmin类 xff0c 集成User类 xff0c 因为User类只包含usernam
  • HDFS入门(三)

    五 HDFS接口 xff08 一 xff09 HDFS命令行接口 HDFS命令行接口作为了解 xff0c 在这里不再赘述 xff08 二 xff09 JAVA API接口 使用URL访问hdfs 1 xff09 怎么访问 xff1f jav
  • 怎么去掉Chrome浏览器新标签页的缩略图

    每次都很烦那八个框框 xff0c 再漂亮的主题图片都被那八个缩略图框搞糟了 xff01 对吧 xff0c 应该有同感 解决办法 xff1a chrome拓展程序里下载拓展程序 Stylish 安装好之后就在拓展程序里找到它 xff0c 打开
  • 上班一个月,我的几点体会

    这篇博文其实在去年就已经在CSDN发过的 后来 xff0c 某次误操作不小心删除了 xff0c 今天找出来重新发一下 我是从3月1号开始上班的 xff0c 今天3月31号 xff0c 刚好一个月结束 xff0c 在这一个月里 xff0c 我
  • NVMe CLI 命令使用

    1 下载地址 https github com linux nvme nvme cli 2 安装 unzip nvme cli master zip cd nvme cli master zip make amp amp make inst
  • 我这一年写的博文

    总结2013 xff0c 展望2014 xff0c gt gt 我的2013年终总结 在苦与乐中成长 下面是我这一年所写的博客 xff0c 主要涉及C xff0c Net Framework xff0c SQL Server xff0c S
  • 我的2013年终总结——在苦与乐中成长

    写在前面 最近正好在三亚旅游 xff0c 空闲下来时 xff0c 便开始进行年终总结 由于去年年末较忙 xff0c 便错过了2012 年的年终总结 xff0c 所以本文将会对 2012 与 2013 两年一起进行总结 说说工作 学生 到 码
  • 走过2014,2015我将继续前行

    写在前面 一转眼 xff0c 一年时光就这么溜走了 在这辞旧迎新之际 xff08 这说法是不是很官方啊 xff0c 呵呵 xff01 xff09 xff0c 我将对即将过去的2014 年进行一番总结 xff0c 并对即将来临的 2015 年

随机推荐

  • csr867x入门之串口AT指令协议(三)

    目录 功能简介 功能实现 功能调试 功能简介 通过封装串口AT指令 xff0c 我们可以把8670作为一个蓝牙外设 xff0c 通过串口的AT指令协议与外部mcu通讯 比如当csr8670连接到pc端 xff0c 再pc端通过串口工具发送指
  • 今天了解Linux体系结构

    上一章我们了解了Linux的一些发展历史 xff0c 那么这一章我们来深入了解一下 xff0c Linux的体系及构成 Linux采用分层设计 xff0c 分层结构 xff0c 它包括 4 个层次 每层只能与相邻的层通信 xff0c 层次间
  • Ubuntu操作系统下Python多版本的安装与切换

    安装替代Python版本 打开终端 xff0c 看下系统中默认安装了按个版本的python 运行以下命令 xff1a python version 如果使用的是Ubuntu 20 04 xff0c 则默认情况下拥有的python版本3 8
  • 51单片机LCD1602液晶屏显示字符,字符串,(有)无符号整数,十六进制数,二进制数等

    1 前言 LCD1602液晶显示器是广泛使用的一种字符型液晶显示模块 液晶显示模块具有体积小 功耗低 显示内容丰富 超薄轻巧等优点 xff0c 在嵌入式应用系统中得到越来越广泛的应用 xff0c 这讲中向大家介绍的LCD1602 液晶显示模
  • 51单片机——DS1302时钟

    目录 1 前言 1 1 实验现象 2 DS1302的介绍 2 1 DS1302的引脚定义以及应用电路 2 2 DS1302内部结构 2 21 DS1302内部特殊寄存器 2 22 时序 2 3 BCD码 3 源码 3 1 main c 3
  • 编译安装GCC12.2.0

    编译安装GCC 记录一下 xff0c 免得每次到处找 安装GCC12 2 0 xff0c 其他版本一样 源码下载地址 xff1a https ftp gnu org gnu gcc 备注 xff1a 配置若有不明白的 xff0c 多用 co
  • 安装vim

    sudo apt get install vim 确认后按y就行了 vim的三种模式 1 普通模式 当vim打开的时候就直接进入普通模式 在普通模式下可以进入命令 插入模式 2 插入模式 可以通过普通模式进入插入模式 插入模式可以编辑文本
  • jetson tx2 刷机,安装 cuda、opencv 详细教程

    jetson tx2 刷机 xff0c 安装 cuda opencv 详细教程 jetson tx2 的详细介绍和用途可见官网 xff1a Nvidia jetson tx2 接下来主要说明jetson tx2 详细刷机过程以及在过程中踩过
  • PX4 编译报错问题解决方法、PX4切换固定版本编译

    PX4 Autopilot 编译报错问题解决方法 1 make 2 没有规则可制作目标 dirlinks 停止 1 1159 Generating platforms nuttx NuttX nuttx config FAILED plat
  • ubuntu 系统狠慢 或者很卡的原因

    1 涉及内存小或者虚拟SWAP分区调整问题 可以通过 系统监视器 进行查看 在UBUNTU系统里面 xff0c 并不是你的物理内存全部耗尽之后 xff0c 系统才使用swap分区 xff01 系统的swappiness设定值 xff0c 对
  • 技巧1——怎样查看linux发行版本名称和版本号?

    假如我们加入了一家新公司 xff0c 需要为开发团队安装所需要的软件并且重启服务 首先要弄清楚它们运行在什么发行版本上以及在哪个版本的系统上 xff0c 才能正确的完成后续的工作 作为一名系统管理员 xff0c 充分了解系统信息是首要的任务
  • 种群共生模型

    种群共生模型 种群共生模型 1 1 背景 1 2 参数假设 1 3 分析 1 4 模型构建 1 5 模型求解 1 6 讨论平衡点稳定性 种群共生模型 1 1 背景 在自然界中 种族的共生现象是比较普遍的 如鳄鱼与千鸟 千鸟不但在鳄鱼身上寻找
  • opendaylight学习笔记——Flows

    Flow java中 private static final String WEB NAME 61 34 Flows 34 定义了在网页中显示时的名称 private static final String WEB ID 61 34 fl
  • NB-IoT 物理层相关

    写在前面 本文作为简单说明 xff0c 详细需具体查阅文档 1 关于 PRB 的理解 RB xff08 Resource Block xff09 xff0c 用于描述某些物理信道到资源元素的映射 xff0c 它有两个概念 xff1a VRB
  • 以太网详解(一)-MAC/PHY/MII/RMII/GMII/RGMII基本介绍

    网络设备中肯定离开不MAC和PHY xff0c 本篇文章将详细介绍下以太网中一些常见术语与接口 MAC和PHY结构 从硬件角度来看以太网是由CPU xff0c MAC xff0c PHY三部分组成的 xff0c 如下图示意 xff1a 上图
  • ubuntu(linux)下smb完整搭建流程-----原来如此简单,一步到位!!!!!!

    1 查看ubuntu版本号命令 lsb release a 2 下载安装 sudo apt get insall samba FAQ 如果出现apt install时候遇到E Unable to fetch some archives ma
  • xilinx fpga xdma

    一 下载XDMA文件 输入命令 sudo git clone https github com Xilinx dma ip drivers 二 编译文件 进入xdma文件夹 xff1a cd dma ip drivers XDMA linu
  • 网络通信原理及流程

    网络通信原理 1 1 互联网的本质就是一系列的网络协议 一台硬设有了操作系统 xff0c 然后装上软件你就可以正常使用了 xff0c 然而你也只能自己使用像这样 xff0c 每个人都拥有一台自己的机器 xff0c 然而彼此孤立吗 xff0c
  • LTE核心网介绍

    1 LTE网络架构 EPS包括核心网EPC xff08 EPC又包括MME xff08 信令处理部分 xff09 S GW xff08 数据处理部分 xff09 P GW xff09 E UTRAN xff08 只包含基站eNodeB一个网
  • FreeRTOS启动第一个任务和任务的切换(Cortex-M4)

    调度器是FreeRTOS操作系统的核心 xff0c 主要负责任务切换 xff0c 即在就绪任务中找出最高优先级的任务 xff0c 并使之获得CPU运行权 调度器并非自动运行的 xff0c 需要人为启动它 API函数vTaskStartSch