飞车回馈点兑换2016:嵌入式操作系统内核实现(二)

来源:百度文库 编辑:九乡新闻网 时间:2024/05/06 13:43:44

1.3 内核临界区处理

       ByCore中提供了两个宏来处理最基本的原子操作,上面提到的锁机制,信号量机制也需要依靠这两个宏来完成功能,比如信号量的值的增加与减少必须是原子操作。说来半天,应该说明一下什么是原子操作了,所谓原子操作就是指,在指令执行的过程中不能被打断的操作,相信操作系统教程上面都有很清楚的说明。在单处理器下(没那个能力讨论多处理机),是什么破坏了原子操作呢?答案很显然就是中断,很自然就能想到关闭中断就能保护原子操作。但是不是只有这样一种策略保护原子操作呢?答案肯定是否定的,据本人当前的知识积累,只知道有的处理器提供硬件级别的原子操作。

对于处理器一级提供的原子操作,ByCore还不能应对(主要是个人能力),所以在ByCore里面简单的使用开关中断来达到原子效果。因此在ByCore中就提供了mac_disable_irq()mac_enable_irq()来开关闭中断,只提供这种方法其实很不好,也显得比较粗暴。关于中断方面的讨论,在Robert Love著的《Linux内核设计与实现》中详细的讨论了中断管理方案,上面的原理、策略对所有的内核都有指导意义,这里就不在深入去讨论这些方法,毕竟写这篇文章的重点在于实现。需要说明的是这两个宏一般会用汇编语言来实现,当然,如果处理器提供了从高级语言里就能操作中断就更好了。好了,对于ByCore中,这两个宏的具体做法如下:

首先:

#define mac_disable_irq() DisableInt()

#define mac_enable_irq() EnableInt()

上面的代码又对这两个宏做了重定义,也就是说真正的开关中断的操作是由DisableInt()nableInt()两个函数实现的,这两个函数由汇编代码实现,在ARM7TDMI核上的实现如下:

    EXPORT DisableInt

DisableInt

    STMDB      SP!,{R0}                                                

    MRS              R0,CPSR                                              

    ORR              R0,R0,#0x80                                         

    MSR              CPSR_cxsf,R0                                      

    LDMIA     SP!,{R0}                                                   

    MOV          PC,LR

 

    EXPORT EnableInt

EnableInt

    STMDB    SP!,{R0}

  MRS         R0,CPSR

  BIC        R0,R0,#0x80

    MSR         CPSR_cxsf,R0

    LDMIA     SP!,{R0}

    MOV        PC,LR

       上面的代码也很好理解,首先保护R0,因为在该函数中需要用到R0(①),这就是汇编与C程序的差别,在汇编函数中,需要自己保护自己使用的寄存器。然后读出CPSR寄存器的指到R0(②),再将R0的中值与0x80按位与(③),这样就关闭了中断,具体可参见ARM7TDMI的数据手册。再将新的控制值回写到CPSR(④),最后恢复R0返回(⑤)。上面代码中的EXPORT的作用是导出DisableInt EnableInt,也就是说这两个函数可以被其他文件中的代码调用。对于EnableInt函数,它的作用是开中断,它的操作与DisableInt相反,具体代码解释也与DisableInt类似所以不再赘述。

 

1.4 struct list结构

       C语言写代码最头痛的问题应该就是类型问题了,对于一些有相同算法流程,只是类型不同的操作,总是需要为每种类型重写他们的操作代码,感觉很不舒服。在ByCore中也遇到了这种问题,内核中有很多链表需要操作,但这些链表的类型却不同,难道需要为每个链表写一组操作,比如插入,删除操作。先看看下面的例子,struct task_ctrl_blkstruct page_struct为任务控制块的数据类型,和内存控制块的数据类型。现在暂时不去管它们里面的数据成员的意思,假设按照如下的定义将它们组织成双链表。

typedef struct task_ctrl_blk{

……

struct task_ctrl_blk next_link;

struct task_ctrl_blk prev_link;

……

}tcb_t;

 

typedef struct page_struct{

……

    struct page_struct next_free_link;

struct page_struct prev_free_link;

  ……

}page_t;

       很显然,如果提供一组函数用于操作双链表,那么应该怎样设计这些接口呢?假设设计一个实现插入功能的函数就应该是:

Insert(类型 head,类型node){

    ……

};

这里问题就来了,“类型”域应该填写什么呢?是struct task_ctrl_blk 还是struct page_struct呢?如果还有很多类型的双链表怎么办呢?可以看出,类型问题在C中让人很难应对。当然在C++里面可以有模板来解决这个问题。

       ByCore中有很多类型的链表,为了实现一组统一的链表操作函数,借鉴了Linux中的处理方法,引入struct list结构,将该结构嵌入到其他的类型中就可实现操作函数的统一。具体的做法请往下看。

       首先看看struct list的原型,

