文章目录

  • 何为微内核???
  • 对MINIX3操作系统整体印象
  • MINIX3操作系统分析
    • 源代码组织
    • 进程
      • 系统初始化
      • MINIX3的中断处理
      • MINIX3的进程间通信
      • 系统任务
      • 时钟任务
    • 输入输出系统
      • 块设备驱动程序
      • 字符设备驱动程序
    • 存储管理
    • 文件系统

本来打算在3月8号还书之前把《操作系统设计与实现》这本书看完的,但是拖到4月4号才看完,趁着清明节,记下笔记。

何为微内核???

这本书实现的是一个微内核的操作系统。刚开始的时候不懂微内核是什么意思,只知道微内核操作系统就是只将必要的功能放到内核中。比如对于本书的MINIX操作系统来说,内核负责进程调度、进程间通信、时钟管理和系统任务,其它的内容,例如设备驱动程序、文件系统、内存管理等内容都放在了用户空间中,为什么要进行这样的设计呢?
在宏内核中,也就是Linux那样的操作系统,内核实现了很多的功能,并且提供了一些接口供应用程序使用(例如我们使用的read()还有write()等系统调用)。这些功能都是内核的一部分,我们使用这些系统调用的时候,一般来说都会进入内核态执行,这样就能使用内核当中的代码来访问硬件了,这就是宏内核。
而在微内核中,内核虽然也提供了同样的功能的系统调用,但是这些系统调用并不是去直接访问硬件的,而是将这次访问交给别的程序去执行
不理解的人可能会认为这个和宏内核是一样的,但是其实不然。在宏内核中,一个系统调用一般不会只由一个函数来完成,在执行的过程中会去调用很多的函数,这里最大的特点在于,这些都是内核态的程序,它们都属于同一个地址空间,或者就叫它内核进程的地址空间(不知道这么说对不对)。而上面所说的交给别的程序去执行,是交给了另一个进程去执行,两部分代码不是同一个进程。这样可以将一个系统调用按照层次来划分,每一层只要保证给上层的接口是不变的(在MINIX3中就是接收的消息类型),就可以进行替换。

对MINIX3操作系统整体印象

书陆陆续续看了有一个月,很多的特性也都记不清楚,现在我还能记住的一些特性,或许就是MINIX3操作系统的精髓吧?

  1. 实际上只有两个系统调用send()receive()
  2. 一共有7种消息供进程间通信使用
  3. 系统可以在32位保护模式和16位实模式之间切换
  4. 设备驱动程序、文件系统是独立的进程,它们初始化完毕后就循环等待消息并进行处理
  5. 进程表被分为了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_masterhwint_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设备都有一个单独的设备驱动程序。这些驱动程序是完整的进程,每个都有自己的状态、寄存器和堆栈等,它们使用消息通信的方式和上层进程以及系统任务进行通信。每种设备驱动程序一般都可以执行下面几种操作:

  1. open
  2. close
  3. read
  4. write
  5. ioctl
  6. 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操作系统分析相关推荐

  1. linux操作系统分析实验—基于mykernel的时间片轮转多道程序实现与分析

    linux操作系统分析实验-基于mykernel的时间片轮转多道程序实现与分析 学号384 原创作业转载请注明出处+中国科学技术大学孟宁老师的Linux操作系统分析 https://github.co ...

  2. 【Linux操作系统分析】设备驱动处理流程

    1 驱动程序,操作系统,文件系统和应用程序之间的关系 字符设备和块设备映射到操作系统中的文件系统,由文件系统向上提供给应用程序统一的接口用以访问设备. Linux把设备视为文件,称为设备文件,通过对设 ...

  3. Linux操作系统分析 | 深入理解系统调用

    Linux操作系统分析 | 深入理解系统调用 实验要求 1.找一个系统调用,系统调用号为学号最后2位相同的系统调用 2.通过汇编指令触发该系统调用 3.通过gdb跟踪该系统调用的内核处理过程 4.重点 ...

  4. Unix/Linux操作系统分析实验二 内存分配与回收:Linux系统下利用链表实现动态内存分配

    Unix/Linux操作系统分析实验一 进程控制与进程互斥 Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件 Unix/Linux操作系统分析实验四 设备驱动: ...

  5. Unix/Linux操作系统分析实验四 设备驱动: Linux系统下的字符设备驱动程序编程

    Unix/Linux操作系统分析实验一 进程控制与进程互斥 Unix/Linux操作系统分析实验二 内存分配与回收:Linux系统下利用链表实现动态内存分配 Unix/Linux操作系统分析实验三 文 ...

  6. linux网卡配子接口,Linux 操作系统分析 中国科学技术大学计算机系 陈香兰( 0512 - 87161312 ) Autumn 2010....

    Linux 操作系统分析 中国科学技术大学计算机系 陈香兰( 0512 - ) Autumn 2010 11/23/09 Linux 操作系统分析 2/92 主要内容  进程描述符  进程切换  ...

  7. Linux操作系统分析——课程总结报告

    一.Linux系统的启动过程 1.POST开机自检 linux开机加电后,系统开始开机自检,该过程主要对计算机各种硬件设备进行检测,如CPU.内存.主板.硬盘.CMOS芯片等,如果出现致命故障则停机, ...

  8. Linux操作系统分析-课程总结报告

    一.结合虚拟化技术分析Linux系统的一般执行过程 a. 一个 Linux 系统在虚拟化技术中的一般执行过程: 用户登录:当用户登录到 Linux 系统时,系统会创建一个用户会话. 系统启动:Linu ...

  9. Linux操作系统分析------期末总结、感谢老师、祝我们越来越好

    王雪 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一.博客目录: 1.第一 ...

最新文章

  1. android 颜色资源文件,android中colors.xml颜色设置资源文件的方法
  2. 补充一种简单的存储过程分页
  3. maven+springmvc+dubbo+zookeeper
  4. 找出两列表共有的元素python,两个列表之间的公共元素未在Python中使用集
  5. wxWidgets:wxWidgets 辅助功能示例
  6. 实用常识_实用垃圾收集,第1部分–简介
  7. 带参方法的使用 0908
  8. 双11首日全国共揽收快递包裹5.69亿件 同比增长28.54%
  9. 在iOS项目中引入MVVM
  10. OSPFv3中LSA详解(一)——概述
  11. winxp java 控制台_winxp系统设置java环境变量的详细教程
  12. 音视频开发著作《Android音视频开发》终于发售了,先来一波签名送书福利!
  13. KNN 分类红酒数据集
  14. 如何设谷歌浏览器黑色浏览背景
  15. JZOJ4722. 跳楼机
  16. html5页面命名,html命名规则
  17. Android AppWidget控制手机上网APN接入点
  18. 取消打印机选择框实现
  19. 解决iperf发包高丢包率的问题
  20. java飞机大战编程_[源码和文档分享]Java飞机大战游戏设计与实现

热门文章

  1. 养了一只黑脸柯基,可洗着洗着居然褪色了?
  2. 跟着狂神学Java-----DAY1
  3. 太强了!GitHub开源了1000本技术类的电子书,直接刷爆朋友圈!
  4. php单选框怎么变成方形,html单选按钮变成方形
  5. torch中contiguous()函数
  6. 群体智能算法之粒子群算法
  7. Pywinauto常用01--print_control_identifiers()
  8. 安装acme.sh生成免费https证书
  9. React弹出框(简易版)
  10. android usb vold,android Vold Usb