【RTOS学习】互斥管理 | 调试 | 信息统计

news2024/11/26 16:37:45

🐱作者:一只大喵咪1201
🐱专栏:《RTOS学习》
🔥格言:你只管努力,剩下的交给时间!
图

互斥管理 | 调试 | 信息统计

  • 🍉互斥管理
    • 🌰屏蔽中断
    • 🌰暂停调度器
  • 🍉调试
  • 🍉信息统计
    • 🌰基本使用:
  • 🍉总结

🍉互斥管理

在前面讲解互斥量时,引入过临界资源的概念,实现了对临界资源的互斥访问。

要独占式地访问临界资源,有 2 种方法:

  • 公平竞争:比如使用互斥量,谁先获得互斥量谁就访问临界资源。
  • 谁要跟我抢,我就灭掉谁:
  • 中断要跟我抢?我屏蔽中断。
  • 其他任务要跟我抢?我禁止调度器,不运行任务切换。

🌰屏蔽中断

图
如上图所示,在M3/M4系列的芯片中,有一个8bit的寄存器来存放各类中断的优先级,其中复位,NMI,硬件错误三种中断优先级最高,不受该寄存器管辖。

该寄存器管理的中断优先级从0到255,数字越低,优先级越高,这256个中断又分为两类:

  • 不会使用到SYSCALL的中断:优先级在0~190范围内的中断。
  • 会使用到SYSCALL的中断:优先级在191~255范围内的中断,由两个宏来确定这个范围。
  • SYSCALL:也就是系统调用,是由FreeRTOS提供的函数接口,比如向队列中写数据xQueueSend等等函数。

屏蔽中断有两套宏:任务中使用、ISR 中使用:

任务中使用:

  • taskENTER_CRITICA():入口处屏蔽中断。

图
如上图代码所示,在函数的入口处调用taskENTER_CRITICAL来屏蔽中断,防止其他中断和当前任务抢夺临界资源。

底层会先调用portDISABLE_INTERRUPTS来实现中断屏蔽,并且会让一个全局变量uxCriticalNesting进行加加。

  • 每屏蔽一次中断,变量uxCriticalNesting就会加加一次。

屏蔽中断的底层函数vPortRaiseBASEPRI中,会将宏configMAX_SYSCALL_INTERRUPT_PRIORITY代表的191赋值给ulNewBASEPRI,然后再通过汇编代码将优先级低于191的所有中断屏蔽掉。

  • 屏蔽中断并不是屏蔽所有中断,而是屏蔽低于191的低优先级中断
  • 高于191的高优先级中断仍然可以产生。

之所以设计为只屏蔽低优先级中断,是因为只有低优先级中断才会调用RTOS的系统调用,这些中断才会涉及到互斥。

  • taskEXIT_CRITICAL():出口处使能中断。

图
如上图所示代码,在函数的出口处调用taskEXIT_CRITICAL来重新使能被屏蔽掉的中断,从而解除屏蔽。

底层会先让全局变量uxCriticalNesting减减,当该全局变量被减为0的时候,再调用portENABLE_INTERRUPTS实现解除屏蔽。

  • 每重新使能一次中断,变量uxCriticalNesting就会减减一次。
  • uxCriticalNesting为0时,才真正解除屏蔽。
  • 全局变量uxCriticalNesting是为了实现递归调用,它的内部会记录嵌套的深度,只有嵌套深度变为 0 时,调用 taskEXIT_CRITICAL()才会重新使能中断。

在任务中屏蔽中断的示例如下:

图
如上图向队列中写入数据的函数,在 taskENTER_CRITICA()/taskEXIT_CRITICAL()之间:进行临界资源的访问。

使用 taskENTER_CRITICA()taskEXIT_CRITICAL()来访问临界资源是很粗鲁的方法:

  • 中断无法正常运行。
  • 任务调度无法进行。
  • 所以,之间的代码要尽可能快速地执行。

ISR中使用:

  • portSET_INTERRUPT_MASK_FROM_ISR():入口处屏蔽中断。

图
如上图代码,在带有后缀FromISR的系统调用函数入口处,调用port_INTERRUPT_MASK_FROM_ISR来屏蔽中断。

