转载自 https://blog.csdn.net/zhoutaopower/article/details/107180016

创建完毕任务,启动调度器,任务控制,系统 SysTick 来临后判断是否需上下文切换;

如果没有其他任务执行的情况下,FreeRTOS 的 Idle 任务将被调度投入运行;

在启动调度器的时候,Idle 任务就被创建了,优先级为最低 0;

void vTaskStartScheduler( void )
{
.....................
xReturn = xTaskCreate( prvIdleTask,
configIDLE_TASK_NAME,
configMINIMAL_STACK_SIZE,
( void * ) NULL,
portPRIVILEGE_BIT,
&xIdleTaskHandle );
.....................
}

当某时刻所有优先级高于 Idle 任务的任务处于被阻塞或者部分被挂起的状态,此刻调度器会调度 Idle 任务运行,它的执行函数为:

/*
* -----------------------------------------------------------
* The Idle task.
* ----------------------------------------------------------
*
* The portTASK_FUNCTION() macro is used to allow port/compiler specific
* language extensions. The equivalent prototype for this function is:
*
* void prvIdleTask( void *pvParameters );
*
*/
static portTASK_FUNCTION( prvIdleTask, pvParameters )
{
/* Stop warnings. */
( void ) pvParameters; /** THIS IS THE RTOS IDLE TASK - WHICH IS CREATED AUTOMATICALLY WHEN THE
SCHEDULER IS STARTED. **/ /* In case a task that has a secure context deletes itself, in which case
the idle task is responsible for deleting the task's secure context, if
any. */
portALLOCATE_SECURE_CONTEXT( configMINIMAL_SECURE_STACK_SIZE ); for( ;; )
{
/* See if any tasks have deleted themselves - if so then the idle task
is responsible for freeing the deleted task's TCB and stack. */
prvCheckTasksWaitingTermination(); #if ( configUSE_PREEMPTION == 0 )
{
/* If we are not using preemption we keep forcing a task switch to
see if any other task has become available. If we are using
preemption we don't need to do this as any task becoming available
will automatically get the processor anyway. */
taskYIELD();
}
#endif /* configUSE_PREEMPTION */ #if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )
{
/* When using preemption tasks of equal priority will be
timesliced. If a task that is sharing the idle priority is ready
to run then the idle task should yield before the end of the
timeslice.
A critical region is not required here as we are just reading from
the list, and an occasional incorrect value will not matter. If
the ready list at the idle priority contains more than one task
then a task other than the idle task is ready to execute. */
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) 1 )
{
taskYIELD();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */ #if ( configUSE_IDLE_HOOK == 1 )
{
extern void vApplicationIdleHook( void ); /* Call the user defined function from within the idle task. This
allows the application designer to add background functionality
without the overhead of a separate task.
NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES,
CALL A FUNCTION THAT MIGHT BLOCK. */
vApplicationIdleHook();
}
#endif /* configUSE_IDLE_HOOK */ /* This conditional compilation should use inequality to 0, not equality
to 1. This is to ensure portSUPPRESS_TICKS_AND_SLEEP() is called when
user defined low power mode implementations require
configUSE_TICKLESS_IDLE to be set to a value other than 1. */
#if ( configUSE_TICKLESS_IDLE != 0 )
{
TickType_t xExpectedIdleTime; /* It is not desirable to suspend then resume the scheduler on
each iteration of the idle task. Therefore, a preliminary
test of the expected idle time is performed without the
scheduler suspended. The result here is not necessarily
valid. */
xExpectedIdleTime = prvGetExpectedIdleTime(); if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
{
vTaskSuspendAll();
{
/* Now the scheduler is suspended, the expected idle
time can be sampled again, and this time its value can
be used. */
configASSERT( xNextTaskUnblockTime >= xTickCount );
xExpectedIdleTime = prvGetExpectedIdleTime(); /* Define the following macro to set xExpectedIdleTime to 0
if the application does not want
portSUPPRESS_TICKS_AND_SLEEP() to be called. */
configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime ); if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
{
traceLOW_POWER_IDLE_BEGIN();
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
traceLOW_POWER_IDLE_END();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
( void ) xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICKLESS_IDLE */
}
}

Idle 任务也是一个无限循环:

1、调用 prvCheckTasksWaitingTermination() 判断是否有需要 Task 自己删除自己,如果有,那么在 Idle 任务中来回收这种类型的场景:

static void prvCheckTasksWaitingTermination( void )
{ /** THIS FUNCTION IS CALLED FROM THE RTOS IDLE TASK **/ #if ( INCLUDE_vTaskDelete == 1 )
{
TCB_t *pxTCB; /* uxDeletedTasksWaitingCleanUp is used to prevent taskENTER_CRITICAL()
being called too often in the idle task. */
while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )
{
taskENTER_CRITICAL();
{
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
--uxCurrentNumberOfTasks;
--uxDeletedTasksWaitingCleanUp;
}
taskEXIT_CRITICAL(); prvDeleteTCB( pxTCB );
}
}
#endif /* INCLUDE_vTaskDelete */
}

如果支持任务删除,而且有需要被删除的任务的话,进入临界区,取出要被删除的任务,更新当前任务个数和待删除任务个数,退出临界区,并调用 prvDeleteTCB() 接口来删除任务的资源,其实就是调用了 vPortFree( pxTCB->pxStack ); 和 vPortFree( pxTCB ); 来释放任务的 TCB 结构和堆栈;

2、如果定义了 configUSE_PREEMPTION 为 1(支持抢占),同时 configIDLE_SHOULD_YIELD 也为 1 (如果有与 Idle 任务相同优先级的任务,并且处于 Ready 状态,那么 Idle 任务将为其让路)的情况,Idle 任务直接调用 taskYIELD(); 引发一次调度,放弃 CPU;

3、如果使能了 configUSE_IDLE_HOOK,也就是用户的 Idle 钩子函数,则调用 vApplicationIdleHook;

4、如果使能了 configUSE_TICKLESS_IDLE,就意味着要进入低功耗场景,当然,既然都调用 Idle 任务了,进入低功耗理所应当;这里的 Tickless 的含义是:进入低功耗后,Systick 不在来中断,因为 Tick 心跳很频繁的话,处理器很快就被唤醒了,失去了低功耗的意义;

5、调用 prvGetExpectedIdleTime 获取距离下一个最近的阻塞任务的执行时间,与 当前的时间做减法,得到最大的可以进入低功耗的时间,当然这里只能判断阻塞在时间上的任务,对于事件,我们并不知道什么时候会来,也许是中断激活事件,不过这样要求中断能够唤醒处理器,否则中断无法得到及时处理,那么 RTOS 的实时任务的运行也得不到实时的保证;

6、如果获取得到的最大进入低功耗的时间 xExpectedIdleTime 大于了我们配置的期望睡眠的最小时间,也就是满足进入低功耗的条件,那么挂起调度器(因为马上要进入 Tickless),调用 portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ); 进入低功耗;

7、portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ) 的实现是和处理器相关,因为是带 port 前缀,每种处理器进入低功耗的方式不尽相同,即便是同一种处理器,进入低功耗也有几种模式,所以这里交给处理器相关 port.c 去实现;

