用一个链表连接不同类型结构体

来源:互联网 发布:怎么应聘淘宝客服 编辑:程序博客网 时间:2024/06/06 02:46

         一直以来对操作系统的内核感兴趣,对操作系统中进程、线程、互斥资源、内存等资源的管理方式充满好奇,最近看了一个学习系统的内核代码,惊奇于它管理方式的奇特,更佩服系统开发者的思维之奇特。现在把它对各种对象的管理方式记录下来,供以后查询源代码时做参考。

在操作系统中,每种资源(如进程、互斥资源等)都对应有一个结构体也就是一种对象的类型,而每种资源都对应有N多对象。如何管理好操作系统中各种不同的类型和各种类型所对应的对象就成为一个难题了,但是在该操作系统中这个问题解决的很好。我也是第一次见到这种奇特的管理方式。

在介绍操作系统如何管理好操作系统中形形色色的对象类型和对象之前,先要复习一下数据结构中的内容——双向循环链表。如下图所示:


为了避免忘记,将部分源代码记录下来:(这只不过是一个双向循环链表的基本操作)

  #include <iostream>  using namespace std;  //该结构体没有数据域,只有两个指针域。  typedef struct M_LIST_ENTRY  {  M_LIST_ENTRY *pre;  M_LIST_ENTRY *next;  }M_LIST_HEAD;  //初始化双向循环链表  void Init(M_LIST_HEAD* pListHead)  {  pListHead->pre = pListHead->next = pListHead;  }  //将某个结构体插入至对应链表头  void InsertIntoHead(M_LIST_HEAD* pListHead, M_LIST_ENTRY* pListEntry)  {  pListHead->next->pre = pListEntry;  pListEntry->next = pListHead->next;  pListHead->next = pListEntry;  pListEntry->pre = pListHead;  pListHead = pListEntry;  }

       下图描述了当该操作系统中存在三种对象类型和若干个对象时,由对象类型和对象组成的链表。这里只列出三种链表,分别是对象类型链表、对象链表以及属于某种对象类型的对象链表。对象类型链表的链表头是 ObpTypeListHead,管理了所有对象类型,该链表在全局是唯一的,并且是一个单向链表。对象类型链表中链接了三种对象类型,分别是 Process 对象类型、Thread 对象类型和 Mutex 对象类型。对象链表的链表头是 ObpObjectListHead,它管理了系统中所有的对象,该链表在全局是唯一的并且是一个双向循环链表。为什么要用双向循环链表呢?因为双向循环链表的优势就在于,当需要从链表中移除一个链表项时,只需要有这个链表项的指针即可完成操作。  

总结:系统中每创建一个对象类型,都会链接在ObpTypeListHead中;每个对象都链接在ObpObjectListHead中。直接上图:



当然这并不是重点,重点是上图如何用代码的形式表示出来,即如何将不同类型的对象连接起来,下面就详细的介绍一下原理,并用代码的形式表示出来。

还记得上面的双向循环链表的结构体不?

  typedef struct M_LIST_ENTRY  {  M_LIST_ENTRY *pre;  M_LIST_ENTRY *next;  }M_LIST_HEAD;

这个结构体只有指针域,并没有实际的数据域。当然我们要的就是这个东西啦。下面看看如何利用它来串联不同结构体对象的。

首先我们要将上面的结构体嵌入其他结构体中。看下面的定义:

  typedef struct Mem1  {  size_t size;  M_LIST_ENTRY mHead1;  }*pMem1;  ////////////////////////////////////////////////////////////////////////////////////  typedef struct Mem2  {  char ch;  M_LIST_ENTRY mHead2;  }*pMem2;  ////////////////////////////////////////////////////////////////////////////////////

这两个不同的结构体中都有一个Head节点,所有这两种类型所创建的对象都插入到对应的链表中。分别由Mem1Mem2创建一个对象m1m2,将这两种对象连接起来就用如下的方法:

M_LIST_HEAD Mhead;//创建一个M_LIST_HEAD类型的头结点用于连接其他对象Init(&Mhead);Mem1 m1;Mem2 m2;m1.size = 10;m2.ch = 'a';  Init(&m1.mHead1);Init(&m2.mHead2);//将这两种对象都插入到以Mhead为头结点的链表中InsertIntoHead(&Mhead, &m1.mHead1);InsertIntoHead(&Mhead, &m2.mHead2);

这样就将这两种不同的对象用M_LIST_ENTRY域连接起来了,这样是爽了,但是我要怎么访问这个对象中所有域呢?我现在只知道M_LIST_ENTRY域啊!接下来要解决的就是这个问题了。在C语言中变量内存布局一文中已经有解决方案了,现在就用一个简单的例子来验证一下吧!

  typedef unsigned long UNLONG_PTR;  struct Node  {  size_t size;  size_t data;  size_t account;}*M,node;

假设我们现在只知道account域的地址。&M->account,首先来看看->是如何获取数据的。

->”操作符首先计算出域 account在M中的偏移值,与 该结构体对象M指向的地址(基址)相加得到地址,再访问内存中的数据。下面就是利用这个原理来获取基址的。

(Node*)((UNLONG_PTR)&node.account - (UNLONG_PTR)&((Node*)0)->account)

其中&((Node*)0)->account得到的是当基址为0时,account域的偏移值。和&node.account相减得到的当然就是M的基址了。因此就可以访问所有的数据成员了。

值得一提的是一定要将地址转化成10进制数据然后进行运算,因为两个地址相减得到的只是两个地址间的距离(4字节)。和16进制相减是不同的!切记切记!!

地址:0xF004 - 地址:0xF000 = 1  16进制 = 0xF004

具体代码如下:

#include <iostream>using namespace std;struct Node{size_t size;size_t data;size_t account;};typedef unsigned long UNLONG_PTR;void main(){Node node;node.size = 1;node.data = 2;node.account = 3;cout<<&node<<endl<<endl;cout<<&node.size<<endl;cout<<&node.data<<endl;cout<<&node.account<<endl;cout<<"---------------------------"<<endl;cout<<"Address = "<<((&node.account - &((Node*)0)->account))<<endl;cout<<"Address * 4 = "<<((&node.account - &((Node*)0)->account))*4<<endl;cout<<(UNLONG_PTR)&node.account - (UNLONG_PTR)&((Node*)0)->account<<endl;cout<<"---------------------------"<<endl;Node* p =(Node*) ((UNLONG_PTR)&node.account - (UNLONG_PTR)&((Node*)0)->account);cout<<p->account<<" "<<p->data<<" "<<p->size<<endl;system("pause");}


原创粉丝点击