可以看到,此时屏蔽中断直接调用ulPortRaiseBASEPRI,中间没有其他封装,也没有记录次数等操作,屏蔽的小于191的低优先级中断,同时将中断状态保存到uxSavedInterruptStatus

  • portCLEAR_INTERRUPT_MASK_FROM_ISR :出口处解除屏蔽。

图
如上图代码,在带有后缀FromISR的函数出口处,调用portCLEAR_INTERRUPT_MASK_FROM_ISR 来解除屏蔽。

可以看到,解除屏蔽时也是直接调用vPortSetBASEPRI,并且将前面屏蔽中断时保存的低优先级中断状态uxSavedInterruptStatus传入,在函数内部将屏蔽的中断解除。


在任ISR中屏蔽中断的示例如下:

图
如上图所示带后缀FromISR的向队列中写入数据的函数,在portSET_INTERRUPT_MASK_FROM_ISR()/portCLEAR_INTERRUPT_MASK_FROM_ISR()之间:访问临界资源。

和在任务中屏蔽中断一样,在访问临界资源的过程中,只有高于191的高优先级中断能产生。

🌰暂停调度器

如果有别的任务来跟你竞争临界资源,你可以把中断关掉,无法产生Tick中断,也就无法调度,这当然可以禁止别的任务运行,但是这代价太大了,它会影响到其他中断的处理。

  • 这种情况是竞争临界资源的既有普通任务,也有中断服务函数,或者只有中断服务函数。

如果只是禁止别的普通任务来跟你竞争,不需要关中断,暂停调度器就可以了:在这期间,中断还是可以发生、处理。

暂停调度器函数

  • vTaskSuspendAll()

图
如上图代码所示,在vTaskSuspendAll函数中,有一个全局变量uxSchedulerSuspended,每暂停一次调度器,该变量就会加加。

恢复调度器

  • xTaskResumeAll()

图
如上图代码所示,在xTaskResumeAll中,会将全局变量uxSchedulerSuspended进行减减,当该变量为0时,恢复调度。

  • uxSchedulerSuspended变量同样也是为了禁止和恢复调度函数的递归调用。

使用示例如下:

图
如上图代码所示,在调用vTaskDelay时,会访问到阻塞链表,这是一个临界资源,竞争该临界资源的只有普通任务。

所以在进入该函数后,先禁止调度,不让其他任务和当前任务竞争,当将该任务的TCB放入到阻塞链表后,再恢复调度。

🍉调试

FreeRTOS提供了很多调试手段:

  • 打印
  • 断言:configASSERT
  • Trace
  • Hook函数(回调函数)

打印:
printf:FreeRTOS工程里使用了microlib,里面实现了printf函数。我们只需实现一下fputc函数即可使用printf:

int fputc( int ch, FILE *f );

这就是本喵一直在使用重定向,将打印信息通过UART1打印出来,当然可以重定向到LCD,OLED等设备上。

断言:

一般的C库里面,断言就是一个函数:

void assert(scalar expression);

它的作用是:确认expression必须为真,如果expression为假的话就中止程序。

在FreeRTOS里,使用的是configASSERT(),比如:

#define configASSERT(x)  if (!x) while(1);

其实就是定义了一个宏函数,如果x为真则能顺利运行,如果为假则会陷入死循环。

我们可以让它提供更多信息,比如:

#define configASSERT(x)  \
	if (!x) \
	{
		printf("%s %s %d\r\n",\
		 __FILE__, __FUNCTION__, __LINE__); \
        while(1); \
 	}

