【06】FreeRTOS临界段代码保护及调度器挂起与恢复

news2024/9/21 16:26:26

目录

1.临界段代码保护简介

2.临界段代码保护函数介绍

2.1任务级临界区调用格式示例

2.2中断级临界区调用格式示例

2.3函数调用特点

2.4任务级进入和退出临界段函数

2.5中断级进入和退出临界段函数

3.任务调度器的挂起和恢复

3.1任务调度器挂起函数vTaskSuspendAll()

3.2任务调度器恢复函数xTaskResumeAll()

4.总结


1.临界段代码保护简介

       临界段代码也叫做临界区,是指那些必须完整运行不能被打断的代码段。

适用的场合,例如

1,外设:需严格按照时序初始化的外设:IIC、SPI等等;

2,系统:系统自身需求(FreeRTOS源码很多地方用到了临界段代码保护,属于系统自身的需求);

3,用户:用户需求(用户想某些代码段不想被打断);

可以打断当前程序运行的情况:1、中断;2、任务调度(高优先级的任务可以抢占低优先级的任务)。

如果不想被打断,则关闭中断。这样做有两个好处,1是关闭了中断,中断将不再打断正在运行的程序;2是PendSV属于最低优先级的中断,在PendSV中进行任务切换和调度,FreeRTOS关闭中断,5~15优先级的中断将不起作用,则不能进行任务切换,但是优先级为0~4的中断可以正常打断正在运行的程序

2.临界段代码保护函数介绍

        FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断,流程图如下所示

