(学习日记)2024.04.03:UCOSIII第三十一节:信号量函数接口讲解

写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。


标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。


点击此处进入学习日记的总目录

2024.04.03:UCOSIII第三十一节:信号量函数接口讲解

  • 四十五、UCOSIII:信号量函数接口讲解
    • 1、创建信号量函数OSSemCreate()
    • 2、信号量删除函数OSSemDel()
    • 3、信号量释放函数OSSemPost()
      • 1. OSSemPost()
      • 2. OS_SemPost()源码
    • 4、信号量获取函数OSSemPend()
    • 5、使用信号量的注意事项

四十五、UCOSIII:信号量函数接口讲解

1、创建信号量函数OSSemCreate()

在定义完信号量结构体变量后就可以调用 OSSemCreate()函数创建一个信号量,跟消息队列的创建差不多。
其实这里的“创建信号量”指的就是对内核对象(信号量)的一些初始化。
要特别注意的是内核对象使用之前一定要先创建, 这个创建过程必须要保证在所有可能使用内核对象的任务之前,所以一般我们都是在创建任务之前就创建好系统需要的内核对象(如信号量等)。
创建信号量函数OSSemCreate()源码具体如下

void  OSSemCreate (OS_SEM   *p_sem,  //(1)    //信号量控制块指针
                CPU_CHAR    *p_name, //(2)    //信号量名称
                OS_SEM_CTR   cnt,    //(3)    //资源数目或事件是否发生标志
                OS_ERR      *p_err)  //(4)    //返回错误类型
{
    CPU_SR_ALLOC();
    //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和定义一个局部变
    //量,用于保存关中断前的 CPU 状态寄存器 SR(临界段关中断只需保存SR)
    //,开中断时将该值还原。

#ifdef OS_SAFETY_CRITICAL//(5)//如果启用(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0)           //如果错误类型实参为空
    {
        OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数
        return;                         //返回,不继续执行
    }
#endif

#ifdef OS_SAFETY_CRITICAL_IEC61508//(6)//如果启用(默认禁用)了安全关键
//如果是在调用OSSafetyCriticalStart()后创建该信号量
    if (OSSafetyCriticalStartFlag == DEF_TRUE)
    {
        *p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME; //错误类型为“非法创建内核对象”
        return;                                   //返回,不继续执行
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u     //(7)
//如果启用(默认启用)了中断中非法调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0)      //如果该函数是在中断中被调用
    {
        *p_err = OS_ERR_CREATE_ISR;         //错误类型为“在中断函数中创建对象”
        return;                                   //返回,不继续执行
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u//(8)//如果启用(默认启用)了参数检测
    if (p_sem == (OS_SEM *)0)         //如果参数 p_sem 为空
    {
        *p_err = OS_ERR_OBJ_PTR_NULL;  //错误类型为“信号量对象为空”
        return;                       //返回,不继续执行
    }
#endif

    OS_CRITICAL_ENTER();                            //进入临界段
    p_sem->Type    = OS_OBJ_TYPE_SEM;       //(9)     //初始化信号量指标
    p_sem->Ctr     = cnt;
    p_sem->TS      = (CPU_TS)0;
    p_sem->NamePtr = p_name;
    OS_PendListInit(&p_sem->PendList);      //(10)//初始化该信号量的等待列表

#if OS_CFG_DBG_EN > 0u//如果启用(默认启用)了调试代码和变量
    OS_SemDbgListAdd(p_sem); //将该定时添加到信号量双向调试链表
#endif
    OSSemQty++;           //(11)//信号量个数加1

    OS_CRITICAL_EXIT_NO_SCHED();     //退出临界段(无调度)
    *p_err = OS_ERR_NONE;             //错误类型为“无错误”
}
  • (1):信号量控制块指针,指向我们定义的信号量控制块结构体变量, 所以在创建之前我们需要先定义一个信号量控制块变量。
  • (2):信号量名称,字符串形式。
  • (3):这个值表示初始化时候资源的个数或事件是否发生标志, 一般信号量是二值信号量的时候,这个值一般为0或者为1,而如果信号量作为计数信号量的时候,这个值一般定义为初始资源的个数。
  • (4):用于保存返回错误类型。
  • (5):如果启用了安全检测(默认禁用),在编译时则会包含安全检测相关的代码, 如果错误类型实参为空,系统会执行安全检测异常函数,然后返回,不执行创建信号量操作。
  • (6):如果启用(默认禁用)了安全关键检测,在编译时则会包含安全关键检测相关的代码, 如果是在调用OSSafetyCriticalStart()后创建该信号量,则是非法的,返回错误类型为“非法创建内核对象”错误代码,并且退出,不执行创建信号量操作。
  • (7):如果启用了中断中非法调用检测(默认启用), 在编译时则会包含中断非法调用检测相关的代码,如果该函数是在中断中被调用,则是非法的,返回错误类型为“在中断中创建对象”的错误代码, 并且退出,不执行创建信号量操作。
  • (8):如果启用了参数检测(默认启用), 在编译时则会包含参数检测相关的代码,如果p_sem参数为空,返回错误类型为“创建对象为空”的错误代码,并且退出,不执行创建信号量操作。
  • (9):进入临界段,然后进行初始化信号量相关信息, 如初始化信号量的类型、名字、可用信号量Ctr、记录时间戳的变量TS等。
  • (10):调用OS_PendListInit()函数初始化该信号量的等待列表。
  • (11):系统信号量个数加1。

如果我们创建一个初始可用信号量个数为5的信号量,那么信号量创建成功的示意图具体见图
在这里插入图片描述

创建信号量函数OSSemCreate()的使用实例具体如下:

OS_SEM SemOfKey;          //标志KEY1是否被按下的信号量

/* 创建信号量 SemOfKey */
OSSemCreate((OS_SEM      *)&SemOfKey,    //指向信号量变量的指针
            (CPU_CHAR    *)"SemOfKey",    //信号量的名字
            (OS_SEM_CTR   )0,
//信号量这里是指示事件发生,所以赋值为0,表示事件还没有发生
            (OS_ERR      *)&err);         //错误类型

2、信号量删除函数OSSemDel()

OSSemDel()用于删除一个信号量,信号量删除函数是根据信号量结构(信号量句柄)直接删除的,删除之后这个信号量的所有信息都会被系统清空, 而且不能再次使用这个信号量了。
但是需要注意的是,如果某个信号量没有被定义,那也是无法被删除的,如果有任务阻塞在该信号量上, 那么尽量不要删除该信号量。
想要使用互斥量删除函数就必须将OS_CFG_SEM_DEL_EN宏定义配置为1,其函数源码具体如下

#if OS_CFG_SEM_DEL_EN > 0u          //如果启用了 OSSemDel() 函数
OS_OBJ_QTY  OSSemDel (OS_SEM  *p_sem,       //(1)     //信号量指针
                    OS_OPT   opt,           //(2)     //选项
                    OS_ERR  *p_err)         //(3)     //返回错误类型
{
    OS_OBJ_QTY     cnt;
    OS_OBJ_QTY     nbr_tasks;
    OS_PEND_DATA  *p_pend_data;
    OS_PEND_LIST  *p_pend_list;
    OS_TCB        *p_tcb;
    CPU_TS         ts;
    CPU_SR_ALLOC();



#ifdef OS_SAFETY_CRITICAL//(4)//如果启用(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0)                  //如果错误类型实参为空
    {
        OS_SAFETY_CRITICAL_EXCEPTION();        //执行安全检测异常函数
        return ((OS_OBJ_QTY)0);                //返回0(有错误),不继续执行
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u//(5)//如果启用了中断中非法调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0)   //如果该函数在中断中被调用
    {
        *p_err = OS_ERR_DEL_ISR;                //返回错误类型为“在中断中删除”
        return ((OS_OBJ_QTY)0);                //返回0(有错误),不继续执行
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u//(6)//如果启用了参数检测
    if (p_sem == (OS_SEM *)0)                 //如果 p_sem 为空
    {
        *p_err = OS_ERR_OBJ_PTR_NULL;          //返回错误类型为“内核对象为空”
        return ((OS_OBJ_QTY)0);               //返回0(有错误),不继续执行
    }
    switch (opt)                    //(7)//根据选项分类处理
    {
    case OS_OPT_DEL_NO_PEND:              //如果选项在预期之内
    case OS_OPT_DEL_ALWAYS:
    break;                           //直接跳出

    default:                        //(8)//如果选项超出预期
        *p_err = OS_ERR_OPT_INVALID;      //返回错误类型为“选项非法”
        return ((OS_OBJ_QTY)0);          //返回0(有错误),不继续执行
    }
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u//(9)//如果启用了对象类型检测
    if (p_sem->Type != OS_OBJ_TYPE_SEM)      //如果 p_sem 不是信号量类型
    {
        *p_err = OS_ERR_OBJ_TYPE;           //返回错误类型为“内核对象类型错误”
        return ((OS_OBJ_QTY)0);            //返回0(有错误),不继续执行
    }
#endif

    CPU_CRITICAL_ENTER();                      //关中断
    p_pend_list = &p_sem->PendList;  //(10)//获取信号量的等待列表到p_pend_list
    cnt         = p_pend_list->NbrEntries;    //获取等待该信号量的任务数
    nbr_tasks   = cnt;
    switch (opt)                         //(11)//根据选项分类处理
    {
    case OS_OPT_DEL_NO_PEND:               //(12)
    //如果只在没有任务等待的情况下删除信号量
        if (nbr_tasks == (OS_OBJ_QTY)0)   //如果没有任务在等待该信号量
        {
#if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量
            OS_SemDbgListRemove(p_sem);   //将该信号量从信号量调试列表移除
#endif
            OSSemQty--;               //(13)//信号量数目减1
            OS_SemClr(p_sem);          //(14)//清除信号量内容
            CPU_CRITICAL_EXIT();          //开中断
            *p_err = OS_ERR_NONE;      //(15)//返回错误类型为“无错误”
        }
        else//(16)//如果有任务在等待该信号量
        {
            CPU_CRITICAL_EXIT();          //开中断
            *p_err = OS_ERR_TASK_WAITING;
        //返回错误类型为“有任务在等待该信号量”
        }
        break;

    case OS_OPT_DEL_ALWAYS:               //(17)//如果必须删除信号量
        OS_CRITICAL_ENTER_CPU_EXIT();                  //锁调度器,并开中断
        ts = OS_TS_GET();                 //(18)//获取时间戳
        while (cnt > 0u)                    //(19)
        //逐个移除该信号量等待列表中的任务
        {
            p_pend_data = p_pend_list->HeadPtr;
            p_tcb       = p_pend_data->TCBPtr;
            OS_PendObjDel((OS_PEND_OBJ *)((void *)p_sem),
                        p_tcb,
                        ts);                        //(20)
            cnt--;
        }
#if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量
        OS_SemDbgListRemove(p_sem);
        //将该信号量从信号量调试列表移除
#endif
        OSSemQty--;                            //(21)//信号量数目减1
        OS_SemClr(p_sem);                      //(22)//清除信号量内容
        OS_CRITICAL_EXIT_NO_SCHED();                //减锁调度器,但不进行调度
        OSSched();                           //(23)
        //任务调度,执行最高优先级的就绪任务
        *p_err = OS_ERR_NONE;                    //返回错误类型为“无错误”
break;

default:                                //(24)//如果选项超出预期
        CPU_CRITICAL_EXIT();                           //开中断
        *p_err = OS_ERR_OPT_INVALID;           //返回错误类型为“选项非法”
break;
    }
    return ((OS_OBJ_QTY)nbr_tasks);          //(25)
    //返回删除信号量前等待其的任务数
}
#endif
  • (1):信号量控制块指针,指向我们定义的信号量控制块结构体变量, 所以在删除之前我们需要先定义一个信号量控制块变量,并且成功创建信号量后再进行删除操作。
  • (2):删除的选项。
  • (3):用于保存返回的错误类型。
  • (4):如果启用了安全检测(默认),在编译时则会包含安全检测相关的代码, 如果错误类型实参为空,系统会执行安全检测异常函数,然后返回,不执行删除信号量操作。
  • (5):如果启用了中断中非法调用检测(默认启用), 在编译时则会包含中断非法调用检测相关的代码,如果该函数是在中断中被调用,则是非法的, 返回错误类型为“在中断中删除对象”的错误代码,并且退出,不执行删除信号量操作。
  • (6):如果启用了参数检测(默认启用),在编译时则会包含参数检测相关的代码, 如果p_sem参数为空,返回错误类型为“内核对象为空”的错误代码,并且退出,不执行删除信号量操作。
  • (7):判断opt选项是否合理,该选项有两个, OS_OPT_DEL_ALWAYS与OS_OPT_DEL_NO_PEND,在os.h文件中定义。此处是判断一下选项是否在预期之内,如果在则跳出switch语句。
  • (8):如果选项超出预期,则返回错误类型为“选项非法”的错误代码,退出,不继续执行。
  • (9):如果启用了对象类型检测,在编译时则会包含对象类型检测相关的代码, 如果p_sem不是信号量类型,返回错误类型为“内核对象类型错误”的错误代码,并且退出,不执行删除信号量操作。
  • (10):程序执行到这里,表示可以删除信号量了, 系统首先获取信号量的等待列表保存到p_pend_list变量中。然后再获取等待该信号量的任务数。
  • (11):根据选项分类处理
  • (12):如果opt是OS_OPT_DEL_NO_PEND,则表示只在没有任务等待的情况下删除信号量, 如果当前系统中有任务阻塞在该信号量上,则不能删除,反之,则可以删除信号量。
  • (13):如果没有任务在等待该信号量,信号量数目减1。
  • (14):清除信号量内容。
  • (15):删除成功,返回错误类型为“无错误”的错误代码。
  • (16):如果有任务在等待该信号量,则返回错误类型为“有任务在等待该信号量”错误代码。
  • (17):如果opt是OS_OPT_DEL_ALWAYS,则表示无论如何都必须删除信号量 ,那么在删除之前,系统会把所有阻塞在该信号量上的任务恢复。
  • (18):获取时间戳,记录一下删除的时间。
  • (19):根据前面cnt记录阻塞在该信号量上的任务个数,逐个移除该信号量等待列表中的任务。
  • (20):调用OS_PendObjDel()函数将阻塞在内核对象(如信号量)上的任务从阻塞态恢复, 此时系统在删除内核对象,删除之后,这些等待事件的任务需要被恢复,其源码具体见 代码清单:消息队列-8。
  • (21):执行到这里,表示已经删除了信号量了,系统信号量个数减1。
  • (22):清除信号量内容。
  • (23):进行一次任务调度。
  • (24):如果选项超出预期,返回错误类型为“选项非法”的错误代码,退出。
  • (25):返回删除信号量前阻塞在该信号量上的任务个数。

信号量删除函数OSSemDel()的使用也是很简单的,只需要传入要删除的信号量的句柄与选项还有保存返回的错误类型即可,调用函数时, 系统将删除这个信号量。
需要注意的是在调用删除信号量函数前,系统应存在已创建的信号量。
如果删除信号量时,系统中有任务正在等待该信号量, 则不应该进行删除操作,因为删除之后的信号量就不可用了。
删除信号量函数OSSemDel()的使用实例具体如下:

OS_SEM SemOfKey;;                             //声明信号量

OS_ERR      err;

/* 删除信号量sem*/
OSSemDel ((OS_SEM  *)&SemOfKey,      //指向信号量的指针
OS_OPT_DEL_NO_PEND,
(OS_ERR *)&err);             //返回错误类型

3、信号量释放函数OSSemPost()

与消息队列的操作一样,信号量的释放可以在任务、中断中使用。

在前面的讲解中,我们知道当信号量有效的时候,任务才能获取信号量,那么,是什么函数使得信号量变得有效?
其实有两个方式:

  • 一个是在创建的时候进行初始化,将它可用的信号量个数设置一个初始值;
    如果该信号量用作二值信号量,那么我们在创建信号量的时候其初始值的范围是0~1, 假如初始值为1个可用的信号量的话,被获取一次就变得无效了。
  • 另一个就是释放信号量,μC/OS提供了信号量释放函数, 每调用一次该函数就释放一个信号量。
    但是有个问题,能不能一直释放?
    很显然如果用作二值信号量的话,一直释放信号量就达不到同步或者互斥访问的效果。
    虽然μC/OS的信号量是允许一直释放的,但是,信号量的范围还需我们用户自己根据需求进行决定,当用作二值信号量的时候, 必须确保其可用值在0~1范围内;
    而用作计数信号量的话,其范围是由用户根据实际情况来决定的,在写代码的时候,我们要注意代码的严谨性罢了

1. OSSemPost()

信号量释放函数的源码具体如下:

OS_SEM_CTR  OSSemPost (OS_SEM  *p_sem,    //(1)       //信号量控制块指针
                    OS_OPT   opt,      //(2)  //选项
                    OS_ERR  *p_err)    //(3)  //返回错误类型
{
    OS_SEM_CTR  ctr;
    CPU_TS      ts;



#ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0)             //如果错误类型实参为空
    {
        OS_SAFETY_CRITICAL_EXCEPTION();   //执行安全检测异常函数
        return ((OS_SEM_CTR)0);           //返回0(有错误),不继续执行
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u//如果启用(默认启用)了参数检测功能
    if (p_sem == (OS_SEM *)0)             //如果 p_sem 为空
    {
        *p_err  = OS_ERR_OBJ_PTR_NULL;     //返回错误类型为“内核对象指针为空”
        return ((OS_SEM_CTR)0);           //返回0(有错误),不继续执行
    }
    switch (opt)                        //根据选项情况分类处理
    {
    case OS_OPT_POST_1:                          //如果选项在预期内,不处理
    case OS_OPT_POST_ALL:
    case OS_OPT_POST_1   | OS_OPT_POST_NO_SCHED:
    case OS_OPT_POST_ALL | OS_OPT_POST_NO_SCHED:
    break;

    default:                                    //如果选项超出预期
        *p_err =  OS_ERR_OPT_INVALID;            //返回错误类型为“选项非法”
        return ((OS_SEM_CTR)0u);                //返回0(有错误),不继续执行
    }
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u//如果启用了对象类型检测
    if (p_sem->Type != OS_OBJ_TYPE_SEM)    //如果 p_sem 的类型不是信号量类型
    {
        *p_err = OS_ERR_OBJ_TYPE;           //返回错误类型为“对象类型错误”
        return ((OS_SEM_CTR)0);            //返回0(有错误),不继续执行
    }
#endif

    ts = OS_TS_GET();                             //获取时间戳

#if OS_CFG_ISR_POST_DEFERRED_EN > 0u//如果启用了中断延迟发布
    if (OSIntNestingCtr > (OS_NESTING_CTR)0)      //如果该函数是在中断中被调用
    {
        OS_IntQPost((OS_OBJ_TYPE)OS_OBJ_TYPE_SEM,//将该信号量发布到中断消息队列
                    (void      *)p_sem,
                    (void      *)0,
                    (OS_MSG_SIZE)0,
                    (OS_FLAGS   )0,
                    (OS_OPT     )opt,
                    (CPU_TS     )ts,
                    (OS_ERR    *)p_err);    //(4)
        return ((OS_SEM_CTR)0);                   //返回0(尚未发布),不继续执行
    }
#endif

    ctr = OS_SemPost(p_sem,                       //将信号量按照普通方式处理
                    opt,
                    ts,
                    p_err);                 //(5)

    return (ctr);                                 //返回信号的当前计数值
}
  • (1):信号量控制块指针。
  • (2):释放信号量的选项,该选项在os.h中定义,具体见
#define  OS_OPT_POST_FIFO                    (OS_OPT)(0x0000u)
//默认采用FIFO方式发布信号量

#define  OS_OPT_POST_LIFO                    (OS_OPT)(0x0010u)
//μC/OS也支持采用FIFO方式发布信号量。

#define  OS_OPT_POST_1                       (OS_OPT)(0x0000u)
//发布给一个任务。

#define  OS_OPT_POST_ALL                     (OS_OPT)(0x0200u)
//发布给所有等待的任务,也叫广播信号量。
  • (3):用于保存返回错误类型。
  • (4):如果启用了中断延迟发布,并且该函数在中断中被调用, 则使用OS_IntQPost()函数将信号量发布到中断消息队列中,OS_IntQPost()函数源码我们在前面已经将近过,就不再重复赘述。
  • (5):将信号量按照普通方式处理

2. OS_SemPost()源码

OS_SEM_CTR  OS_SemPost (OS_SEM  *p_sem,             //信号量指针
                        OS_OPT   opt,       //选项
                        CPU_TS   ts,        //时间戳
                        OS_ERR  *p_err)     //返回错误类型
{
    OS_OBJ_QTY     cnt;
    OS_SEM_CTR     ctr;
    OS_PEND_LIST  *p_pend_list;
    OS_PEND_DATA  *p_pend_data;
    OS_PEND_DATA  *p_pend_data_next;
    OS_TCB        *p_tcb;
    CPU_SR_ALLOC();



    CPU_CRITICAL_ENTER();                     //关中断
    p_pend_list = &p_sem->PendList;       //(1)//取出该信号量的等待列表
    //如果没有任务在等待该信号量
    if (p_pend_list->NbrEntries == (OS_OBJ_QTY)0)   //(2)
    {
        //判断是否将导致该信号量计数值溢出,
        switch (sizeof(OS_SEM_CTR)) //(3)
        {
            case 1u:                                   //(4)

            //如果溢出,则开中断,返回错误类型为
            if (p_sem->Ctr == DEF_INT_08U_MAX_VAL)
            //“计数值溢出”,返回0(有错误),
            {
                CPU_CRITICAL_EXIT();                   //不继续执行。
                *p_err = OS_ERR_SEM_OVF;
                return ((OS_SEM_CTR)0);
            }
            break;

            case 2u:
            if (p_sem->Ctr == DEF_INT_16U_MAX_VAL)
            {
                CPU_CRITICAL_EXIT();
                *p_err = OS_ERR_SEM_OVF;
                return ((OS_SEM_CTR)0);
            }
            break;

            case 4u:
            if (p_sem->Ctr == DEF_INT_32U_MAX_VAL)
            {
                CPU_CRITICAL_EXIT();
                *p_err = OS_ERR_SEM_OVF;
                return ((OS_SEM_CTR)0);
            }
            break;

        default:
        break;
        }
        p_sem->Ctr++;                    //(5)//信号量计数值不溢出则加1
        ctr       = p_sem->Ctr;         //获取信号量计数值到 ctr
        p_sem->TS = ts;                  //(6)//保存时间戳
        CPU_CRITICAL_EXIT();                                //则开中断
        *p_err     = OS_ERR_NONE;               //返回错误类型为“无错误”
        return (ctr);                    //(7)
        //返回信号量的计数值,不继续执行
    }

    OS_CRITICAL_ENTER_CPU_EXIT();        //(8)//加锁调度器,但开中断
    if ((opt & OS_OPT_POST_ALL) != (OS_OPT)0)
    //如果要将信号量发布给所有等待任务
    {
        cnt = p_pend_list->NbrEntries;    //(9)//获取等待任务数目到 cnt
    }
    else
    //如果要将信号量发布给优先级最高的等待任务
    {
        cnt = (OS_OBJ_QTY)1;          //(10)//将要操作的任务数为1,cnt置1

    }
    p_pend_data = p_pend_list->HeadPtr; //获取等待列表的首个任务到p_pend_data

    while (cnt > 0u)                     //(11)//逐个处理要发布的任务
    {
        p_tcb            = p_pend_data->TCBPtr;        //取出当前任务
        p_pend_data_next = p_pend_data->NextPtr;       //取出下一个任务
        OS_Post((OS_PEND_OBJ *)((void *)p_sem),        //发布信号量给当前任务
                p_tcb,
                (void      *)0,
                (OS_MSG_SIZE)0,
                ts);                        //(12)
        p_pend_data = p_pend_data_next;             //处理下一个任务
        cnt--;                      //(13)
    }
    ctr = p_sem->Ctr;  //获取信号量计数值到 ctr
    OS_CRITICAL_EXIT_NO_SCHED();     //(14)
    //减锁调度器,但不执行任务调度
    //如果 opt没选择“发布时不调度任务”
    if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0)
    {
        OSSched();    //(15)//任务调度
    }
    *p_err = OS_ERR_NONE;                //(16)//返回错误类型为“无错误”
    return (ctr);                                   //返回信号量的当前计数值
}
  • (1):取出该信号量的等待列表保存在p_pend_list变量中。
  • (2):判断一下有没有任务在等待该信号量, 如果没有任务在等待该信号量,则要先看看信号量的信号量计数值是否即将溢出。
  • (3):怎么判断计数值是否溢出呢?μC/OS支持多个数据类型的信号量计数值, 可以是8位的,16位的,32位的,具体是多少位是由我们自己定义的。
  • (4):先看看OS_SEM_CTR的大小是个字节,如果是1个字节,表示Ctr计数值是8位的, 则判断一下Ctr是否到达了DEF_INT_08U_MAX_VAL,如果到达了,再释放信号量就会溢出,那么就会返回错误类型为“计数值溢出”的错误代码。 而对于OS_SEM_CTR是2字节、4字节的也是同样的判断操作。
  • (5):程序能执行到这里,说明信号量的计数值不溢出,此时释放信号量则需要将Ctr加1。
  • (6):保存释放信号量时的时间戳。
  • (7):返回错误类型为“无错误”的错误代码,然后返回信号量的计数值,不继续执行。
  • (8):而程序能执行到这里,说明系统中有任务阻塞在该信号量上, 此时我们释放了一个信号量,就要将等待的任务进行恢复,但是恢复一个还是恢复所有任务得看用户自定义的释放信号量选项。 所以此时不管三七二十一将调度器锁定,但开中断,因为接下来的操作是需要操作任务与信号量的列表,系统不希望其他任务来打扰。
  • (9):如果要将信号量释放给所有等待任务, 首先获取等待该信号量的任务个数到变量cnt中,用来记录即将要进行释放信号量操作的任务个数。
  • (10):如果要将信号量释放给优先级最高的等待任务,将要操作的任务数为1,所以将cnt置1。
  • (11):逐个处理要释放信号量的任务。
  • (12):调用OS_Post()函数进行对应的任务释放信号量, 因为该源码在前面就讲解过了,此处就不再重复赘述,具体见 代码清单:消息队列-14。
  • (13):处理下一个任务。
  • (14):减锁调度器,但不执行任务调度.
  • (15):如果 opt 没选择“发布时不调度任务”,那么就进行任务调度。
  • (16):操作完成,返回错误类型为“无错误”的错误代码,并且返回信号量的当前计数值。

如果可用信号量未满,信号量控制块结构体成员变量Ctr就会加1,然后判断是否有阻塞的任务。
如果有的话就会恢复阻塞的任务,然后返回成功信息。
用户可以选择只释放(发布)给一个任务或者是释放(发布)给所有在等待信号量的任务(广播信号量), 并且用户可以选择在释放(发布)完成的时候要不要进行任务调度。
如果信号量在中断中释放,用户可以选择是否需要延迟释放(发布)。

信号量的释放函数的使用很简单,具体如下:

OS_SEM SemOfKey;          //标志KEY1是否被按下的信号量
OSSemPost((OS_SEM  *)&SemOfKey,       //发布SemOfKey
        (OS_OPT   )OS_OPT_POST_ALL,  //发布给所有等待任务
        (OS_ERR  *)&err);           //返回错误类型

4、信号量获取函数OSSemPend()

与消息队列的操作一样,信号量的获取可以在任务中使用。

与释放信号量对应的是获取信号量,我们知道,当信号量有效的时候,任务才能获取信号量。
当任务获取了某个信号量的时候, 该信号量的可用个数就减一,当它减到0的时候,任务就无法再获取了,并且获取的任务会进入阻塞态(假如用户指定了阻塞超时时间的话)。
如果某个信号量中当前拥有1个可用的信号量的话,被获取一次就变得无效了,那么此时另外一个任务获取该信号量的时候,就会无法获取成功, 该任务便会进入阻塞态,阻塞时间由用户指定。

μC/OS支持系统中多个任务获取同一个信号量,假如信号量中已有多个任务在等待,那么这些任务会按照优先级顺序进行排列。
如果信号量在释放的时候选择只释放给一个任务,那么在所有等待任务中最高优先级的任务优先获得信号量, 而如果信号量在释放的时候选择释放给所有任务,则所有等待的任务都会获取到信号量。

信号量获取函数OSSemPend()源码具体如下:

OS_SEM_CTR  OSSemPend (OS_SEM   *p_sem,   (1)       //信号量指针
                    OS_TICK   timeout, (2)  //等待超时时间
                    OS_OPT    opt,     (3)  //选项
                    CPU_TS   *p_ts,    (4)  //等到信号量时的时间戳
                    OS_ERR   *p_err)   (5)  //返回错误类型
{
    OS_SEM_CTR    ctr;
    OS_PEND_DATA  pend_data;
    CPU_SR_ALLOC();



#ifdef OS_SAFETY_CRITICAL(6)//如果启用(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0)                   //如果错误类型实参为空
    {
        OS_SAFETY_CRITICAL_EXCEPTION();//执行安全检测异常函数
        return ((OS_SEM_CTR)0);                 //返回0(有错误),不继续执行
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u(7)//如果启用了中断中非法调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0)    //如果该函数在中断中被调用
    {
        *p_err = OS_ERR_PEND_ISR;                //返回错误类型为“在中断中等待”
        return ((OS_SEM_CTR)0);                 //返回0(有错误),不继续执行
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u(8)//如果启用了参数检测
    if (p_sem == (OS_SEM *)0)                  //如果 p_sem 为空
    {
        *p_err = OS_ERR_OBJ_PTR_NULL;           //返回错误类型为“内核对象为空”
        return ((OS_SEM_CTR)0);                //返回0(有错误),不继续执行
    }
    switch (opt)                      (9)//根据选项分类处理
    {
    case OS_OPT_PEND_BLOCKING:             //如果选择“等待不到对象进行阻塞”
    case OS_OPT_PEND_NON_BLOCKING:         //如果选择“等待不到对象不进行阻塞”
    break;                            //直接跳出,不处理

    default:                           (10)//如果选项超出预期
        *p_err = OS_ERR_OPT_INVALID;       //返回错误类型为“选项非法”
        return ((OS_SEM_CTR)0);           //返回0(有错误),不继续执行
    }
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u(11)//如果启用了对象类型检测
    if (p_sem->Type != OS_OBJ_TYPE_SEM)       //如果 p_sem 不是信号量类型
    {
        *p_err = OS_ERR_OBJ_TYPE;            //返回错误类型为“内核对象类型错误”
        return ((OS_SEM_CTR)0);               //返回0(有错误),不继续执行
    }
#endif

    if (p_ts != (CPU_TS *)0)        (12)//如果 p_ts 非空
    {
        *p_ts  = (CPU_TS)0;
        //初始化(清零)p_ts,待用于返回时间戳
    }
    CPU_CRITICAL_ENTER();                     //关中断
    if (p_sem->Ctr > (OS_SEM_CTR)0)    (13)//如果资源可用
    {
        p_sem->Ctr--;                  (14)//资源数目减1
        if (p_ts != (CPU_TS *)0)       (15)//如果 p_ts 非空
        {
            *p_ts  = p_sem->TS;             //获取该信号量最后一次发布的时间戳
        }
        ctr   = p_sem->Ctr;             (16)//获取信号量的当前资源数目
        CPU_CRITICAL_EXIT();                  //开中断
        *p_err = OS_ERR_NONE;                  //返回错误类型为“无错误”
        return (ctr);
        //返回信号量的当前资源数目,不继续执行
    }

    if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0)      (17)
    //如果没有资源可用,而且选择了不阻塞任务
    {
        ctr   = p_sem->Ctr;                     //获取信号量的资源数目到 ctr
        CPU_CRITICAL_EXIT();                                //开中断
        *p_err = OS_ERR_PEND_WOULD_BLOCK;
        //返回错误类型为“等待渴求阻塞”
        return (ctr);
        //返回信号量的当前资源数目,不继续执行
    }
    else
    //如果没有资源可用,但选择了阻塞任务             (18)
    {
        if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0)(19)//如果调度器被锁
        {
            CPU_CRITICAL_EXIT();                            //开中断
            *p_err = OS_ERR_SCHED_LOCKED;
            //返回错误类型为“调度器被锁”
            return ((OS_SEM_CTR)0);
            //返回0(有错误),不继续执行
        }
    }

    OS_CRITICAL_ENTER_CPU_EXIT();      (20) //锁调度器,并重开中断
    OS_Pend(&pend_data,
            //阻塞等待任务,将当前任务脱离就绪列表,
            (OS_PEND_OBJ *)((void *)p_sem),
            //并插入节拍列表和等待列表。
            OS_TASK_PEND_ON_SEM,
            timeout);                               (21)

    OS_CRITICAL_EXIT_NO_SCHED();              //开调度器,但不进行调度

    OSSched();                               (22)
    //找到并调度最高优先级就绪任务
    /* 当前任务(获得信号量)得以继续运行 */
    CPU_CRITICAL_ENTER();                                   //关中断
    switch (OSTCBCurPtr->PendStatus)         (23)
    //根据当前运行任务的等待状态分类处理
    {
    case OS_STATUS_PEND_OK:                (24)//如果等待状态正常
    if (p_ts != (CPU_TS *)0)                       //如果 p_ts 非空
        {
            *p_ts  =  OSTCBCurPtr->TS;         //获取信号被发布的时间戳
        }
        *p_err = OS_ERR_NONE;                 //返回错误类型为“无错误”
    break;

    case OS_STATUS_PEND_ABORT:           (25)//如果等待被终止中止
    if (p_ts != (CPU_TS *)0)               //如果 p_ts 非空
        {
            *p_ts  =  OSTCBCurPtr->TS;         //获取等待被中止的时间戳
        }
        *p_err = OS_ERR_PEND_ABORT;            //返回错误类型为“等待被中止”
    break;

    case OS_STATUS_PEND_TIMEOUT:        (26)//如果等待超时
    if (p_ts != (CPU_TS *)0)                       //如果 p_ts 非空
        {
            *p_ts  = (CPU_TS  )0;                       //清零 p_ts
        }
        *p_err = OS_ERR_TIMEOUT;               //返回错误类型为“等待超时”
    break;

    case OS_STATUS_PEND_DEL:            (27)//如果等待的内核对象被删除
    if (p_ts != (CPU_TS *)0)              //如果 p_ts 非空
        {
            *p_ts  =  OSTCBCurPtr->TS;        //获取内核对象被删除的时间戳
        }
        *p_err = OS_ERR_OBJ_DEL;
    //返回错误类型为“等待对象被删除”
    break;

    default:                           (28)//如果等待状态超出预期
        *p_err = OS_ERR_STATUS_INVALID;
        //返回错误类型为“等待状态非法”
        CPU_CRITICAL_EXIT();                //开中断
        return ((OS_SEM_CTR)0);             //返回0(有错误),不继续执行
    }
    ctr = p_sem->Ctr;                       //获取信号量的当前资源数目
    CPU_CRITICAL_EXIT();                    //开中断
    return (ctr);                   (29)//返回信号量的当前资源数目
}
  • (1):信号量指针。
  • (2):用户自定义的阻塞超时时间
  • (3):获取信号量的选项,当信号量不可用的时候,用户可以选择阻塞或者不阻塞。
  • (4):用于保存返回等到信号量时的时间戳。
  • (5):用于保存返回的错误类型,用户可以根据此变量得知错误的原因。
  • (6):如果启用(默认禁用)了安全检测,在编译时则会包含安全检测相关的代码, 如果错误类型实参为空,系统会执行安全检测异常函数,然后返回,停止执行。
  • (7):如果启用了中断中非法调用检测,并且如果该函数在中断中被调用, 则返回错误类型为“在中断获取信号量”的错误代码,然后退出,停止执行。
  • (8):如果启用了参数检测,在编译时则会包含参数检测相关的代码, 如果 p_sem参数为空,返回错误类型为“内核对象为空”的错误代码,并且退出,不执行获取消息操作。
  • (9):判断一下opt选项是否合理,如果选择“等待不到对象进行阻塞” (OS_OPT_PEND_BLOCKING)或者选择“等待不到对象不进行阻塞”(OS_OPT_PEND_NON_BLOCKING),则是合理的,跳出switch语句。
  • (10):如果选项超出预期,则返回错误类型为“选项非法”的错误代码,并且推出。
  • (11):如果启用了对象类型检测,在编译时则会包含对象类型检测相关代码, 如果 p_sem不是信号量类型,那么返回错误类型为“对象类型有误”的错误代码,并且退出,不执行获取信号量操作。
  • (12):如果 p_ts 非空,就初始化(清零)p_ts,待用于返回时间戳。
  • (13):如果当前信号量资源可用。
  • (14):那么被获取的信号量资源中的Ctr成员变量个数就要减一。
  • (15):如果 p_ts 非空,获取该信号量最后一次发布的时间戳。
  • (16):获取信号量的当前资源数目用于返回,执行完成,那么返回错误类型为“无错误”的错误代码,退出。
  • (17):如果没有资源可用,而且用户选择了不阻塞任务, 获取信号量的资源数目到ctr变量用于返回,然后返回错误类型为“等待渴求组塞”的错误代码,退出操作。
  • (18):如果没有资源可用,但用户选择了阻塞任务,则需要判断一下调度器是否被锁。
  • (19):如果调度器被锁,返回错误类型为“调度器被锁”的错误代码,然后退出,不执行信号量获取操作。
  • (20):如果调度器未被锁,就锁定调度器,重新打开中断。 此次可能有同学就会问了,为什么刚刚调度器被锁就错误的呢,而现在又要锁定调度器?那是因为之前锁定的调度器不是由这个函数进行锁定的, 这是不允许的,因为现在要阻塞当前任务,而调度器锁定了就表示无法进行任务调度,这也是不允许的。那为什么又要关闭调度器呢, 因为接下来的操作是需要操作队列与任务的列表,这个时间就不会很短,系统不希望有其他任务来操作任务列表, 因为可能引起其他任务解除阻塞,这可能会发生优先级翻转。比如任务A的优先级低于当前任务,但是在当前任务进入阻塞的过程中, 任务A却因为其他原因解除阻塞了,那系统肯定是会去运行任务A,这显然是要绝对禁止的, 因为挂起调度器意味着任务不能切换并且不准调用可能引起任务切换的API函数,所以,锁定调度器,打开中断这样的处理, 既不会影响中断的响应,又避免了其他任务来操作队列与任务的列表。
  • (21):调用OS_Pend()函数将当前任务脱离就绪列表, 并根据用户指定的阻塞时间插入节拍列表和队列等待列表,然后打开调度器,但不进行调度。
  • (22):当前任务阻塞了,就要进行一次任务的调度。
  • (23):当程序能执行到这里,就说明大体上有两种情况, 要么是信号量中有可以的信号量了,任务获取到信号量了;要么任务还没获取到信号量(任务没获取到信号量的情况有很多种), 无论是哪种情况,都先把中断关掉再说,再根据当前运行任务的等待状态分类处理。
  • (24):如果任务状态是OS_STATUS_PEND_OK,则表示任务获取到信号量了, 获取信号被释放时候的时间戳,返回错误类型为“无错误”的错误代码。
  • (25):如果任务在等待(阻塞)被中止,则表示任务没有获取到信号量, 如果p_ts非空,获取等待被中止时的时间戳,返回错误类型为“等待被中止”的错误代码,跳出switch语句。
  • (26):如果等待(阻塞)超时,说明等待的时间过去了, 任务也没获取到信号量,如果p_ts非空,将p_ts清零,返回错误类型为“等待超时”的错误代码,跳出switch语句。
  • (27):如果等待的内核对象被删除, 如果p_ts非空,获取对象被删时的时间戳,返回错误类型为“等待对象被删”的错误代码,跳出switch语句。
  • (28):如果等待状态超出预期,则返回错误类型为“状态非法”的错误代码,跳出switch语句。
  • (29):打开中断,返回信号量的当前资源数目。

当有任务试图获取信号量的时候,当且仅当信号量有效的时候,任务才能获取到信号量。
如果信号量无效,在用户指定的阻塞超时时间中, 该任务将保持阻塞状态以等待信号量有效。
当其他任务或中断释放了有效的信号量,该任务将自动由阻塞态转移为就绪态。
当任务等待的时间超过了指定的阻塞时间,即使信号量中还是没有可用信号量,任务也会自动从阻塞态转移为就绪态。

信号量获取函数OSSemPend()的使用实例具体如下:

OSSemPend ((OS_SEM   *)&SemOfKey,             //等待该信号量被发布
        (OS_TICK   )0,                    //无期限等待
        (OS_OPT    )OS_OPT_PEND_BLOCKING, //如果没有信号量可用就等待
        (CPU_TS   *)&ts_sem_post,        //获取信号量最后一次被发布的时间戳
        (OS_ERR   *)&err);                //返回错误类型

5、使用信号量的注意事项

  • 信号量访问共享资源不会导致中断延迟。当任务在执行信号量所保护的共享资源时, ISR或高优先级任务可以抢占该任务。
  • 应用中可以有任意个信号量用于保护共享资源。然而,推荐将信号量用于I/O端口的保护,而不是内存地址。
  • 信号量经常会被过度使用。很多情况下,访问一个简短的共享资源时不推荐使用信号量,请求和释放信号量会消耗CPU时间。
    通过关/开中断能更有效地执行这些操作。假设两个任务共享一个32位的整数变量。第一个任务将这个整数变量加1,第二个任务将这个变量清零。
    考虑到执行这些操作用时很短,不需要使用信号量。执行这个操作前任务只需关中断,执行完毕后再开中断。
    但是若操作浮点数变量且处理器不支持硬件浮点操作时,就需要用到信号量。
    因为在这种情况下处理浮点数变量需较长时间。
  • 信号量会导致一种严重的问题:优先级翻转。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux (Ubuntu)- mysql8 部署

目录 1.基本部署 2.修改密码 3.开启root可远程连接配置 1.基本部署 01》》先查看OS类型,如果是Ubuntu在往下边看 rootspray:/etc/mysql/mysql.conf.d# lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: …

12-1-CSS 常用样式属性

个人主页:学习前端的小z 个人专栏:HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结,欢迎大家在评论区交流讨论! 文章目录 CSS 常用样式属性1 CSS 三角形2 CSS 用户界面样式2.1 什么是界面样式2.2 鼠标…

【Spring】AOP——使用@around实现面向切面的方法增强

工作业务中,有大量分布式加锁的重复代码,存在两个问题,一是代码重复率高,二是容易产生霰弹式修改,使用注解和AOP可以实现代码复用,简化分布式锁加锁和解锁流程。 around注解是AspectJ框架提供的&#xff0c…

RK3568测试

作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生在读,研究方向无线联邦学习 擅长领域:驱动开发,嵌入式软件开发,BSP开发 作者主页:一个平凡而乐于分享的小比特的个人主页…

Prometheus+grafana环境搭建Docker服务(docker+二进制两种方式安装)(八)

由于所有组件写一篇幅过长,所以每个组件分一篇方便查看,前七篇链接如下 Prometheusgrafana环境搭建方法及流程两种方式(docker和源码包)(一)-CSDN博客 Prometheusgrafana环境搭建rabbitmq(docker二进制两种方式安装)(二)-CSDN博客 Prometheusgrafana环…

java数据结构与算法刷题-----LeetCode172. 阶乘后的零

java数据结构与算法刷题目录(剑指Offer、LeetCode、ACM)-----主目录-----持续更新(进不去说明我没写完):https://blog.csdn.net/grd_java/article/details/123063846 文章目录 数学:阶乘的10因子个数数学优化:思路转变为求5的倍数…

【蓝桥杯选拔赛真题57】C++字符串反转 第十四届蓝桥杯青少年创意编程大赛 算法思维 C++编程选拔赛真题解

目录 C字符串反转 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、推荐资料 C字符串反转 第十四届蓝桥杯青少年创意编程大赛C选拔赛真题 一、题目要求 1、编程实现 给定一个只包含大写字母"M…

C++进阶--C++11(2)

C11第一篇 C11是C编程语言的一个版本,于2011年发布。C11引入了许多新特性,为C语言提供了更强大和更现代化的编程能力。 可变参数模板 在C11中,可变参数模板可以定义接受任意数量和类型参数的函数模板或类模板。它可以表示0到任意个数&…

蓝桥杯 第2155题质因数个数 C++ Java Python

题目 思路和解题方法 目标是计算给定数 n 的质因数个数。可以使用了试除法来找到 n 的所有质因数 读取输入的数 n。从 2 开始遍历到 sqrt(n),对于每个数 i: 如果 n 能被 i 整除,则进行以下操作: 将 n 除以 i,直到 n 不…

leetcode.面试题 02.07. 链表相交

题目 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。 图示两个链表在节点 c1 开始相交: 思路 假a在链表A上移动,b在链表B上移动,a移动完在B上开始&…

鸿蒙Lottie动画-实现控制动画的播放、暂停、倍速播放、播放顺序

介绍 本示例展示了lottie对动画的操作功能。引入Lottie模块,实现控制动画的播放、暂停、倍速播放、播放顺序、播放到指定帧停止或从指定帧开始播放、侦听事件等功能,动画资源路径必须是json格式。 效果预览 使用说明: 进入页面默认开始201…

铸铁平台的平面度

铸铁平台的平面度是指平台的表面平整程度,即平台表面与其理论平面之间的最大偏差。平台的平面度通常使用国际标准符号GD&T中的平面度符号(ⓨ)表示,单位为毫米(mm)或微米(μm)。 …

docker搭建EFK(未完待续)

目录 elasticsearch1.创建网络2.拉取镜像3.创建容器如果出现启动失败,提示目录挂载失败,可以考虑如下措施 开放防火墙端口4.验证安装成功重置es密码关闭https连接创建kibana用户创建新账户给账户授权 kibana1.创建容器2.验证安装成功3.es为kibana创建用户…

2024年第八届人工智能与虚拟现实国际会议(AIVR 2024)即将召开!

2024年第八届人工智能与虚拟现实国际会议(AIVR 2024)将2024年7月19-21日在日本福冈举行。人工智能与虚拟现实的发展对推动科技进步、促进经济发展、提升人类生活质量等具有重要意义。AIVR 2024将携手各专家学者,共同挖掘智能与虚拟的无限可能…

Day03-Ansible playbook

Day03-Ansible playbook 1. Ansible Playbook基本概述1.1 什么是playbook?1.2 Ansible playbook与AD-Hoc的关系1.3 Ansible Playbook书写格式1.4 Ansible Playbook练习实验1.4.1 playbook剧本初使用1.4.2 playbook剧本-部署配置nfs1.4.3 playbook剧本-部署配置lnmp 1. Ansible…

多线程学习-线程池

目录 1.线程池的作用 2.线程池的实现 3.自定义创建线程池 1.线程池的作用 当我们使用Thread的实现类来创建线程并调用start运行线程时,这个线程只会使用一次并且执行的任务是固定的,等run方法中的代码执行完之后这个线程就会变成垃圾等待被回收掉。如…

7(8)-2-CSS 盒子模型

个人主页:学习前端的小z 个人专栏:HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结,欢迎大家在评论区交流讨论! 文章目录 CSS 盒子模型1 盒子模型(Box Model)组成2 边框&#x…

传统海外仓的管理模式有什么缺点?使用位像素海外仓系统的海外仓有什么优势?

传统的海外仓管理模式主要需要大量的人工操作和相对简单的信息化手段进行仓库的日常运营。因此,传统海外仓的运作比较依赖仓库员工的手工记录、核对和处理各种仓储和物流信息。 然而,传统海外仓管理模式通常存在一些缺点: 效率低下 因为需…

LLMOps快速入门,轻松开发部署大语言模型

大家好,如今我们能够与ChatGPT进行轻松互动:只需输入提示,按下回车,就能迅速得到回应。然而,这个无缝互动的底层,是一系列复杂而有序的自动执行步骤,即大型语言模型运营(LLMOps&…

postgresql数据库|

概述 Oracle_fdw 是一种postgresql外部表插件,可以读取到Oracle上面的数据。是一种非常方便且常见的pg与Oracle的同步数据的方法 配置Oracle环境 Oracle_fdw 的编译依赖pg_config和Oracle的客户端环境 pg_config 是什么呢?其实就是postgresql的一个可执…