这在很多场合都有使用,比如:

  • 队列操作:
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
                              const void * const pvItemToQueue,
                              TickType_t xTicksToWait,
                              const BaseType_t xCopyPosition )
{
    BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
    TimeOut_t xTimeOut;
    Queue_t * const pxQueue = xQueue;

    configASSERT( pxQueue );
    configASSERT(!((pvItemToQueue == NULL) && (pxQueue->uxItemSize != (UBaseType_t)0U)));
    configASSERT( !((xCopyPosition == queueOVERWRITE) && (pxQueue->uxLength != 1 )));

队列不能为空,写入的数据也不能是空指针,并且不能超出队列长度,一旦不符合要求就会发生断言错误。

  • 中断级别的判断
void vPortValidateInterruptPriority( void )
{
	uint32_t ulCurrentInterrupt;
	uint8_t ucCurrentPriority;

	/* Obtain the number of the currently executing interrupt. */
	ulCurrentInterrupt = vPortGetIPSR();

	/* Is the interrupt number a user defined interrupt? */
	if( ulCurrentInterrupt >= portFIRST_USER_INTERRUPT_NUMBER )
	{
		/* Look up the interrupt's priority. */
		ucCurrentPriority = pcInterruptPriorityRegisters[ ulCurrentInterrupt ];

		configASSERT( ucCurrentPriority >= ucMaxSysCallPriority );
	}

Trace:

FreeRTOS中定义了很多trace开头的宏,这些宏被放在系统个关键位置。

它们一般都是空的宏,这不会影响代码:不影响编程处理的程序大小、不影响运行时间。

我们要调试某些功能时,可以修改宏:修改某些标记变量、打印信息等待。

图
如上图,在向队列中写数据的时候会调用traceQUEUE_SEND宏,它原本是一个空的宏,现在本喵让他打印当前任务的名字。

trace宏描述
traceTASK_INCREMENT_TICK(xTickCount)当tick计数自增之前此宏函数被调用。参数xTickCount当前的Tick值还没有增加。
traceTASK_SWITCHED_OUT()vTaskSwitchContext中,把当前任务切换出去之前调用此宏函数。
traceTASK_SWITCHED_IN()vTaskSwitchContext中,新的任务已经被切换进来了,就调用此函数。
traceBLOCKING_ON_QUEUE_RECEIVE(pxQueue)当正在执行的当前任务因为试图去读取一个空的队列、信号或者互斥量而进入阻塞状态时,此函数会被立即调用。参数pxQueue保存的是试图读取的目标队列、信号或者互斥量的句柄,传递给此宏函数。
traceBLOCKING_ON_QUEUE_SEND(pxQueue)当正在执行的当前任务因为试图往一个已经写满的队列或者信号或者互斥量而进入了阻塞状态时,此函数会被立即调用。参数pxQueue保存的是试图写入的目标队列、信号或者互斥量的句柄,传递给此宏函数。
traceQUEUE_SEND(pxQueue)当一个队列或者信号发送成功时,此宏函数会在内核函数xQueueSend(),xQueueSendToFront(),xQueueSendToBack(),以及所有的信号give函数中被调用,参数pxQueue是要发送的目标队列或信号的句柄,传递给此宏函数。
traceQUEUE_SEND_FAILED(pxQueue)当一个队列或者信号发送失败时,此宏函数会在内核函数xQueueSend(),xQueueSendToFront(),xQueueSendToBack(),以及所有的信号give函数中被调用,参数pxQueue是要发送的目标队列或信号的句柄,传递给此宏函数。
traceQUEUE_RECEIVE(pxQueue)当读取一个队列或者接收信号成功时,此宏函数会在内核函数xQueueReceive()以及所有的信号take函数中被调用,参数pxQueue是要接收的目标队列或信号的句柄,传递给此宏函数。
traceQUEUE_RECEIVE_FAILED(pxQueue)当读取一个队列或者接收信号失败时,此宏函数会在内核函数xQueueReceive()以及所有的信号take函数中被调用,参数pxQueue是要接收的目标队列或信号的句柄,传递给此宏函数。
traceQUEUE_SEND_FROM_ISR(pxQueue)当在中断中发送一个队列成功时,此函数会在xQueueSendFromISR()中被调用。参数pxQueue是要发送的目标队列的句柄。
traceQUEUE_SEND_FROM_ISR_FAILED(pxQueue)当在中断中发送一个队列失败时,此函数会在xQueueSendFromISR()中被调用。参数pxQueue是要发送的目标队列的句柄。
traceQUEUE_RECEIVE_FROM_ISR(pxQueue)当在中断中读取一个队列成功时,此函数会在xQueueReceiveFromISR()中被调用。参数pxQueue是要发送的目标队列的句柄。
traceQUEUE_RECEIVE_FROM_ISR_FAILED(pxQueue)当在中断中读取一个队列失败时,此函数会在xQueueReceiveFromISR()中被调用。参数pxQueue是要发送的目标队列的句柄。
traceTASK_DELAY_UNTIL()当一个任务因为调用了vTaskDelayUntil()进入了阻塞状态的前一刻此宏函数会在vTaskDelayUntil()中被立即调用。
traceTASK_DELAY()当一个任务因为调用了vTaskDelay()进入了阻塞状态的前一刻此宏函数会在vTaskDelay中被立即调用。

Malloc Hook函数:

编程时,一般的逻辑错误都容易解决。难以处理的是内存越界、栈溢出等。内存越界经常发生在堆的使用过程总:堆,就是使用malloc得到的内存。

并没有很好的方法检测内存越界,但是FreeRTOS提供了一些回调函数:

  • 使用pvPortMalloc失败时,如果在FreeRTOSConfig.h里配置configUSE_MALLOC_FAILED_HOOK为1,会调用:
void vApplicationMallocFailedHook( void );

该函数和空闲任务的钩子函数一样,也需要我们自己定义,可以在该函数内输出一些错误信息,提示程序员动态开辟内存失败了。

栈溢出Hook函数:

在切换任务(vTaskSwitchContext)时调用taskCHECK_FOR_STACK_OVERFLOW来检测栈是否溢出,如果溢出会调用:

void vApplicationStackOverflowHook( TaskHandle_t xTask, char * pcTaskName );

该函数需要程序员自己定义实现,可以在里面添加一些大于信息等等。

FreeRTOS是怎么判断栈溢出?有两种方法:

  • 方法1:

图

  • 当前任务被切换出去之前,它的整个运行现场都被保存在栈里,这时很可能就是它对栈的使用到达了峰值。

此时就会比较任务TCB中的当前栈顶pxTopOfStack和创建任务时设置的最高栈顶pxStack作比较,如果pxTopOfStack <= pxStack则说明栈溢出。

  • 栈是向下生长的。
  • 这方法很高效,但是并不精确。

图
如上图,在任务A中不断调用FunctionA函数,该函数会创建一个1024字节大小的数组,但是创建任务A时指定的栈大小就是4*100字节,所以在调用FunctionA函数的过程中,会产生栈溢出。

但是在红色线条位置,FunctionA函数函数已经执行完一次了,它创建的1024字节所用的空间也被回收了,也就是pxTopOfStack + 1024,栈顶向上移动回去了。

这个时候任务A被切换走了,而pxTopOfStack > pxStack,此时就会判断没有栈溢出,实际上已经发生过溢出了。

  • 方法2

图

  • 创建任务时,它的整个栈被填入固定的值,比如:0xa5

  • 检测栈里最后16字节的数据,如果不是0xa5的话表示栈即将、或者已经被用完了

  • 没有方法1快速,但是也足够快,能捕获几乎所有的栈溢出

  • 为什么是几乎所有?可能有些函数使用栈时,非常凑巧地把栈最后16个字节设置为0xa5:几乎不可能。
  • configCHECK_FOR_STACK_OVERFLOW = 1时使用第一种方法检测栈溢出,configCHECK_FOR_STACK_OVERFLOW > 1时使用第二种方法检测栈溢出。

🍉信息统计

在Windows中,当系统卡顿时我们可以查看任务管理器找到最消耗CPU资源的程序。在FreeRTOS中,我们也可以查看任务使用CPU的情况、使用栈的情况,然后针对性地进行优化。

栈使用情况:

在创建任务时分配了栈,可以填入固定的数值比如0xa5,以后可以使用以下函数查看栈的高水位,也就是还有多少空余的栈空间:

UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
  • 任务运行时、任务被切换时,都会用到栈,栈里原来值(0xa5)就会被覆盖。
  • 该函数从栈的尾部开始判断,连续为0xa5的个数,就是栈的空闲空间个数(字节为单位)。
参数/返回值说明
xTask哪个任务
返回值任务运行过程中空闲内存容量的最小值。
  • 假设从栈尾开始连续为0xa5的栈空间是N字节,返回值是N/4。
  • 因为创建任务时,指定栈大小时的单位是字(Word)。

任务运行时间统计:

对于同优先级的任务,它们按照时间片轮流运行:你执行一个Tick,我执行一个Tick。是否可以在Tick中断函数中,统计当前任务的累计运行时间?

不行!很不精确,因为有更高优先级的任务就绪时,当前任务还没运行一个完整的Tick就被抢占了。

  • 我们需要比Tick更快的时钟,Tick周期时1ms,我们可以使用另一个定时器,让它发生中断的周期时0.1ms甚至更短。

图
如上图,来看task1对CPU的占用率:

  • 切换到Task1时,使用更快的定时器记录当前时间T1。
  • Task1被切换出去时,使用更快的定时器记录当前时间T4。
  • (T4-T1)就是它运行的时间。

任务1,2,3一共占用CPU的时间是13T,task1两次调度占用的时间是(4 + 2 = 6T),所以:

图

  • 关键点:在vTaskSwitchContext函数中,切换任务的时候,会使用更快的定时器统计运行时间。
  • 定时器超时时间越短,得到的CPU占用率精度越高。

🌰基本使用:

函数:

  • 获得任务统计信息:
UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray,
                                        const UBaseType_t uxArraySize,
                                        uint32_t * const pulTotalRunTime );
参数描述
pxTaskStatusArray指向一个TaskStatus_t结构体数组,用来保存任务的统计信息。
有多少个任务?可以用uxTaskGetNumberOfTasks()来获得。
uxArraySize数组大小、数组项个数,必须大于或等于uxTaskGetNumberOfTasks()
pulTotalRunTime用来保存当前总的运行时间(更快的定时器),可以传入NULL
返回值传入的pxTaskStatusArray数组,被设置了几个数组项。
注意:如果传入的uxArraySize小于uxTaskGetNumberOfTasks(),返回值就是0
  • 获得任务的统计信息,形式为可读的字符串:
void vTaskList( signed char *pcWriteBuffer );
  • pcWriteBuffer:系统输出信息到pcWriteBuffer,必须足够大。

图
如上图,使用该函数可以得到上面的信息格式,包括任务的状态,优先级,空闲栈(水位),任务号等等信息。

  • 获得任务的运行信息,形式为可读的字符串:
void vTaskGetRunTimeStats( signed char *pcWriteBuffer );
  • pcWriteBuffer:系统输出信息到pcWriteBuffer,必须足够大。

tu
如上图,可以获得上面格式的运行信息,包括任务名,运行时间,CPU占用率等等。

配置:

首先要配置一些宏开关,配置:

#define configGENERATE_RUN_TIME_STATS 1
#define configUSE_TRACE_FACILITY    1
#define configUSE_STATS_FORMATTING_FUNCTIONS  1

还需要实现宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(),它用来初始化更快的定时器,具体的定时器初始化函数需要我们自己实现。

实现这两个宏之一,它们用来返回当前时钟值(更快的定时器)

  • portGET_RUN_TIME_COUNTER_VALUE():直接返回时钟值
  • portALT_GET_RUN_TIME_COUNTER_VALUE(&Time):设置Time变量等于时钟值

初始化定时器:

配置一个片内的定时器作为更快的定时器,详细过程本喵就不讲解了,直接贴代码:
图
如上图所示定时器初始化代码,初始化函数是TimerInit()

图
如上图,在定时器中断函数中,反转运行标志位test_cnt,然后将计数值g_timer_cnt加加,每产生一次超时加加一次。

还实现了一个TimerGetCount函数来获取计数值g_timer_cnt,该值可以反应时长。


图
如上图,原本是需要在main函数中初始化定时器的,但是此时是让系统自动初始化,所以需要将初始化定义到宏中:

#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS 	TimerInit

图
如上图,在启动调度器的时候,会调用portCONFIGURE_TIMER_FOR_RUN_TIME_STATS ,从而就实现了更快定时器的自动初始化。

然后就是将获取计数值值的函数定义到宏中:

#define portGET_RUN_TIME_COUNTER_VALUE 			TimerGetCount

图
如上图代码,在切换任务的时候,先调用portGET_RUN_TIME_COUNTER_VALUE拿到当前时间,然后计算出运行时间,再累加运行时间,最后更新当前时间。

  • 此时就得到了该任务此次执行所花费的时间。

图
如上图红色框中所示,此时就完成了使用更快定时器来统计系统信息的所有配置。

演示:

tu
如上图代码所示,创建两个任务,任务1用来获取统计信息,任务2此时什么都不做,只是和任务1来竞争CPU资源。

图
如上图,此时的统计信息中,任务有四个,任务1和任务2,空闲任务,以及更快的定时器中断任务,以及它们的优先级水位线等等信息。


图

如上图,让任务2获取并打印运行信息,任务1什么都不做,只是和任务2竞争CPU资源。

图
如上图所示结果,任务仍然是这四个,还有每个任务的运行时间,以及CPU占用率,任务1几乎在一直运行,所以它的CPU占用率接近百分之百。

🍉总结

对于互斥的管理,虽然是简单粗暴的禁止其他任务,但是禁止又有两类:

  • 如果和当前任务竞争的既有普通任务也有中断任务,或者仅有中断任务,就需要禁止中断(包括普通任务禁止中断和ISR禁止中断两种)。
  • 如果和当前任务竞争的只是普通任务,那么就只需要禁止调度器即可。

禁止中断时,普通任务禁止会考虑到递归调用,有一个计数值,ISR禁止不考虑递归调用,直接禁止,没有计数值。


调试和信息统计虽然看着有点鸡肋,但是有时候还是还是非常有用的,要知道有这样的功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1146818.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VMware Horizon 8 2309 Enterprise虚拟桌面

VMware Horizon 8 2309 Enterprise虚拟桌面 一、虚拟桌面二、产品发布三、VMware Horizon 8 2309 Enterprise1.VMware Horizon 8 2309 Enterprise产品清单2.安装部署3. 优化工具总结 一、虚拟桌面 利用虚拟桌面和应用随时随地进行访问。 从云端进行管理 使用云端控制台和 Saa…

IO线程及相关函数

进程是资源分配的最小单位&#xff0c;线程是cpu调度的最小单位 一、概念&#xff1a; 线程指的是共享相同地址空间的多个任务 是一个轻量级的进程&#xff0c;为了提高系统的性能引入线程&#xff0c;线程和进程都参与统一的调度 在同一个进程中创建的线程共享该进程的地址空间…

预制菜配送小程序商城的效果如何

预制菜是近些年热度较高的新赛道&#xff0c;很多商家品牌入局&#xff0c;而投入到市场中也受到不少商家的喜欢&#xff0c;各种品牌和经销商层出不穷&#xff0c;目前各品牌主要以拓展市场和研究菜品为主&#xff0c;而线上无疑是很好的宣传销售渠道。 接下来让我们看看通过…

搭建gnn环境

1.无法激活 激活pytorch遇到报错usage: conda-script.py [-h] [--no-plugins] [-V] COMMAND ... conda-script.py: error: arg-CSDN博客 参考教程 【精选】手把手教你在windows10安装GNN相关环境&#xff08;torchtorch_geometricrdkitdeepchem&#xff09;_gnn环境相关的包-…

Docker Consul概述及构建

Docker Consul概述及构建 一、Consul概述1.1、什么是Consul1.2、consul 容器服务更新与发现1.3、服务注册与发现的含义1.4、consul-template概述1.5、registrator的作用 二、consul部署2.1、环境配置2.2、在主节点上部署consul2.3 、配置容器服务自动加入nginx集群2.3.1、安装G…

【Linux】:进程程序替换

进程程序替换 一.替换原理二.替换函数三.exec类函数 一.替换原理 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支)&#xff0c;子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时&#xff0c;该进程的用户空间代码和数据完全…

