350 likes | 468 Views
13.3.2 设备驱动程序的实现. 1 .块设备 块设备主要包括硬盘、软盘等存储设备,前面已经提到过,块设备驱动程序有一部分是相同的,不同的主要是真正的 I/O 操作的那部分。 设备驱动程序一共可能有 6 种操作: OPEN: 验证设备是否可用; CLOSE: 确保把采用延迟写方式处理的数据真正写到设备上; READ: 将数据从设备读到调用进程所在的内存区域; WRITE: 与 READ 执行的操作相反; IOCTL: 负责对 I/O 设备的操作参数进行检查和修改; SCATTERED_IO: 允许执行同时读写多个块的操作。. 13.3.2 设备驱动程序的实现.
E N D
13.3.2 设备驱动程序的实现 1.块设备 • 块设备主要包括硬盘、软盘等存储设备,前面已经提到过,块设备驱动程序有一部分是相同的,不同的主要是真正的I/O操作的那部分。 • 设备驱动程序一共可能有6种操作: • OPEN: 验证设备是否可用; • CLOSE:确保把采用延迟写方式处理的数据真正写到设备上; • READ: 将数据从设备读到调用进程所在的内存区域; • WRITE:与READ执行的操作相反; • IOCTL:负责对I/O设备的操作参数进行检查和修改; • SCATTERED_IO:允许执行同时读写多个块的操作。
13.3.2 设备驱动程序的实现 • ① 块设备驱动程序软件。 MINIX在头文件driver.h中定义了块设备驱动程序需要用到的结构定义,它们包括: • driver结构:保存各驱动程序执行I/O操作所需调用函数的地址; • device结构:以字节为单位保存基地址、长度等与分区相关的主要信息。
13.3.2 设备驱动程序的实现 • ② 驱动程序库 • 可移植性是在MINIX中得到了相当的重视,MINIX的目标是当它被移植到一台机器上时,应该能够使用这台机器上原先运行的操作系统采用的分区表格式。 • 在IBM兼容机上,硬盘分区的标准由MS-DOS的fdisk命令确定。 • MINIX包含了支持IBM兼容机分区的源代码,为了方便移植,这些与平台有关但是与具体硬件无关的代码没有被放在driver.c文件中,而是单独的放在drvlib.h和drvlib.c文件中。
13.3.2 设备驱动程序的实现 • ③ RAM盘 在MINIX中存在一个RAM盘的概念,简而言之就是保留一部分内存作为磁盘来使用,对于用户来说,它就像一个磁盘一样。 RAM盘的驱动程序实际上由四个紧密联系的部分组成,也就是具有以下四个次设备: • 0:/dev/ram:真正的RAM盘,它的大小和基址在MINIX启动时由FS确定; • 1:/dev/mem:用于读写物理内存,通常读出的是起始于内存零地址的内容,写操作则会改写中断向量; • 2:/dev/kmem:用于读写内核内存,与1的功能类似; • 3:/dev/null:负责接收数据并把数据抛弃掉。
13.3.2 设备驱动程序的实现 2.MINIX的系统任务 • MINIX中将文件管理和内存管理摒弃在内核之外,禁止它们把信息写入内核.所以,如果内存管理系统想把消息通知给内核时,就需要借助于一个第三者——对内核表拥有存取权的内核任务——的帮助,这个任务一般被称之为系统任务。 • 系统任务和I/O任务十分类似,它们都实现了一个接口,具有相同的权限,都被链入内核中,所不同的仅仅是系统任务不控制具体I/O设备,它所服务的对象是系统中大部分的内部组件。
13.3.2 设备驱动程序的实现 系统任务的18种消息:
13.4 内存管理 13.4.1内存管理概述 由于MINIX是专门设计在IBM兼容机等微型机上运行的操作系统,因此它的内存管理(MM)是比较简单的,不分页,也不交换: • MM保存一张按照地址顺序排列的空闲地址列表,当系统调用FORK和EXEC请求内存时,MM利用首次适配算法找到一块足够大的空闲内存分配给程序使用。 • 而一旦程序装入内存则一直在原位置运行到结束。 • 可以说,内存管理的主要工作就是操作进程表和空闲地址列表这两张表格,以及处理系统调用FORK和EXEC。
13.4.1内存管理概述 1.内存的消息处理 • 和I/O相仿,MM在系统初始化以后进入自己的主循环,等待消息,一旦收到消息就进行处理并发送应答消息。
13.4.1内存管理概述 与MM通信的消息类型、入口参数和应答:
13.4.1内存管理概述 2.系统调用 • 下面我们来看看系统调用FORK、EXEC和BRK的处理过程。 ① FORK。 • 执行FORK的过程很简单,收到FORK调用的请求后,MM就检查进程表中是否存在空闲位置,如果存在,就尝试为新建立的子进程分配内存,随后就将内存地址等信息填入一个空闲的进程表项,最后发出通知公布建立了一个新进程。 • 进程的终止则更复杂一些,当进程自己退出(或被信号杀死)并且被父进程通过WAIT调用观察到以后,该进程才会真正被删除,如果第二个条件没有满足,子进程就会被挂起,这种状态在MINIX中称为“僵死”。
13.4.1内存管理概述 ② EXEC。这个调用负责内存映像的更新,包括设置新堆栈,它也是MINIX中最复杂的系统调用。EXEC的复杂主要是由两个方面的因素决定的: • 次数众多的检测:EXEC执行时十分谨慎,为了保证有足够内存容纳新映像,必须进行检测。 • 设置初始堆栈:我们通常用库函数execve来调用EXEC,这个函数包含三个参数:被执行文件名的地址指针;一个指针数组的地址指针,数组中的元素分别指向一个参数;第三个参数同样是一个指针数组的地址指针。
13.4.1内存管理概述 ③ BRK。BRK调用可以调整数据段的上限。有两个过程都可以调用BRK: • brk过程以数据段的绝对长度为参数; • sbrk以当前长度的增量为参数,计算出绝对长度后调用BRK。 执行BRK所完成的工作就是检查地址空间是否够用,并根据情况调整表格,然后通知内核。
13.4.1内存管理概述 3. 内存数据结构 主要了解两个关键的数据结构:进程表和空闲表。 • ① 进程表。MINIX中,内核、FS和MM都拥有各自的进程表,每部分的进程表包含本部分必需的域,同时,三张进程表是对应的,也就是说,三张表的第n个表项指的都是同一个进程。也正是由于这个原因,三张表必须被同步更新。 • ② 空闲表。定义在alloc.h文件中的空闲表按照内存地址递增的顺序列出空闲块。 空闲表的表项主要包含三个域:空闲块组的基地址、空闲块组的大小及指向下一个空闲块组的指针。 表项以单向链表的形式链接。
13.4.2 实现 1. 头文件与主程序 • MM中的table.c文件的主要作用是为全局变量服务,在table.c被编译时,它会自动为下面将介绍到的一些全局变量保留存储空间。 • 内存管理部分有许多自己使用的头文件,这些头文件有的和其它部分的头文件具有相同的名字,但由于它们位于不同的目录下为不同的部分服务,所以编译使用时不会出现错误。
13.4.2 实现 • ① 头文件。在内存管理器MM中存在着一个私有的头文件mm.h,可以将它理解为MM范围内的主控头文件,它囊括了位于/usr/include及其子目录中的所有头文件,以及/kernel/kernel.h中包含的大部分头文件。MM中每个文件的编译都需要引用mm.h。 • ② 主程序。MM的主程序也位于main.c中,执行的过程和I/O任务的主程序类似。首先调用mm_init,mm_init通过过程sys_getmap来获得内核的内存使用信息,为所有第二层及第三层的进程初始化进程表项,同时也为init进程初始化进程表项。
13.4.2 实现 2. 系统调用的实现 • ① FORK。FORK调用由文件forkexit.c中的do_fork过程来实现。 • ② EXIT。由过程do_mm_exit接收调用,然后具体工作交由mm_exit完成。 • ③ WAIT。WAIT和WAITPID调用都由过程do_waitpid完成。 • ④ EXEC。过程do_exec负责执行本调用。 • ⑤ BRK。BRK调用的处理代码位于break.c中,由过程do_brk完成。
13.4.2 实现 3.信号处理的实现 MINIX中所有信号及与信号有关的系统调用都被包含在文件signal.c中,具体功能如下所示: • ALARM:经过一段时间后向自己发送ALARM消息; • KILL:给别的进程发出信号指示其下一步的动作; • PAUSE:在收到下一个信号前挂起自己; • REBOOT:发出信号终止所有的进程; • SIGACTION:改变调用进程对将来收到信号的响应方式; • SIGPROCMASK:改变阻塞信号的集合; • SIGSUSPEND:改变阻塞信号集合后执行PAUSE调用; • SIGPENDING:对未处理的阻塞信号进行检查; • SIGRETURN:处理完信号后进行清理工作。
13.4.2 实现 4.其它的系统调用 MM还有几个很简单但是又必需的系统调用,MINIX把它们单独放在一个文件getset.c中,由过程do_getset执行: • GETUID:返回有效的用户标号; • GETGID:返回有效的分组号; • GETPID:返回父子进程的进程号; • SETUID:设置调用者有效的用户号; • SETGID:设置调用者的有效分组号; • SETSID:创建新的会话并返回进程号; • GETPGRP:返回进程的分组标识。
13.5 文件系统 13.5.1文件系统概述 MINIX文件系统(FS)的功能包括两个方面的内容: • 站在用户的角度,文件系统使得用户可以知道文件由什么组成、如何命名以及可以怎样操作文件。可以理解为文件系统为用户和文件间提供了一个接口。 • 从系统的角度来考虑,文件系统的功能包括:决定以怎样的方式存储文件和目录,包括分配空间和释放空间的过程;管理磁盘空间,比如是以链接表或是以位图的方式记录空闲存储空间等等。本节中将详细介绍以上这些功能怎样实现。
13.5.1文件系统概述 1.消息 MINIX文件系统接收的消息种类比较多,一共有39种,其中两个是异常消息。其中下图列出的前面31种来自用户进程。后面6种来自系统调用,它们由MM先处理,然后调用FS完成其余工作。
13.5.1文件系统概述 2. i节点和位图 • 在MINIX里为了改进顺序读取文件时的性能,要确保同一文件的所有磁盘块都位于同一个柱面上,为此引入了区段的概念,一个区段包含了多个块。 • MINIX中为每个文件建立了一个索引表,表中存储了文件属性和各个块在磁盘上的地址,这个索引表被称为i节点。 • MINIX使用位图来记录空闲的i节点和区段,保存位图的磁盘块被称为位图块。当创建一个文件时,FS在位图块中查找第一个空闲的i节点,在这个i节点被分配后,就修改指针指向下一个节点。
13.5.1文件系统概述 3. 目录管理 目录管理是文件系统的一个重要功能,我们查找一个文件,实际上就是先在目录树中找到文件名,然后通过对应的i节点在磁盘上找到文件数据。 目录查找过程:
13.5.1文件系统概述 4.管道和设备文件 • 管道和设备文件与普通文件的最大不同在于,普通文件的操作通常很快就能完成,而管道有时则需要等待几个小时才能有进程把数据写进来。而调用设备文件的进程,为了等待终端或其它设备上的I/O,时间也是不能确定的。 • FS通过不发送信号的方式使得等待应答信号的调用进程被阻塞,一旦有别的进程修改了管道的状态使得被挂起的进程可以运行,FS就会设置一个标志。到下一次主循环时,FS提取以前保存在进程表中的各项参数,继续执行先前被挂起的进程。
13.5.2 实现 1.头文件及全局变量 FS使用的数据结构和表,这些在头文件中得到定义。 • const.h:定义了表长、标志位等一些文件系统中使用的常量的值; • glo.h:和内存管理中的glo.h文件一样,定义了一些全局变量; • proto.h:提供了ANSI标准C编译器所支持的函数原型; • type.h:定义了i节点在磁盘上的组织结构。
13.5.2 实现 2.表格管理及其使用 • 块、超级块及i节点是文件系统中最重要的存储结构,对它们的管理和使用是通过一系列的过程来实现的。 • 用于管理块的过程有: • alloc_zone:用于在区段位图中查找空闲区段,并将其分配给文件; • flushall:通常被系统调用SYNC所调用,刷新某设备的缓冲,同时将其中内容写回设备; • get_block:取要读写的块,先检查高速缓存,若找到就直接返回指针,否则就将其读入缓存; • put_block:释放由get_block请求得到的块;
13.5.2 实现 • free_zone:负责将不再使用的区段归还给空闲区段位图,以区段位图和位号为参数调用free_bit; • rw_block:提供了一个简单的磁盘界面,负责在内存和磁盘间传送块; • invalidate:删除某个设备在高速缓存中用过的所有块; • rw_scattered:以设备标志符、缓冲区指针数组的指针、数组长度及读写标志为参数,执行在设备上读/写分散数据的任务; • rm_lru:只被get_block调用,负责从LRU链中删除一个块。
13.5.2 实现 • 用于超级块管理的过程略少一些,下面的五个过程用于超级块和位图的管理: • alloc_bit:从区段位图或i节点位图中分配一位,通常被alloc_inode或alloc_zone以循环嵌套的方式调用; • free_bit:从区段位图或i节点位图中释放一位,首先计算哪一个位图块包含了要释放的位,调用get_block将这个位图块读入内存,将相应位置0,再调用put_block将该块写回磁盘; • get_super:用于在超级块表中搜索特定设备; • mounted:关闭块设备时调用,以一个指向设备i节点的指针为参数,返回报告这个i节点是否在被挂装的文件系统上。 • Read_super:在读超级块时调用,检查所读出的文件系统的版本号,并进行相应的转换。
13.5.2 实现 • 用于管理i节点的过程有: • get_inode:将一个i节点读入内存,首先搜索inode表,若找到则将指针返回;若不在内存中则调用rw_inode将其读入; • put_inode:返回不再使用的i节点,并把计数器icount减1; • alloc_inode:为新文件分配i节点; • wipe_inode:将i节点中某些域清除,同时也负责部分初始化i节点的工作; • free_inode:释放i节点; • update_times:从系统时钟获得时间,同步修改i节点中的时间域; • rw_inode:负责在内存和磁盘间传送i节点; • old_icopy:转换要执行写操作的i节点内容; • new_icopy:对由i节点读入的数据进行转换; • dup_inode:向其它进程标明某个进程正在使用i节点。
13.5.2 实现 3.管道 • 管道文件的代码在文件pipe.c中,它和普通文件在很多方面都类似,我们主要看看它们的不同之处。 • PIPE调用通过过程do_pipe创建一个拥有权属于系统的管道,并保存在指定的管道设备上。Do_pipe负责为管道分配一个i节点,同时返回两个文件描述符。 • Pipe_check过程利用release过程来判断能否唤醒那些被挂起的过程。除此之外,它也利用函数suspend将调用参数保存在进程表中同时将dont_reply置为TRUE,从而使进程挂起,以便进行各种检查,保证对管道的操作能够完成。 • 过程do_unpause用于处理MM向FS发送的消息,若这条消息的目标进程被阻塞,则do_unpause负责将进程唤醒。
13.5.2 实现 4.主程序 • FS的主循环包含在main.c中,它的结构和内核类似,首先它将进行以下的设置: • who:全局变量,调用进程的进程表插槽号; • fs_call:全局变量,待执行的系统调用的编号; • fp:指向调用者的进程表表项入口; • super_user:标明调用者是否为超级用户; • dont_reply:标志位,标明是否发送回答信号。 • 主循环设置完变量后就执行系统调用的过程,用fs_call作为参数选定被调用的进程。 • 重新回到主循环后,如果dont_reply被设置,那么就不发送应答信号,否则送回一个应答消息。
13.5.2 实现 5.对文件的操作 • ① 创建文件。过程common_open负责执行创建文件的调用CREAT,具体工作由过程new_node完成。 • ② 打开文件。打开文件调用OPEN也由common_open来完成。首先要检查文件的类型和模式,这个工作由过程forbidden对rwx位进行检查来完成。
13.5.2 实现 • ③ 关闭文件。关闭文件通过过程do_close完成,对于正规文件,我们要减少filp计数器的值并检查该计数器是否为0,若为0则表示文件不再被使用,调用put_inode来收回i节点。 • ④ 读文件。读操作由do_read接收请求,但具体的操作则是通过设置READING标志调用公共过程read_write来实现的。 • ⑤ 写文件。和读文件类似,do_write过程所做的工作也只是以WRITING标志调用read_write。
13.5.2 实现 6.目录与路径 MINIX中通常要先把路径名转换为i节点。路径名的分析是在文件path.c中进行。首先在过程eat_path中调用了两个函数,这两个函数分别是: • last_dir:检查路径名以判断是相对路径还是绝对路径。 • advance:用来取得路径的最后一个部分,它以目录指针和字符串为参数,在目录中进行查找,一旦找到匹配的路径名(和参数串匹配),就返回相应i节点的指针。