全部博文(71)
分类: LINUX
2008-04-22 11:06:15
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
图中描述了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各个部分的实现