RT_Thread内核源码分析(二)——链表和对象管理

        实时操作系统基本上都是通过一些链表进行线程、信号、队列的管理,RT_Thread也不例外,本章主要讲解RT_Thread的链表结构和对象管理。

本章基于RT_Thread Nano V3.1.5版本分析

      

1、链表

          RT_Thread使用的链表非常简单,链表节点只有节点指针,各节点在实例结构体中定义,可以通过头节点定位链表,可以通过节点在结构体中的偏移定位实例结构体。

1.1 双向链表

1.1.1 链表结构

        双向链表结构声明如下

struct rt_list_node
{struct rt_list_node *next;                          /**< 指向下一个节点. */struct rt_list_node *prev;                          /**< 指向上一个节点. */
};

        双向链表通过一个头节点对整个链表管理,如下所示,NODE1为头节点,用于定位链表,NODE2~NODEn为被链表管理的节点。

        双向链表主要用于对象、线程、定时器等实例管理,这些管理均需要提前设置头节点,如下所示,为系统内核定义的双链表头节点,还有一些头节点在结构体中定义,此处不再详述。

/********线程全局变量********/
extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]; // 就绪线程链表数组
extern rt_list_t rt_thread_defunct;                                // 失效线程链表
/* 软件定时器链表 soft timer list */
static rt_list_t rt_soft_timer_list[RT_TIMER_SKIP_LIST_LEVEL];
/* 硬件定时器 hard timer list */
static rt_list_t rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL];

1.1.2 链表操作

        链表操作采用内联函数和宏定义的方式,可以免去操作时的环境存储与恢复,提高运行效率。操作接口主要有初始化、插入节点(表头或表尾)、删除节点。

初始化

/*节点初始化(节点指针均指向自己)*/
#define RT_LIST_OBJECT_INIT(object) { &(object), &(object) }/*链表头节点初始化(节点指针均指向自己)*/
rt_inline void rt_list_init(rt_list_t *l)
{l->next = l->prev = l;
}

插入节点至表头

/*节点n插入到链表l的头部*/
rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
{l->next->prev = n;n->next = l->next;l->next = n;n->prev = l;
}

  NODE1为头节点,用于定位链表,NODE2~NODEn为链表管理的节点,NewNode为新插入的节点。

   插入节点至表尾

/*节点n插入到链表l的尾部*/
rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
{l->prev->next = n;n->prev = l->prev;l->prev = n;n->next = l;
}

         NODE1为头节点,用于定位链表,NODE2~NODEn为链表管理的节点,NewNode为新插入的节点。

删除节点

/*链表移除节点n*/ 
rt_inline void rt_list_remove(rt_list_t *n)
{n->next->prev = n->prev;n->prev->next = n->next;n->next = n->prev = n;
}

         NODE1为头节点,用于定位链表,DeleteNode、NODE2~NODEn为链表管理的节点,该图为删除节点DeleteNode。

1.1.3 链表遍历

        链表遍历接口采用内联函数和宏定义的方式,可以免去操作时的环境存储与恢复,提高运行效率。

        链表节点多数存储在各类对象的结构体中,当遍历到一个节点时,可通过节点在结构体中的偏移获取结构体指针。所以链表本质上是对对象(结构体)的管理,因此,链表遍历不仅可以遍历节点,还可以遍历对象(结构体)。

