FreeRTOS --(13)任务管理之空闲任务
转载自 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)任务管理之空闲任务的更多相关文章
- (41)freeRTOS之任务管理
1. 简介: 在 FreeRTOS 中没有线程和进程的区别,只有一个被翻译成任务的程序,相当于进程的概念,拥有独立的栈空间. 对于实时性,可以分为 软实时.硬实时:桌面电脑的输入处理可以看做是软实时, ...
- dbcp连接池配置参数
1.<!-- 数据源1 --> 2. <bean id="dataSource" 3. class="org.apache.commons.dbcp.B ...
- 用xshell操作linux系统的常用命令
(1)命令ls——列出文件 ls -la 给出当前目录下所有文件的一个长列表,包括以句点开头的“隐藏”文件 ls a* 列出当前目录下以字母a开头的所有文件 ls -l *.doc 给出当前目录下以. ...
- [CSAPP笔记][第九章虚拟存储器][吐血1500行]
9.虚拟存储器 为了更加有效地管理存储器且少出错,现代系统提供了对主存的抽象概念,叫做虚拟存储器(VM). 虚拟存储器是硬件异常,硬件地址翻译,主存,磁盘文件和内核软件的完美交互. 为每个进程提供一个 ...
- Win7+CentOS双系统,最清晰细致的教程!
Win7的系统下安装CentOS,实现双系统切换使用的目的,希望对大家有帮助. 注意: 1.由于涉及到对硬盘操作,请妥善备份数据,避免损失. 2.我的步骤是绝对正确和缺一不可的,大家一定要按照我的操作 ...
- (转载博文)VC++API速查
窗口处理 2.1 窗口简介 2.2.1 创建普通窗口(CreateWindow.CreateWindowEx) 2.2.2 关闭窗口(CloseWindow) 2.2.3 销毁窗口(DestroyWi ...
- DBCP|C3P0参数详解
1.<!-- 数据源1 --> 2. <bean id="dataSource" 3. class="org.apache.commons.dbcp.B ...
- tomcat线程初探
博主:handsomecui,希望路过的各位大佬留下你们宝贵的意见,在这里祝大家冬至快乐. 缘由: 初探缘由,在业务层想要通过(当前线程的栈)来获取到控制层的类名,然后打日志,可是发现并不能通过当前线 ...
- 【unix网络编程第三版】ubuntu端口占用问题
<unix网络编程>一书中的代码并不是能直接运行,有时候需要结合各方面的知识来解决,大家在这本书的时候,一定要把代码都跑通,不难你会错过很多学习的机会! 1.问题描述 本人在阅读<U ...
随机推荐
- spring JDBC API 中存在哪些类?
JdbcTemplate SimpleJdbcTemplate NamedParameterJdbcTemplate SimpleJdbcInsert SimpleJdbcCall
- 详细描述一下 Elasticsearch 索引文档的过程?
协调节点默认使用文档 ID 参与计算(也支持通过 routing),以便为路由提供合适的分片. shard = hash(document_id) % (num_of_primary_shards) ...
- java-面向对象相关
public class DemoMethodOverload { public static void main(String[] args) { int[] array = new int[]{1 ...
- vue中v-model 数据双向绑定
表单输入绑定 v-model 数据双向绑定,只能应用在input /textare /select <div id="app"> <input type=&quo ...
- mybatis源码之我见
以前一直想看mybatis的源代码,但是一直没找到入口(傻),最近看教程,有些感悟. 和起以前一样,关键代码我会用红色标记. 首先,先贴下我的dao和mapper,代码很简单,和平时写的hello w ...
- SaltStack项目实战(一)
系统架构图 一.初始化 1.salt环境配置,定义基础环境.生产环境(base.prod) ? 1 2 3 4 5 6 7 8 9 10 vim /etc/salt/master 修改file_r ...
- C++大作业——教职工管理系统
教职工信息管理系统 1.问题描述: 设计一个学校职工管理系统,要求实现如下功能:建立职工信息数据, 包括职工编号.姓名. 性别.工资.出生时间.岗位.参加工作时间和年 龄(必须计算得到),初始模拟数据 ...
- 8_根轨迹_Part2_根轨迹手绘技巧
传递函数分母部分相同
- [CSS]《CSS揭秘》第四章——视觉效果
投影 单侧投影 box-shadow:0px 10px 10px -5px black; 邻边投影 box-shadow:10px 10px 10px 2px black; 双侧投影 box-shad ...
- 一个html标签到底包含了多少信息(1)
先来看一段代码: var dom = document.querySelector('body'); for(var i in dom){ console.log(i,dom[i]) } 可以看到很多 ...