q 用ioctl_disk_get_drive_geometry或ioctl_storage_get_media_types_ex隻能得到很少的磁盤參數,我想獲得包括硬碟序列号在内的更加詳細的資訊,有什麼辦法呀?
a 确實,用你所說的i/o控制碼,隻能得到最基本的磁盤參數。擷取磁盤出廠資訊的i/o控制碼,微軟在vc/mfc環境中沒有開放,在ddk中可以發現一些線索。早先,lynn mcguire寫了一個很出名的擷取ide硬碟詳細資訊的程式diskid32,下面的例子是在其基礎上經過增删和改進而成的。
本例中,我們要用到ata/apapi的identify device指令。ata/apapi是國際組織t13起草和釋出的ide/eide/udma硬碟及其它可移動儲存設備與主機接口的标準,至今已經到了ata/apapi-7版本。該接口标準規定了ata/atapi裝置的輸入輸出寄存器和指令集。欲了解更詳細的ata/atapi技術資料,可通路t13的站點。
用到的常量及資料結構有以下一些:
// ioctl控制碼
// #define dfp_send_drive_command 0x0007c084
#define dfp_send_drive_command ctl_code(ioctl_disk_base, 0x0021, method_buffered, file_read_access | file_write_access)
// #define dfp_receive_drive_data 0x0007c088
#define dfp_receive_drive_data ctl_code(ioctl_disk_base, 0x0022, method_buffered, file_read_access | file_write_access)
#define file_device_scsi 0x0000001b
#define ioctl_scsi_miniport_identify ((file_device_scsi << 16) + 0x0501)
#define ioctl_scsi_miniport 0x0004d008 // see ntddscsi.h for definition
// ata/atapi指令
#define ide_ata_identify 0xec // ata的id指令(identify device)
// ide指令寄存器
typedef struct _ideregs
{
byte bfeaturesreg; // 特征寄存器(用于smart指令)
byte bsectorcountreg; // 扇區數目寄存器
byte bsectornumberreg; // 開始扇區寄存器
byte bcyllowreg; // 開始柱面低位元組寄存器
byte bcylhighreg; // 開始柱面高位元組寄存器
byte bdriveheadreg; // 驅動器/磁頭寄存器
byte bcommandreg; // 指令寄存器
byte breserved; // 保留
} ideregs, *pideregs, *lpideregs;
// 從驅動程式傳回的狀态
typedef struct _driverstatus
byte bdrivererror; // 錯誤碼
byte bidestatus; // ide狀态寄存器
byte breserved[2]; // 保留
dword dwreserved[2]; // 保留
} driverstatus, *pdriverstatus, *lpdriverstatus;
// ide裝置ioctl輸入資料結構
typedef struct _sendcmdinparams
dword cbuffersize; // 緩沖區位元組數
ideregs irdriveregs; // ide寄存器組
byte bdrivenumber; // 驅動器号
byte breserved[3]; // 保留
dword dwreserved[4]; // 保留
byte bbuffer[1]; // 輸入緩沖區(此處象征性地包含1位元組)
} sendcmdinparams, *psendcmdinparams, *lpsendcmdinparams;
// ide裝置ioctl輸出資料結構
typedef struct _sendcmdoutparams
dword cbuffersize; // 緩沖區位元組數
driverstatus driverstatus; // 驅動程式傳回狀态
byte bbuffer[1]; // 輸入緩沖區(此處象征性地包含1位元組)
} sendcmdoutparams, *psendcmdoutparams, *lpsendcmdoutparams;
// ide的id指令傳回的資料
// 共512位元組(256個word),這裡僅定義了一些感興趣的項(基本上依據ata/atapi-4)
typedef struct _idinfo
ushort wgenconfig; // word 0: 基本資訊字
ushort wnumcyls; // word 1: 柱面數
ushort wreserved2; // word 2: 保留
ushort wnumheads; // word 3: 磁頭數
ushort wreserved4; // word 4: 保留
ushort wreserved5; // word 5: 保留
ushort wnumsectorspertrack; // word 6: 每磁道扇區數
ushort wvendorunique[3]; // word 7-9: 廠家設定值
char sserialnumber[20]; // word 10-19:序列号
ushort wbuffertype; // word 20: 緩沖類型
ushort wbuffersize; // word 21: 緩沖大小
ushort weccsize; // word 22: ecc校驗大小
char sfirmwarerev[8]; // word 23-26: 固件版本
char smodelnumber[40]; // word 27-46: 内部型号
ushort wmorevendorunique; // word 47: 廠家設定值
ushort wreserved48; // word 48: 保留
struct {
ushort reserved1:8;
ushort dma:1; // 1=支援dma
ushort lba:1; // 1=支援lba
ushort disiordy:1; // 1=可不使用iordy
ushort iordy:1; // 1=支援iordy
ushort softreset:1; // 1=需要ata軟啟動
ushort overlap:1; // 1=支援重疊操作
ushort queue:1; // 1=支援指令隊列
ushort inldma:1; // 1=支援交叉存取dma
} wcapabilities; // word 49: 一般能力
ushort wreserved1; // word 50: 保留
ushort wpiotiming; // word 51: pio時序
ushort wdmatiming; // word 52: dma時序
ushort chsnumber:1; // 1=word 54-58有效
ushort cyclenumber:1; // 1=word 64-70有效
ushort unltradma:1; // 1=word 88有效
ushort reserved:13;
} wfieldvalidity; // word 53: 後續字段有效性标志
ushort wnumcurcyls; // word 54: chs可尋址的柱面數
ushort wnumcurheads; // word 55: chs可尋址的磁頭數
ushort wnumcursectorspertrack; // word 56: chs可尋址每磁道扇區數
ushort wcursectorslow; // word 57: chs可尋址的扇區數低位字
ushort wcursectorshigh; // word 58: chs可尋址的扇區數高位字
ushort curnumber:8; // 目前一次性可讀寫扇區數
ushort multi:1; // 1=已選擇多扇區讀寫
ushort reserved1:7;
} wmultsectorstuff; // word 59: 多扇區讀寫設定
ulong dwtotalsectors; // word 60-61: lba可尋址的扇區數
ushort wsingleworddma; // word 62: 單位元組dma支援能力
ushort mode0:1; // 1=支援模式0 (4.17mb/s)
ushort mode1:1; // 1=支援模式1 (13.3mb/s)
ushort mode2:1; // 1=支援模式2 (16.7mb/s)
ushort reserved1:5;
ushort mode0sel:1; // 1=已選擇模式0
ushort mode1sel:1; // 1=已選擇模式1
ushort mode2sel:1; // 1=已選擇模式2
ushort reserved2:5;
} wmultiworddma; // word 63: 多位元組dma支援能力
ushort advpoimodes:8; // 支援進階poi模式數
ushort reserved:8;
} wpiocapacity; // word 64: 進階pio支援能力
ushort wminmultiworddmacycle; // word 65: 多位元組dma傳輸周期的最小值
ushort wrecmultiworddmacycle; // word 66: 多位元組dma傳輸周期的建議值
ushort wminpionoflowcycle; // word 67: 無流控制時pio傳輸周期的最小值
ushort wminpoiflowcycle; // word 68: 有流控制時pio傳輸周期的最小值
ushort wreserved69[11]; // word 69-79: 保留
ushort reserved1:1;
ushort ata1:1; // 1=支援ata-1
ushort ata2:1; // 1=支援ata-2
ushort ata3:1; // 1=支援ata-3
ushort ata4:1; // 1=支援ata/atapi-4
ushort ata5:1; // 1=支援ata/atapi-5
ushort ata6:1; // 1=支援ata/atapi-6
ushort ata7:1; // 1=支援ata/atapi-7
ushort ata8:1; // 1=支援ata/atapi-8
ushort ata9:1; // 1=支援ata/atapi-9
ushort ata10:1; // 1=支援ata/atapi-10
ushort ata11:1; // 1=支援ata/atapi-11
ushort ata12:1; // 1=支援ata/atapi-12
ushort ata13:1; // 1=支援ata/atapi-13
ushort ata14:1; // 1=支援ata/atapi-14
ushort reserved2:1;
} wmajorversion; // word 80: 主版本
ushort wminorversion; // word 81: 副版本
ushort wreserved82[6]; // word 82-87: 保留
ushort mode0:1; // 1=支援模式0 (16.7mb/s)
ushort mode1:1; // 1=支援模式1 (25mb/s)
ushort mode2:1; // 1=支援模式2 (33mb/s)
ushort mode3:1; // 1=支援模式3 (44mb/s)
ushort mode4:1; // 1=支援模式4 (66mb/s)
ushort mode5:1; // 1=支援模式5 (100mb/s)
ushort mode6:1; // 1=支援模式6 (133mb/s)
ushort mode7:1; // 1=支援模式7 (166mb/s) ???
ushort mode3sel:1; // 1=已選擇模式3
ushort mode4sel:1; // 1=已選擇模式4
ushort mode5sel:1; // 1=已選擇模式5
ushort mode6sel:1; // 1=已選擇模式6
ushort mode7sel:1; // 1=已選擇模式7
} wultradma; // word 88: ultra dma支援能力
ushort wreserved89[167]; // word 89-255
} idinfo, *pidinfo;
// scsi驅動所需的輸入輸出共用的結構
typedef struct _srb_io_control
ulong headerlength; // 頭長度
uchar signature[8]; // 特征名稱
ulong timeout; // 逾時時間
ulong controlcode; // 控制碼
ulong returncode; // 傳回碼
ulong length; // 緩沖區長度
} srb_io_control, *psrb_io_control;
需要引起注意的是idinfo第57-58 word (chs可尋址的扇區數),因為不滿足32位對齊的要求,不可定義為一個ulong字段。lynn mcguire的程式裡正是由于定義為一個ulong字段,導緻該結構不可用。
以下是核心代碼:
// 打開裝置
// filename: 裝置的“檔案名”(裝置路徑)
handle opendevice(lpctstr filename)
handle hdevice;
// 打開裝置
hdevice = ::createfile(filename, // 檔案名
generic_read | generic_write, // 讀寫方式
file_share_read | file_share_write, // 共享方式
null, // 預設的安全描述符
open_existing, // 建立方式
0, // 不需設定檔案屬性
null); // 不需參照模闆檔案
return hdevice;
}
// 向驅動發“identify device”指令,獲得裝置資訊
// hdevice: 裝置句柄
// pidinfo: 裝置資訊結構指針
bool identifydevice(handle hdevice, pidinfo pidinfo)
psendcmdinparams pscip; // 輸入資料結構指針
psendcmdoutparams pscop; // 輸出資料結構指針
dword dwoutbytes; // ioctl輸出資料長度
bool bresult; // ioctl傳回值
// 申請輸入/輸出資料結構空間
pscip = (psendcmdinparams)::globalalloc(lmem_zeroinit, sizeof(sendcmdinparams) - 1);
pscop = (psendcmdoutparams)::globalalloc(lmem_zeroinit, sizeof(sendcmdoutparams) + sizeof(idinfo) - 1);
// 指定ata/atapi指令的寄存器值
// pscip->irdriveregs.bfeaturesreg = 0;
// pscip->irdriveregs.bsectorcountreg = 0;
// pscip->irdriveregs.bsectornumberreg = 0;
// pscip->irdriveregs.bcyllowreg = 0;
// pscip->irdriveregs.bcylhighreg = 0;
// pscip->irdriveregs.bdriveheadreg = 0;
pscip->irdriveregs.bcommandreg = ide_ata_identify;
// 指定輸入/輸出資料緩沖區大小
pscip->cbuffersize = 0;
pscop->cbuffersize = sizeof(idinfo);
// identify device
bresult = ::deviceiocontrol(hdevice, // 裝置句柄
dfp_receive_drive_data, // 指定ioctl
pscip, sizeof(sendcmdinparams) - 1, // 輸入資料緩沖區
pscop, sizeof(sendcmdoutparams) + sizeof(idinfo) - 1, // 輸出資料緩沖區
&dwoutbytes, // 輸出資料長度
(lpoverlapped)null); // 用同步i/o
// 複制裝置參數結構
::memcpy(pidinfo, pscop->bbuffer, sizeof(idinfo));
// 釋放輸入/輸出資料空間
::globalfree(pscop);
::globalfree(pscip);
return bresult;
// 向scsi mini-port驅動發“identify device”指令,獲得裝置資訊
bool identifydeviceasscsi(handle hdevice, int ndrive, pidinfo pidinfo)
psendcmdinparams pscip; // 輸入資料結構指針
psendcmdoutparams pscop; // 輸出資料結構指針
psrb_io_control psrbio; // scsi輸入輸出資料結構指針
dword dwoutbytes; // ioctl輸出資料長度
bool bresult; // ioctl傳回值
psrbio = (psrb_io_control)::globalalloc(lmem_zeroinit,
sizeof(srb_io_control) + sizeof(sendcmdoutparams) + sizeof(idinfo) - 1);
pscip = (psendcmdinparams)((char *)psrbio + sizeof(srb_io_control));
pscop = (psendcmdoutparams)((char *)psrbio + sizeof(srb_io_control));
// 填充輸入/輸出資料
psrbio->headerlength = sizeof(srb_io_control);
psrbio->timeout = 10000;
psrbio->length = sizeof(sendcmdoutparams) + sizeof(idinfo) - 1;
psrbio->controlcode = ioctl_scsi_miniport_identify;
::strncpy ((char *)psrbio->signature, "scsidisk", 8);
pscip->bdrivenumber = ndrive;
bresult = ::deviceiocontrol(hdevice, // 裝置句柄
ioctl_scsi_miniport, // 指定ioctl
psrbio, sizeof(srb_io_control) + sizeof(sendcmdinparams) - 1, // 輸入資料緩沖區
psrbio, sizeof(srb_io_control) + sizeof(sendcmdoutparams) + sizeof(idinfo) - 1, // 輸出資料緩沖區
&dwoutbytes, // 輸出資料長度
(lpoverlapped)null); // 用同步i/o
::globalfree(psrbio);
// 将串中的字元兩兩颠倒
// 原因是ata/atapi中的word,與windows采用的位元組順序相反
// 驅動程式中已經将收到的資料全部反過來,我們來個負負得正
void adjuststring(char* str, int len)
char ch;
int i;
// 兩兩颠倒
for (i = 0; i < len; i += 2)
{
ch = str[i];
str[i] = str[i + 1];
str[i + 1] = ch;
}
// 若是右對齊的,調整為左對齊 (去掉左邊的空格)
i = 0;
while ((i < len) && (str[i] == ' ')) i++;
::memmove(str, &str[i], len - i);
// 去掉右邊的空格
i = len - 1;
while ((i >= 0) && (str[i] == ' '))
str[i] = '\0';
i--;
// 讀取ide硬碟的裝置資訊,必須有足夠權限
// ndrive: 驅動器号(0=第一個硬碟,1=0=第二個硬碟,......)
// pidinfo: 裝置資訊結構指針
bool getphysicaldriveinfoinnt(int ndrive, pidinfo pidinfo)
handle hdevice; // 裝置句柄
bool bresult; // 傳回結果
char szfilename[20]; // 檔案名
hdevice = ::opendevice(szfilename);
if (hdevice == invalid_handle_value)
return false;
bresult = ::identifydevice(hdevice, pidinfo);
if (bresult)
// 調整字元串
::adjuststring(pidinfo->sserialnumber, 20);
::adjuststring(pidinfo->smodelnumber, 40);
::adjuststring(pidinfo->sfirmwarerev, 8);
::closehandle (hdevice);
// 用scsi驅動讀取ide硬碟的裝置資訊,不受權限制約
// ndrive: 驅動器号(0=primary master, 1=promary slave, 2=secondary master, 3=secondary slave)
bool getidedriveasscsiinfoinnt(int ndrive, pidinfo pidinfo)
bresult = ::identifydeviceasscsi(hdevice, ndrive%2, pidinfo);
// 檢查是不是空串
if (pidinfo->smodelnumber[0] == '\0')
bresult = false;
q 我注意到ata/atapi裡,以及diskid32裡,有一個“identify packet device”指令,與“identify device”有什麼差別?
a identify device專門用于固定硬碟,而identify packet device用于可移動儲存設備如cdrom、cf、mo、zip、tape等。因為驅動程式的原因,實際上用本例的方法,不管是identify device也好,identify packet device也好,擷取可移動儲存設備的詳細資訊,一般是做不到的。而且除了ide硬碟,對scsi、usb等接口的硬碟也不起作用。除非廠商提供的驅動支援這樣的功能。
q ata/atapi有很多指令,如read sectors, write sectors, security, sleep, standby等,利用上述方法,是否可進行相應操作?
a 應該沒問題。但切記,要慎重慎重再慎重!
q 關于權限問題,請解釋一下好嗎?
a 在nt/2000/xp下,administrator可以管理裝置,上述兩種通路驅動的方法都行。但在user身份下,或者登入到域後,使用者無法通路physicaldrive驅動的核心層,但scsi mini-port驅動卻可以。目前是可以,不知道windows以後的版本是否支援。因為這肯定是一個安全隐患。
另外,我們着重讨論nt/2000/xp中deviceiocontrol的應用,如果需要在98/me中得到包括硬碟序列号在内的更加詳細的資訊,請參考diskid32。