/****判断链表条目数为空****/ 
rt_inline int rt_list_isempty(const rt_list_t *l)
{return l->next == l;
}
/****获取链表长度****/
rt_inline unsigned int rt_list_len(const rt_list_t *l)
{unsigned int len = 0;const rt_list_t *p = l;while (p->next != l){p = p->next;len ++;}return len;
}
/****遍历链表节点(pos为遍历出的节点,head为头节点)*/
#define rt_list_for_each(pos, head)       \  
for(pos=(head)->next;pos!= (head);pos = pos->next)/*****遍历链表节点(安全模式,防止误删除,pos为遍历出的节点,n指向pos下个节点,head为头节点) */
#define rt_list_for_each_safe(pos, n, head) \
for(pos=(head)->next,n=pos->next; pos!=(head); pos=n, n=pos->next)/****定位对象(根据节点定位)***/
#define rt_container_of(ptr,type,member) ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
/****获取元素*/
#define rt_list_entry(node, type, member) rt_container_of(node, type, member)/*****遍历对象*/
#define rt_list_for_each_entry(pos, head, member) \
for(pos=rt_list_entry((head)->next,typeof(*pos),member);&pos->member!=(head);pos = rt_list_entry(pos->member.next, typeof(*pos), member)/******遍历对象(安全模式,防止误删除)  */
#define rt_list_for_each_entry_safe(pos, n, head, member) \for (pos = rt_list_entry((head)->next, typeof(*pos), member), \n = rt_list_entry(pos->member.next, typeof(*pos), member); \&pos->member != (head); \pos = n, n = rt_list_entry(n->member.next, typeof(*n), member))/*****获取链表第一个对象*/
#define rt_list_first_entry(ptr, type, member)  rt_list_entry((ptr)->next, type, member)

举例,比如遍历定时器,定时器结构体和链表如下所示:

/* 软件定时器链表 */
static rt_list_t rt_soft_timer_list;
/* 定时器结构体 */
struct rt_timer
{struct rt_object parent;                        /**< 基类对象*/rt_list_t   row;                                /**< 定时器链表节点*/void (*timeout_func)(void *parameter);          /**< 定时器函数指针 */void *parameter;                                /**< 定时器函数参数*/rt_tick_t        init_tick;                     /**< 定时器定时时间*/rt_tick_t        timeout_tick;                  /**< 定时器定时时刻*/
};
typedef struct rt_timer *rt_timer_t;
/*临时定时器*/
rt_timer_t  timerTemp;

         定时器链表节点在定时器结构体上的内存偏移为((struct rt_timer*)(0x0)->row),假设节点n为某个定时器的链表节点,获取定时器指针方法如下:

// 直接计算
struct rt_timer* pTimer=&n-((struct rt_timer*)(0x0)->row);
// 使用系统接口
struct rt_timer* pTimer=rt_container_of(&n,struct rt_timer*,row);

        假设定时器timerTemp已经插入到了链表rt_soft_timer_list中,在链表rt_soft_timer_list中定位到timerTemp的方法如下

/*在链表中定位定时器链表节点timerTemp.row*/ 
rt_list_for_each(&timerTemp->row, rt_soft_timer_list, row) /*在链表中定位定时器timerTemp*/
rt_list_for_each_entry(&timerTemp, rt_soft_timer_list, row) 

        由上述代码可见,rt_list_for_each与rt_list_for_each_entry接口作用相同,rt_list_for_each无疑性能更优,所以内核代码中并未使用rt_list_for_each_entry,而rt_list_for_each主要用于计算节点个数或判断节点是否连接在链表。

1.2 单向链表

        单向链表主要在内存池管理中使用,本章只罗列单向链表接口,其详细使用过程可以参考双向链表,二者接口相似。

1.2.1 链表结构 

struct rt_slist_node
{struct rt_slist_node *next;                         /**< 指向下一个节点*/
};

  1.2.2 链表操作

/*****对象节点初始化(初始化被管理的节点)*****/
#define RT_SLIST_OBJECT_INIT(object) { RT_NULL }/****链表头节点初始化***/
rt_inline void rt_slist_init(rt_slist_t *l)
{l->next = RT_NULL;
}/*****节点n插入到链表l的尾部*****/
rt_inline void rt_slist_append(rt_slist_t *l, rt_slist_t *n)
{struct rt_slist_node *node;node = l;while (node->next) node = node->next;node->next = n;n->next = RT_NULL;
}/*****节点n插入到链表l的头部*****/
rt_inline void rt_slist_insert(rt_slist_t *l, rt_slist_t *n)
{n->next = l->next;l->next = n;
}/*****从链表l移除节点n*****/
rt_inline rt_slist_t *rt_slist_remove(rt_slist_t *l, rt_slist_t *n)
{struct rt_slist_node *node = l;while (node->next && node->next != n) node = node->next;if (node->next != (rt_slist_t *)0) node->next = node->next->next;return l;
}

  1.2.3 链表遍历

 /********定位元素member所在的结构体指针,结构体类型为type**********/
#define rt_container_of(ptr, type, member)   ((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))/******链表节点个数******/
rt_inline unsigned int rt_slist_len(const rt_slist_t *l)
{unsigned int len = 0;const rt_slist_t *list = l->next;while (list != RT_NULL){list = list->next;len ++;}return len;
}
/*******单链表第一个节点*******/
rt_inline rt_slist_t *rt_slist_first(rt_slist_t *l)
{return l->next;
}
/*******单链表最后一个节点*******/
rt_inline rt_slist_t *rt_slist_tail(rt_slist_t *l)
{while (l->next) l = l->next;return l;
}
/*******节点n的下一个节点*******/
rt_inline rt_slist_t *rt_slist_next(rt_slist_t *n)
{return n->next;
}
/*******单链表为空*******/
rt_inline int rt_slist_isempty(rt_slist_t *l)
{return l->next == RT_NULL;
}/********获取节点所在的结构体*******/
#define rt_slist_entry(node, type, member)  rt_container_of(node, type, member)/********遍历链表的节点********/
#define rt_slist_for_each(pos, head) for(pos=(head)->next; pos!=RT_NULL; pos=pos->next)/*********遍历链表节点对应的结构体****/
#define rt_slist_for_each_entry(pos, head, member) \for(pos=rt_slist_entry((head)->next,typeof(*pos),member); &pos->member!=(RT_NULL); \pos=rt_slist_entry(pos->member.next,typeof(*pos),member))/**********获取链表第一个节点对应的结构体 *********/
#define rt_slist_first_entry(ptr, type, member) rt_slist_entry((ptr)->next, type, member)/**********获取链表最后一个节点对应的结构体*********/
#define rt_slist_tail_entry(ptr,type,member) rt_slist_entry(rt_slist_tail(ptr),type,member)

2、对象

       RT_Thread操作系统为方便统一管理,采用的面相对象的思想,系统定义了内核对象(等同于C++中的基类),线程、信号、消息、内存池等继承内核对象(线程、信号、消息等同于派生类)。

2.1 内核对象结构

        内核对象节点list用于链表管理,flag参数在不同对象中起着不同的含义,比如在定时器对象中表示定时器状态和定时器类型。type参数表示对象类型,见【2.2内核对象分类】

 /*基类  内核对象*/
struct rt_object
{char       name[RT_NAME_MAX];                       /**< 内核对象名称 */rt_uint8_t type;                                    /**< 内核对象类型 */rt_uint8_t flag;                                    /**< 内核对象标志  */rt_list_t  list;                                    /**< 内核对象链表节点  */
};

 2.2 内核对象分类

        内核结构中type元素表示对象类型,对象类型如下所示,其中【静态对象掩码】并不是一类独立对象,而是表示对象的属性,即对象是静态对象(静态存储)还是动态对象(申请动态内存存储)。例如:静态内存创建的线程,其类型为RT_Object_Class_Thread+RT_Object_Class_Static=0x81

 /*对象类型枚举*/
enum rt_object_class_type
{RT_Object_Class_Null          = 0x00,       /**< 未使用. */RT_Object_Class_Thread        = 0x01,       /**< 线程对象. */RT_Object_Class_Semaphore     = 0x02,       /**< 信号对象. */RT_Object_Class_Mutex         = 0x03,       /**< 互斥对象. */RT_Object_Class_Event         = 0x04,       /**< 事件对象. */RT_Object_Class_MailBox       = 0x05,       /**< 邮件对象 */RT_Object_Class_MessageQueue  = 0x06,       /**< 消息对象. */RT_Object_Class_MemHeap       = 0x07,       /**< 内存堆对象. */RT_Object_Class_MemPool       = 0x08,       /**< 内存池对象. */RT_Object_Class_Device        = 0x09,       /**< 设备驱动对象. */RT_Object_Class_Timer         = 0x0a,       /**< 定时器对象. */RT_Object_Class_Unknown       = 0x0c,       /**< 未知对象. */RT_Object_Class_Static        = 0x80        /**< 静态对象掩码. */
};

2.3 内核对象容器

        内核对象容器,主要通过双向链表管理对象,其结构体中的链表节点即为链表头节点。

        对象容器结构体

struct rt_object_information
{enum rt_object_class_type type;                     /**< 对象类型 */rt_list_t                 object_list;              /**< 对象链表 */rt_size_t                 object_size;              /**< 对象大小 */
};

        对象容器定义

        通过定义可见,对象容器是一数组,可根据宏定义自动裁剪。

 /*对象容器枚举*/
enum rt_object_info_type
{RT_Object_Info_Thread = 0,                         /**< 线程对象. */
#ifdef RT_USING_SEMAPHORERT_Object_Info_Semaphore,                          /**< 信号对象. */
#endif
#ifdef RT_USING_MUTEXRT_Object_Info_Mutex,                              /**< 互斥对象. */
#endif
#ifdef RT_USING_EVENTRT_Object_Info_Event,                              /**< 事件对象 */
#endif
#ifdef RT_USING_MAILBOXRT_Object_Info_MailBox,                            /**< 邮件对象 */
#endif
#ifdef RT_USING_MESSAGEQUEUERT_Object_Info_MessageQueue,                       /**< 消息对象. */
#endif
#ifdef RT_USING_MEMHEAPRT_Object_Info_MemHeap,                            /**< 堆对象*/
#endif
#ifdef RT_USING_MEMPOOLRT_Object_Info_MemPool,                            /**< 内存池对象. */
#endif
#ifdef RT_USING_DEVICERT_Object_Info_Device,                             /**< 驱动对象 */
#endifRT_Object_Info_Timer,                              /**< 定时器对象 */RT_Object_Info_Unknown,                            /**< 未知对象. */
};// 对象监视器初始化(指针指向自己)
#define _OBJ_CONTAINER_LIST_INIT(c) \
{&(rt_object_container[c].object_list),&(rt_object_container[c].object_list)}// 对象容器
static struct rt_object_information rt_object_container[RT_Object_Info_Unknown] =
{/* 初始化对象容器(线程) */{RT_Object_Class_Thread, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Thread), sizeof(struct rt_thread)},#ifdef RT_USING_SEMAPHORE/* 初始化对象容器(信号) */{RT_Object_Class_Semaphore, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Semaphore), sizeof(struct rt_semaphore)},#endif#ifdef RT_USING_MUTEX/* 初始化对象容器(互斥信号) */{RT_Object_Class_Mutex, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Mutex), sizeof(struct rt_mutex)},
#endif
#ifdef RT_USING_EVENT/* 初始化对象容器(事件) */{RT_Object_Class_Event, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Event), sizeof(struct rt_event)},
#endif
#ifdef RT_USING_MAILBOX/* 初始化对象容器(邮件)  */{RT_Object_Class_MailBox, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MailBox), sizeof(struct rt_mailbox)},
#endif#ifdef RT_USING_MESSAGEQUEUE/* 初始化对象容器(队列)  */{RT_Object_Class_MessageQueue, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MessageQueue), sizeof(struct rt_messagequeue)},
#endif#ifdef RT_USING_MEMHEAP/* 初始化对象容器(内存堆) */{RT_Object_Class_MemHeap, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MemHeap), sizeof(struct rt_memheap)},
#endif#ifdef RT_USING_MEMPOOL/* 初始化对象容器(内存池) */{RT_Object_Class_MemPool, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MemPool), sizeof(struct rt_mempool)},
#endif#ifdef RT_USING_DEVICE/* 初始化对象容器(驱动) */{RT_Object_Class_Device, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Device), sizeof(struct rt_device)},
#endif/* 初始化对象容器(定时器) */{RT_Object_Class_Timer, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Timer), sizeof(struct rt_timer)},
};