CleanMyMac X2024永久免费版mac电脑管家

日常使用中&#xff0c;很多用户忽略了Mac清除数据的重要性。当Mac运行速度越来越慢&#xff0c;发热严重&#xff0c;储存空间总是不足的时候&#xff0c;才意识到保持日常清理才是解决这些问题的根本。经常清理Mac&#xff0c;还有你意想不到的好处。 提高系统性能&#xff…

React-快速搭建开发环境

1.安装 说明&#xff1a;react-excise-01是创建的文件名 npx create-react-app react-excise-01 2. 打开文件 说明:we suggest that you begin by typing:下面即是步骤。 cd react-excise-01 npm start 3.显示

HIT_OS_LAB1 调试分析 Linux 0.00 引导程序

操作系统实验一 姓名&#xff1a;董帅学号&#xff1a;2021111547班级&#xff1a;21R0312 1.1 实验目的 熟悉实验环境掌握如何手写Bochs虚拟机的配置文件掌握Bochs虚拟机的调试技巧掌握操作系统启动的步骤 1.2 实验内容 1.2.1 掌握如何手写Bochs虚拟机的配置文件 boot: f…

拉扎维模拟CMOS集成电路设计西交张鸿老师课程P10~13视频学习记录

--------------------------------------------------------------------------------------------------------------------------------- p10 短沟道&#xff0c;除了沟长调&#xff0c;还可能出现速度饱和问题&#xff1b; 但是在拉扎维这本书里面没有考虑这个问题&#…

python随手小练12(南农作业题)

题目&#xff1a; 设有四个字母:a bcd,能组成多少个互不相同且无重复数字的三个字母的单词?各是什么? 解题思路:遍历全部可能的组合,把有重复的删除 具体操作&#xff1a; total 0 s [0, a, b, c, d] #注意&#xff1a;不能写成[0, a, b, c, d] for i in range(1, 5): …

为什么数组的下标是从0开始呢?

我们在许多的编程语言中&#xff0c;大部分的数组下标都是从零开始的&#xff0c;那为什么不是从一开始的呢&#xff1f; 首先我们&#xff0c;先要了解数组相关的定义。 数组&#xff08;Array&#xff09;是一种线性表数据结构。它用一组连续的内存空间&#xff0c;来存储一…

关于FTP的一些往事

公司每天都要从美国的服务器下载大量的语音文件。然后根据语音的内容完成相关的医疗报告。不同语音的实时性要求是不一样的&#xff0c;有些要求6小时内完成&#xff08;TAT6&#xff09; &#xff0c;有些则是12小时。中美之间的网速又特别慢&#xff0c;所以&#xff0c;如何…

计算机网络基础三

课程目标 理解路由表的作用 能够读懂路由表信息 能够使用图形抓包工具 wireshark 进行数据包的抓取 &#xff0c;如&#xff08; TCP/IP 的三次握手四次断开&#xff09; 一、路由表 思考&#xff1a; 什么是交换,什么是路由,什么是路由表&#xff1f;1. 交换是指同网络访…

