RT-Thread实时操作系统(1)——准备工作

在学习RT-Thread实时操作系统中,对于一些C语言和数据结构的知识还是需要再加强一些。

一、指针

        有关指针的知识,详细学习到这个地步大家都有一定的了解,但是对于学习RT-Thread嵌入式实时操作系统来说,我们对于下面三部分还需要再进行加强。

1.1函数指针

        函数指针本质还是指针,只是指针所指的地址是函数的地址,什么是函数的地址?如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址,函数名表示的就是这个地址。类似于数据,数组名就是数组的地址,那么函数名也就是函数的地址。

int  add(int a,int b)
{
    return a + b;
}
 
 int *p;
int (*p)(int ,int);//p是一个指针变量,指向的函数类型,函数必须返回值是int ,参数的顺序int,int
p  = add;//地址
int a = (*p)(10,10); //a得到的值是20

        函数指针特点,定义时需要说明函数的类型以及函数参数的类型。 指针的类型是对应函数返回指针的类型。

1.2指针函数

        指针函数就相对来说能简单一些,本质上是函数,只不过函数的返回值不是整型,浮点型等,而是指针类型。

float *fun()
{
    //函数内容
     float *a;
     float b=3;
     a=&b;
    return a;
}
int main()
{
    float *p;
    p = fun();
}

1.3结构体指针

        在实战编程的过程中,结构体的应用是相当的广泛,对于结构体指针我们说明,所谓结构体指针就是指向结构体变量的指针,一个结构体变量的起始地址就是这个结构体变量的指针。如果把一个结构体变量的起始地址存放在一个指针变量中,那么,这个指针变量就指向该结构体变量。

struct node
{
    int a;
    int b;
    char c;
};
struct node *p;
strruct node n;
p=&n;

二、链表

2.1链表定义

        链表是通过节点,把离散的数据连成一个表,通过对节点的插入和删除从而实现对数据的存取。链表分为:单链表、循环链表、双向链表,双向循环链表;

单链表:

循环链表:

双链表:

双向循环链表:

        在数据结构中的链表和RT-Threat操作系统中的链表有所不同,在数据结构中,数据和指针存放在一个结构体中,指针直接指向的是结构体的地址。而在实时操作系统中,(双向链表)指针专门为一个结构体,这样指针指向的就是指针所在的结构体的地址,对于节点的数据不是很友好,需要一定的操作才能利用指针读取数据。

在RT-Thread操作系统中,malloc函数的使用用RT_KERNEL_MALLOC进行替换;

2.2链表使用

节点初始化:   

/**
 * @brief initialize a list
 *
 * @param l list to be initialized
 */
rt_inline void rt_list_init(rt_list_t *l)
{
    l->next = l->prev = l;
}

前插:

/**
 * @brief insert a node before a list
 *
 * @param n new node to be inserted
 * @param l list to insert it
 */
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;
}

后插:

/**
 * @brief insert a node after a list
 *
 * @param l list to insert it
 * @param n new node to be inserted
 */
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;
}

删除:

/**
 * @brief remove node from list.
 * @param n the node to remove from the list.
 */
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;
}

遍历:

/**
 * @brief get the list length
 * @param l the list to get.
 */
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;
}

rt_list_entry函数:函数的作用是根据已知成员的地址,算出其结构体的首地址。函数定义如下(在rtservice.h中):

#define rt_list_entry(node, type, member) \
    rt_container_of(node, type, member)

#define rt_container_of(ptr, type, member) \
    ((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))

其中,node—已知成员的地址;type—结构体类型;member—结构体成员变量                    

 案例:

        利用单片机输出,链表的使用:

#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>



struct info
{
    int age;
    char name[20];
    rt_list_t list;
};

 unsigned int rt_list_log(const rt_list_t *l)
{
    unsigned int len = 0;
    const rt_list_t *p = l;
    struct info *sp=NULL;
    while (p->next != l)
    {
        sp=rt_list_entry(p, struct info, list);
        LOG_D("Personage:%d,name:%s\n",sp->age,sp->name);
        p = p->next;
        len ++;
    }
        sp=rt_list_entry(p, struct info, list);
        LOG_D("Personage:%d,name:%s\n",sp->age,sp->name);
    return len;
}