对象容器管理图示,容器有2个线程对象。

2.4 内核对象继承 

        线程、信号、互斥等对象的结构体,第一个成员必须是内核对象,等同于C++的public继承;如下例所示:

        

2.5 内核对象操作

/*系统对象(静态对象)初始化*/
void rt_system_object_init(void){}/*获取对象所在的容器(依据类型枚举)*/
struct rt_object_information *rt_object_get_information(enum rt_object_class_type type)
{int index;for (index = 0; index < RT_Object_Info_Unknown; index ++)if (rt_object_container[index].type == type) return &rt_object_container[index];return RT_NULL;
}/*获取对象所在链表长度,既同类对象个数 (依据类型枚举)*/
int rt_object_get_length(enum rt_object_class_type type)
{int count = 0;rt_ubase_t level;struct rt_list_node *node = RT_NULL;struct rt_object_information *information = RT_NULL;information = rt_object_get_information((enum rt_object_class_type)type);if (information == RT_NULL) return 0;// 进入临界区level = rt_hw_interrupt_disable();/*计算有效对象个数*/rt_list_for_each(node, &(information->object_list)){count ++;}// 退出临界区rt_hw_interrupt_enable(level);return count;
}/* 复制type类型的对象指针至pointers中,最大maxlen个*/
int rt_object_get_pointers(enum rt_object_class_type type, rt_object_t *pointers, int maxlen)
{int index = 0;rt_ubase_t level;struct rt_object *object;struct rt_list_node *node = RT_NULL;struct rt_object_information *information = RT_NULL;if (maxlen <= 0) return 0;// 获取对应容器information = rt_object_get_information((enum rt_object_class_type)type);if (information == RT_NULL) return 0;// 进入临界区level = rt_hw_interrupt_disable();/* 检索节点,并复制对象指针*/rt_list_for_each(node, &(information->object_list)){   /*根据节点获取对象指针*/object = rt_list_entry(node, struct rt_object, list);pointers[index] = object;index ++;if (index >= maxlen) break;}// 退出临界区rt_hw_interrupt_enable(level);return index;
}/*初始化静态内核对象 */
void rt_object_init(struct rt_object         *object,enum rt_object_class_type type,const char               *name)
{register rt_base_t temp;struct rt_list_node *node = RT_NULL;struct rt_object_information *information;/*获取对象容器*/information = rt_object_get_information(type);RT_ASSERT(information != RT_NULL);/* 退出线程调度*/rt_enter_critical();/* 检查对象,避免重复初始化*/
for(node=information->object_list.next;node!=&(information->object_list);node=node->next){struct rt_object *obj;obj = rt_list_entry(node, struct rt_object, list);if (obj){/*检查是否存在重复初始化*/RT_ASSERT(obj != object);}}/* 恢复线程调度*/rt_exit_critical();/* 设置静态标识*/object->type = type | RT_Object_Class_Static;/* 设置名称 */rt_strncpy(object->name, name, RT_NAME_MAX);/* 对象新增函数调用钩子*/RT_OBJECT_HOOK_CALL(rt_object_attach_hook, (object));/* 进入临界区lock interrupt */temp = rt_hw_interrupt_disable();/*对象插入值链表头部*/rt_list_insert_after(&(information->object_list), &(object->list));/* 退出临界区*/rt_hw_interrupt_enable(temp);
}/*移除静态内核对象*/
void rt_object_detach(rt_object_t object)
{register rt_base_t temp;/* 断言*/RT_ASSERT(object != RT_NULL);/* 执行对象移除钩子 */RT_OBJECT_HOOK_CALL(rt_object_detach_hook, (object));/*清除对象类型*/object->type = 0;/* 进入临界区*/temp = rt_hw_interrupt_disable();/* 从链表 移除对象*/rt_list_remove(&(object->list));/* 退出临界区 */rt_hw_interrupt_enable(temp);
}/*初始化动态内核对象(依据类型分配)*/
rt_object_t rt_object_allocate(enum rt_object_class_type type, const char *name)
{struct rt_object *object;register rt_base_t temp;struct rt_object_information *information;/*中断断言*/RT_DEBUG_NOT_IN_INTERRUPT;/* 获取对象容器*/information = rt_object_get_information(type);//断言RT_ASSERT(information != RT_NULL);/*动态内存申请*/object = (struct rt_object *)RT_KERNEL_MALLOC(information->object_size);if (object == RT_NULL){/*动态内存申请失败*/return RT_NULL;}/* 动态内存初始化为0*/rt_memset(object, 0x0, information->object_size);/* 设置对象类型 */object->type = type;/* 清除对象标志 */object->flag = 0;/* 设置名称 */rt_strncpy(object->name, name, RT_NAME_MAX);/* 执行钩子 */RT_OBJECT_HOOK_CALL(rt_object_attach_hook, (object));/* 进入临界区*/temp = rt_hw_interrupt_disable();/* 插入对象至管理器链表头部*/rt_list_insert_after(&(information->object_list), &(object->list));/* 退出临界区*/rt_hw_interrupt_enable(temp);/* 返回对象指针*/return object;
}/**删除并释放动态对象*/
void rt_object_delete(rt_object_t object)
{register rt_base_t temp;/* 断言 */RT_ASSERT(object != RT_NULL);RT_ASSERT(!(object->type & RT_Object_Class_Static));RT_OBJECT_HOOK_CALL(rt_object_detach_hook, (object));/* 清除类型 */object->type = RT_Object_Class_Null;/* 进入临界区 */temp = rt_hw_interrupt_disable();/* 从链表移除*/rt_list_remove(&(object->list));/* 退出临界区*/rt_hw_interrupt_enable(temp);/* 释放对象内存 */RT_KERNEL_FREE(object);
}
#endif/**检查对象类型是否为系统类型(静态类型)*/
rt_bool_t rt_object_is_systemobject(rt_object_t object)
{/* 对象断言*/RT_ASSERT(object != RT_NULL);if (object->type & RT_Object_Class_Static)return RT_TRUE;return RT_FALSE;
}/**获取对象类型*/
rt_uint8_t rt_object_get_type(rt_object_t object)
{/*断言*/RT_ASSERT(object != RT_NULL);return object->type & ~RT_Object_Class_Static;
}/*查找对象(name对象名称,type对象类型,依据对象名称和类型,该函数不能在中断调用)*/
rt_object_t rt_object_find(const char *name, rt_uint8_t type)
{struct rt_object *object = RT_NULL;struct rt_list_node *node = RT_NULL;struct rt_object_information *information = RT_NULL;/*获取对象容器*/information = rt_object_get_information((enum rt_object_class_type)type);/* 参数检查 */if ((name == RT_NULL) || (information == RT_NULL)) return RT_NULL;/* which is invoke in interrupt status */RT_DEBUG_NOT_IN_INTERRUPT;/* 退出线程调度*/rt_enter_critical();/* 遍历对象节点 */rt_list_for_each(node, &(information->object_list)){/*获取对象*/object = rt_list_entry(node, struct rt_object, list);/*判断对象*/if (rt_strncmp(object->name, name, RT_NAME_MAX) == 0){/* 恢复线程调度*/rt_exit_critical();return object;}}/* 恢复线程调度 */rt_exit_critical();return RT_NULL;
}

