RT-Thread 内核基础(五)

2024-01-08 16:43:04

使用static修饰全局变量作用

限制作用域

  • 如果全局变量前面加上’static’关键字,那么该变量的作用域将被限制在声明它的源文件中,即它将成为一个文件作用域的静态变量。
  • 其它源文件无法访问这个变量。这对于控制变量的可见性和避免命名冲突是有用的,尤其在大型项目中。

持久性

  • 静态全局变量在程序的整个生命周期内都存在,不像普通的全局变量那样在程序执行到声明它的文件时被创建,在离开作用域时被销毁。
  • 静态全局变量的生命周期与程序运行时间相同,但仍然受到作用域的限制。

内核对象模式

RT-Thread内核采用面向对象的设计思想进行设计,系统级的基础设施都是一种内核对象,例如线程,信号量,互斥量,定时器等。

内核对象分为两类:静态内核对象和动态内核对象。
静态内核对象通常放在RW段和ZI段中,在系统启动后在程序中初始化。
动态内核对象则是从内存堆中创建的,然后手工做初始化。

关于静态线程和动态线程的例子

static struct rt_thread thread1;
static rt_uint8_t thread1_stack[512];

void thread1_entry(void *parameter)
{
	int i;
	while(1)
	{
		for(i = 0; i<10; i++)
		{
			rt_kprintf("%d\n",i);
			rt_thread_mdelay(100);
		}
	}
}

/* 线程 2 入口 */
void thread2_entry(void* parameter)
{
     int count = 0;
     while (1)
     {
         rt_kprintf("Thread2 count:%d\n", ++count);

        /* 延时 50ms */
        rt_thread_mdelay(50);
    }
}

/* 线程例程初始化 */
int thread_sample_init()
{
	rt_thread_t thread2_ptr;
	rt_err_t result;
	 result = rt_thread_init(&thread1,
                            "thread1",
                            thread1_entry, RT_NULL,
                            &thread1_stack[0], sizeof(thread1_stack),
                            200, 10);

    /* 启动线程 */
    if (result == RT_EOK) rt_thread_startup(&thread1);

    /* 创建线程 2 */
    /* 线程的入口是 thread2_entry, 参数是 RT_NULL
     * 栈空间是 512,优先级是 250,时间片是 25 个 OS Tick
     */
    thread2_ptr = rt_thread_create("thread2",
                                thread2_entry, RT_NULL,
                                512, 250, 25);

    /* 启动线程 */
    if (thread2_ptr != RT_NULL) rt_thread_startup(thread2_ptr);
}

在这个例子中,thread1是一个静态线程对象,而thread2是一个动态线程对象。
thread1对象的内存空间,包括线程控制块thread1与栈空间thread1_stack都是编译时决定的,因为代码中都不存在初始值,都统一放在未初始化数据段中。
thread2运行中用到的空间都是动态分配的,包括线程控制块(thread2_ptr指向的内容)和栈空间。

静态对象会占用RAM空间,不依赖于内存堆管理器,内存分配时间确定。
动态对象则依赖于内存堆管理器,运行时申请RAM空间,当对象被删除后,占用的RAM空间被释放。
这两种方式各有利弊,可以根据实际环境需求选择具体使用方式。

内核对象管理架构

RT-Thread采用内核对象管理系统来访问/管理所有内核对象,内核对象包含了内核中绝大部分设施,这些内核对象可以是静态分配的静态对象,也可以是从系统内存堆中分配的动态对象。

通过这种内核对象的设计方式,RT-Thread做到了不依赖于具体的内存分配方式,系统的灵活性得到极大的提高。

RT-Thread 内核对象包括:线程,信号量,互斥量,事件,邮箱,消息队列和定时器,内存池,设备驱动等。
对象容器中包含了每类内核对象的信息,包括对象类型,大小等。

对象容器给每类内核对象分配了一个链表,所有的内核对象都被链接到该链表上。

在这里插入图片描述

下图则显示了各类内核对象的派生和继承关系。
对于每一种具体内核对象和对象控制块,除了基本结构外,还有自己的扩展属性(私有属性)。
例如,对于线程控制块,在基类对象基础上进行扩展,增加了线程状态、优先级等属性。这些属性在基类对象的操作中不会用到,只有在与具体线程相关的操作中才会使用。
因此从面向对象的观点,可以认为每一种具体对象是抽象对象的派生,继承了基本对象的属性并在此基础上扩展了与自己相关的属性。
在这里插入图片描述
在对象管理模块中,定义了通用的数据结构,用来保存各种对象的共同属性,各种具体对象只需要在此基础上加上自己的某些特别的属性,就可以清楚地表示自己的特征。

