免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 852 | 回复: 0
打印 上一主题 下一主题

嵌入式操作系统内核实现(二) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-04-22 11:06 |只看该作者 |倒序浏览

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_blk和struct 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_t和page_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_t,page_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,该宏可以根据task的link域得到task的首地址。例如:
假设plist指针指向task的link域,现在就可以使用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各个部分的实现


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/59291/showart_572374.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP