自己动手写Windows分区工具(一)

    分区体系有DOS、Apple、BSD等,Windows分区使用的是DOS分区体系,用的是MBR分区表,一般我们的硬盘用的都是这种方式。MBR分区表下,LBA(Logical Block Address,逻辑块地址)地址最大为32位,按每扇区512Byte计算最大能描述2TB的空间大小,若硬盘容量超过2TB左右便无法全部描述。除此之外,还有一种分区表叫作GPT(GUID Partition Table,全局唯一标识符分区表),这种分区表用的寻址方式是64位,不受前者的限制。不过,以目前的情况,2TB硬盘虽然并不少见,但是通常我们也不用到,何况现今固态硬盘(SSD)已成为主流,所以Win操作系统中MBR分区表仍然是大部分人的选择,而Linux下用的则是GPT分区表。下面我们来谈一下MBR分区表的结构,关于GPT分区表以后再谈。

    首先看硬盘。我们都知道,硬盘上存在着所谓的柱面(Cylinder)、磁头(Head)和扇区(Sector),也就是所谓的CHS,通过CHS地址,我们可以定位到硬盘的唯一一个扇区。此外,还有种地址叫做LBA,也就是硬盘逻辑地址,物理硬盘上每磁道一般有63个扇区,每扇区从1开始编号,而逻辑地址下,扇区从0编号,直到最大扇区号,最大扇区号为硬盘扇区总数减1。所以说,U盘等移动存储设备也是有“扇区”的,这里的扇区对应的是U盘芯片里的数据块(Block)。另外,对物理硬盘,柱面和磁道差不多是一样的概念,它们也就是一个个的同心圆,但从微观角度看扇区是矩形而非扇形。硬盘下每扇区一般是512字节,这是一种约定俗成的标准(最新的SSD可能会有1024字节),磁头是最少的,一般有几个到十几个不等,不过,如果你用Diskgen软件查看硬盘会发现,可能会显示255个磁头,硬盘每盘片上一般有两个磁头,硬盘一般只有几张盘片,怎么可能有255个磁头呢?甚至连U盘也显示255个磁头,这是怎么回事呢?其实这里的磁头是指逻辑磁头数,而非硬盘物理磁头数,Windows操作系统会把硬盘分为每扇区字节数(512),每磁道扇区数(63),每柱面磁道数(255),以及柱面数(上万个),这里少个磁头数,因为以上这些都操作系统定义的逻辑数据,而非硬盘真实的结构,前面说了,对物理硬盘而言柱面和磁道是等价的东西,而Diskgen显示的磁头数应该是磁道数(估计连diskgen自己也不清楚,按照Windows的DISK_GEOMETRY结构定义,成员TracksPerCylinder的意思确实是“每柱面磁道数”,看来Windows把柱面和磁道分开定义了。但请注意,实际硬盘上,柱面就是磁道,磁道就是柱面,两者没有区别),如果用Everest软件查看会发现,硬盘的磁头数仅为十几个,而柱面数仍然为几万个。

    下面看分区表。用WinHex打开物理硬盘,第0号扇区就是MBR(Master Boot Recorder,主引导记录),前446字节为引导代码,第447字节也就是偏移0x1BE处开始就是主分区表,大小为64个字节,最后两字节是55AA标志。分区表中共有4个分区表项,每个项大小为16字节,第1个字节为可引导标志,0表示不可引导,0x80表示可引导,之后3个字节是分区起始CHS地址,然后是分区标志,占1字节,例如0x0F或0x05代表扩展分区,0x17代表隐藏NTFS分区,然后是分区结束CHS地址,也是3字节,最后两个是分区起始LBA和分区大小扇区数,各占4字节。这里的4个分区都叫做主分区,如果是扩展分区也可以叫主扩展分区。主分区表中最多可建4个主分区,但扩展分区只能建1个。主分区一般描述C盘,而主扩展分区同样描述了另一个空间,可以把这个空间当作一个新的磁盘,在这个空间上可以建立若干逻辑分区(D、E、F)。主扩展分区的起始处一个扇区为引导记录(可以称作EBR,扩展引导记录),同样的位置0x1BE处又描述了一张分区表,可称之为扩展分区表,这个表只能表示两个分区表项,第1个表项为文件系统分区(一般为第二个逻辑盘),属于二级文件系统分区,第2个表项为二级扩展分区,然后二级扩展分区的EBR中又能表示两个分区,第1个分区为三级文件系统分区,第2个分区为三级扩展分区,所有扩展分区的结束部分一般会到头,也就是会占满硬盘,否则会多出空闲空间。照这个方式类推,最终遍历完所有分区的结构。分区一般为主分区和逻辑分区,扩展分区并不能算真正意义的分区,而是属于硬盘分区后的剩余空间,所以通常我们说到分区,一般不包括扩展分区。每个分区(不包括扩展分区)的开始一个扇区为DBR(DOS Boot Recorder,DOS引导记录),里面记录着分区的信息,比如分区大小总扇区数、每扇区字节数、每簇扇区数、保留扇区数、根目录簇等文件系统信息。

    知道了以上这些要素,我们可以动手写一个自己的分区工具了。下面的这个分区程序只是个测试,功能比较简单,大概的功能有快速分区并格式化、调整分区信息、删除分区、隐藏分区、设置活动分区、重建MBR等。由于代码太长,所以只例举了部分最核心的代码。

