HelloOs总结之辅助性功能实现

HelloOs总结之辅助性功能实现

  • 六、辅助性功能实现
    • 6.1 通用链表
    • 6.2 string类操作函数
    • 6.3 PrintF打印函数
    • 6.4 信号量、锁的实现
    • 6.5 debug调试相关

六、辅助性功能实现

   这一章主要讲些HelloOs中的主要辅助性功能的实现。相对来说,它们不算是HelloOs的主线部分,但是其大都是作为辅助部分穿插在各个章节,下面对这些部分做一简要介绍。


 

6.1 通用链表

   链表作为线性表的一种,在操作系统内核中有着重要的作用。关于链表最基本的理论内容,数据结构书中都有基本的介绍。这里,仅以HelloOs中实现的链表简单介绍其作用。
   我们一般在刷题构造链表的时候,串起来都是链表的整个node结点,但是在实际的应用中,尤其是linux内核中,为了抽象出通用的链表操作,其一般串起来的都是node结点(当成一般的结构体就可以)中的一个成员变量,然后通过这个成员变量来找到这个node结点。如下图6.1所示。

HelloOs总结之辅助性功能实现_第1张图片

  • 图6.1 链表串联示意图

   关于如何从结构体成员寻找整个结构体,我以前在一篇博客中有介绍,可以参照 https://blog.csdn.net/plm199513100/article/details/102490680
这里就不赘述了。

   有了以上的了解之后,内核建立的通用的链表就只是普通的链表。HelloOs中通用的链表结构体(HelloOs使用的是双向链表)如下图6.2所示。然后利用这个通用的链表结构体可以形成链表的一些基本操作,如插入、删除等等。

在这里插入图片描述

  • 图6.2 通用的链表结构体示意图

   建立了通用的链表数据结构之后,如果想串起像图6.1所示的链表串,只需要在在目标结构体结点中添加一个list_head的成员变量,用于串串的操作就可以了。在HelloOs中用这个通用结构体串起了两种资源,一种是内存结构体,一种是pcb。如下图6.3和6.4所示。

HelloOs总结之辅助性功能实现_第2张图片

  • 图6.3 用list串起的内存page示意图

HelloOs总结之辅助性功能实现_第3张图片

  • 图6.4 用xx_tag串起的pcb示意图

   从某种程度上说,抽象出这种通用的链表是一种面向对象的思维,即通用的链表是父类,想要串起的资源目标结构体是“继承”的子类,只不过这种“继承” 并不是使用面向对象语言中标准的继承手段,而是使用的组合,即子类包含父类的一个对象。 以前写过一篇《Linux内核之C语言实现面向对象机制》博客,感兴趣的可以了解一下。下面是链接:
https://blog.csdn.net/plm199513100/article/details/102468196


 

6.2 string类操作函数

   字符串类函数库应该是每一个稍微大一点的系统都应该有的部分,其提供了基本的字符串类操作。HelloOs中也有不少的对字符串的操作,因此也提供了一部分的字符串的操作函数,如memset、memcmp、strlen等等,放在string.c文件中。这些函数基本原理都很简单,在此就不说了。这里提一下string函数库主要是为了完整性。


 

6.3 PrintF打印函数

   printf函数应该算是每一个学编程的人第一个用到的库函数了,但是你知道其是怎么实现的吗?说实话,就一个printf函数的实现,还并不很简单呢!这涉及到进制的相互转换,字符串解析、变长参数等相关知识点。 以前写过一个关于《printf的底层原理浅析》(https://blog.csdn.net/plm199513100/article/details/104905990),
   和这里的HelloOs实现的大同小异(HelloOs中稍微有点不一样,因为这里是调用了系统调用write) ,在此就不赘述了。

   对了,这里稍微提一点,HelloOs中内核的输出使用的是console_put_xxx函数,用户进程使用的是printF(这里使用函数名printF而不是printf是为了防止在编译的时候和C语言的内置库函数冲突)。


 

6.4 信号量、锁的实现

   为了各个线程之间的对资源的互斥访问,这里简单实现了信号量和锁机制。关于信号量的相关理论内容请参照操作系统相关资料,这里就不赘述了。
   下面简单说说HelloOs是如何实现信号量和锁机制的。
   其实实现过程说白了也很简单,就是将未获取到信号量资源的线程阻塞起来,然后当信号量代表的资源被释放时,把阻塞的线程唤醒。这里所说的阻塞和唤醒其实是进程调度里的两个知识点,当时没细说。其实也很简单,所谓阻塞线程,就是当信号量资源不可得时,将线程移除就绪队列,放到对应阻塞的信号量上(这也叫做阻塞在信号量上)。而唤醒,就是当资源可获得时,将阻塞在信号量上的线程移除(一般是仅移出第一个线程),放回到就绪队列等待调度。过程如下图6.5、6.6、6.7所示。

HelloOs总结之辅助性功能实现_第4张图片

  • 图6.5 threadC请求信号量资源

HelloOs总结之辅助性功能实现_第5张图片

  • 图6.6 资源不可得时,threadC阻塞

HelloOs总结之辅助性功能实现_第6张图片

  • 图6.7 资源可获得时,threadA移入就绪队列

   关于互斥锁的实现,其实本质上就是利用了信号量,只不过信号量的资源是只有1(也就是value初始化是1或者是0)就是了。


 

6.5 debug调试相关

   最后谈谈关于debug调试的那些事儿。

   debug是程序员在编码过程中不可缺少的部分(当然,这一部分应该是每个人都想剪除的),毕竟程序员也是人,人不可能像机器那般思维缜密,逻辑不出错,这是人的天性使然。既然debug不可避免,所以猿们就发明了各种各样的debug工具(检测语法的、语义的、逻辑的、内存空间的、多线程的、死锁的等等)来快速找到bug所在。有的甚至为了避免一些常出错的地方,从语言的角度就规避了这些点。比如说java天生就没有指针,从一定程度上避免了滥用指针出错的尴尬场景。
   在HelloOs中,除了skyeye本身提供的非常简单的类似gdb的debug工具外,最基本的调试手段就是打印输出了(这个打印输出应该也算是用的相当广泛的一种调试手段了吧),在可能出现问题的地方输出相关的参数看看,在一定程度上可以找到bug所在(当然,这需要对整个代码的运行过程有较为深刻的理解),尤其是在多线程调试的时候。
   对于打印输出,实现上没什么好说的。同样,不赘述。

   我们来说说HelloOs中另一个类似的debug方式:断言(ASSERT)。
   所谓断言,简单来说,就是按照目前的逻辑,到当前位置,某个条件一定是正确的。如果发生了错误,说明程序的执行和逻辑上有出入,就打印输出当前的环境信息(错误行啦、不满足的条件啦等等)。从本质上说,ASSERT是打印输出的一个改良版。HelloOs实现的ASSERT如下图6.8所示。

HelloOs总结之辅助性功能实现_第7张图片

  • 图6.8 ASSERT实现代码示意图

   其中,panic_spin函数主要是在关中断的情况下输出出错的文件、出错的行、出错的函数、以及出错的条件。这些出错的信息是编译器提供在编译时(确切的说是预处理时)给我们提供的。

你可能感兴趣的:(HelloOs总结)