2.6 内核对象钩子

        操作系统通过钩子(函数指针)给用户侧提供调试、监视或其他用途的接口,这些接口分布在对象操作的重要位置,用户侧可以根据需求选配使用。

/*****对象钩子*****/
static void (*rt_object_attach_hook)(struct rt_object *object); // 对象增加调用钩子
static void (*rt_object_detach_hook)(struct rt_object *object); // 对象移除调用钩子
void (*rt_object_trytake_hook)(struct rt_object *object);       // 对象将被占用调用钩子
void (*rt_object_take_hook)(struct rt_object *object);          // 对象已经被占用调用钩子
void (*rt_object_put_hook)(struct rt_object *object);           // 对象被释放调用钩子/*****对象动态增加调用钩子设置*****/
void rt_object_attach_sethook(void (*hook)(struct rt_object *object))
{rt_object_attach_hook = hook;
}/*****对象移除调用钩子设置*****/
void rt_object_detach_sethook(void (*hook)(struct rt_object *object))
{rt_object_detach_hook = hook;
}/*****对象将要占用调用钩子设置,主要应用于信号、互斥信号、事件、邮件、消息队列将要阻塞线程时*****/
void rt_object_trytake_sethook(void (*hook)(struct rt_object *object))
{rt_object_trytake_hook = hook;
}/*****对象已经占用调用钩子设置,主要应用于信号、互斥信号、事件、邮件、消息队列、定时器返回线程,检测阻塞***/
void rt_object_take_sethook(void (*hook)(struct rt_object *object))
{rt_object_take_hook = hook;
}
/****对象释放调用钩子设置,比如消息对象发送消息,释放线程****/
void rt_object_put_sethook(void (*hook)(struct rt_object *object))
{rt_object_put_hook = hook;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度学习(2):梯度下降

文章目录 梯度下降梯度是什么常见梯度下降算法 代码实现批量梯度下降 梯度下降 梯度是什么 类似y ax b这种单变量的函数来说&#xff0c;导数就是它的斜率&#xff0c;这种情况下可以说梯度就是导数。 但在多变量函数中&#xff0c;梯度是一个向量&#xff0c;其分量是各个…

frp内网穿透服务器+客户端详细配置

当我们拥有一台云服务器时&#xff0c;可以将局域网服务器的服务通过内网穿透发布到公网上。frp是一个主流的内网穿透软件&#xff0c;本文讲解frp内网穿透服务器客户端详细配置。 一、需要准备的内容&#xff1a; 腾讯云服务器&#xff1a;https://curl.qcloud.com/Sjy0zKjy 2…

安卓13修改设置设备型号和设备名称分析与更改-android13设置设备型号和设备名称更改

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改5.编译6.彩蛋1.前言 用户要定制一些系统显示的设备型号和设备名称,这就需要我们分析设置里面的相关信息来找到对应的位置进行修改了。 2.问题分析 像这种信息要么是config.xml里面写死了,要…

开源 AI 智能名片与 S2B2C 商城小程序:嫁接权威实现信任与增长

摘要&#xff1a;本文探讨了嫁接权威在产品营销中的重要性&#xff0c;并结合开源 AI 智能名片与 S2B2C 商城小程序&#xff0c;阐述了如何通过与权威关联来建立客户信任&#xff0c;提升产品竞争力。强调了在当今商业环境中&#xff0c;巧妙运用嫁接权威的方法&#xff0c;能够…

Kafka系列之:安装使用kafka_exporter详细步骤

Kafka系列之:安装使用kafka_exporter详细步骤 一、kafka_exporter二、下载kafka_exporter三、理解Topic Metrics指标四、理解Consumer Groups Metrics指标五、启动kafka_exporter六、查看页面七、systemctl托管服务一、kafka_exporter kafka_exporter源码kafka_exporter下载页…

JVM的基本组成

一、JDK\JRE\JVM JDK: 全称 "Java Development Kit" &#xff0c;Java 开发工具包&#xff0c;提供 javac 编译器、jheap、jconsole 等监控工具;JRE: 全称"Java Runtime Environment"&#xff0c;Java 运行环境&#xff0c;提供Class Library 核心类库 JV…

典型的MVC设计模式:使用JSP和JavaBean相结合的方式来动态生成网页内容典型的MVC设计模式

先看代码与实现&#xff1a; 文件结构 triangle_area4.jsp <% page contentType"text/html;charsetUTF-8" pageEncoding"UTF-8" %> <html> <body> <%--<jsp:useBean>&#xff1a;用于在JSP中实例化JavaBean。在这里&#xff0c…

C++ —— 关于list

目录 链接 前言 1. 迭代器浅解 2. 接口 2.1 构造函数 2.2 push_back 2.3 emplace_back 2.4 insert 2.5 erase 2.6 reverse 2.7 sort 2.8 merge 2.9 unique 2.10 splice 链接 cplusplus.com/reference/list/list/?kwlisthttps://cplusplus.com/reference/list/list…

公安局软件管理平台建设方案和必要性,论文-———未来之窗行业应用跨平台架构

一、平台方略 由于csdn拦截关键信息&#xff0c;我发发布方案&#xff0c;请留意后面文章

云服务器(华为云)安装java环境。

这篇文章主要是介绍如何搭建华为云服务器中的java环境&#xff0c;也就是jdk的安装。 这里华为云服务器使用的是liunx系统。 uname -a Linux操作系统的版本信息。具体来说&#xff0c;它表明使用的是Ubuntu系统&#xff0c;内核版本是5.15.0&#xff0c;构建于2023年1月20日&a…

16年408-数据结构

第一题&#xff1a; 解析&#xff1a; 经过查表可知&#xff1a;a的链接地址是1010H&#xff0c;而1010H正是表中e所在的位置。 由题可知f存放的位置是1014H&#xff0c; 要将f链接在a和e的中间&#xff0c;则a后面要链接f&#xff0c;f后面要链接e&#xff0c;e的链接地址不变…

【HTTP】请求“报头”,Referer 和 Cookie

Referer 描述了当前这个页面是从哪里来的&#xff08;从哪个页面跳转过来的&#xff09; 浏览器中&#xff0c;直接输入 URL/点击收藏夹打开的网页&#xff0c;此时是没有 referer。当你在 sogou 页面进行搜索时&#xff0c;新进入的网页就会有 referer 有一个非常典型的用…

LeetCode 257. 二叉树的所有路径,dfs

LeetCode 257. 二叉树的所有路径 给定一个二叉树&#xff0c;返回所有从根节点到叶子节点的路径。 说明: 叶子节点是指没有子节点的节点。 目录 LeetCode 257. 二叉树的所有路径算法选择数据结构解题步骤算法流程算法代码算法分析易错点和注意事项相似题目 算法选择 深度优…

Excel 冻结多行多列

背景 版本&#xff1a;office 2021 专业版 无法像下图内某些版本一样&#xff0c;识别选中框选的多行多列。 如下选中后毫无反应&#xff0c;点击【视图】->【冻结窗口】->【冻结窗格】后自动设置为冻结第一列。 操作 如下&#xff0c;要把前两排冻结起来。 选择 C1&a…

2-102基于matlab的蒙特卡洛仿真

基于matlab的蒙特卡洛仿真&#xff0c;对64QAM和BPSK进行蒙特卡洛仿真&#xff0c;并绘出误码率曲线。程序已调通&#xff0c;可直接运行。 下载源程序请点链接&#xff1a; 2-102基于matlab的蒙特卡洛仿真

详解机器学习经典模型(原理及应用)——支持向量机

一、什么是支持向量机 支持向量机&#xff08;Support Vector Machine, SVM&#xff09;是一种强大的机器学习算法&#xff0c;可用于解决数据分类&#xff08;二分类&#xff09;和回归问题。在分类问题上&#xff0c;SVM的核心思想是在特征空间中找到一个最优的超平面&#x…

jupyter安装与使用——Ubuntu服务器

jupyter安装与使用——Ubuntu服务器 一、安装miniconda3/anaconda31. 下载miniconda32. 安装miniconda33. 切换到bin文件夹4. 输入pwd获取路径5. 打开用户环境编辑页面6. 重新加载用户环境变量7. 初始化conda8.验证是否安装成功9.conda配置 二、安装jupyter2.1 conda安装2.2 配…

视频汇聚/视频存储/安防视频监控EasyCVR平台RTMP推流显示离线是什么原因?

视频汇聚/视频存储/安防视频监控EasyCVR视频汇聚平台兼容性强、支持灵活拓展&#xff0c;平台可提供视频远程监控、录像、存储与回放、视频转码、视频快照、告警、云台控制、语音对讲、平台级联等视频能力。 EasyCVR安防监控视频综合管理平台采用先进的网络传输技术&#xff0…

PHP探索校园新生态校园帮小程序系统小程序源码

探索校园新生态 —— 校园帮小程序系统&#xff0c;让生活更精彩&#xff01; &#x1f331;【开篇&#xff1a;走进未来校园&#xff0c;遇见新生态】&#x1f331; 你是否厌倦了传统校园的繁琐与单调&#xff1f;是否渴望在校园里也能享受到便捷、智能的生活体验&#xff1…

MySQL基础篇(黑马程序员2022-01-18)

1 MySQL数据库概述 1.1 MySQL数据库的下载,安装,启动停止 1.2 数据模型 (1)关系型数据库(RDBMS) 概念&#xff1a;建立在关系模型基础上&#xff0c;由多张相互连接的二维表组成的数据库。 特点&#xff1a; A. 使用表存储数据&#xff0c;格式统一&#xff0c;便于维护。…