之前v1.1.0的版本已经可以通过ATAPI驱动来读取光盘里的二进制数据,现在只需要对这些数据加以解析,就可以访问到光盘里所需的文件和目录了。 光盘所使用的标准文件系统是ISO 9660,至少GRUB所生成的zenglOX.iso使用的就是这种标准的文件系统格式,我们只要掌握了ISO 9660的结构,就可以从光盘中读取出所需的文件了...

    v1.2.0的项目地址:

    github.com地址:https://github.com/zenglong/zenglOX (只包含git提交上来的源代码)
   
    Dropbox地址:点此进入Dropbox网盘  该版本位于zenglOX_v1.2.0的文件夹,该文件夹里的zenglOX_v1.2.0.zip的压缩包为源代码,readme.txt为当前版本的简单说明。

    Google Drive地址:点此进入Google Drive云端硬盘 该版本位于zenglOX_v1.2.0的文件夹,里面包含了和上面Dropbox里一样的2个文件。

    sourceforge地址:https://sourceforge.net/projects/zenglox/files  该版本位于zenglOX_v1.2.0的文件夹,里面包含了和上面Dropbox里一样的2个文件。

    Dropbox与Google Drive如果正常途径访问不了,则需要使用代理访问。

    有关zenglOX的源码编译及gdb调试的方法,请参考zenglOX v0.0.1里的文章。

    之前v1.1.0的版本已经可以通过ATAPI驱动来读取光盘里的二进制数据,现在只需要对这些数据加以解析,就可以访问到光盘里所需的文件和目录了。

    光盘所使用的标准文件系统是ISO 9660,至少GRUB所生成的zenglOX.iso使用的就是这种标准的文件系统格式,我们只要掌握了ISO 9660的结构,就可以从光盘中读取出所需的文件了。

    在 http://wiki.osdev.org/ISO_9660http://en.wikipedia.org/wiki/ISO_9660 这两个链接里(最主要的是第一个osdev的链接里),已经对ISO 9660结构做了详细的介绍,下面只对主要的地方进行简单的介绍。

    标准的ISO 9660文件系统中,完整文件名由实际文件名 + 分号 + 版本号组成,例如:GRUB.CFG;1 (前面的GRUB.CFG为实际文件名,然后是分号加版本号)。

    实际文件名又由文件名称 + 点 + 后缀组成,例如上面的GRUB.CFG (点之前的GRUB为文件名称,点之后的CFG为后缀),另外,文件名称与后缀中只能包含A到Z的大写字母、数字和下划线。如果实际文件名里没有后缀的话,文件名称后面的点也不能省略,例如:当cpuid文件被放置到zenglOX.iso的根目录后,cpuid在zenglOX.iso里的完整文件名就会变为 CPUID.;1 ,其中的实际文件名为 CPUID. (CPUID后面的点不能省略)。

    在zenglOX的命令行下,必须输入完整的包含了分号与版本号的文件名才能搜索到所需的文件。

    此外,实际文件名的长度也有限制,有三种级别的文件名,第一种级别中,文件名称最多只能有8个字符的长度,后缀则最多只能有3个字符的长度,例如:当helloworld.modsx被放置到iso镜像文件后,它在ISO系统里的实际文件名就会变为HELLOWOR.MOD,文件名称与后缀中超过的字符都会被截掉。第二种和第三种级别的文件名里,文件名称与后缀组合在一起,最多可以达到30个字符的长度。由于zenglOX.iso镜像文件里使用的是第一种级别,所以第二种和第三种级别就不考虑了。

    至于目录名则比较单纯一些,它没有点、后缀、分号与版本号这些东东,目录名只能由A到Z的大写字母、数字和下划线组成,例如BOOT目录,GRUB目录等等。

    ISO 9660文件系统里,每个文件和目录都有自己唯一的起始LBA(Logic Block Address 逻辑块地址),一个逻辑块对应一个扇区,一个扇区的标准大小是2048字节。

    ISO 9660文件系统的大致结构图如下(该图对应的表格,位于开头介绍过的 http://en.wikipedia.org/wiki/ISO_9660 的链接里):


图1

    ISO 9660文件系统开始的32K(一共16个扇区)区域是System Area系统区域,可以用于存储任意的数据,这里通常被用于存储一些启动引导信息,例如:MBR(Master Boot Record 主引导记录)、GUID分区表、APM(Apple Partition Map -- Apple分区图)等。

    System Area后面是Data Area数据区域,数据区的开始部分是由多个连续的Volume Descriptor(卷描述符)组成,这些卷描述符是以Volume Descriptor Set Terminator来和后面的数据分隔开的。

    每个Volume Descriptor都是2048字节(即一个扇区)的大小,且都有各自的类型,当我们准备加载一个CD光盘时,首先要做的第一件事情就是读取Volume Descriptor(卷描述符)里的信息。

    Volume Descriptor(卷描述符)的格式如下(该图对应的表格,位于开头介绍过的 http://wiki.osdev.org/ISO_9660 的链接里):
 

图2

    offset字段表示字段的起始偏移量,Length表示字段的字节大小,Field name表示字段名,Datatype表示数据类型,Description表示字段的描述信息。Datatype数据类型部分请参考 http://wiki.osdev.org/ISO_9660#Numerical_formats

    从上图可以看到,第一个Type字段表示Volume Descriptor的类型,不同的类型有不同的值,下面会介绍有哪些类型,第二个Identifier字段是卷描述符的标识符,都是统一的"CD001",第三个Version字段表示卷描述符的版本号,目前是固定的值:0x01 。接着从偏移位置7开始的字段会根据不同的卷描述符类型而有所不同。

    Volume Descriptor(卷描述符)可用的类型值如下表所示(该表也位于 http://wiki.osdev.org/ISO_9660 的链接里):

Value
Description
描述
0 Boot Record
引导记录
1 Primary Volume Descriptor
主卷描述符
2 Supplementary Volume Descriptor
补充卷描述符
3 Volume Partition Descriptor
卷分区描述符
4-254 Reserved
保留值
255 Volume Descriptor Set Terminator
卷集结束

    由于我们只通过Primary Volume Descriptor(主卷描述符)来查找文件或目录,所以这里只对主卷描述符进行介绍,Boot Record与Volume Descriptor Set Terminator类型请参考 http://wiki.osdev.org/ISO_9660

    在我们通过make iso命令生成的zenglOX.iso文件里,0x8000位置处,即System Area(系统区)后面的第一个卷描述符就是Primary Volume Descriptor(主卷描述符):


图3

    0x8000的第1个字节0x01是卷描述符的类型,由之前的类型表可知,这是一个Primary Volume Descriptor(主卷描述符),第2到第6个字节是固定的标识符:CD001 可以从上图右侧的红框框里直接看到,第7个字节是固定的值0x01表示卷描述符的版本号。第8个字节开始的字段就需要参考下表(该表位于 http://wiki.osdev.org/ISO_9660#The_Primary_Volume_Descriptor 的链接里),该表格里有很多的字段,程序开发时会用到的字段已经用棕色标记了出来,并且给出了对应的中文注释:

Offset Length (bytes) Field name Datatype Description
0 1 Type Code
类型代码
int8
8位的字节值
Always 0x01 for a Primary Volume Descriptor.
主卷描述符类型代码始终是0x01
1 5 Standard Identifier
标识符
strA
A类型的字符串
Always 'CD001'.
标识符始终是 'CD001'
6 1 Version
版本号
int8
8位的字节值
Always 0x01.
版本号始终是 0x01
7 1 Unused - Always 0x00.
8 32 System Identifier strA The name of the system that can act upon sectors 0x00-0x0F for the volume.
40 32 Volume Identifier strD Identification of this volume.
72 8 Unused Field - All zeroes.
80 8 Volume Space Size int32_LSB-MSB Number of Logical Blocks in which the volume is recorded.
88 32 Unused Field - All zeroes.
120 4 Volume Set Size int16_LSB-MSB The size of the set in this logical volume (number of disks).
124 4 Volume Sequence Number int16_LSB-MSB The number of this disk in the Volume Set.
128 4 Logical Block Size int16_LSB-MSB The size in bytes of a logical block. NB: This means that a logical block on a CD could be something other than 2 KiB!
132 8 Path Table Size
路径表大小
int32_LSB-MSB
先是小字节序的32位
整数值,
然后再跟随一个
大字节序的
32位整数值
The size in bytes of the path table.
路径表的以字节为单位的尺寸大小
140 4 Location of Type-L Path Table
小字节序的路径表的LBA位置
int32_LSB
小字节序的32位
整数值
LBA location of the path table. The path table pointed to contains only little-endian values.
路径表的LBA位置,该路径表里的所有字段的值都是小字节序的
144 4 Location of the Optional Type-L Path Table int32_LSB LBA location of the optional path table. The path table pointed to contains only little-endian values. Zero means that no optional path table exists.
148 4 Location of Type-M Path Table int32_MSB LBA location of the path table. The path table pointed to contains only big-endian values.
152 4 Location of Optional Type-M Path Table int32_MSB LBA location of the optional path table. The path table pointed to contains only big-endian values. Zero means that no optional path table exists.
156 34 Directory record for the root directory
Root根目录的目录记录
-
Directory record
有自己独立的结构
Note that this is not an LBA address, it is the actual Directory Record, which contains a zero-length Directory Identifier, hence the fixed 34 byte size.
这是一个独立的完整的Directory record(目录记录),由于该目录记录里的目录名有固定的长度,所以尺寸就是固定的值34
190 128 Volume Set Identifier strD Identifier of the volume set of which this volume is a member.
318 128 Publisher Identifier strA The volume publisher. For extended publisher information, the first byte should be 0x5F, followed by the filename of a file in the root directory. If not specified, all bytes should be 0x20.
446 128 Data Preparer Identifier strA The identifier of the person(s) who prepared the data for this volume. For extended preparation information, the first byte should be 0x5F, followed by the filename of a file in the root directory. If not specified, all bytes should be 0x20.
574 128 Application Identifier strA Identifies how the data are recorded on this volume. For extended information, the first byte should be 0x5F, followed by the filename of a file in the root directory. If not specified, all bytes should be 0x20.
702 38 Copyright File Identifier strD Filename of a file in the root directory that contains copyright information for this volume set. If not specified, all bytes should be 0x20.
740 36 Abstract File Identifier strD Filename of a file in the root directory that contains abstract information for this volume set. If not specified, all bytes should be 0x20.
776 37 Bibliographic File Identifier strD Filename of a file in the root directory that contains bibliographic information for this volume set. If not specified, all bytes should be 0x20.
813 17 Volume Creation Date and Time dec-datetime The date and time of when the volume was created.
830 17 Volume Modification Date and Time dec-datetime The date and time of when the volume was modified.
847 17 Volume Expiration Date and Time dec-datetime The date and time after which this volume is considered to be obsolete. If not specified, then the volume is never considered to be obsolete.
864 17 Volume Effective Date and Time dec-datetime The date and time after which the volume may be used. If not specified, the volume may be used immediately.
881 1 File Structure Version int8 The directory records and path table version (always 0x01).
882 1 Unused - Always 0x00.
883 512 Application Used - Contents not defined by ISO 9660.
1395 653 Reserved - Reserved by ISO.

    上表里,最开始的三个字段都已经介绍过,主卷描述符中,最重要的三个字段是132偏移处的Path Table Size、140偏移处的Type-L Path Table的LBA值,以及156偏移处的Root根目录的Directory record 。

    这三个字段在zenglOX.iso里的位置如下:


图4

    0x8000是主卷描述符的起始偏移值,0x8000加上132(十六进制为0x84)即zenglOX.iso的0x8084的位置处为Path Table Size字段,该字段有8个字节,前4个字节是按小字节序排列的(x86体系使用的都是小字节序),后4个字节是按大字节序排列的,可以从大字节序直接看出Path Table Size的值为0x40,所以路径表的大小为0x40(十进制为64)个字节的大小。

    同理,0x808C位置处为Type-L Path Table的LBA值,Type-L表示该路径表里存储的值都是小字节序的,上图显示LBA值为0x24,即zenglOX.iso的36号扇区(LBA是从0开始的)为Path Table,该Path Table(路径表)的大小为64个字节(由之前的Path Table Size字段得出)。Path Table(路径表)的结构下面会进行介绍。

    上图黄线标注的是根目录的Directory record,Directory record的结构可以参考下表 (该表位于 http://wiki.osdev.org/ISO_9660#Directories 的链接里)。
   
Offset Size Description
0 1 Length of Directory Record.
1 1 Extended Attribute Record length.
2 8 Location of extent (LBA) in both-endian format.
10 8 Data length (size of extent) in both-endian format.
18 7 Recording date and time (see format below).
25 1 File flags (see below).
26 1 File unit size for files recorded in interleaved mode, zero otherwise.
27 1 Interleave gap size for files recorded in interleaved mode, zero otherwise.
28 4 Volume sequence number - the volume that this extent is recorded on, in 16 bit both-endian format.
32 1 Length of file identifier (file name). This terminates with a ';' character followed by the file ID number in ASCII coded decimal ('1').
33 (variable) File identifier.
(variable) 1 Padding field - zero if length of file identifier is even, otherwise, this field is not present. This means that a directory entry will always start on an even byte number.
(variable) (variable)

System Use - The remaining bytes up to the maximum record size of 255 may be used for extensions of ISO 9660. The most common one is the System Use Share Protocol (SUSP) and its application, the Rock Ridge Interchange Protocol (RRIP).


    上表可以和上面图4里黄线标注的部分进行对照,图4中0x809C处的第1个字节0x22为Length of Directory Record,表示当前Directory Record的长度为34个字节,第2个字节0x0为Extended Attribute Record length,表示该记录的扩展属性的长度为0 。从第3到第10个字节 1300 0000 0000 0013 为当前Directory Record对应的extent(数据部分)的LBA值,这里的8个字节中,前4个字节为小字节序,后4个字节为大字节序,所以LBA值为0x13,即zenglOX.iso的19号扇区为当前Directory Record的数据部分,由于当前Directory Record为Root根目录,所以我们可以从19号扇区(也就是第20个扇区)找到根目录下的子目录与文件的相关的LBA等信息。

    从第11个字节到第18个字节:0008 0000 0000 0800 为extent(数据部分)的尺寸大小,也是前4个字节为小字节序,后4个字节为大字节序,所以extent(数据部分)的尺寸为0x800即2048个字节的大小。

    第19到第25这7个字节:7206 070a 2430 00 为Recording date and time(当前目录记录的日期和时间信息),该日期和时间的具体格式如下表所示 (该表也位于 http://wiki.osdev.org/ISO_9660 的链接里):

Offset Size Description
0 1 Number of years since 1900.
1 1 Month of the year from 1 to 12.
2 1 Day of the month from 1 to 31.
3 1 Hour of the day from 0 to 23.
4 1 Minute of the hour from 0 to 59.
5 1 Second of the minute from 0 to 59.
6 1 Offset from GMT in 15 minute intervals from -48 (West) to +52 (East).

    偏移处0的第1个字节0x72表示自1900年以来的年数,0x72是十六进制,对应十进制为114,所以年份就是1900 + 114 = 2014年,第2个字节0x06表示6月份,第3个字节0x07表示7日,第4个字节0x0a表示10小时(至于当地的北京时间,要根据最后一个字节所表示的时区来进行换算),第5个字节0x24表示36分,第6个字节0x30表示48秒,第7个字节0x00表示该时间是GMT格林威治的标准时间,所以小时数10需要再加8,即18点才是当地北京时间。因此,该目录对应的完整的当地时间为2014年6月7日 18点36分48秒。

    日期时间字段后面的 02 为File flags(文件标志),文件标志的二进制位含义如下:

Bit Description
0 If set, the existence of this file need not be made known to the user (basically a 'hidden' flag.
1 If set, this record describes a directory (in other words, it is a subdirectory extent).
2 If set, this file is an "Associated File".
3 If set, the extended attribute record contains information about the format of this file.
4 If set, owner and group permissions are set in the extended attribute record.
5 & 6 Reserved
7 If set, this is not the final directory record for this file (for files spanning several extents, for example files over 4GiB long.

    上表也位于 http://wiki.osdev.org/ISO_9660 的链接里,0x02对应的二进制格式为:0000 0010 ,由于位1被设置,所以当前Directory record里存储的是一个目录的信息。

    File flags后面的 0000 表示File unit size与Interleave gap size两个字段的值都是0 。

    再往后的 0100 0001 为Volume sequence number(卷序列号),前2个字节 0100 为小字节序,后2个字节 0001 为大字节序,因此卷序列号为0x01,说明当前目录的extent(数据部分)被记录在1号卷上面。

    卷序列号后面的 01 为Length of file identifier (file name)即目录或文件名的长度,说明Root根目录的目录名的长度为1

    长度字段后面的 00 为File identifier即目录或文件名,说明Root根目录的目录名为 0x0,下面要提到的任一目录里的当前目录的目录名也是 0x0 ,任一目录的父目录的目录名为 0x01 ,当前目录与父目录在Linux系统下喜欢分别用"."和".."来表示,但是它在ISO 9660里的实际目录名却分别是 0x0 与 0x1 的两个数值,这是需要特别注意的地方,另外,Linux里根目录喜欢用"/"字符来表示,但是根目录在ISO 9660里的实际目录名却是一个0x0的数值形式,在给用户进行显示时,你可以用"." ,".." 及 "/" 来进行显示,zenglOX里目前并没有这方面的显示。

    当我们要在ISO 9660文件系统里查找类似 "iso/BOOT/GRUB" 之类的路径时,一般有两种方式:一种是像Linux那样先从根目录的Directory record里得到根目录的extent(数据部分)的LBA值,由该LBA找到根目录的extent,再在该extent(实际数据部分)搜索子目录例如BOOT的Directory record,然后从BOOT子目录的Directory record里得到BOOT目录的extent的LBA,由BOOT的extent搜索GRUB子目录,这样就得到GRUB目录的Directory record结构了,如果还要搜索GRUB目录下面的文件,还可以继续从GRUB的extent中搜索文件名,找到目标文件的Directory record结构,从Directory record里的LBA值,就可以找到目标文件的extent(实际数据部分)了。

    我们可以先看下zenglOX.iso里根目录的extent(数据部分),上面图4黄线标注的部分是根目录的Directory record,该Directory record的结构上面已经详细讲解过,上面提到根目录的extent的LBA值为0x13,那么extent在iso文件里的起始偏移值就是:0x13 * 0x800 = 0x9800 (0x800是2048的十六进制格式),我们可以在vim编辑器里找到0x9800偏移处的数据:


图5

    上图用黄,绿,红,蓝这4条线标注出4个Directory record结构,每个Directory record对应一个目录或文件,从之前Directory record的表格里,我们可以看到,Directory record最后的File identifier(文件或目录名)与System Use字段的长度是variable(可变的),因此,我们只有通过Directory record的第1个字节来确定Directory record的总大小,也只有第1个字节的值才能确定下一个Directory record结构的起始位置。File identifier(文件或目录名)字段使用的是ISO 9660的标准文件名格式,即文件名称长度不会超过8个字符,文件后缀长度不会超过3个字符,像上图蓝线标注的Directory record结构里标准文件名为BOOT.CAT;1,其实际的文件名是boot.catalog,后缀被截掉了一部分,实际的文件名被GRUB在生成ISO时放在了System Use字段的最后位置,System Use字段是用于ISO 9660扩展用的,GRUB软件将实际的完整文件名放在了这个字段里,你在开发时,也可以通过完整文件名来进行搜索,不过zenglOX里目前只对标准文件名进行搜索。

    在根目录的extent里进行搜索时,就是一个一个的搜索Directory record结构,比较Directory record结构中的目录或文件名,以找到所需的文件或目录,其他的子目录的extent部分也是类似图5的连续的Directory record结构。

    文件的extent部分则是该文件的实际内容。

    上面提到搜索路径时有两种方式,第一种方式介绍过了,第二种方式就是像Windows系统那样使用Path Table(路径表),Path Table(路径表)的结构如下:

Offset Size Description
0 1 Length of Directory Identifier
1 1 Extended Attribute Record Length
2 4 Location of Extent (LBA). This is in a different format depending on whether this is the L-Table or M-Table (see explanation above).
6 2 Directory number of parent directory (an index in to the path table). This is the field that limits the table to 65536 records.
8 (variable) Directory Identifier (name) in d-characters.
(variable) 1 Padding Field - contains a zero if the Length of Directory Identifier field is odd, not present otherwise. This means that each table entry will always start on an even byte number.

    上表也位于 http://wiki.osdev.org/ISO_9660 的链接里,我们可以对照zenglOX.iso里的Path Table来分析上表。之前,我们在zenglOX.iso的PVD(主卷描述符)里找到的Path Table的LBA值为0x24,因此,Path Table位于0x24 * 0x800 = 0x12000的偏移位置:


图6

    红框框标注的区域为zenglOX.iso里的完整的Path Table(路径表)的所有内容,里面存储了5个Path Table结构,分别对应根目录,BOOT目录,GRUB目录,I386_PC目录以及LOCALE目录,zenglOX.iso里一共也就只有这5个目录,因此如果我们想搜索iso/BOOT/GRUB/GRUB.CFG的话(iso表示根目录),只需在路径表中一路往下找到GRUB的Path Table结构,然后从该结构里找出extent的LBA值,最后再从extent的连续的Directory record中搜索到GRUB.CFG文件的Directory record即可。

    以根目录为例,根目录的Path Table结构的第1个字节 01 为Length of Directory Identifier,表示Directory Identifier(目录名)的长度,第2个字节 00 为Extended Attribute Record Length,表示目录扩展的属性记录长度,第3到第6个字节:1300 0000 为Location of Extent (LBA)即extent的LBA值,该LBA值是小字节序,因此根目录extent的LBA值为0x13 ,和我们之前直接从PVD里找出来的值相同。第7到第8个字节:0100 为Directory number of parent directory,也就是该目录的父目录在Path Table路径表数组里的索引值(从1开始),这里为1,即根目录的父目录就是它自己。第9个字节 00 为根目录的目录名,前面说过是一个0值,目录名后面第10个字节 00 是填充用的,以确保下一个Path Table结构可以从偶数字节开始。

    其余目录的Path Table结构可以同理进行分析,虽然路径表是一个Path Table结构的数组,但是它和C语言里标准的数组结构有所不同,路径表数组里的每个元素的大小都不完全一样,因为目录名是一个可变长度的字段。目录的extent(数据部分)的连续的Directory record结构的长度也都不太一样,这样也就给查找工作带来了一定的难度。

    此外,Path Table(路径表)还有个65536的限制(目录数不可以超过这个数目),这是因为Path Table结构里的Directory number of parent directory字段只有两个字节的大小。如果使用第一种查找方法,从根目录的extent一级一级找下去的话,就不会存在路径表的这种65536的限制,zenglOX里使用的是和Windows一样的查找路径表的方式,因为这种方式容易实现,对光盘的读次数也可以降到最低。

    以上就是ISO 9660文件系统的大致结构,以及进行路径搜索的两种方式。

    zenglOX v1.2.0版本中,和ISO 9660文件系统相关的文件为zlox_iso.c与zlox_iso.h 。zlox_iso.h头文件里定义了和ISO 9660文件系统相关的结构和相关的宏:

/* zlox_iso.h -- 包含和iso 9660文件系统相关的结构定义 */

#ifndef _ZLOX_ISO_H_
#define _ZLOX_ISO_H_

#include "zlox_common.h"
#include "zlox_fs.h"

#define ZLOX_PVD_START_LBA 16
#define ZLOX_PVD_TYPE 1
#define ZLOX_VD_TERMINATOR 255
#define ZLOX_VD_ID_1 0x43    // 'C'
#define ZLOX_VD_ID_2 0x44    // 'D'
#define ZLOX_VD_ID_3 0x30    // '0'
#define ZLOX_VD_ID_4 0x30    // '0'
#define ZLOX_VD_ID_5 0x31    // '1'
#define ZLOX_PVD_PTSZ_OFFSET 132
#define ZLOX_PVD_LPT_OFFSET 140
#define ZLOX_PVD_ROOT_OFFSET 156

struct _ZLOX_PATH_TABLE_ENTRY
{
	ZLOX_UINT8 dir_id_length;
	ZLOX_UINT8 ext_length;
	ZLOX_UINT32 lba;
	ZLOX_UINT16 parent_index;
}__attribute__((packed));

typedef struct _ZLOX_PATH_TABLE_ENTRY ZLOX_PATH_TABLE_ENTRY;

struct _ZLOX_ISO_DIR_RECORD
{
	ZLOX_UINT8 length;
	ZLOX_UINT8 ext_length;
	ZLOX_UINT32 l_lba;
	ZLOX_UINT32 m_lba;
	ZLOX_UINT32 l_datasize;
	ZLOX_UINT32 m_datasize;
	ZLOX_UINT8 datatime[7];
	ZLOX_UINT8 file_flags;
	ZLOX_UINT8 file_unit_size;
	ZLOX_UINT8 interleave_gap_size;
	ZLOX_UINT32 vol_seq_number;
	ZLOX_UINT8 file_name_length;
}__attribute__((packed));

typedef struct _ZLOX_ISO_DIR_RECORD ZLOX_ISO_DIR_RECORD;

typedef struct _ZLOX_ISO_CACHE
{
	ZLOX_UINT8 * ptr;
	ZLOX_UINT32 lba;
	ZLOX_UINT32 size;
}ZLOX_ISO_CACHE;

typedef struct _ZLOX_ISO_PATH_TABLE_EXTDATA
{
	ZLOX_UINT32 pt_offset;
	ZLOX_UINT32 datasize;
}ZLOX_ISO_PATH_TABLE_EXTDATA;

// 卸载iso目录,将堆中分配的相关内存资源释放掉
ZLOX_FS_NODE * zlox_unmount_iso();
// 挂载CDROM到iso目录下
ZLOX_FS_NODE * zlox_mount_iso();

#endif // _ZLOX_ISO_H_


    至于zlox_iso.c文件里则定义了和ISO 9660文件系统相关的函数,其中的zlox_mount_iso与zlox_unmount_iso函数是为mount_iso与unmount_iso两个系统调用准备的:

// 卸载iso目录,将堆中分配的相关内存资源释放掉
ZLOX_FS_NODE * zlox_unmount_iso()
{
	if(iso_root != ZLOX_NULL)
	{
		zlox_kfree(iso_root);
		iso_root = ZLOX_NULL;
		iso_ide_index = -1;
	}
	if(iso_path_table != ZLOX_NULL)
	{
		zlox_kfree(iso_path_table);
		iso_path_table = ZLOX_NULL;
		iso_path_table_sz = 0;
	}
	if(iso_path_table_extdata != ZLOX_NULL)
	{
		zlox_kfree(iso_path_table_extdata);
		iso_path_table_extdata = ZLOX_NULL;
	}
	if(iso_cache.ptr != ZLOX_NULL)
	{
		zlox_kfree(iso_cache.ptr);
		iso_cache.ptr = ZLOX_NULL;
		iso_cache.lba = 0;
		iso_cache.size = 0;
	}
	return iso_root;
}

// 挂载CDROM到iso目录下
ZLOX_FS_NODE * zlox_mount_iso()
{
	ZLOX_SINT32 i,j;
	iso_root = ZLOX_NULL;
	for(i = 0 ; i < 4 ; i++)
	{
		// 寻找ATAPI光盘设备
		if(ide_devices[i].Reserved == 0 || ide_devices[i].Type != ZLOX_IDE_ATAPI)
			continue;

		// 初始化ISO文件系统的根节点
		iso_root = (ZLOX_FS_NODE *)zlox_kmalloc(sizeof(ZLOX_FS_NODE));
		zlox_strcpy(iso_root->name, "iso");
		iso_root->mask = iso_root->uid = iso_root->gid = iso_root->inode = iso_root->length = 0;
		iso_root->flags = ZLOX_FS_DIRECTORY;
		iso_root->read = 0;
		iso_root->write = 0;
		iso_root->open = 0;
		iso_root->close = 0;
		iso_root->readdir = &zlox_iso_readdir;
		iso_root->finddir = &zlox_iso_finddir;
		iso_root->ptr = 0;
		iso_root->impl = 1;

		//设置iso目录对应的IDE设备号,访问iso时,会使用该设备号来读写对应的ATAPI光盘设备
		iso_ide_index = i;
		//Primary Volume Descriptor (主卷描述符,里面存储着Path Table路径表和Root根目录的LBA寻址信息)
		ZLOX_UINT8 * pvd = (ZLOX_UINT8 *)zlox_kmalloc(ZLOX_ATAPI_SECTOR_SIZE);
		//查找Primary Volume Descriptor (主卷描述符)
		for(j=0;;j++)
		{
			zlox_atapi_drive_read_sector(iso_ide_index,ZLOX_PVD_START_LBA + j,pvd);
			if(pvd[1] != ZLOX_VD_ID_1 || pvd[2] != ZLOX_VD_ID_2 ||
			   pvd[3] != ZLOX_VD_ID_3 || pvd[4] != ZLOX_VD_ID_4 || pvd[5] != ZLOX_VD_ID_5)
				goto err;
			else if(pvd[0] == ZLOX_PVD_TYPE)
				break;
			else if(pvd[0] == ZLOX_VD_TERMINATOR)
				goto err;
		}

		//获取根目录的directory record(目录记录,里面存储了目录的寻址等信息)
		ZLOX_ISO_DIR_RECORD * dir_record = (ZLOX_ISO_DIR_RECORD *)(pvd + ZLOX_PVD_ROOT_OFFSET);
		iso_root->inode = dir_record->l_lba;
		iso_root->length = dir_record->l_datasize;

		//将Path Table路径表缓存到iso_path_table对应的堆空间
		ZLOX_UINT32 path_table_sz = *((ZLOX_UINT32 *)(pvd + ZLOX_PVD_PTSZ_OFFSET));
		ZLOX_UINT32 path_table_lba = *((ZLOX_UINT32 *)(pvd + ZLOX_PVD_LPT_OFFSET));
		zlox_atapi_drive_read_sector(iso_ide_index,path_table_lba,pvd);
		iso_path_table = (ZLOX_UINT8 *)zlox_kmalloc(path_table_sz);
		zlox_memcpy(iso_path_table,pvd,path_table_sz);
		iso_path_table_sz = path_table_sz;
		zlox_kfree(pvd);
		zlox_init_path_table_extdata();
		break;

err:
		zlox_monitor_write("kernel: iso mount err...\n");
		zlox_kfree(iso_root);
		zlox_kfree(pvd);
		iso_root = ZLOX_NULL;
		break;
	}

	return iso_root;
}


    在zlox_mount_iso函数里,会先创建一个iso_root的根目录的文件节点,当在zenglOX里搜索iso开头的路径时,就会转到iso_root根目录节点去进行搜索操作,在ISO相关的文件节点里,inode成员表示该节点对应的目录或文件在ISO文件系统里的extent(数据部分)的LBA值,impl字段表示目录在Path Table(路径表)中的索引值(从1开始),length表示目录或文件的extent的尺寸大小。

    为了方便搜索路径,在zlox_mount_iso函数创建了iso_root根目录节点后,就会在堆里根据路径表的大小,分配一段堆空间,然后将ISO 9660文件系统的Path Table(路径表)读取到该空间缓存起来,并为路径表创建iso_path_table_extdata额外数据,该额外数据可以提供路径表以外的信息,例如目录的extent的尺寸大小信息等,以方便路径的搜索工作。

    zlox_unmount_iso函数用于清理iso_root及相关的资源,以卸载掉iso目录。

    zlox_iso.c文件里的其他函数是和ISO 9660文件系统具体的读操作相关的函数,都是依据之前介绍的内容来编写的代码,而且附带了一些中文注释,下面只对zlox_iso_readdir函数的一个需要注意的地方进行说明:

// 从node目录节点中搜索index索引对应的文件或目录的基本信息
static ZLOX_DIRENT * zlox_iso_readdir(ZLOX_FS_NODE *node, ZLOX_UINT32 index)
{	
...........................................
...........................................
	for(i=0;i <= index;i++)
	{
		dir_offset = (ZLOX_UINT32)dir_record - (ZLOX_UINT32)iso_cache.ptr;
		dir_sector = dir_offset / ZLOX_ATAPI_SECTOR_SIZE + 1;
		if(dir_offset >= cur_datasize)
			return 0;
		if(dir_record->length == 0)
		{
			if(dir_sector == cur_datasize / ZLOX_ATAPI_SECTOR_SIZE)
				return 0;
			// 当目录的extents(数据块)的某个扇区快用完时,它会从下一个扇区开始放置数据,所以这里需要直接跳到下一个扇区
			dir_record = (ZLOX_ISO_DIR_RECORD *)(iso_cache.ptr + 
						(dir_sector * ZLOX_ATAPI_SECTOR_SIZE));
		}
		if(i < index)
			dir_record = (ZLOX_ISO_DIR_RECORD *)((ZLOX_UINT8 *)dir_record + dir_record->length);
	}
...........................................
...........................................
}


    上面for循环里有个注释:当目录的extents(数据块)的某个扇区快用完时,它会从下一个扇区开始放置数据,要理解这点,可以查看zenglOX.iso里I386_PC目录的extent部分:


图7

    从上图可以看到,由于gcry_sha512.mod所在的扇区无法再容纳一个完整的GCRY_TIG.MOD的Directory record结构,所以就将GCRY_TIG.MOD的Directory record放到了下一个扇区的开始位置,gcry_sha512.mod与GCRY_TIG.MOD之间空出来的部分就全部被0填充。

    zlox_iso.c的其他函数的代码,可以根据之前介绍的理论来进行分析。

    在build_initrd_img目录中,新增的mount.c与unmount.c文件,分别为mount与unmount工具的源代码,它们分别利用mount_iso与unmount_iso系统调用来完成加载与卸载iso目录的操作。
 
   
图8

    可以看到在mount iso之前,ls命令只显示出了ram disk里的文件,在mount iso执行完后,ls命令就显示出了一个iso目录,zenglOX里使用中括号加目录名来表示一个目录。

    我们可以使用ls iso来查看iso目录里的文件:
 

图9

    上图使用ls iso来查看ISO根目录下的文件,并使用ls iso/BOOT/GRUB来查看GRUB目录下的文件,还可以使用cat iso/BOOT/GRUB/GRUB.CFG;1来查看GRUB目录里GRUB.CFG;1文件的内容,最后还可以直接执行iso里的ELF格式的可执行文件,例如上图的iso/CPUID.;1命令就显示出了处理器供应商的ID字符串信息,只不过在访问ISO里的文件时,文件名后面的点,分号,版本号不能省略,因为zenglOX目前是根据ISO 9660的标准文件名的格式来进行搜索的。

    OK,就到这里,休息,休息一下 o(∩_∩)o~
 
上下篇

下一篇: zenglOX v1.3.0 动态链接库, 固定位置的内核栈, double fault(双误异常检测内核栈溢出)

上一篇: zenglOX v1.1.0 通过ATAPI读取光盘里的数据

相关文章

zenglOX v0.0.2 VGA输出显示字符串

zenglOX v0.0.8 Multitask(多任务)

zenglOX v0.0.5 分页

zenglOX v3.2.0 USB v1.1, UHCI, USB KeyBoard, USB Mouse

zenglOX v1.6.0 保护模式下, VGA图形模式驱动程式

zenglOX v1.0.0 shell(命令行程式及各种小工具)