5.1设计目的
OSKit是一个用来帮助我们研发操作系统的工具,如果你想更好的掌握它,除了对其本身进行分析之外,还有一个更加实际的方法,那就是利用这个工具开发一个我们自己的操作系统。
我们开发这个系统的初衷主要是为了学习,因此并没有想把它设计成像Windows、UNIX那么复杂。我们的目标是很明确的,那就是在大致搞明白OSKit应当如何使用之后,做一次尝试,用较短的时间,设计出一个具有一些最基本功能的系统。这对于我们课题组的每名成员来说,都意味着挑战。
5.2系统的功能
这个系统支持些什么?这是在开始设计之前我们要首先确定的一个问题。下面列出了一些我们希望实现的功能。
通过Multiboot或者LILO进行系统引导:这个系统可以通过所有支持Multiboot的引导程序进行引导,或者通过引导Linux的LILO进行引导。引导方式可以根据用户的要求,选择是通过软盘还是硬盘。
多线程:用户可以通过我们提供的系统调用,创建或杀死自己的线程。由于并不支持文件系统,因此新的线程恐怕只能是一个函数了。从理论上讲,我们可以让这个系统支持很多线程,但因为这仅仅是一个简单的演示性系统,因此我们把线程数限制在32个,当然这个数字也可根据用户的需求任意更改。
进程间通信:这个系统可以支持一些简单的进程间通信机制,比如信号量、消息队列等等。
外设:在众多的外设之中,我们选择了应用范围比较广的串口。利用目前OSKit所提供的机制,支持更多的设备是不成问题的,但加在这个系统中意义并不十分的大。另外,需要强调的是,访问串口的方法,是由我们自己规定,不允许用户更改。
简单的内存管理:应用程序需要的内存可以通过malloc函数动态申请。
总体构想
在该操作系统中,我们规定用户只能创建用户级线程,所有对于核心的操作都将由系统来完成。线程采用RR调度算法,也就是时间片轮转法,并允许抢占,线程在该系统中有四种状态,它们是运行态、阻塞态、睡眠态和就绪态,用户可以设定和改变线程的优先级。在线程中用户可以动态申请内存,但是需要由用户来释放。线程间提供简单的通信机制,包括信号量和消息队列。
在设备方面,对于终端,本系统将提供简单的输入输出功能。而对于串口的功能,本系统可以完整的实现,也就是说,用户既可以从串口读也可以向串口写。
为了方便起见,我们为这个系统起一个很简单的名字,叫acos,这个名字并没有什么实际的意义,仅仅为了叫起来方便。
虽然OSKit已经为我们提供了很多设计操作系统所必须的功能,比如内存分配中的malloc函数,通过Multiboot引导等等。然而,仍有许多工作是要由我们自己完成。
5.3 我们自己所完成的工作:
5.3.1 系统的启动
这里我们所说的系统启动,并不是指通常人们所说的通过软盘或硬盘引导,而是在引导结束之后,操作系统的核心进程如何启动第一个用户进程。通常,在UNIX系统中,内核启动之后,内核程序会去执行文件系统上的init程序,这个程序是整个操作系统中的第一个用户进程,剩下的进程,都必须通过这个进程才能启动。在acos中,我们规定,用户必须要提供一个acos_init( ) 函数,系统在启动初始化工作全部完成之后,acos_init( )函数将被作为系统中的第一个进程启动。这之后的工作,就完全由用户来完成了。
当进程acos_init( ) 结束时,整个系统也将终止运行。
在acos_init( )启动之前,究竟还有哪些事情需要做呢?下面我们就列出要做的事情:
对整个系统进行初始化,注册设备,检测设备,初始化设备需要的数据结构,初始化线程库,创建线程需要的数据结构。
5.3.2 线程管理
所谓操作系统的线程管理,一般包括了一下的三大部分:线程的创建,线程间通信和线程调度。
线程的创建包括了初始化线程和创建线程的属性。
线程间通信中,我们使用了信号量,条件变量。
在线程调度中,用户线程只能使用时间片轮转法作为调度算法,并且规定在调度过程中调度算法不允许更改。
5.3.3 外设(串口)
在UNIX系统中,所有的设备都可以通过文件系统进行访问,读、写设备就像读写文件一样。在我们设计的这个acos中,并没有对文件系统提供支持,也不提供像读写文件系统一样的方法来访问设备。但是,我们提供一套基本的函数,供用户线程对串口进行读写操作,这些函数专门用于处理串口。
一个设备在同一时刻只能被一个线程使用,因此,在使用设备以前要申请,只有申请成功的线程才能够使用设备。
5.4 用户手册
5.4.1 安装
用户需要在Linux下键入 tar -zxvf acos-0.0.1.tar.gz 。
./setup ,详细的安装信息请参见README文件。
进入usr/local/bin/acos之后,根据我们所提供的API手册编写自己的应用程序,然后将这些应用程序的源文件编译成一个"filename.o",用户通过执行acbuild filename.o命令将应用程序链入操作系统内核,根据你所使用的核心格式执行mkXXXimage命令生成所需要的核心映像,选择恰当的装入程序装入启动核心映像。
5.4.2 用户编程接口
如果您对操作系统的概念有所了解,并详细阅读了我们为您提供的技术文档之后,便可以利用用户编程接口自行开发了。以下我将向各位详细介绍每个接口所提供的功能,希望对您能有所帮助。
一、管理线程的API
1.1 初始化线程
ac_init_pthreads( );
1.2 线程创建
ac_thread_create ( void *name, void *arg,
int *out_thread_id );
1.3 设置线程的优先级
ac_thread_setprio(int thread_id, int pri);
1.4 撤销线程
ac_thread_cancel(int thread_id);
1.5 线程休眠
ac_thread_sleep(ac_s64_t millisecond);
1.6 将该线程与某一个线程相关联
ac_thread_join(int tid, void **status);
1.7 撤销该线程与某线程的关联
pthread_attr_setdetachstate ( pthread_attr_t *attr, int detachstate )
二、管理互斥量的API
2.1 初始化互斥量
ac_mutex_init ( ac_pthread_mutex_t* mutex,
ac_pthread_mutexattr_t* attr);
2.2 设置互斥量协议
ac_mutexattr_setprotocol ( ac_pthread_mutexattr_t* attr, int protocol);
2.3 设置互斥量类型
ac_mutexattr_settype ( ac_pthread_mutexattr_t* attr, int type);
2.4初始化互斥量属性
ac_mutexattr_init(ac_pthread_mutexattr_t* attr);
2.5互斥量加锁
ac_mutex_lock(ac_pthread_mutex_t *mutex);
2.6互斥量解锁
ac_mutex_unlock(ac_pthread_mutex_t *mutex);
2.7撤销互斥量
ac_mutex_destroy(ac_pthread_mutex_t *mutex);
三、管理IPC的API
3.1消息发送( IPC机制 )
ac_ipc_send ( int thread_id, void* msg,ac_size_t msgsize,ac_s32_t timeout);
3.2消息接受( IPC机制 )
ac_ipc_recv(int thread_id, void* msg,ac_size_t msgsize,ac_size_t *actual, ac_s32_t timeout);
3.3消息等待( IPC机制 )
ac_ipc_wait(int *src_id, void *msg,ac_size_t msgsize,ac_size_t *actual, ac_s32_t timeout);
3.4接受同步消息
ac_ipc_reply(int desthread, void *msg, ac_size_t msg_size);
3.5发送同步消息
ac_ipc_call(int dst, void *send_msg, ac_size_t send_msg_size,
void *recv_msg, ac_size_t recv_msg_size,
ac_size_t *actual,ac_s32_t timeout);
四、管理条件变量的API
4.1条件变量初始化
ac_cond_init(ac_pthread_cond_t *cond, ac_pthread_condattr_t* attr);
4.2某线程等待条件变量
ac_cond_wait(ac_pthread_cond_t *cond, ac_pthread_mutex_t *mutex);
4.3唤醒等待该条件变量的线程
ac_cond_signal(ac_pthread_cond_t *cond);
4.4符合条件变量的线程广播
ac_cond_broacast(ac_pthread_cond_t *cond);
五、硬件部分
5.1打开串口
ac_serial_open(int serial_num, ac_u32_t flags);
5.2监听串口
ac_serial_listen(int serial_num, ac_u32_t flags);
5.3关闭串口
ac_serial_close(ac_ttystream_t* a_stream);
5.4从串口读
ac_serial_read(ac_ttystream_t* s, void *buf, ac_u32_t len,ac_u32_t *out_actual);
5.5往串口写
ac_serial_write(ac_ttystream_t* s, const void *buf,ac_u32_t len, ac_u32_t *out_actual);
5.4.3 应用程序示例
为了能让各位老师对我们的演示系统一目了然,我们特地选取了在操作系统中比较有代表性的4个例子供大家欣赏,它们是,哲学家算法,时钟演示,线程调度和中断响应。
所需的资源:演示前三个例子需要一台intel 386或更新的计算机,一张1.44MB的软盘;演示第四个例子除了上述设备之外,还需要一根串口线,一台装有Windows 9x的计算机作发送机,并在其上安装有能向串口发送信息的软件。
1.哲学家算法
哲学家算法也称哲学家进餐的同步问题,最早是在1965年由Dijkstra提出并解决的。下面我就简单描述一下这个经典IPC问题的大意:7个哲学家围坐在一张圆桌周围,每个哲学家面前有一个碟通心面,由于面条很滑,所以要用两把叉子才能夹住。相邻两个碟子之间有一把叉子。哲学家的生活包括三种状态:即吃饭,思考和挨饿,当一个哲学家觉得饿时,他试图分两次去取他左边和右边的叉子,每次拿一把,但不分次序。如果成功地获得了两把叉子,他就开始吃饭,吃完以后放下叉子继续思考。这里的问题是:为每一个哲学家写一段程序来描述起行为,要求不能死锁。
在这个例子中我们设定了7个哲学家,且不允许哲学家饿死。我们为每个哲学家创建一个线程,把叉子看作条件变量,让线程等待条件变量,如果满足条件,则线程执行(哲学家的吃饭状态),执行一定时间之后,释放条件变量,进入休眠状态(哲学家的思考状态),当休眠时间过去之后,线程被唤醒,重新等待条件变量(哲学家的挨饿状态)。
2.时钟演示
计算机是以严格精确的时间进行数值运算和数据处理的,最基本的时间单元是时钟周期,OSKit最小可以精确到纳秒一级,其时钟系统的特点是允许在系统时钟之上创建自己的时钟,用于控制周期性线程。
我们的演示系统的源代码看似比较简单,但实际上却是建立在一个庞大的时间系统之上的,这个时间系统是整个操作系统活动的动力。
我们的演示程序将演示控制计算机的时钟计时( 每400毫秒显示一次 ),计时10次后返回初始菜单。
3.线程调度
在此个演示程序中,一开始有两个线程,它们所采取的调度算法是时间片轮转法,与此同时还有一个优先级最高的线程在休眠,它每400毫秒睡醒一次,打断此时处于运行状态的线程,那个优先级最高的线程转执行态,等它执行完它的时间片之后,前两个线程继续执行。
4.中断响应
这个例子演示了操作系统对外设的支持,我们选择了串口。演示这个例子,需要两台计算机和一根串口线,其中一台计算机安装我们的演示系统,另一台需要有一个向串口发送信息的软件,此软件我们是用Delphi编写的,运行在Windows 98系统下,两台计算机用我们自己制作的串口线相连。演示程序的一开始,有两个优先级相同的线程在时间片轮转。当我们从另一台装有Windows 98的机器上给演示机发送信息时,原来处于运行状态的两个线程被中断,演示机会成功的收到发送机发送的信息,然后等待操作人员的处理,是否继续运行该例子或者退出。
本 章 小 结
本章是为最后提供给各位老师欣赏的演示系统所写的,针对每个例子,先从理论上论述了它的概念以及我们是怎么实现的,然后附上了部分程序。
注意:由于演示系统所使用的OSKit的系统调用基本在前三章已经逐一做了详细的介绍。