核心代码:

'//枚举物理磁盘
Public Function m_EnumDisk() As Long
        Dim i           As Long
        Dim n           As Long
        Dim bytBuffer() As Byte
        Dim sdd         As STORAGE_DEVICE_DESCRIPTOR
        Dim szName      As String
        Dim szId        As String
        Dim nDev        As Long
        Dim DiskId()    As Long
       
        nDev = EnumDiskDevice(DiskId) '枚举所有磁盘
        If nDev = 0 Then
                nDev = 16 '强制遍历16个设备
                ReDim DiskId(nDev - 1)
                For i = 0 To nDev - 1
                        DiskId(i) = i
                Next i
        End If
       
        ReDim bytBuffer(0 To 7)
        Erase DiskData
        For i = 0 To nDev - 1
                If m_OpenDisk(DEVICE_NAME & DiskId(i), True) Then
                        lResult = DeviceIoControl(hDisk, IOCTL_DISK_GET_LENGTH_INFO, ByVal 0&, 0, bytBuffer(0), 8, dwOutBytes, ByVal 0&)
                        sdd.Size = LenB(sdd)
                        GetDiskAttr sdd
                       
                        '磁盘固件信息,制造商和产品Id
                        If sdd.VendorIdOffset >= 36 Then
                                szName = String(512, Chr(0))
                                CopyMemory ByVal StrPtr(szName), sdd.RawDeviceProperties(sdd.VendorIdOffset - 36), 512 - (sdd.VendorIdOffset - 36)
                                szName = Trim(TrimNull(StrConv(szName, vbUnicode)))
                        End If
                        If sdd.ProductIdOffset >= 36 Then
                                szId = String(512, Chr(0))
                                CopyMemory ByVal StrPtr(szId), sdd.RawDeviceProperties(sdd.ProductIdOffset - 36), 512 - (sdd.ProductIdOffset - 36)
                                szId = Trim(TrimNull(StrConv(szId, vbUnicode)))
                        End If
                       
                        ReDim Preserve DiskData(n)
                        With DiskData(n)
                                .DiskId = DiskId(i)
                                CopyMemory .ddSize, bytBuffer(0), 8
                                .ddSize = .ddSize * 10000 '总容量,字节
                                .ddBusType = sdd.BusType '总线
                                .ddName = szName & " " & szId
                                .ddBPS = BytesPerSector 'BPS,每扇区字节数
                        End With
                        n = n + 1
                        m_CloseDisk
                End If
        Next i
        Erase bytBuffer
        m_EnumDisk = n
End Function

