一向的觀點就是“別在內核裡面處理字符串”!事實上,確實應該如此!
Linux內核的塊設備驅動有能力讀取磁盤的序列號,這個數據存儲在磁盤的控制芯片ROM裡面。內核應該以怎樣的形式將這個序列號呈現給調用者呢?我們ls一下這個目錄:
/dev/disk/by-id
ll /dev/disk/by-id/ ... lrwxrwxrwx 1 root root 9 10月 17 10:11 scsi-SATA_ST3500413AS_Z2A2AGQA -> ../../sdb 可以看到,一個磁盤可以用傳統的/dev/sdX來索引,也可以by-YY來索引,其中by-id就是以序列號來索引,上述輸出中,下劃線後面的就是序列號。同樣,我們可以用hdparm工具程序來讀取,可以讀到同樣的結果。在hdparm的代碼實現中,我們可以看到如下的代碼段:
static char *strip (char *s) { char *e; while (*s == ' ') ++s; if (*s) for (e = s + strlen(s); *--e == ' '; *e = '\0'); return s; } static void dump_identity (__u16 *idw) { int i; char pmodes[64] = {0,}, dmodes[128]={0,}, umodes[128]={0,}; char *model = strip(strndup((char *)&idw[27], 40)); char *fwrev = strip(strndup((char *)&idw[23], 8)); char *serno = strip(strndup((char *)&idw[10], 20)); __u8 tPIO; printf("\n Model=%.40s, FwRev=%.8s, SerialNo=%.20s", model, fwrev, serno); ... }
很明顯,在顯示序列號時,strip去掉了首尾的空格,因為空格顯示出來是沒有意義的。這十分正常。然而... 然而在2.6的老版本的內核比如2.6.8版本中,我們看到了do_identify中有下面的調用: ide_fixstring(id->serial_no, sizeof(id->serial_no), bswap); 那麼這個ide_fixstring是干什麼的呢?它的實現如下,詳細的注釋已經給出了答案:
void ide_fixstring (u8 *s, const int bytecount, const int byteswap) { u8 *p = s, *end = &s[bytecount & ~1]; /* bytecount must be even */ if (byteswap) { /* convert from big-endian to host byte order */ for (p = end ; p != s;) { unsigned short *pp = (unsigned short *) (p -= 2); *pp = ntohs(*pp); } } /* strip leading blanks */ while (s != end && *s == ' ') ++s; /* compress internal blanks and strip trailing blanks */ while (s != end && *s) { if (*s++ != ' ' || (s != end && *s && *s != ' ')) *p++ = *(s-1); } /* wipe out trailing garbage */ while (p != end) *p++ = '\0'; }
幾乎是hdparm的strip更加嚴格意義上的翻版!這有什麼問題呢?問題大了。這個內核沒有辦法給用戶呈現一個原始的磁盤序列號,也就是序列號本身。為何不把處理留給應用程序呢?
Linux內核應該迅速返回最原始的二進制信息,將解析任務留給應用程序,不光是效率考慮,更多的是內核根本不知道如何去解讀這些信息!幸運的是,高版本的內核不再處理磁盤序列號了,僅僅返回了原始信息,不幸的是,它帶來了問題! 要不是工作中遇到了問題,我也不會閒到去折騰什麼塊設備驅動。系統僅僅升級了內核,然而升級前後都需要讀取磁盤序列號和保存的序列號比對,老版本的內核和新版本內核對磁盤序列號讀取行為的不同導致出現了不同的結果,內核也就不再對應用程序透明了!那怎麼辦?只好修改高版本內核驅動去迎合老版本的錯誤方式了! 兼容就是一大萬人坑,埋葬了不知多少精英!