int main(void)
{
    int count = 1;
    struct info *person1;
    struct info *person2;
    struct info *person3;
    struct info *person4;
    person1=(struct info *)RT_KERNEL_MALLOC(sizeof(struct info));
    if(person1==NULL)
    {
        LOG_E("person1 RT_KERNEL_MALLOC error!");
        return -1;
    }
    person2=(struct info *)RT_KERNEL_MALLOC(sizeof(struct info));
    if(person2==NULL)
       {
           LOG_E("person1 RT_KERNEL_MALLOC error!");
           return -1;
       }
    person3=(struct info *)RT_KERNEL_MALLOC(sizeof(struct info));
    if(person3==NULL)
       {
           LOG_E("person1 RT_KERNEL_MALLOC error!");
           return -1;
       }
    person4=(struct info *)RT_KERNEL_MALLOC(sizeof(struct info));
    if(person4==NULL)
       {
           LOG_E("person1 RT_KERNEL_MALLOC error!");
           return -1;
       }

    person1->age=8;
    strcpy(person1->name,"wangfeng");

    person2->age=23;
    strcpy(person2->name,"LBJ");

    person3->age=30;
    strcpy(person3->name,"curry");

    person4->age=35;
    strcpy(person4->name,"duruante");


     rt_list_init(&(person1->list));
     LOG_D("Lest len:%d",rt_list_len(&(person1->list)));
     rt_list_insert_after(&(person1->list),&(person2->list));
     LOG_D("Lest len:%d",rt_list_len(&(person1->list)));
     rt_list_insert_after(&(person2->list),&(person3->list));
     LOG_D("Lest len:%d",rt_list_len(&(person1->list)));
     rt_list_insert_after(&(person3->list),&(person4->list));
     LOG_D("Lest len:%d",rt_list_len(&(person1->list)));
     //rt_list_remove(&(person3->list));
     LOG_D("Lest len:%d",rt_list_len(&(person1->list)));

     while(1)
     {
         rt_list_log(&(person1->list));
         rt_thread_mdelay(1000);
         LOG_D("Lest len:%d",rt_list_len(&(person1->list)));
     }

}

运行结果: 

2.3链表与数组区别

 存储空间:

        数组连续存储:数组从栈上分配内存,使用方便,但是自由度小;

         链表不连续存储:链表从堆上分配内存,自由度大,但是要注意内存泄漏。

        链表采用动态内存分配的方式,在内存中不连续,支持动态增加或者删除元素;需要时可以使用malloc或者new开辟内存,不需要时使用free或者delete来释放内存。

数据长度:

        在C语言中数组的长度是不可变的,定义时就已经确认好数组长度。链表的长度可便,可以动态的增删数据节点。

访问效率:

        数组在内存中顺序存储,可以通过下标访问,访问效率高;链表访问效率低,如果要访问某个元素,需要从头遍历。

三、内存分配 

1、代码段(.text)存放的是程序的机器码和只读数据,这个段在内存中只读,写操作会导致错误。

2、静态存储区:

        内存分配在编译之前完成,且在程序的整个运行周期都存在,存储全局变量个静态变量。

数据段:已初始化的数据段(.data)和未初始化的数据段(.bbs)。

data:用来存放保存已初始化的静态变量和全局变量;

bbd:用来存放保存未初始化的静态你变量和全局变量。

3、堆(heap):用来存储程序运行时分配的变量.由程序员进行分配

        堆的大小并不固定,可动态扩张或缩减。其分配由malloc free等这类实时内存分配函数来实现.

        堆的内存释放由应用程序去控制,通常一个new()就要对应一个delete(),如果程序员没有释放掉,那么在程序结束后操作系统会自动回收。

4、栈(stack):

        一种用来存储函数调用时的临时信息的结构,如函数调用所传递的参数、函数的返回地址、函数的局部变量等。在程序运行时由编译器在需要的时候分配,在不需要的时候自动清除。
栈结构的特点:先入后出,后入先出;
栈的基本操作: PUSH操作:向栈中添加数据,称为压栈,数据将放置在栈顶;
POP操作:POP操作相反,在栈顶部移去一个元素,并将栈的大小减一,称为弹栈。

最后还有常量区,常量存储区用于存储常量数据,如字符串常量,const修饰的变量,敞亮存储区通常位于静态存储区。

      在linux操作系统中,上述的内容都是存放在内存中的,但是 ,对于在单片机中,Flash是ROM(磁盘),掉电数据不会丢失,程序的运行在ROM里面,其访问速度比较慢。RAM就是运行内存,其他的数据是保存在RAM中的。

#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

int main(void)
{
    int count = 1;
    while (count++)
    {
        LOG_D("count address:%p",&count);
        LOG_D("main address:%p",main);
        LOG_D("main address:%p",&main);
        //LOG_D("Hello RT-Thread!");
        rt_thread_mdelay(1000);
    }
    return RT_EOK;
}

STM32中的内存分配: 

运行结果:

        由上述运行结果可知:

1、程序(.text)存储在单片机的ROM(Flash)中,而count变量存储在RAM中。

2、函数名就是函数的地址。

 栈和堆的区别:

管理方式由程序员控制由编译器自动管理
内存管理机制程序员利用对应的函数malloc、free进行内存的申请和释放。只要申请的内存小于剩余的内存,都可以进行申请,否则存在异常。
空间大小对视不连续的内存区域,堆的大小受限与计算机的虚拟内存,空间比较灵活,比较大。展示一块连续的内存区域,大小事由操作系统预定好的。
碎片问题对于堆,频繁free会造成大量碎片使得程序效率低下对于栈,它是一个先进后出的队列,不会产生碎片
生长方式向上生长,从低地址到高地址。向下生长,从高地址到地址。
分配方式动态分配静态分配

 四、其他

4.1const关键词

        常变量,在定义变量时,前面加一个关键字const,其变量在存在期其值不能改变;

        常变量,本质上还是变量,只是值不能发生改变,常变量与常量的区别,常量是没有名字的不变,不分配存储单元。而常变量具有变量的属性:有类型,占据内存单元。

        也就是说经过const修饰的变量、函数参数、函数返回值;它们只具有读的性质,不能进行写的操作。

        下图为const的应用:

 如果对const修饰的变量进行修改,在编译的时候就回出错;

 4.2static关键词

            修饰局部变量,局部变量的值就不会再发生更改,能保存在bbs中,可以认为是一个全局变量。

            修饰全局变量,不能在被其他文件使用,即便是用extern,作用将全局变量限制在本文件中。

             修饰函数,将函数锁定在本文件中,外部文件不能在被使用。

4.3内联函数

        内联函数的意思是: 将项目中某些常用的简单逻辑的函数申请为内联函数,在编译之前,就会将主调函数中调用该内联函数的位置,直接替换为该函数体的内容,再进行编译,这样就省去了运行时,调用函数的CPU开销时间,大大的提升程序的执行效率。

4.4大小端

        数据在计算机中内存存放的方式称为字节序,字节序分为大端与小端;大端字节序是指一个整数的最高位字节存储在内存的低地址处,低位字节存储在内存的高地址处;小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处。

小端字节序:

0X 01 02 03 04 四个字节;

内存方向:低位——高位;

内存存储顺序:04 03 02 01(高字节在高位,低字节在低位);

大端字节序:

0X 01 02 03 04 四个字节 ;

内存方向:低位——高位;

内存存储顺序: 01 02 03 04(高字节在低位,低字节在高位);

        大小端判断,根据处理器有关,那么如何判断对应处理器的大小端方式呢?

在32位操作系统中int a=1占据四个字节;

00000000 00000000 00000000 00000001前面八位称为高字节,后面八位称为低字节;比如存放在0x100-0x103四个字节,那么如果是大端存储:

0x100-00000000、0x101-00000000、0x102-00000000、0x103-00000001;

那如果是小端存储:

0x100-00000001、0x101-00000000、0x102-00000000、0x103-00000000;

方式一: 利用联合体进行(union)判断

#include <stdio.h>
union Test
{
	short i;
	char ch[2];
};

int main()
{
	union Test t;
	t.i = 0x0102;
	if (t.ch[0] == 0x02)
		printf("小端存储;\n");
	else if (t.ch[0] == 0x01)
		printf("大端存储;\n");
	return 0;
}

方式二:通过强制类型转换的方式判断

#include <stdio.h>
int main()
{
	int i = 1;
	char *p = (char *)&i;
	if (*p==1)
		printf("小端存储;\n");
	else if (*p==0)
		printf("大端存储;\n");
	return 0;
}

方法三:通过移位操作进行判断

#include <stdio.h>
int main()
{
	int i = 1;
	i=i >> 1;
	if (i==0)
		printf("小端存储;\n");
	else
		printf("大端存储;\n");
	return 0;
}

4.5枚举

如果有的变量的取值只有几种可能性,则可以定义为枚举类型;所谓的枚举就是将可能出现的值一一进行列举。

        声明枚举类型用enum开头;例如

        eunm Weekday{sun,mon,tue,wed,thu,fri,sat};

        以上声明了一个枚举类型enum Weekday。然后可以用此类型来定义变量。

        enum Weekday workday,weekend;

其中,workday与weekend被定义为枚举变量,花括号中称为枚举元素或枚举常量。。

声明枚举类型的一般形式:

        enum [枚举名] {枚举元素列表};

4.6共用体

        利用同一段内存单元存放不同的数据类型。例如:把一个短整型变量、一个字符型变量和一个实行变量,放在同一个地址开始的内存单元中。这几个不同的变量共享同一段内存的结构;变量在内存中占据的字节数不同,但是都是从同一个地址开始的,使用了数据覆盖技术;

#include <stdio.h>

int main()
{
	union person
	{
		char  name[10];
		char  sex[2];	          
	};
	union person  lili;
	lili.name [0]= 'a';
	lili.name[1] = 'b';
	lili.name[2] = 'c';
	lili.name[3] = 'd';
	lili.name[4] = 'e';

	lili.sex[0] = '1';
	lili.sex[1] = '2';
	for (int i = 0; i < 5; i++)
	{
		printf("%c", lili.name[i]);	
	}
	return 0;
}

运行结果: 

4.7结构体的内存对齐

        结构体的大小如何计算?是将结构体的成员的内存大小加起来吗?其实关于结构体的大小存在一个内存对齐规则,用空间换时间。

结构体的内存对齐规则一共有4条:

1、第一个成员在结构体变量偏移量为0的地址处,也就时候第一个成员必须从头开始;

2、其他成员变量要对齐某个数字(对齐数)的整数倍的地址,对齐数为编译器的一个对齐数与该成员大小的较小值。

3、结构体总大小为最大对齐数的整倍数;

4、如果嵌套结构体,嵌套结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体的对齐数)的整数倍。

 案例代码:

#include <stdio.h>
#include <string.h>

int main()
{
	struct node1
	{
		char a;
		int b;
		char c;
	};
	struct node2
	{
		int a;
		char b;
		char c;
	};
	struct node3
	{
		char a;
		int b;
	    struct node1 c;
	};

	printf("%d\n", sizeof(struct node1));
	printf("%d\n", sizeof(struct node2));
	printf("%d\n", sizeof(struct node3));
	return 0;

}

运行结果: 

为什么要进行内存对齐呢?

 1、平台原因

           不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台只能在某些特定地址处取某些特定的数据,否则就会抛出硬件异常。也就是说在计算机在内存读取数据时,只能在规定的地址处读数据,而不是内存中任意地址都是可以读取的。

2、性能原因。

           正是由于只能在特定的地址处读取数据,所以在访问一些数据时,对于访问未对齐的内存,处理器需要进行两次访问;而对于对齐的内存,只需要访问一次就可以。 其实这是一种以空间换时间的做法,但这种做法是值得的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值