MINIX3操作系统分析
文章目录
- 何为微内核???
- 对MINIX3操作系统整体印象
- MINIX3操作系统分析
- 源代码组织
- 进程
- 系统初始化
- MINIX3的中断处理
- MINIX3的进程间通信
- 系统任务
- 时钟任务
- 输入输出系统
- 块设备驱动程序
- 字符设备驱动程序
- 存储管理
- 文件系统
本来打算在3月8号还书之前把《操作系统设计与实现》这本书看完的,但是拖到4月4号才看完,趁着清明节,记下笔记。
何为微内核???
这本书实现的是一个微内核的操作系统。刚开始的时候不懂微内核是什么意思,只知道微内核操作系统就是只将必要的功能放到内核中。比如对于本书的MINIX操作系统来说,内核负责进程调度、进程间通信、时钟管理和系统任务,其它的内容,例如设备驱动程序、文件系统、内存管理等内容都放在了用户空间中,为什么要进行这样的设计呢?
在宏内核中,也就是Linux那样的操作系统,内核实现了很多的功能,并且提供了一些接口供应用程序使用(例如我们使用的read()
还有write()
等系统调用)。这些功能都是内核的一部分,我们使用这些系统调用的时候,一般来说都会进入内核态执行,这样就能使用内核当中的代码来访问硬件了,这就是宏内核。
而在微内核中,内核虽然也提供了同样的功能的系统调用,但是这些系统调用并不是去直接访问硬件的,而是将这次访问交给别的程序去执行
。
不理解的人可能会认为这个和宏内核是一样的,但是其实不然。在宏内核中,一个系统调用一般不会只由一个函数来完成,在执行的过程中会去调用很多的函数,这里最大的特点在于,这些都是内核态的程序,它们都属于同一个地址空间,或者就叫它内核进程的地址空间(不知道这么说对不对)。而上面所说的交给别的程序去执行
,是交给了另一个进程去执行,两部分代码不是同一个进程。这样可以将一个系统调用按照层次来划分,每一层只要保证给上层的接口是不变的(在MINIX3中就是接收的消息类型),就可以进行替换。
对MINIX3操作系统整体印象
书陆陆续续看了有一个月,很多的特性也都记不清楚,现在我还能记住的一些特性,或许就是MINIX3操作系统的精髓吧?
- 实际上只有两个系统调用
send()
和receive()
- 一共有7种消息供进程间通信使用
- 系统可以在32位保护模式和16位实模式之间切换
- 设备驱动程序、文件系统是独立的进程,它们初始化完毕后就循环等待消息并进行处理
- 进程表被分为了3个部分,分别存在于内核、进程、存储管理器和文件系统中
MINIX3操作系统分析
这里使用的是http://www.minix3.org网站上minix-3.1.0-book.iso.bz2这个镜像中提取出来的代码。
源代码组织
系统被分成了很多独立的进程,每个进程都自己的头文件,其中定义了全局变量、宏和函数。在定义全局变量的时候,glo.h
中的全局变量都冠以EXTERN
这个宏(定义在src/include/minix/const.h中),所以在大多数的源文件中,全局变量都是外部声明,而在table.c
这个源文件中,EXTERN
宏被定义为空,为全局变量分配了存储空间。
进程
系统初始化
书里面没有讲系统是如何引导的,这里我也就不总结了(源码中src/boot/目录下是引导程序),直接来看初始化。引导完毕后的代码执行流程如下,位于src/kernel/mpx386.s中。
......
121: MINIX: ! this is the entry point for the MINIX kernel
122: jmp over_flags ! skip over the next few bytes
......
176: ! Call C startup code to set up a proper environment to run main().
177: push edx
178: push ebx
179: push SS_SELECTOR
180: push DS_SELECTOR
181: push CS_SELECTOR
182: call _cstart ! cstart(cs, ds, mds, parmoff, parmlen)
183: add esp, 5*4
......
204: jmp _main ! main()
......
在汇编中引用C符号需要在其前面加上_前缀,所以_cstart实际上是src\kernel\start.c中的cstart函数,其中调用prot_init()
函数来进行保护模式的初始化。
21: PUBLIC void cstart(cs, ds, mds, parmoff, parmsize)
......
48: /* Initialize protected mode descriptors. */
49: prot_init();
main()
函数在src\kernel\main.c中,主要任务是初始化中断、进程表和特权表、调用announce()
和restart()
函数。对restart()
的调用将启动第一个任务,控制权将不再返回到main()
中。
31: PUBLIC void main()
......
44: /* Initialize the interrupt controller. */
45: intr_init(1);
...... /*初始化内核中的进程表和特权表*/
165: announce(); /* print MINIX startup banner */
166: restart();
restart()
调用的是汇编程序_restart
,位于src/kernel/mpx386.s中,最后使用中断返回指令iret
指令执行下一个进程。
382: _restart:
......
397: o16 pop gs
398: o16 pop fs
399: o16 pop es
400: o16 pop ds
401: popad
402: add esp, 4 ! skip return adr
403: iretd ! continue process
MINIX3的中断处理
当32位Intel处理器收到一个中断时,会新建立一个栈供中断处理程序使用,栈的位置由TSS(任务状态段)中的位置决定,在MINIX3中,中断处理程序将使用0级的栈基址和栈指针,这个栈就是内核进程表的stackframe_s
结构的结尾处。当中断发生时,就会执行相应的中断处理程序,在MINIX3中就是hwint_master
和hwint_master
这两个宏定义的代码,首先调用save
保存寄存器,然后执行相应的中断处理程序,最后的ret
将返回到_restart
或者restart1
处进行中断返回,上面的代码位于src/kernel/mpx386.s中。
216: #define hwint_master(irq) \
217: call save /* save interrupted process state */;\
218: push (_irq_handlers+4*irq) /* irq_handlers[irq] */;\
219: call _intr_handle /* intr_handle(irq_handlers[irq]) */;\
220: pop ecx ;\
......
228: ret /* restart (another) process */
......
323: save:
......
339: jmp RETADR-P_STACKBASE(eax)
MINIX3的进程间通信
MINIX3的进程间通信主要使用三条原语,send(dest, &message)
用来向dest
进程发送一条消息,receive(source, &message)
用来接收一条来自source
进程的消息,sendrev(src_dst, &message)
用来发送一条消息,并等待一个进程的应答。主要代码位于src/kernel/proc.c中。
对于发送消息的进程来说,如果dest
进程正在等待发送进程的消息,或者等待任何消息,那么就通过内核将消息复制到接收进程;否则就将发送进程阻塞。
200: PRIVATE int mini_send(caller_ptr, dst, m_ptr, flags)
......
224: if ( (dst_ptr->p_rts_flags & (RECEIVING | SENDING)) == RECEIVING &&
225: (dst_ptr->p_getfrom == ANY || dst_ptr->p_getfrom == caller_ptr->p_nr)) {226: /* Destination is indeed waiting for this message. */
227: CopyMess(caller_ptr->p_nr, caller_ptr, m_ptr, dst_ptr,
228: dst_ptr->p_messbuf);
229: if ((dst_ptr->p_rts_flags &= ~RECEIVING) == 0) enqueue(dst_ptr);
230: } else if ( ! (flags & NON_BLOCKING)) {231: /* Destination is not waiting. Block and dequeue caller. */
232: caller_ptr->p_messbuf = m_ptr;
233: if (caller_ptr->p_rts_flags == 0) dequeue(caller_ptr);
234: caller_ptr->p_rts_flags |= SENDING;
235: caller_ptr->p_sendto = dst;
236:
237: /* Process is now blocked. Put in on the destination's queue. */
238: xpp = &dst_ptr->p_caller_q; /* find end of list */
239: while (*xpp != NIL_PROC) xpp = &(*xpp)->p_q_link;
240: *xpp = caller_ptr; /* add caller to end */
241: caller_ptr->p_q_link = NIL_PROC; /* mark new end of list */
242: }
对于接收消息的进程来说,它首先会检查通知,然后才是向本进程发送的消息,如果没有等待接收的消息或者通知,就将自己阻塞。
251: PRIVATE int mini_receive(caller_ptr, src, m_ptr, flags)
......
269: /* Check to see if a message from desired source is already available.
270: * The caller's SENDING flag may be set if SENDREC couldn't send. If it is
271: * set, the process should be blocked.
272: */
273: if (!(caller_ptr->p_rts_flags & SENDING)) {......
290: /* Found a suitable source, deliver the notification message. */
291: BuildMess(&m, src_proc_nr, caller_ptr); /* assemble message */
292: CopyMess(src_proc_nr, proc_addr(HARDWARE), &m, caller_ptr, m_ptr);
......
297: /* Check caller queue. Use pointer pointers to keep code simple. */
298: xpp = &caller_ptr->p_caller_q;
299: while (*xpp != NIL_PROC) {300: if (src == ANY || src == proc_nr(*xpp)) {301: /* Found acceptable message. Copy it and update status. */
302: CopyMess((*xpp)->p_nr, *xpp, (*xpp)->p_messbuf, caller_ptr, m_ptr);
303: if (((*xpp)->p_rts_flags &= ~SENDING) == 0) enqueue(*xpp);
304: *xpp = (*xpp)->p_q_link; /* remove from queue */
305: return(OK); /* report success */
306: }
307: xpp = &(*xpp)->p_q_link; /* proceed to next */
308: }
309: }
通知消息是在接收进程处理的时候构建并拷贝的,如果接收进程没有等待通知,那么就将该通知位置位。
328: PRIVATE int mini_notify(caller_ptr, dst)
......
331: {332: register struct proc *dst_ptr = proc_addr(dst);
333: int src_id; /* source id for late delivery */
334: message m; /* the notification message */
335:
336: /* Check to see if target is blocked waiting for this message. A process
337: * can be both sending and receiving during a SENDREC system call.
338: */
339: if ((dst_ptr->p_rts_flags & (RECEIVING|SENDING)) == RECEIVING &&
340: ! (priv(dst_ptr)->s_flags & SENDREC_BUSY) &&
341: (dst_ptr->p_getfrom == ANY || dst_ptr->p_getfrom == caller_ptr->p_nr)) {342:
343: /* Destination is indeed waiting for a message. Assemble a notification
344: * message and deliver it. Copy from pseudo-source HARDWARE, since the
345: * message is in the kernel's address space.
346: */
347: BuildMess(&m, proc_nr(caller_ptr), dst_ptr);
348: CopyMess(proc_nr(caller_ptr), proc_addr(HARDWARE), &m,
349: dst_ptr, dst_ptr->p_messbuf);
350: dst_ptr->p_rts_flags &= ~RECEIVING; /* deblock destination */
351: if (dst_ptr->p_rts_flags == 0) enqueue(dst_ptr);
352: return(OK);
353: }
354:
355: /* Destination is not ready to receive the notification. Add it to the
356: * bit map with pending notifications. Note the indirectness: the system id
357: * instead of the process number is used in the pending bit map.
358: */
359: src_id = priv(caller_ptr)->s_id;
360: set_sys_bit(priv(dst_ptr)->s_notify_pending, src_id);
361: return(OK);
362: }
系统任务
将驱动程序放到内核外部的结果就是它们不能执行特权指令,也就无法进行I/O操作,所以在MINIX3的内核中为驱动程序提供了一组服务,这些服务由系统任务来实现。虽然系统任务属于内核的一部分,但是它也是一个单独的进程,所以它的用一个循环实现,代码在src/kernel/system.c中。
59: PUBLIC void sys_task()
60: {61: /* Main entry point of sys_task. Get the message and dispatch on type. */
62: static message m;
63: register int result;
64: register struct proc *caller_ptr;
65: unsigned int call_nr;
66: int s;
67:
68: /* Initialize the system task. */
69: initialize();
70:
71: while (TRUE) {72: /* Get work. Block and wait until a request message arrives. */
73: receive(ANY, &m);
......
99: }
100:}
时钟任务
对于分时系统来说时钟是很必要的。虽然它是驱动程序,但又不同于传统意义的块设备和字符设备,它也属于内核的一部分,并作为一个单独的进程来执行,只接收时钟中断,代码在src\kernel\clock.c中。
69: PUBLIC void clock_task()
70: {71: /* Main program of clock task. If the call is not HARD_INT it is an error.
72: */
73: message m; /* message buffer for both input and output */
74: int result; /* result returned by the handler */
75:
76: init_clock(); /* initialize clock task */
77:
78: /* Main loop of the clock task. Get work, process it. Never reply. */
79: while (TRUE) {80:
81: /* Go get a message. */
82: receive(ANY, &m);
83:
84: /* Handle the request. Only clock ticks are expected. */
85: switch (m.m_type) {86: case HARD_INT:
87: result = do_clocktick(&m); /* handle clock tick */
88: break;
89: default: /* illegal request type */
90: kprintf("CLOCK: illegal request %d from %d.\n", m.m_type,m.m_source);
91: }
92: }
93: }
输入输出系统
MINIX3系统中的每一类I/O设备都有一个单独的设备驱动程序。这些驱动程序是完整的进程,每个都有自己的状态、寄存器和堆栈等,它们使用消息通信的方式和上层进程以及系统任务进行通信。每种设备驱动程序一般都可以执行下面几种操作:
- open
- close
- read
- write
- ioctl
- scattered_io(这个我没怎么见过,书中说是读写多个块使用的)
块设备驱动程序
所有的块设备驱动程序共用的代码在src\drivers\libdriver\driver.c中,和系统任务和时钟任务类似,块设备驱动程序的主程序也是循环接收消息,根据消息类型,通过函数指针调用该驱动程序的相应处理函数。
73: PUBLIC void driver_task(dp)
......
75: {......
81: /* Get a DMA buffer. */
82: init_buffer();
83:
84: /* Here is the main loop of the disk task. It waits for a message, carries
85: * it out, and sends a reply.
86: */
87: while (TRUE) {88:
89: /* Wait for a request to read or write a disk block. */
90: if(receive(ANY, &mess) != OK) continue;
91:
92: device_caller = mess.m_source;
93: proc_nr = mess.PROC_NR;
94:
95: /* Now carry out the work. */
96: switch(mess.m_type) {97: case DEV_OPEN: r = (*dp->dr_open)(dp, &mess); break;
98: case DEV_CLOSE: r = (*dp->dr_close)(dp, &mess); break;
99: case DEV_IOCTL: r = (*dp->dr_ioctl)(dp, &mess); break;
100: case CANCEL: r = (*dp->dr_cancel)(dp, &mess);break;
101: case DEV_SELECT: r = (*dp->dr_select)(dp, &mess);break;
102:
103: case DEV_READ:
104: case DEV_WRITE: r = do_rdwt(dp, &mess); break;
105: case DEV_GATHER:
106: case DEV_SCATTER: r = do_vrdwt(dp, &mess); break;
......
123: }
......
129: if (r != EDONTREPLY) {130: mess.m_type = TASK_REPLY;
131: mess.REP_PROC_NR = proc_nr;
132: /* Status is # of bytes transferred or error code. */
133: mess.REP_STATUS = r;
134: send(device_caller, &mess);
135: }
136: }
137:}
比如对于RAM盘和硬盘来说,上面的函数在各自的main()
中被调用。
src\drivers\memory\memory.c
73: PUBLIC int main(void)
74: {75: /* Main program. Initialize the memory driver and start the main loop. */
76: m_init();
77: driver_task(&m_dtab);
78: return(OK);
79: }
src\drivers\at_wini\at_wini.c
291: PUBLIC int main()
292: {293: /* Set special disk parameters then call the generic main loop. */
294: init_params();
295: driver_task(&w_dtab);
296: return(OK);
297: }
字符设备驱动程序
字符设备驱动程序处理键盘,也处理显示器,是MINIX3中规模最大的驱动程序,但是它仍然是和上述程序一样的结构,入口点在src\drivers\tty\tty.c中。
157: PUBLIC void main(void)
158: {......
168: /* Initialize the TTY driver. */
169: tty_init();
......
176: /* Final one-time keyboard initialization. */
177: kb_init_once();
......
181: while (TRUE) {......
188: /* Get a request message. */
189: receive(ANY, &tty_mess);
......
293: }
294: }
存储管理
我感觉这部分实现得挺草率的,把进程管理和存储管理组合成了一个叫pm(进程管理器)的进程,而且没有什么有用的东西,主程序的入口在src\servers\pm\main.c中,call_vec
最终调用相应的程序执行系统调用。
42: PUBLIC int main()
43: {......
49: pm_init(); /* initialize process manager tables */
50:
51: /* This is PM's main loop- get work and do it, forever and forever. */
52: while (TRUE) {53: get_work(); /* wait for an PM system call */
......
68: result = (*call_vec[call_nr])();
......
93: }
94: return(OK);
95: }
文件系统
其实无论是什么样的文件系统,都需要存储至少两种类型的文件,即目录文件和常规文件,而且都实现了一些基本的操作(创建、删除、打开、关闭、读、写、上锁等)。MINIX3的文件系统是用索引节点(inode)的实现方式,也作为一个单独的进程来实现,进程入口点在src\servers\fs\main.c中,call_vec
最终调用相应的程序执行系统调用。
41: PUBLIC int main()
42: {......
50: fs_init();
51:
52: /* This is the main loop that gets work, processes it, and sends replies. */
53: while (TRUE) {54: get_work(); /* sets who and call_nr */
......
84: error = (*call_vec[call_nr])();
......
93: }
94: return(OK); /* shouldn't come here */
95: }
文件系统中用了一个叫做高速缓存的东西,其实就是在内存中保留一些文件的数据块,这样能提高速度。为了能快速的判断某个文件的内容是否在高速缓存中,MINIX3使用了哈希表,通过设备号和块号来计算哈希值,并且使用链地址法来处理冲突。没有使用的高速缓冲块被链接在LRU(最近最少使用)链表中,如果需要分配一个新的缓冲块,就从LRU的头部取出一个块,删除它原来所在的哈希链表,插入到新的哈希链表头部,在使用完毕后放入LRU的尾部。
MINIX3操作系统分析相关推荐
- linux操作系统分析实验—基于mykernel的时间片轮转多道程序实现与分析
linux操作系统分析实验-基于mykernel的时间片轮转多道程序实现与分析 学号384 原创作业转载请注明出处+中国科学技术大学孟宁老师的Linux操作系统分析 https://github.co ...
- 【Linux操作系统分析】设备驱动处理流程
1 驱动程序,操作系统,文件系统和应用程序之间的关系 字符设备和块设备映射到操作系统中的文件系统,由文件系统向上提供给应用程序统一的接口用以访问设备. Linux把设备视为文件,称为设备文件,通过对设 ...
- Linux操作系统分析 | 深入理解系统调用
Linux操作系统分析 | 深入理解系统调用 实验要求 1.找一个系统调用,系统调用号为学号最后2位相同的系统调用 2.通过汇编指令触发该系统调用 3.通过gdb跟踪该系统调用的内核处理过程 4.重点 ...
- Unix/Linux操作系统分析实验二 内存分配与回收:Linux系统下利用链表实现动态内存分配
Unix/Linux操作系统分析实验一 进程控制与进程互斥 Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件 Unix/Linux操作系统分析实验四 设备驱动: ...
- Unix/Linux操作系统分析实验四 设备驱动: Linux系统下的字符设备驱动程序编程
Unix/Linux操作系统分析实验一 进程控制与进程互斥 Unix/Linux操作系统分析实验二 内存分配与回收:Linux系统下利用链表实现动态内存分配 Unix/Linux操作系统分析实验三 文 ...
- linux网卡配子接口,Linux 操作系统分析 中国科学技术大学计算机系 陈香兰( 0512 - 87161312 ) Autumn 2010....
Linux 操作系统分析 中国科学技术大学计算机系 陈香兰( 0512 - ) Autumn 2010 11/23/09 Linux 操作系统分析 2/92 主要内容 进程描述符 进程切换 ...
- Linux操作系统分析——课程总结报告
一.Linux系统的启动过程 1.POST开机自检 linux开机加电后,系统开始开机自检,该过程主要对计算机各种硬件设备进行检测,如CPU.内存.主板.硬盘.CMOS芯片等,如果出现致命故障则停机, ...
- Linux操作系统分析-课程总结报告
一.结合虚拟化技术分析Linux系统的一般执行过程 a. 一个 Linux 系统在虚拟化技术中的一般执行过程: 用户登录:当用户登录到 Linux 系统时,系统会创建一个用户会话. 系统启动:Linu ...
- Linux操作系统分析------期末总结、感谢老师、祝我们越来越好
王雪 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一.博客目录: 1.第一 ...
最新文章
- android 颜色资源文件,android中colors.xml颜色设置资源文件的方法
- 补充一种简单的存储过程分页
- maven+springmvc+dubbo+zookeeper
- 找出两列表共有的元素python,两个列表之间的公共元素未在Python中使用集
- wxWidgets:wxWidgets 辅助功能示例
- 实用常识_实用垃圾收集,第1部分–简介
- 带参方法的使用 0908
- 双11首日全国共揽收快递包裹5.69亿件 同比增长28.54%
- 在iOS项目中引入MVVM
- OSPFv3中LSA详解(一)——概述
- winxp java 控制台_winxp系统设置java环境变量的详细教程
- 音视频开发著作《Android音视频开发》终于发售了,先来一波签名送书福利!
- KNN 分类红酒数据集
- 如何设谷歌浏览器黑色浏览背景
- JZOJ4722. 跳楼机
- html5页面命名,html命名规则
- Android AppWidget控制手机上网APN接入点
- 取消打印机选择框实现
- 解决iperf发包高丢包率的问题
- java飞机大战编程_[源码和文档分享]Java飞机大战游戏设计与实现
热门文章
- 养了一只黑脸柯基,可洗着洗着居然褪色了?
- 跟着狂神学Java-----DAY1
- 太强了!GitHub开源了1000本技术类的电子书,直接刷爆朋友圈!
- php单选框怎么变成方形,html单选按钮变成方形
- torch中contiguous()函数
- 群体智能算法之粒子群算法
- Pywinauto常用01--print_control_identifiers()
- 安装acme.sh生成免费https证书
- React弹出框(简易版)
- android usb vold,android Vold Usb