在vscode中运行c++代码,windows 11系统

缘起 工作中用mac电脑&#xff0c;装了vscode和c&#xff0c;在家里的windows电脑上&#xff0c;也想运行vscode&#xff0c;这时候就遇到问题了。现在将问题解决方案记录下来&#xff0c;以供参考。 解决方案 开始比较懒&#xff0c;直接百度一下&#xff0c;找了几篇文章&…

高阶数据结构学习 —— 图(1)

文章目录 1、并查集2、了解图3、邻接矩阵4、压缩路径5、基本概念6、邻接表 1、并查集 并查集是一个森林&#xff0c;是由多棵树组成的。这相当于整套数据&#xff0c;分成多个集合。查找有交集的集合们&#xff0c;会把它们合并起来&#xff0c;所以叫并查集。 一开始拿到的是…

idea上怎么将新创建项目转为maven项目

场景 在刚创建的一个项目中&#xff0c;往往没有被识别为maven项目&#xff0c;怎么做呢&#xff1f; 方法 然后选maven&#xff0c;这样这个项目就变成了maven项目

计数排序——不用比较的排序

原理&#xff1a; 代码&#xff1a; void CountSort(int* a, int n) {int max a[0];int min a[0];for (int i 0; i < n; i){if (a[i] > max){max a[i];}if (a[i] < min){min a[i];}}int grang max - min 1;int* count (int*)malloc(sizeof(int) * grang);me…

【SpringBoot】Docker部署

docker部署是主流的部署方式&#xff0c;极大的方便了开发部署环境&#xff0c;保持了环境的统一&#xff0c;也是实现自动化部署的前提。 1 项目的目录结构 package: 点击打包&#xff0c;生成 xxx-SNAPSHOT.jar target目录: 打包生成目录&#xff0c;生成的jar存放位置Docke…

Leetcode—2558.从数量最多的堆取走礼物【简单】

2023每日刷题&#xff08;十二&#xff09; Leetcode—2558.从数量最多的堆取走礼物 大顶堆实现代码 void swap(int *a, int *b) {int tmp *a;*a *b;*b tmp; }void downAdjustHeap(int *heap, int low, int high) {int i low;int j 2 * i 1;while(j < high) {if(j …