8、唤醒后,一般的,原地继续执行,调用 xTaskResumeAll 恢复调度器;

针对 Cortex-M3 进入睡眠部分:

#define portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ) \
vPortSuppressTicksAndSleep( xExpectedIdleTime )

调用到了 vPortSuppressTicksAndSleep(xExpectedIdleTime)入参是期待睡眠的时间;也就是 Tick 个数:

#if( configUSE_TICKLESS_IDLE == 1 )

    __weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
{
uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements;
TickType_t xModifiableIdleTime; /* Make sure the SysTick reload value does not overflow the counter. */
/* 睡眠的最大值不能够超过处理器支持的最大值 */
if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )
{
xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
} /* Stop the SysTick momentarily. The time the SysTick is stopped for
is accounted for as best it can be, but using the tickless mode will
inevitably result in some tiny drift of the time maintained by the
kernel with respect to calendar time. */
/* 禁止 SysTick */
portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT; /* Calculate the reload value required to wait xExpectedIdleTime
tick periods. -1 is used because this code will execute part way
through one of the tick periods. */
/* 计算将要配置到 SYSTICK 用于唤醒的值 */
ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );
if( ulReloadValue > ulStoppedTimerCompensation )
{
ulReloadValue -= ulStoppedTimerCompensation;
} /* Enter a critical section but don't use the taskENTER_CRITICAL()
method as that will mask interrupts that should exit sleep mode. */
/* 关闭中断 */
__disable_irq();
__dsb( portSY_FULL_READ_WRITE );
__isb( portSY_FULL_READ_WRITE ); /* If a context switch is pending or a task is waiting for the scheduler
to be unsuspended then abandon the low power entry. */
/* 再次 Check 有没有非 Idle 状态待执行的任务 */
if( eTaskConfirmSleepModeStatus() == eAbortSleep )
{
/* Restart from whatever is left in the count register to complete
this tick period. */
/* 重新配置 SysTICK 终止睡眠流程 */
portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG; /* Restart SysTick. */
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT; /* Reset the reload register to the value required for normal tick
periods. */
portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL; /* Re-enable interrupts - see comments above __disable_irq() call
above. */
/* 打开中断 */
__enable_irq();
}
else
{
/* Set the new reload value. */
/* 将 SysTick 的时间配置为之前计算好的时间 */
portNVIC_SYSTICK_LOAD_REG = ulReloadValue; /* Clear the SysTick count flag and set the count value back to
zero. */
/* 清除当前 SysTick 的值 */
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL; /* Restart SysTick. */
/* 开启 SysTick */
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT; /* Sleep until something happens. configPRE_SLEEP_PROCESSING() can
set its parameter to 0 to indicate that its implementation contains
its own wait for interrupt or wait for event instruction, and so wfi
should not be executed again. However, the original expected idle
time variable must remain unmodified, so a copy is taken. */
xModifiableIdleTime = xExpectedIdleTime;
configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
/* 执行 WFI 睡眠 */
if( xModifiableIdleTime > 0 )
{
__dsb( portSY_FULL_READ_WRITE );
__wfi();
__isb( portSY_FULL_READ_WRITE );
}
/* 此处为唤醒 */
configPOST_SLEEP_PROCESSING( xExpectedIdleTime ); /* Re-enable interrupts to allow the interrupt that brought the MCU
out of sleep mode to execute immediately. see comments above
__disable_interrupt() call above. */
/* 因为可能是其他中断唤醒的 WFI,立马开启中断,进入 ISR */
__enable_irq();
__dsb( portSY_FULL_READ_WRITE );
__isb( portSY_FULL_READ_WRITE ); /* Disable interrupts again because the clock is about to be stopped
and interrupts that execute while the clock is stopped will increase
any slippage between the time maintained by the RTOS and calendar
time. */
/* 关闭中断,做处理 */
__disable_irq();
__dsb( portSY_FULL_READ_WRITE );
__isb( portSY_FULL_READ_WRITE ); /* Disable the SysTick clock without reading the
portNVIC_SYSTICK_CTRL_REG register to ensure the
portNVIC_SYSTICK_COUNT_FLAG_BIT is not cleared if it is set. Again,
the time the SysTick is stopped for is accounted for as best it can
be, but using the tickless mode will inevitably result in some tiny
drift of the time maintained by the kernel with respect to calendar
time*/
/* 重新配置 SysTick */
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT ); /* Determine if the SysTick clock has already counted to zero and
been set back to the current reload value (the reload back being
correct for the entire expected idle time) or if the SysTick is yet
to count to zero (in which case an interrupt other than the SysTick
must have brought the system out of sleep mode). */
if( ( portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 )
{
uint32_t ulCalculatedLoadValue; /* The tick interrupt is already pending, and the SysTick count
reloaded with ulReloadValue. Reset the
portNVIC_SYSTICK_LOAD_REG with whatever remains of this tick
period. */
ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG ); /* Don't allow a tiny value, or values that have somehow
underflowed because the post sleep hook did something
that took too long. */
if( ( ulCalculatedLoadValue < ulStoppedTimerCompensation ) || ( ulCalculatedLoadValue > ulTimerCountsForOneTick ) )
{
ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL );
} portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue; /* As the pending tick will be processed as soon as this
function exits, the tick value maintained by the tick is stepped
forward by one less than the time spent waiting. */
ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
}
else
{
/* Something other than the tick interrupt ended the sleep.
Work out how long the sleep lasted rounded to complete tick
periods (not the ulReload value which accounted for part
ticks). */
ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG; /* How many complete tick periods passed while the processor
was waiting? */
ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick; /* The reload value is set to whatever fraction of a single tick
period remains. */
portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1UL ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
} /* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG
again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
value. */
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
vTaskStepTick( ulCompleteTickPeriods );
portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL; /* Exit with interrpts enabled. */
__enable_irq();
}
} #endif /* #if configUSE_TICKLESS_IDLE */

内容不少,慢慢看即可:

0、入参是期望睡眠的 Tick 数目,这里不用这么抽象,比如配置的 Tick 周期为 1ms,那么这里就是 ms 数;

这里有 3 个全局变量需要说明一下:

/*
* The number of SysTick increments that make up one tick period.
*/
#if( configUSE_TICKLESS_IDLE == 1 )
static uint32_t ulTimerCountsForOneTick = 0;
#endif /* configUSE_TICKLESS_IDLE */ /*
* The maximum number of tick periods that can be suppressed is limited by the
* 24 bit resolution of the SysTick timer.
*/
#if( configUSE_TICKLESS_IDLE == 1 )
static uint32_t xMaximumPossibleSuppressedTicks = 0;
#endif /* configUSE_TICKLESS_IDLE */ /*
* Compensate for the CPU cycles that pass while the SysTick is stopped (low
* power functionality only.
*/
#if( configUSE_TICKLESS_IDLE == 1 )
static uint32_t ulStoppedTimerCompensation = 0;
#endif /* configUSE_TICKLESS_IDLE */

在开启调度器,配置 SysTick 的时候,这 3 个全局变量被赋值初始化:

#if( configUSE_TICKLESS_IDLE == 1 )
{
ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
}
#endif /* configUSE_TICKLESS_IDLE */

ulTimerCountsForOneTick :代表了一个 SysTick 配置到寄存器的 Tick 的 Count;换句话来说,就是产生 1ms 的 SysTick 中断,需要配置给寄存器的值;

xMaximumPossibleSuppressedTicks:代表了在溢出之前,硬件最大支持多少个 Tick;(因为 Cortex-M3 处理器配置给硬件的 Tick Count 最大是 24 bit 的,所以这里用 24bit 的全 1 除以 ulTimerCountsForOneTick );

ulStoppedTimerCompensation:代表了一个时钟补偿的因子,这里是固定的 45;

1、首先判断期望睡眠的值是否大于了处理器的 xMaximumPossibleSuppressedTicks,如果是,那么将睡眠的值限定在 xMaximumPossibleSuppressedTicks;

2、禁止 SysTick 模块;

3、计算新的 SysTick 的 Load 值,这里的原理是,因为需要让处理器进入 WFI (Waiting For Interrupt),进入 WFI 后,处理器可以被中断唤醒,并继续执行;其实这里的 Tickless 并不是真的一直关闭了 SysTick ,而是将睡眠的时间配置到了 SysTick 中,所以这里才会限制睡眠时间;

ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + \
( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) ); if( ulReloadValue > ulStoppedTimerCompensation )
{
ulReloadValue -= ulStoppedTimerCompensation;
}

使用当前的 SysTick 的值,加上睡眠的时间乘以每个 Tick 的 Count,计算出即将配置到 SysTick 硬件寄存器的值;然后对补偿因子做减法;

4、__disable_irq,关闭中断,刷指令和数据流水线;

5、判断是否还有需要被执行的任务,如果有,那么重新配置 SysTick 还是为 1ms,并使能 SysTick,开启中断,退出睡眠逻辑;

6、如果没有要被执行的任务,将计算出来最大的睡眠时间 ulReloadValue 配置进 SysTick 计数器寄存器,开启 SysTick,此刻的 SysTick 便是睡眠的时间;

7、进入 WFI 睡眠;

8、如果有中断,则对 WFI 原地唤醒,继续执行,这里可能是 SysTick 的 IRQ 唤醒,也可能是其他中断唤醒;

9、__enable_irq,开启中断,因为可能是被其他 IRQ 唤醒,这里需要立马执行 ISR;

10、__disable_irq,关闭中断,刷指令和数据流水线,因为下面的配置不允许被打断;

11、重新配置 SysTick 成为 OS 的心跳(也就是 1ms),并使能 SysTick;

FreeRTOS --(13)任务管理之空闲任务的更多相关文章

  1. (41)freeRTOS之任务管理

    1. 简介: 在 FreeRTOS 中没有线程和进程的区别,只有一个被翻译成任务的程序,相当于进程的概念,拥有独立的栈空间. 对于实时性,可以分为 软实时.硬实时:桌面电脑的输入处理可以看做是软实时, ...

  2. dbcp连接池配置参数

    1.<!-- 数据源1 --> 2. <bean id="dataSource" 3. class="org.apache.commons.dbcp.B ...

  3. 用xshell操作linux系统的常用命令

    (1)命令ls——列出文件 ls -la 给出当前目录下所有文件的一个长列表,包括以句点开头的“隐藏”文件 ls a* 列出当前目录下以字母a开头的所有文件 ls -l *.doc 给出当前目录下以. ...

  4. [CSAPP笔记][第九章虚拟存储器][吐血1500行]

    9.虚拟存储器 为了更加有效地管理存储器且少出错,现代系统提供了对主存的抽象概念,叫做虚拟存储器(VM). 虚拟存储器是硬件异常,硬件地址翻译,主存,磁盘文件和内核软件的完美交互. 为每个进程提供一个 ...

  5. Win7+CentOS双系统,最清晰细致的教程!

    Win7的系统下安装CentOS,实现双系统切换使用的目的,希望对大家有帮助. 注意: 1.由于涉及到对硬盘操作,请妥善备份数据,避免损失. 2.我的步骤是绝对正确和缺一不可的,大家一定要按照我的操作 ...

  6. (转载博文)VC++API速查

    窗口处理 2.1 窗口简介 2.2.1 创建普通窗口(CreateWindow.CreateWindowEx) 2.2.2 关闭窗口(CloseWindow) 2.2.3 销毁窗口(DestroyWi ...

  7. DBCP|C3P0参数详解

    1.<!-- 数据源1 --> 2. <bean id="dataSource" 3. class="org.apache.commons.dbcp.B ...

  8. tomcat线程初探

    博主:handsomecui,希望路过的各位大佬留下你们宝贵的意见,在这里祝大家冬至快乐. 缘由: 初探缘由,在业务层想要通过(当前线程的栈)来获取到控制层的类名,然后打日志,可是发现并不能通过当前线 ...

  9. 【unix网络编程第三版】ubuntu端口占用问题

    <unix网络编程>一书中的代码并不是能直接运行,有时候需要结合各方面的知识来解决,大家在这本书的时候,一定要把代码都跑通,不难你会错过很多学习的机会! 1.问题描述 本人在阅读<U ...

随机推荐

  1. List 操作add 报错

    操作List报java.lang.UnsupportedOperationException 2018.03.12 16:52:01字数 230阅读 1683 问题描述 今天在项目中调用List的ad ...

  2. 用TLS/SSL保证EMQ的网络传输安全

    作为基于现代密码学公钥算法的安全协议,TLS/SSL能在计算机通讯网络上保证传输安全,EMQ的MQTT broker支持TLS,也可以用这种方式来确保传输安全. 参考官网:https://www.em ...

  3. Dubbo telnet 命令能做什么?

    dubbo 服务发布之后,我们可以利用 telnet 命令进行调试.管理. Dubbo2.0.5 以上版本服务提供端口支持 telnet 命令 连接服务 telnet localhost 20880 ...

  4. (转载)MySQL删除所有表的外键约束、禁用外键约束

    其实如果想删除所有表可以直接如下操作: 在navicat中直接选中所有表,然后右键删除表即可,会有提示,一路确定,就会先删掉没有外键的表和字表,只要一路确定,删几批就把表都删完了,并不算太麻烦. 转: ...

  5. Eclipse建立Web项目,手动生成web.xml文件

    相关文章:https://blog.csdn.net/ys_code/article/details/79156188(Web项目建立,手动生成web.xml文件

  6. Redis 回收进程如何工作的?

    一个客户端运行了新的命令,添加了新的数据.Redi 检查内存使用情况,如 果大于 maxmemory 的限制, 则根据设定好的策略进行回收.一个新的命令被执 行,等等.所以我们不断地穿越内存限制的边界 ...

  7. 『忘了再学』Shell基础 — 7、Bash基本功能(多命令顺序执行)

    目录 1.多命令执行符: 2.多命令执行符&& 3.多命令执行符|| 4.&&和||联合应用 Linux系统支持多条命令顺序执行,就是我可以依次输入多条命令后,统一按E ...

  8. (十)React Ant Design Pro + .Net5 WebApi:后端环境搭建-IdentityServer4(二)授权模式

    一.前言 先交代一下整个Demo项目结构: 一个认证服务(端口5000)IdentityServer4.Authentication 五个授权模式(两个控制台程序,三个MVC项目端口5001)文件夹G ...

  9. 体验js之美第八课-面向对象创建和继承终结篇

    概述 到这里我们讲说js面向对象的系列部分的最后一个课程,面向对象必须掌握两个东西一个是对象的创建一个是继承.这节课我们重点说说这两个问题最后我们说下在ES6里面面向对象怎么玩. 1对象的创建 我们第 ...

  10. 如何用vue打造一个移动端音乐播放器

    写在前面 没错,这就是慕课网上的那个vue音乐播放器,后台是某音乐播放器的线上接口扒取,虽然这类项目写的人很多,但不得不说这还是个少有的适合vue提升的好项目,做这个项目除了想写一个比较大并且功能复杂 ...