typedef struct list{

    struct list *prev;

    struct list *next;

}list_t;

       很显然,struct list只有两个指向本身类型的指针,主要用于处理双链表(如果这里还看不懂就麻烦了,建议去补补数据结构的知识了)。然后被重新定义为list_t,后面就使用list_t这个类型了。

       对于上面的tcb_tpage_t类型做点变形:将它们的连接域变成list_t类型。

typedef struct task_ctrl_blk{

……

list_t link

……

}tcb_t;

 

typedef struct page_struct{

……

list_t free_link

    ……

}page_t;

然后,将所有关于双链表的操作函数的类型域都变成list_t类型,而不向使用tcb_t或者page_t类型作为连接域,由于每种需要双链表的类型都采用list_t类型作为连接域,所以对于双链表的操作函数参数类型问题就能解决了。因此ByCore实现了如下的函数来操作双链表。

static void __add_node(list_t *next,list_t *prev,list_t *new){

  next->prev=new;

  new->next=next;

  new->prev=prev;

  prev->next=new;

}

 

static void add_que_rear(list_t *rear,list_t *new){

  __add_node(rear->next,rear,new);

}

 

static void del_que(list_t *next,list_t *prev){

  next->prev = prev;

  prev->next = next;

}

 

static void list_node_init(list_t *node){

  node->next = node;

  node->prev = node;

}

 

void add_node_seque_rear(list_t *head,list_t *pnew){

  if(head->next == head && head->prev == head){

    head->next = pnew;

    head->prev = pnew;

    pnew->next = pnew;

    pnew->prev = pnew;

  }else{

    add_que_rear(head->prev,pnew);

    head->prev = pnew;

  }

}

 

list_t *del_node_seque(list_t *head,list_t *pdel){

  list_t *renode = pdel;

  /* nodes have existed in the queue */

  if(head->next != head && head->prev != head){

 

    /* there is only one node in queue currently, */

    if(head->next == pdel && head->prev == pdel){

      head->next = head;

      head->prev = head;

    }

 

    /* node is in the head of queue */

    else if(head->next == pdel && head->prev != pdel){

      head->next = pdel->next;

      del_que(pdel->next,pdel->prev);

    }

 

    /* node is in the rear of queue */

    else if(head->next != pdel && head->prev == pdel){

      head->prev = pdel->prev;

      del_que(pdel->next,pdel->prev);

    }

    else del_que(pdel->next,pdel->prev);

    list_node_init(pdel);

    return(renode);

  }

       else return 0;

}

这些函数应该不用再多说了吧,数据结构的基本知识哦。其实,这组函数中,能被其他代码使用的只有add_node_seque_rear()del_node_seque()函数,前者用于向链表添加一个节点,后者从链表中删除一个节点,并返回该删除的节点。

       类型问题是解决了,又有一个问题来了,我们知道了list_t类型被嵌入到了其他类型(如tcb_tpage_t)当中,在实际使用时需要的往往不是list_t类型,然而双链表都是在对list_t操作,如果不能将list_t转换成它的宿主类型(如tcb_t)那这样做就没有多大意义了。内核有一个宏mac_find_entry解决了此问题,它的原型如下:

#define mac_find_entry(ptr, type, member) \

  ((type *)((char_t*)(ptr)-(uword_t)(&((type *)0)->member)))

它的功能可以根据list_t的成员值,得到宿主的数据类型,比如一个tcb_t的变量task,该宏可以根据tasklink域得到task的首地址。例如:

假设plist指针指向tasklink域,现在就可以使用mac_find_entry找到task的首地址,具体代码表达如下:

list_t *plsit = &task.link

tcb_t *ptcb;

ptcb = mac_find_entry(plist,tcb_t,link);

通过上面的操作ptcb就指向了task的首地址。说了这么多,是否应该解释一下该宏的代码的意思了,还是使用tcb_t类型来说事吧,先看看下面的图1.2再说。

……

 

link

……

plist

ptcb

offset

task

  

 1.2 mac_find_entry示意图

图中描述了task在内存中的布局情况,其实真正的问题可以归结为,知道plist指向的区域,求task的首地址,也就是ptcb指向的地址。相信聪明的你,看到这个图,算法应该出来了吧,用plist指向的内存地址值减去offset就可以得到task的首地址了,没错,就是这样。那么怎样才能得到offset呢?好了看看mac_find_entry宏吧,注意&((type *)0)->member),首先type就是宿主类型,这里就是tcb_t,它把0地址处强制转换成tcb_t类型,然后取link域的地址,由于起始地址为0,所以&((type *)0)->member)代表的地址就是offset。最后mac_find_entry宏使用传递的ptr(这里就是plist)指针减去offset就得到了宿主类型变量的起始地址。说来这么多,好像比教复杂,不过没关系根据图1.2,再想想应该没问题的。~_!

       好了,第一章好像也啰嗦完了,在这里大概说了说ByCore的大体结构,list_t结构类型等等热身工作,下面的文字就会较为详细的介绍ByCore各个部分的实现。