'//获取分区信息
Public Function m_GetPartition(ByVal DiskId As Long, Optional ByVal bGetDriveName As Boolean) As Long
        If m_OpenDisk(DEVICE_NAME & DiskId, True) = False Then
                MsgBox "无法打开磁盘。" & DEVICE_NAME & DiskId & " 请以管理员运行,并退出其它程序。", vbCritical
                Exit Function
        End If
       
        Erase PartData
       
        Dim bytBuf()    As Byte
        Dim n           As Long
        Dim i           As Long
        Dim bIsExt      As Boolean
       
        ReDim bytBuf(BytesPerSector - 1)
        lResult = m_ReadDisk(0, bytBuf())
        Dim ptd(3) As PART_TABLE_DATA
        CopyMemory ptd(0), bytBuf(PT_OFFSET), PT_SIZE * 4
        For i = 0 To 3
                If ptd(i).StartLBA <> 0 Then
                        bIsExt = IsExtPart(ptd(i).PartFlag)
                        ReDim Preserve PartData(n)
                        With PartData(n)
                                .PartTable = ptd(i)
                                .PTSector = 0
                                .PTIndex = i
                                .StartLBA = ptd(i).StartLBA
                                .SecCount = ptd(i).cntSectors
                        End With
                        If Not bIsExt Then
                                PartData(n).FileSystem = GetFileSys(ptd(i).StartLBA) '文件系统
                                PartData(n).PartType = ptMain
                        Else
                                PartData(n).FileSystem = FILE_SYSTEM.Extended
                                PartData(n).PartType = ptExtended
                        End If
                        n = n + 1
                        If bIsExt Then '扩展分区
                                ExtPartLBA = ptd(i).StartLBA '扩展分区LBA
                                GetSubPart ptd(i).StartLBA, n
                        End If
                End If
        Next i
       
        '未使用部分
        Dim DiskSec As Currency
        Dim StartSec As Currency
        Dim UnusedSec As Currency
        Dim EndSec As Currency
        Dim bFlag As Boolean
       
        DiskSec = vSectorCount '物理扇区数
        If n = 0 Then
                bFlag = True
                StartSec = 0
                UnusedSec = DiskSec
        Else
                EndSec = ToInt64((PartData(n - 1).StartLBA)) + PartData(n - 1).SecCount
                If EndSec < DiskSec Then
                        bFlag = True
                        StartSec = EndSec
                        UnusedSec = DiskSec - EndSec
                End If
        End If
        If bFlag Then
                ReDim Preserve PartData(n)
                With PartData(n)
                        .StartLBA = StartSec
                        .SecCount = UnusedSec
                        .PartType = ptUnused
                End With
                n = n + 1
        End If
       
        If bGetDriveName Then
                'API获取分区参数,以获取分区字母  //仅供参考,物理方式获取分区不一定与逻辑驱动器对应
                '//若逻辑分区存在错误,比如根目录簇非.法,则可能会死锁  WinHex打开磁盘时也会死掉(遍历读取逻辑分区引起),只能用Diskgen修改物理磁盘数据
                Dim pdi()       As PARTITION_DEVICE_INFO
                Dim cntPI       As Long
                Dim j           As Long
                Dim Id          As Long
               
                Id = 0
                cntPI = EnumPartitionInfo(pdi)
                For i = 0 To cntPI - 1
                        If DiskId = pdi(i).DeviceId Then
                                Id = 0
                                For j = 0 To n - 1
                                        If PartData(j).PartType = ptMain Or PartData(j).PartType = ptLogic Then
                                                Id = Id + 1
                                                If Id = pdi(i).PartitionId Then
                                                        PartData(j).DriveName = pdi(i).DriveName '逻辑驱动器字母,隐藏分区同样占编号
                                                        Exit For
                                                End If
                                        End If
                                Next j
                        End If
                Next i
        End If
       
        Erase bytBuf
        m_GetPartition = n
        m_CloseDisk
End Function

'//获取子分区
Private Function GetSubPart(ByVal PLBA As Long, Idx As Long) As Long
        Dim i           As Long
        Dim bytBuf()    As Byte
        Dim ptd(3)      As PART_TABLE_DATA
        Dim n           As Long
        Dim cnt         As Long
        Dim nFlag       As Long
        
        ReDim bytBuf(BytesPerSector - 1)
        If m_ReadDisk(PLBA, bytBuf()) = 0 Then Exit Function
        CopyMemory ptd(0), bytBuf(PT_OFFSET), PT_SIZE * 4
        'Debug.Print bytBuf(0), bytBuf(1)
        n = 0
        For i = 0 To 1
                If ptd(i).StartLBA <> 0 Then
                        nFlag = 0
                        If IsExtPart(ptd(i).PartFlag) Then
                                cnt = GetSubPart(ExtPartLBA + ptd(i).StartLBA, Idx) '递归
                                If cnt = 0 Then nFlag = 1 '如果没有下级分区,保留扩展分区
                        Else
                                nFlag = 2 '是逻辑分区
                        End If
                        If nFlag <> 0 Then
                                ReDim Preserve PartData(Idx)
                                With PartData(Idx)
                                        .PartTable = ptd(i)     '分区表
                                        .PTSector = PLBA        '分区表所在扇区
                                        .PTIndex = i            '分区表索引
                                        .StartLBA = PLBA + ptd(i).StartLBA 'LBA绝对地址
                                        .SecCount = ptd(i).cntSectors
                                        If nFlag = 2 Then
                                                .PartType = ptLogic
                                                .FileSystem = GetFileSys(.StartLBA)
                                        Else
                                                .PartType = ptExtended
                                                .FileSystem = FILE_SYSTEM.Extended
                                        End If
                                End With
                                Idx = Idx + 1
                        End If
                        n = n + 1
                End If
        Next i
        GetSubPart = n
        Erase bytBuf
End Function

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值