这种设计方法的优点有:

  1. 提高了系统的可重用性和扩展性,增加新的对象类别很容易,只需要继承通用对象的属性再加少量扩展即可。
  2. 提供统一的对象操作方式,简化了各种具体对象的操作,提高了系统的可靠性。
    上图中由对象控制块 rt_object 派生出来的有:线程对象、内存池对象、定时器对象、设备对象和 IPC 对象(IPC:Inter-Process Communication,进程间通信。在实时操作系统中,IPC对象的作用是进行线程间同步与通信)。
    由 IPC 对象派生出信号量、互斥量、事件、邮箱与消息队列、信号等对象。

对象控制块

内核对象控制块的数据结构:

struct rt_object
{
	/* 内核对象名称     */
     char      name[RT_NAME_MAX];
     /* 内核对象类型     */
     rt_uint8_t  type;
     /* 内核对象的参数   */
     rt_uint8_t  flag;
     /* 内核对象管理链表 */
     rt_list_t   list;
}

目前内核对象支持的类型如下:

enum rt_object_class_type
{
    RT_Object_Class_Null          = 0x00,      /**< The object is not used. */
    RT_Object_Class_Thread        = 0x01,      /**< The object is a thread. */
    RT_Object_Class_Semaphore     = 0x02,      /**< The object is a semaphore. */
    RT_Object_Class_Mutex         = 0x03,      /**< The object is a mutex. */
    RT_Object_Class_Event         = 0x04,      /**< The object is a event. */
    RT_Object_Class_MailBox       = 0x05,      /**< The object is a mail box. */
    RT_Object_Class_MessageQueue  = 0x06,      /**< The object is a message queue. */
    RT_Object_Class_MemHeap       = 0x07,      /**< The object is a memory heap. */
    RT_Object_Class_MemPool       = 0x08,      /**< The object is a memory pool. */
    RT_Object_Class_Device        = 0x09,      /**< The object is a device. */
    RT_Object_Class_Timer         = 0x0a,      /**< The object is a timer. */
    RT_Object_Class_Module        = 0x0b,      /**< The object is a module. */
    RT_Object_Class_Memory        = 0x0c,      /**< The object is a memory. */
    RT_Object_Class_Unknown       = 0x0e,      /**< The object is unknown. */
    RT_Object_Class_Static        = 0x80       /**< The object is a static object. */
};

如果是静态对象,那么对象类型的最高位将是1(是 RT_Object_Class_Static 与其他对象类型的与操作),否则就是动态对象,系统最多能够容纳的对象类别数目是127个。

内核对象管理方式

内核对象容器的数据结构:

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

一类对象由一个rt_object_information结构体来管理,每一个这类对象的具体实例都通过链表的形式挂接在object_list上。而这一类对象的内存块尺寸由 object_size 标识出来(每一类对象的具体实例,他们占有的内存块大小都是相同的)。

初始化对象

在使用一个未初始化的静态对象前必须先对其进行初始化。初始化对象使用以下接口:

void rt_object_init(struct rt_object* object, enum rt_object_class_type type, const char* name)

当调用这个函数进行对象初始化时,系统会把这个对象放置到对象容器中进行管理,即初始化对象的一些参数,然后把这个对象节点插入到对象容器的对象链表中,对该函数的输入参数的描述如下:
在这里插入图片描述

脱离对象

从内核对象管理器中脱离一个对象。脱离对象使用以下接口:

void rt_object_detach(rt_object_t object);

调用该接口,可使得一个静态内核对象从内核对象容器中脱离出来,即从内核对象容器链表上删除相应的对象节点。
对象脱离后,占用的内存并不会被释放。

分配对象

上述描述的都是对象初始化、脱离的接口,都是面向对象内存块已经有的情况下,而动态的对象则可以在需要时申请,不需要时释放出内存空间给其他应用使用。申请分配新的对象可以使用以下接口:

rt_object_t rt_object_allocate(enum  rt_object_class_type type ,
                               const  char*  name)

在调用以上接口时,系统首先需要根据对象类型获取对象信息(特别是对象类型的大小信息以用于系统能够分配正确大小的内存数据块),而后从内存堆中分配对象所对应大小的内存空间,然后再对该对象进行必要的初始化,最后将其插入对象容器链表中。

如何遍历内核对象

以遍历所有线程为例:

rt_thread_t thread = RT_NULL;
struct rt_list_node *node = RT_NULL;
struct rt_object_information *information = RT_NULL;

information = rt_object_get_information(RT_Object_Class_Thread);

rt_list_for_each(node, &(information->object_list))
{
	thread = (rt_thread_t)rt_list_entry(node, struct rt_object, list);
	rt_kprintf("name:%s\n", thread->name);
}

文章来源:https://blog.csdn.net/Caramel_biscuit/article/details/135456163
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。