临界段代码保护函数
函数描述
taskENTER_CRITICAL()任务级进入临界段
taskEXIT_CRITICAL()任务级退出临界段
taskENTER_CRITICAL_FROM_ISR()中断级进入临界段(中断服务函数中调用
taskEXIT_CRITICAL_FROM_ISR()中断级退出临界段(中断服务函数中调用

         进入或退出临界段,本质是关闭或开启中断。taskENTER_CRITICAL()taskEXIT_CRITICAL()与【05】FreeRTOS的中断管理此篇文章中提到的关闭和开启中断,本质也是调用【05】文章中提到的关闭中断函数portENABLE_INTERRUPTS()和开启中断函数portDISABLE_INTERRUPTS(),只不过做了嵌套功能。

2.1任务级临界区调用格式示例

taskENTER_CRITICAL() ;//关闭中断
{
        … …	/* 临界区 */
}
taskEXIT_CRITICAL()//开启中断

2.2中断级临界区调用格式示例

        taskENTER_CRITICAL_FROM_ISR()函数提前返回之前中断屏蔽寄存器的值,并关闭中断。taskEXIT_CRITICAL_FROM_ISR()函数退出临界区时,将之前保存的“值”赋值给中断屏蔽寄存器,保持原有的状态。

uint32_t  save_status;
save_status  = taskENTER_CRITICAL_FROM_ISR();
{
        … …	/* 临界区 */
}
taskEXIT_CRITICAL_FROM_ISR(save_status );	

2.3函数调用特点

1、成对使用

2、支持嵌套(使用两个进入临界段,则有两个退出临界段

3、尽量保持临界段耗时短

2.4任务级进入和退出临界段函数

       进入临界段函数 taskENTER_CRITICAL()最底层为以下函数,portDISABLE_INTERRUPTS()关闭中断,变量uxCriticalNesting计算进入临界段的次数。

void vPortEnterCritical( void )
{
    portDISABLE_INTERRUPTS();
    uxCriticalNesting++;

    /* This is not the interrupt safe version of the enter critical function so
     * assert() if it is being called from an interrupt context.  Only API
     * functions that end in "FromISR" can be used in an interrupt.  Only assert if
     * the critical nesting count is 1 to protect against recursive calls if the
     * assert function also uses a critical section. */
    if( uxCriticalNesting == 1 )
    {
        configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
    }
}

         退出临界段函数taskEXIT_CRITICAL()最低层函数为以下函数,将变量uxCriticalNesting递减,直至变量为0时开启中断。变量uxCriticalNesting不能为0,如果为0,减1后则变成负值。

void vPortExitCritical( void )
{
    configASSERT( uxCriticalNesting );
    uxCriticalNesting--;

    if( uxCriticalNesting == 0 )
    {
        portENABLE_INTERRUPTS();
    }
}

        configASSERT()断言功能是进行调试的,报一些提示、报错,判断x是否等于0,如果等于0则打印错误。

#define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ )

2.5中断级进入和退出临界段函数

        进入临界段函数taskENTER_CRITICAL_FROM_ISR()最底层为以下函数,返回无符号32位。

mrs ulReturn, basepri:将中断屏蔽寄存器中的值读取出来;

msr basepri, ulNewBASEPRI:关闭中断;

    static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
    {
        uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

        __asm
        {
            /* Set BASEPRI to the max syscall priority to effect a critical
             * section. */
/* *INDENT-OFF* */
            mrs ulReturn, basepri
            msr basepri, ulNewBASEPRI
            dsb
            isb
/* *INDENT-ON* */
        }

        return ulReturn;
    }

        退出临界段函数taskEXIT_CRITICAL_FROM_ISR()最低层函数为以下函数,将关闭中断时返回的值代入中断屏蔽寄存器中,保持进入临界段和退出临界段时中断状态一致。

    static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
    {
        __asm
        {
            /* Barrier instructions are not used as this function is only used to
             * lower the BASEPRI value. */
/* *INDENT-OFF* */
            msr basepri, ulBASEPRI
/* *INDENT-ON* */
        }
    }

3.任务调度器的挂起和恢复

        挂起,意思就是暂停,任务调度器停止,代表任务将不能切换。挂起任务调度器, 任务将不能进行调度、切换(中断依旧可以响应),调用此函数不需要关闭中断。

函数描述
vTaskSuspendAll()挂起任务调度器
xTaskResumeAll()恢复任务调度器(具有返回值,判断是否需要进行任务切换

 使用格式示例(首先挂起任务调度器,执行内容不允许被其他内容打断,中断可以正常打断执行的内容,防止任务与任务之间的资源抢夺,恢复任务调度器):

vTaskSuspendAll() ;
{
        … …	/* 内容 */
}
xTaskResumeAll()	;

1、与临界区不一样的是,挂起任务调度器,未关闭中断;

2、它仅仅是防止了任务之间的资源争夺,中断照样可以直接响应;

3、挂起调度器的方式,适用于临界区位于任务与任务之间(任务与任务之间不抢夺,属于任务与任务之间的临界区);既不用去延时中断,又可以做到临界区的安全(如果临界区代码量过大,执行时间较长,将导致延时中断响应,使用此方式可以避免延时中断

3.1任务调度器挂起函数vTaskSuspendAll()

        任务调度器挂起函数只是将变量uxSchedulerSuspended自加,根本上任务调度器是进行任务的切换,任务切换是PendSV中断进行的,在嘀嗒定时器中断服务函数SysTick_Handler()中触发PendSV中断。  变量uxSchedulerSuspended默认为0,pdFALSE。

void vTaskSuspendAll( void )
{
    /* A critical section is not required as the variable is of type
     * BaseType_t.  Please read Richard Barry's reply in the following link to a
     * post in the FreeRTOS support forum before reporting this as a bug! -
     * https://goo.gl/wu4acr */

    /* portSOFTWARE_BARRIER() is only implemented for emulated/simulated ports that
     * do not otherwise exhibit real time behaviour. */
    portSOFTWARE_BARRIER();

    /* The scheduler is suspended if uxSchedulerSuspended is non-zero.  An increment
     * is used to allow calls to vTaskSuspendAll() to nest. */
    ++uxSchedulerSuspended;

    /* Enforces ordering for ports and optimised compilers that may otherwise place
     * the above increment elsewhere. */
    portMEMORY_BARRIER();
}

嘀嗒定时器中断服务函数SysTick_Handler():

void SysTick_Handler(void)
{	
    HAL_IncTick();
	if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)					//OS开始跑了,才执行正常的调度处理
	{
		xPortSysTickHandler();
	}
}

         xPortSysTickHandler()函数中,只要xTaskIncrementTick()函数返回值不为pdFALSE ,则触发PendSV中断。

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();
}

        由于变量uxSchedulerSuspended已经自加,则不能进入第一个if,则最终返回变量xSwitchRequired的值为pdFALSE。由以上函数知则触发不了PendSV中断,任务调度器被挂起。

BaseType_t xTaskIncrementTick( void )
{
    TCB_t * pxTCB;
    TickType_t xItemValue;
    BaseType_t xSwitchRequired = pdFALSE;

    /* Called by the portable layer each time a tick interrupt occurs.
     * Increments the tick then checks to see if the new tick value will cause any
     * tasks to be unblocked. */
    traceTASK_INCREMENT_TICK( xTickCount );

    if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
    {
        /* Minor optimisation.  The tick count cannot change in this
         * block. */
        const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;

        /* Increment the RTOS tick, switching the delayed and overflowed
         * delayed lists if it wraps to 0. */
        xTickCount = xConstTickCount;

        if( xConstTickCount == ( TickType_t ) 0U ) /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. */
        {
            taskSWITCH_DELAYED_LISTS();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        /* See if this tick has made a timeout expire.  Tasks are stored in
         * the  queue in the order of their wake time - meaning once one task
         * has been found whose block time has not expired there is no need to
         * look any further down the list. */
        if( xConstTickCount >= xNextTaskUnblockTime )
        {
            for( ; ; )
            {
                if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
                {
                    /* The delayed list is empty.  Set xNextTaskUnblockTime
                     * to the maximum possible value so it is extremely
                     * unlikely that the
                     * if( xTickCount >= xNextTaskUnblockTime ) test will pass
                     * next time through. */
                    xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
                    break;
                }
                else
                {
                    /* The delayed list is not empty, get the value of the
                     * item at the head of the delayed list.  This is the time
                     * at which the task at the head of the delayed list must
                     * be removed from the Blocked state. */
                    pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*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. */
                    xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );

                    if( xConstTickCount < xItemValue )
                    {
                        /* It is not time to unblock this item yet, but the
                         * item value is the time at which the task at the head
                         * of the blocked list must be removed from the Blocked
                         * state -  so record the item value in
                         * xNextTaskUnblockTime. */
                        xNextTaskUnblockTime = xItemValue;
                        break; /*lint !e9011 Code structure here is deemed easier to understand with multiple breaks. */
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }

                    /* It is time to remove the item from the Blocked state. */
                    listREMOVE_ITEM( &( pxTCB->xStateListItem ) );

                    /* Is the task waiting on an event also?  If so remove
                     * it from the event list. */
                    if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
                    {
                        listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }

                    /* Place the unblocked task into the appropriate ready
                     * list. */
                    prvAddTaskToReadyList( pxTCB );

                    /* A task being unblocked cannot cause an immediate
                     * context switch if preemption is turned off. */
                    #if ( configUSE_PREEMPTION == 1 )
                        {
                            /* Preemption is on, but a context switch should
                             * only be performed if the unblocked task has a
                             * priority that is equal to or higher than the
                             * currently executing task. */
                            if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
                            {
                                xSwitchRequired = pdTRUE;
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                    #endif /* configUSE_PREEMPTION */
                }
            }
        }

        /* Tasks of equal priority to the currently running task will share
         * processing time (time slice) if preemption is on, and the application
         * writer has not explicitly turned time slicing off. */
        #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
            {
                if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
                {
                    xSwitchRequired = pdTRUE;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */

        #if ( configUSE_TICK_HOOK == 1 )
            {
                /* Guard against the tick hook being called when the pended tick
                 * count is being unwound (when the scheduler is being unlocked). */
                if( xPendedTicks == ( TickType_t ) 0 )
                {
                    vApplicationTickHook();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        #endif /* configUSE_TICK_HOOK */

        #if ( configUSE_PREEMPTION == 1 )
            {
                if( xYieldPending != pdFALSE )
                {
                    xSwitchRequired = pdTRUE;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        #endif /* configUSE_PREEMPTION */
    }
    else
    {
        ++xPendedTicks;

        /* The tick hook gets called at regular intervals, even if the
         * scheduler is locked. */
        #if ( configUSE_TICK_HOOK == 1 )
            {
                vApplicationTickHook();
            }
        #endif
    }

    return xSwitchRequired;
}

3.2任务调度器恢复函数xTaskResumeAll()

        taskENTER_CRITICAL()函数进入临界区,关闭中断(避免执行后面程序时,被中断或任务切换所打断)。

BaseType_t xTaskResumeAll( void )
{
    TCB_t * pxTCB = NULL;
    BaseType_t xAlreadyYielded = pdFALSE;

    /* If uxSchedulerSuspended is zero then this function does not match a
     * previous call to vTaskSuspendAll(). */
    configASSERT( uxSchedulerSuspended );

    /* It is possible that an ISR caused a task to be removed from an event
     * list while the scheduler was suspended.  If this was the case then the
     * removed task will have been added to the xPendingReadyList.  Once the
     * scheduler has been resumed it is safe to move all the pending ready
     * tasks from this list into their appropriate ready list. */
    taskENTER_CRITICAL();
    {

将变量uxSchedulerSuspended自减,如果变量减至0,则表明任务调度器已经被恢复。 

        --uxSchedulerSuspended;
        if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
        {

         判断当前任务总量是否大于0,如果当前创建任务总数不大于0,则恢复任务调度器无意义

            if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
            {
                /* Move any readied tasks from the pending list into the
                 * appropriate ready list. */

        判断等待就绪列表中是否有任务,如果有任务则将列表中任务全部移除,添加到就绪列表。在中断级恢复任务中,当调度器被挂起时,恢复任务不是恢复到就绪列表,而是等待就绪列表中


                while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
                {
                    pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) ); /*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. */
                    listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
                    portMEMORY_BARRIER();
                    listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
                    prvAddTaskToReadyList( pxTCB );

        判断就绪列表中的任务优先级是否大于等于正在运行任务的优先级,是则进行任务切换。

                    /* If the moved task has a priority higher than or equal to
                     * the current task then a yield must be performed. */
                    if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
                    {
                        xYieldPending = pdTRUE;
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }

        更新下一次阻塞的时间。 

                if( pxTCB != NULL )
                {
                    /* A task was unblocked while the scheduler was suspended,
                     * which may have prevented the next unblock time from being
                     * re-calculated, in which case re-calculate it now.  Mainly
                     * important for low power tickless implementations, where
                     * this can prevent an unnecessary exit from low power
                     * state. */
                    prvResetNextTaskUnblockTime();
                }

         恢复滴答定时器,在任务调度器被挂起时丢失的节拍数(只有任务调度器未被挂起时,节拍数才会自加)。任务被挂起时自加的变量xPendedTicks,恢复后将变量的值赋值给变量xPendedCounts

                /* If any ticks occurred while the scheduler was suspended then
                 * they should be processed now.  This ensures the tick count does
                 * not  slip, and that any delayed tasks are resumed at the correct
                 * time. */
                {
                    TickType_t xPendedCounts = xPendedTicks; /* Non-volatile copy. */

        如果xPendedCounts大于0,则调用函数xTaskIncrementTick(),将丢失的节拍数进行加回。通过函数xTaskIncrementTick()的返回值判断是否需要任务切换,每补齐一次,则xPendedCounts自减。

                    if( xPendedCounts > ( TickType_t ) 0U )
                    {
                        do
                        {
                            if( xTaskIncrementTick() != pdFALSE )
                            {
                                xYieldPending = pdTRUE;
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }

                            --xPendedCounts;
                        } while( xPendedCounts > ( TickType_t ) 0U );

                        xPendedTicks = 0;
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }

         如果xYieldPending不等于pdFALSE则进行任务切换。宏configUSE_PREEMPTION(在FreeRTOSCofig.h中配置)判断是否是抢占式任务调度器,是则将xAlreadyYielded置1。函数taskYIELD_IF_USING_PREEMPTION()进行任务切换。

                if( xYieldPending != pdFALSE )
                {
                    #if ( configUSE_PREEMPTION != 0 )
                        {
                            xAlreadyYielded = pdTRUE;
                        }
                    #endif
                    taskYIELD_IF_USING_PREEMPTION();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    taskEXIT_CRITICAL();

    return xAlreadyYielded;
}

4.总结

 ​​​​​​​

 

 

 

 

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

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

相关文章

为什么大部分虚拟主机都配置SSD

对于任何站长来说&#xff0c;拥有一个不会加载的漂亮网站可能是毁灭性的。选择正确的托管服务对于确保网站始终以最佳状态运行至关重要。而由于新手站长呈爆发性增长态势&#xff0c;选择虚拟主机的站长日趋增多。本文就将介绍大部分虚拟主机都配置SSD的原因。SSD优势SSD在数据…

Windows下MySQL5与MySQL8的下载、安装、配置

MySQL版本简介 MySQL Community Server 社区版本&#xff0c;开源免费&#xff0c;自由下载&#xff0c;但不提供官方技术支持&#xff0c;适用于 大多数普通用户。MySQL Enterprise Edition 企业版本&#xff0c;需付费&#xff0c;不能在线下载&#xff0c;可以试用30天。提供…

EMT4J详细介绍与使用,帮你找到Java版本升级带来的问题,让你在项目jdk升级不在头疼

Java版本升级带来的问题 前因 java更新迭代速度巨快无比&#xff0c;Spring Framework 6 等项目已经至少需要 Java 17。但是&#xff0c;对于 Java 版本的采用是相对缓慢的。例如&#xff0c;在 Java 11 发布四年之后&#xff08;2022年&#xff09;&#xff0c;只有不到 49%…

[C语言]操作符

目录 1.操作符分类 2.算术操作符 3.位移操作符 3.1左移操作符 3.2右移操作符 4.位操作符 4.1’&‘&#xff08;按位与&#xff09; 4.2’|‘&#xff08;按位或&#xff09; 4.3‘^’&#xff08;按位异或&#xff09; 5.赋值操作符 5.1复合赋值符 6.单目操作…

IDEA搭建Finchley.SR2版本的SpringCloud父子基础项目-------Hystrix断路器

1.1分布式系统面临的问题 复杂分布式体系结构中的应用程序有数十个依赖关系&#xff0c;每个依赖关系在某些时候将不可避免地失败。 服务雪崩 多个微服务之间调用的时候&#xff0c;假设微服务A调用微服务B和微服务C&#xff0c;微服务B和微服务C又调用其它的微服务&#xff0…

高薪前端都应该具备的开发好习惯

格拉德威尔曾提出过一个“一万小时定律”&#xff0c;即任何人从平凡到大师的必要条件&#xff0c;就是历经1万小时的锤炼&#xff0c;而这“1万小时”也不是达到就行&#xff1b;如何构成&#xff0c;才是能否成为行业资深的关键。总结起来&#xff0c;就是四个字&#xff1a;…

Databend 开源周报 第 77 期

Databend 是一款强大的云数仓。专为弹性和高效设计。自由且开源。 即刻体验云服务&#xff1a;https://app.databend.com 。 What’s New 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 Features & Improvements Meta 使用 expressin::TableSch…

蓝桥杯单片机快速得奖方法(分享一些实用技巧)

文章目录前言一、蓝桥杯单片机痛点1.LED灯微亮2.数码管微亮3.蜂鸣器乱叫4.驱动程序不会写5.按键程序灵敏度低容易误操作6.矩阵按键代码总是记不住一写就忘记7.使用大量延时函数导致程序运行效率低下二、痛点解决方法1.锁存器的错误操作2.代码不熟练3.没有高效的代码总结前言 又…

微信小程序 java python django加油站服务系统

目 录 摘 要 I ABSTRACT II 第一章 绪 论 1 1.1选题背景 2 1.2研究现状 3 1.3研究内容 7 第二章 开发工具及关键技术介绍 8 2.1微信开发者工具 8 2.2小程序框架以及目录结构介绍 8 第三章 系统分析 10 3.1需求分析 10 3.2可行性分析 1…

Appium是如何工作的

Appium是由node.js开发的开源自动化测试工具&#xff0c;可用来测试移动端的Native、Hybrid和移动Web应用&#xff0c;被测平台包括Android和iOS&#xff08;最近宣称已支持Windows&#xff09;。 Native apps – 使用Android、iOS和Windows SDK开发的应用。 Mobile web apps …

存储区域网络将占下一代数据存储市场的 7%

根据 Future Market Insights 的最新行业分析&#xff0c;全球存储区域网络 (SAN) 市场预计将显示稳定的增长机会&#xff0c;在 2022 年至 2029 年的评估期内复合年增长率约为 3.9%。 2021 年全球市场估值达到 195.76 亿美元&#xff0c;到 2029 年将进一步扩大至 268.67 亿美…

车载以太网 - SomeIP测试专栏 - SomeIP Entry - 04

前面总纲内容有说,车载以太网中的SomeIP内容是非常多的,并且为了实现SOA的相关需求,提供了多种多样的报文类型,因此也就有了今天要说的SomeIP-SD中的重要组成部分之一的条目(Entry)部分,而SomeIP-SD在车载网络中占有相当大的比重,可以当做是一定要实现的,如果这块不实…

实现自己的数据库四

一前言上一篇已经说明了B树的一些原理&#xff0c;也讲到&#xff0c;我们目前采用的持久化数据的方式&#xff0c;而且我们是单独的插入数据&#xff0c;没有任何元数据信息&#xff0c;虽然插入的速度很快&#xff0c;因为是采用追加的方式。但是这种方式插入速度很快&#x…

Pd1 药物研发进展|销售数据|市场规模|竞争格局|前景分析

Programmed Death-1 (PD-1; CD279) 是一种在活化 T 细胞中诱导的抑制性受体&#xff0c;作为多种癌症的一线治疗药物。然而&#xff0c;严重的免疫相关不良反应限制了PD-1/PD-L1单克隆抗体的临床应用&#xff0c;尽管其疗效良好。 也迫切需要开发针对 PD-1/PD-L1 轴的新型抑制剂…

Torch 论文复现:Vision Transformer (ViT)

论文标题&#xff1a;An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale 从 TPUv3-core-days 可以看到&#xff0c;ViT 所需的训练时间比 ResNet 更短&#xff0c;同时 ViT 取得了更高的准确率 ViT 的基本思想是&#xff0c;把一张图片拆分成若干个…

Paddle入门实战系列(四):中文场景文字识别

✨写在前面&#xff1a;强烈推荐给大家一个优秀的人工智能学习网站&#xff0c;内容包括人工智能基础、机器学习、深度学习神经网络等&#xff0c;详细介绍各部分概念及实战教程&#xff0c;通俗易懂&#xff0c;非常适合人工智能领域初学者及研究者学习。➡️点击跳转到网站。…

每日学术速递1.31

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 今天带来的arXiv上最新发表的3篇NLP论文。 Subjects: cs.CL、cs.AI、cs.DB、cs.LG 1.Editing Language Model-based Knowledge Graph Embeddings 标题&#xff1a;编辑基于语言模型的知识图谱嵌入 作…

C语言求幂运算——奇特中文变量命名

写在前面 主要涉及C/C趣味编程应用及解析面向初学者撰写专栏&#xff0c;个人代码原创如有错误之处请各位读者指正&#xff0c;各位可以类比做自己的编程作业请读者评论回复、参与投票&#xff0c;反馈给作者&#xff0c;我会获得持续更新各类干货的动力。 致粉丝&#xff1a;…

【Rust】8. 包、Crate 和 模块管理(公有、私有特性)

8.1 包和 Crate 8.1.1 基本概念 crate 是 Rust 在编译时最小的代码单位&#xff1b;crate 有两种形式&#xff1a;二进制项&#xff08;可以被编译为可执行程序&#xff09;和库&#xff08;没有 main 函数&#xff0c;也不会编译为可执行程序&#xff0c;而是提供一些诸如函…

Selenium+Java+Maven(12):引入Allure作为报告生成器

一、前言 本篇作为SeleniumJava系列的补充&#xff0c;讲了如何使用Allure作为测试报告生成器&#xff0c;来替代TestNG自带的测试报告或ReportNG测试报告&#xff0c;生成更加美观的&#xff08;领导更喜欢的&#xff09;测试报表。话不多说&#xff0c;一起来学习吧~ 二、A…