Linux模塊
一、為什么要使用模塊
? ? ? 由于linux使用的是整體結(jié)構(gòu),不是模塊化的結(jié)構(gòu),整體結(jié)構(gòu)實(shí)現(xiàn)的操作系統(tǒng)可擴(kuò)展性差。linux為了擴(kuò)展系統(tǒng),使用了模塊的技術(shù),模塊能夠從系統(tǒng)中動(dòng)態(tài)裝入和卸載,這樣使得linux也具有很好的可擴(kuò)展性。
?
二、linux中哪些代碼作為模塊實(shí)現(xiàn),哪些直接編譯進(jìn)內(nèi)核?
? ? ? 當(dāng)然我們是盡量把代碼編譯成模塊,這樣就可以根據(jù)需要進(jìn)行鏈接,內(nèi)核的代碼量也會少很多。幾乎所有的高層組件—文件系統(tǒng)、設(shè)備驅(qū)動(dòng)程序、可執(zhí)行格式、網(wǎng)絡(luò)層等等—都可以作為模塊進(jìn)行編譯。
? ? ? 然而有些代碼確必須直接編譯進(jìn)內(nèi)核。這些代碼通常是對數(shù)據(jù)結(jié)構(gòu)或者函數(shù)進(jìn)行修改。如內(nèi)核中已經(jīng)定義好了的數(shù)據(jù)結(jié)構(gòu),如果要改變這個(gè)數(shù)據(jù)結(jié)構(gòu),那么只有從新編譯內(nèi)核了。
?
三、管理模塊
? ? ?內(nèi)核主要完成管理模塊的兩個(gè)任務(wù)。第一個(gè)任務(wù)是確保內(nèi)核的其它部分可以訪問該模塊的全局符號,模塊還必須知道全局符號在內(nèi)核及其它模塊中的地址。因此,在鏈接模塊時(shí),一定要解決模塊間的引用關(guān)系。第二個(gè)任務(wù)是記錄模塊的使用情況,以便再其它模塊或者內(nèi)核的其它部分正在使用這個(gè)模塊時(shí),不能卸載這個(gè)模塊。
?
四、模塊使用的數(shù)據(jù)結(jié)構(gòu)
? ? ? 每個(gè)模塊都用一個(gè)module描述符描述,并且鏈接到一個(gè)以modules變量為鏈表頭的雙向循環(huán)鏈表中。
module描述符:
struct module { enum module_state state; // 模塊內(nèi)部狀態(tài) struct list_head list; // 用于鏈接到鏈表中 char name[MODULE_NAME_LEN]; // 模塊名字 struct module_kobject mkobj; // 用于Sysfs的kobject struct module_param_attrs *param_attrs; // 指向模塊參數(shù)描述符 const struct kernel_symbol *syms; // 指向?qū)С龇枖?shù)組的指針 unsigned int num_syms; // 導(dǎo)出符號數(shù) const unsigned long *crcs; // 指向?qū)С龇朇RC值數(shù)組指針 const struct kernel_symbol *gpl_syms; // GPL格式導(dǎo)出符號 unsigned int num_gpl_syms; const unsigned long * gpl_crcs; unsigned int num_exentries; // 模塊異常表項(xiàng)數(shù) const struct exception_table_entry *extable; // 指向模塊異常表的指針 int (*init)( void ); // 模塊初始化方法 void *module_init; // 用于模塊初始化的動(dòng)態(tài)內(nèi)存區(qū)指針 void *module_core; // 用于模塊核心函數(shù)與數(shù)據(jù)結(jié)構(gòu)的動(dòng)態(tài)內(nèi)存區(qū)指針 unsigned long init_size, core_size; // 模塊初始化動(dòng)態(tài)內(nèi)存區(qū)大小,模塊核心函數(shù)與數(shù)據(jù)結(jié)構(gòu)的動(dòng)態(tài)內(nèi)存區(qū)大小 unsigned long init_text_size, core_text_size; // 模塊初始化的可執(zhí)行代碼大小,模塊核心可執(zhí)行代碼的大小,只在連接模塊時(shí)使用 struct mod_arch_specific arch; int unsafe ; int license_gplok; #ifdef CONFIG_MODULE_UNLOAD struct module_ref ref [NR_CPUS]; // 每cpu使用計(jì)數(shù)器變量 /* What modules depend on me? */ struct list_head modules_which_use_me; // 依賴于該模塊的模塊鏈表 struct task_struct *waiter; // 正在等待模塊被卸載的進(jìn)程,即卸載模塊的進(jìn)程 void (*exit)( void ); // 模塊退出的方法 #endif #ifdef CONFIG_KALLSYMS Elf_Sym *symtab; // proc/kallsysms文件中所列模塊ELF符號數(shù)組指針 unsigned long num_symtab; char *strtab; // proc/kallsysms文件中所列模塊ELF符號的字符串表 struct module_sect_attrs *sect_attrs; // 模塊分節(jié)屬性描述符數(shù)組指針 #endif void * percpu; char *args; // 模塊連接時(shí)使用的命令行參數(shù) };
? ? ? module數(shù)據(jù)結(jié)構(gòu)主要描述了模塊導(dǎo)出符號,模塊使用的動(dòng)態(tài)內(nèi)存,模塊的加載和釋放函數(shù),模塊的引用等。
? ? ? 當(dāng)裝載一個(gè)模塊到內(nèi)核中時(shí),必須用合適的地址替換在模塊對象代碼中引用的所有全局內(nèi)核符號。這主要由insmod程序來完成。內(nèi)核使用一些專門的內(nèi)核符號表,用于保存模塊訪問的符號和相應(yīng)的地址。它們在內(nèi)核代碼分三節(jié):__kstrtab節(jié)(保存符號名)、__ksymtab節(jié)(所有模塊可使用的符號地址)和__ksymtab_gpl節(jié)(GPL兼容許可證下發(fā)布的模塊可以使用的符號地址)。
? ? ? 已經(jīng)裝載到內(nèi)核中的模塊也可以導(dǎo)出自己的符號,這樣其它模塊就可以訪問這些符號。模塊符號部分表保存在模塊代碼段__ksymtab、__ksymtab_gpl和__kstrtab部分中。可以使用宏EXPOPT_SYMBOL和EXPORT_SYMPOL_GPL來導(dǎo)出符號。當(dāng)模塊裝載進(jìn)內(nèi)核時(shí),模塊的導(dǎo)出符號被拷貝到兩個(gè)內(nèi)存數(shù)組中,而數(shù)組的地址保存在module描述符的syms和gpl_syms字段中。
? ? ? 一個(gè)模塊可以引用另一個(gè)模塊所導(dǎo)出的符號。module描述符中有個(gè)字段modules_which_use_me,它是一個(gè)依賴鏈表的頭部,該鏈表保存了使用該模塊的所有其他模塊。鏈表中每個(gè)元素都是一個(gè)module_use描述符,該描述符保存指向鏈表中相鄰元素的指針以及一個(gè)指向相應(yīng)模塊對象的指針。只有依賴鏈表不為空,就不能卸載該模塊。
?
五、模塊的裝載
? ? ? 模塊的裝載主要通過sys_init_module服務(wù)例程來實(shí)現(xiàn)的,是由insmod外部程序通過系統(tǒng)調(diào)用來調(diào)用該函數(shù)。下面我們來分析sys_init_module函數(shù):
asmlinkage long sys_init_module( void __user * umod, unsigned long len, const char __user * uargs) { struct module * mod; int ret = 0 ; … mod = load_module(umod, len, uargs); … if (mod->init != NULL) ret = mod->init(); // 調(diào)用模塊初始化函數(shù)初始化模塊 … mod ->state = MODULE_STATE_LIVE; module_free(mod, mod ->module_init); // 釋放初始化使用的內(nèi)存 mod->module_init = NULL; mod ->init_size = 0 ; mod ->init_text_size = 0 ; … }
? ? ? 這個(gè)函數(shù)主要是調(diào)用load_module函數(shù)加載模塊代碼到內(nèi)存中,并初始化該模塊對象mod;調(diào)用初始化模塊函數(shù)初始化模塊,釋放模塊中的初始化代碼動(dòng)態(tài)內(nèi)存空間。其中傳遞的參數(shù)umod是insmod程序在用戶態(tài)時(shí)將模塊文件拷貝到內(nèi)存中的起始地址,len是模塊文件的大小,uargs是調(diào)用命令insmod時(shí)的命令行參數(shù)。
? ? ? 加載模塊的工作其實(shí)主要還是由函數(shù)load_module來完成,這個(gè)函數(shù)完成了將模塊文件從用戶空間加載到臨時(shí)內(nèi)核空間,對模塊文件進(jìn)行合法性檢查,并抽取出模塊文件中的核心函數(shù)和數(shù)據(jù)結(jié)構(gòu)到內(nèi)核的另一個(gè)動(dòng)態(tài)內(nèi)存區(qū),并重定位模塊中的符號,初始化module對象,將mod對象加入到sysfs文件系統(tǒng)中。
static struct module *load_module( void __user * umod, unsigned long len, const char __user * uargs) { Elf_Ehdr * hdr; Elf_Shdr * sechdrs; char *secstrings, *args, *modmagic, *strtab = NULL; unsigned int i, symindex = 0 , strindex = 0 , setupindex, exindex, exportindex, modindex, obsparmindex, infoindex, gplindex, crcindex, gplcrcindex, versindex, pcpuindex; long arglen; struct module * mod; long err = 0 ; void *percpu = NULL, *ptr = NULL; /* Stops spurious gcc warning */ struct exception_table_entry * extable; … if (len > 64 * 1024 * 1024 || (hdr = vmalloc(len)) == NULL) // 超過64MB,或者分配內(nèi)存失敗,否則分配一個(gè)臨時(shí)的內(nèi)核空間來存放內(nèi)核模塊 return ERR_PTR(- ENOMEM); if (copy_from_user(hdr, umod, len) != 0 ) { // 用空間將模塊目標(biāo)代碼拷貝到內(nèi)核 err = - EFAULT; goto free_hdr; } … // 省略的代碼為檢查模塊的合法性 sechdrs = ( void *)hdr + hdr->e_shoff; // 節(jié)的頭表 secstrings = ( void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset; // 節(jié)點(diǎn)頭字符串表 sechdrs[ 0 ].sh_addr = 0 ; for (i = 1 ; i < hdr->e_shnum; i++ ) { if (sechdrs[i].sh_type != SHT_NOBITS // SHT_NOBITS表示該節(jié)點(diǎn)在文件中無內(nèi)容 && len < sechdrs[i].sh_offset + sechdrs[i].sh_size) goto truncated; /* Mark all sections sh_addr with their address in the temporary image. */ sechdrs[i].sh_addr = (size_t)hdr + sechdrs[i].sh_offset; // 把每個(gè)節(jié)點(diǎn)的地址設(shè)置為在內(nèi)存中對應(yīng)的地址 if (sechdrs[i].sh_type == SHT_SYMTAB) { // 節(jié)點(diǎn)為符號表 symindex = i; strindex = sechdrs[i].sh_link; // 字符串表在節(jié)點(diǎn)頭表中的索引 strtab = ( char *)hdr + sechdrs[strindex].sh_offset; // 字符串表 } #ifndef CONFIG_MODULE_UNLOAD // 沒有定義模塊卸載 /* Don't load .exit sections */ // 不將.exit節(jié)加載到內(nèi)存 if (strncmp(secstrings+sechdrs[i].sh_name, " .exit " , 5 ) == 0 ) sechdrs[i].sh_flags &= ~(unsigned long )SHF_ALLOC; #endif } modindex = find_sec(hdr, sechdrs, secstrings, " .gnu.linkonce.this_module " ); // .gnu.linkonce.this_module在節(jié)點(diǎn)頭表中的索引 … mod = ( void * )sechdrs[modindex].sh_addr; … // 省略代碼處理參數(shù)和處理每cpu變量 mod->state = MODULE_STATE_COMING; layout_sections(mod, hdr, sechdrs, secstrings); // 節(jié)的從新布局,合并所有帶有SHF_ALLOC標(biāo)記的節(jié),并計(jì)算每個(gè)節(jié)的大小和偏移量,包括計(jì)算初始化代碼和核心代碼的空間大小 ptr = module_alloc(mod->core_size); // 為模塊代碼分配動(dòng)態(tài)內(nèi)存 … memset(ptr, 0 , mod-> core_size); mod ->module_core = ptr; ptr = module_alloc(mod->init_size); // 為模塊初始化代碼分配動(dòng)態(tài)內(nèi)存 … memset(ptr, 0 , mod-> init_size); mod ->module_init = ptr; … for (i = 0 ; i < hdr->e_shnum; i++) { // 將臨時(shí)內(nèi)核模塊的數(shù)據(jù)拷貝到新的動(dòng)態(tài)內(nèi)存中 void * dest; if (!(sechdrs[i].sh_flags & SHF_ALLOC)) continue ; if (sechdrs[i].sh_entsize & INIT_OFFSET_MASK) dest = mod-> module_init + (sechdrs[i].sh_entsize & ~ INIT_OFFSET_MASK); else dest = mod->module_core + sechdrs[i].sh_entsize; if (sechdrs[i].sh_type != SHT_NOBITS) memcpy(dest, ( void * )sechdrs[i].sh_addr, sechdrs[i].sh_size); sechdrs[i].sh_addr = (unsigned long )dest; // 更新節(jié)在內(nèi)存中的地址 DEBUGP( " \t0x%lx %s\n " , sechdrs[i].sh_addr, secstrings + sechdrs[i].sh_name); } mod = ( void *)sechdrs[modindex].sh_addr; // mod指向新內(nèi)存 module_unload_init(mod); // 初始化mod的卸載字段 // 修正符號表中的地址值 err = simplify_symbols(sechdrs, symindex, strtab, versindex, pcpuindex, mod); … for (i = 1 ; i < hdr->e_shnum; i++) { // 重定位各個(gè)節(jié)中的符號 const char *strtab = ( char * )sechdrs[strindex].sh_addr; unsigned int info = sechdrs[i].sh_info; if (info >= hdr-> e_shnum) continue ; if (!(sechdrs[info].sh_flags & SHF_ALLOC)) continue ; if (sechdrs[i].sh_type == SHT_REL) // 當(dāng)前節(jié)是重定位節(jié) err = apply_relocate(sechdrs, strtab, symindex, i,mod); if (err < 0 ) goto cleanup; } … vfree(hdr); // 釋放臨時(shí)分配的內(nèi)核空間 … }
? ? ? 代碼中的simplify_symbols主要就是查找內(nèi)核符號表,將模塊符號表中未決的符號修改為內(nèi)核符號表中對應(yīng)的符號的值,即符號對應(yīng)的線性地址。apply_relocate函數(shù)主要就是通過模塊中重定位節(jié)的信息將模塊中需要重定位的符號地址重新定位。
?
六、模塊的卸載
? ? ? 模塊卸載主要完成對模塊是否可以卸載,先是檢查用戶是否有這個(gè)權(quán)限,如果沒有權(quán)限是不能卸載模塊的。如果有其它模塊在引用該模塊,也不能卸載該模塊,根據(jù)用戶給的模塊名到模塊鏈表中查找模塊,如果引用模塊的計(jì)數(shù)不為0,則阻塞當(dāng)前進(jìn)程,否則將模塊從modules鏈中刪除;如果模塊自定義了exit函數(shù),則執(zhí)行該函數(shù),將模塊從文件系統(tǒng)sysfs注銷,釋放模塊占用的內(nèi)存區(qū)。
asmlinkage long sys_delete_module( const char __user *name_user, unsigned int flags) { struct module * mod; char name[MODULE_NAME_LEN]; int ret, forced = 0 ; if (! capable(CAP_SYS_MODULE)) return - EPERM; if (strncpy_from_user(name, name_user, MODULE_NAME_LEN- 1 ) < 0 ) return - EFAULT; name[MODULE_NAME_LEN - 1 ] = ' \0 ' ; mod = find_module(name); // 查找模塊 if (!list_empty(&mod->modules_which_use_me)) { // 查看是否有其它模塊使用當(dāng)前模塊 ret = - EWOULDBLOCK; goto out ; } if (mod->state != MODULE_STATE_LIVE) { // 判斷模塊是否是正常運(yùn)行的 /* FIXME: if (force), slam module count and wake up waiter --RR */ DEBUGP( " %s already dying\n " , mod-> name); ret = - EBUSY; goto out ; } if ((mod->init != NULL && mod->exit == NULL) // 如果模塊有init卻沒有exit,則不能卸載模塊 || mod-> unsafe ) { forced = try_force(flags); if (! forced) { ret = - EBUSY; goto out ; } } mod ->waiter = current; // 卸載該模塊的進(jìn)程 ret = try_stop_module(mod, flags, & forced); if (!forced && module_refcount(mod) != 0 ) // 等待模塊引用計(jì)數(shù)為0 wait_for_zero_refcount(mod); if (mod->exit != NULL) { // 調(diào)用該模塊定義的exit函數(shù) up(& module_mutex); mod -> exit(); down( & module_mutex); } free_module(mod); // 將模塊從sysfs注銷和釋放模塊占用的內(nèi)存 … }
在Linux下和Windows下遍歷目錄的方法及如何達(dá)成一致性操作
最近因?yàn)闇y試目的需要遍歷一個(gè)目錄下面的所有文件進(jìn)行操作,主要是讀每個(gè)文件的內(nèi)容,只要知道文件名就OK了。在Java中直接用File類就可以搞定,因?yàn)镴ava中使用了組合模式,使得客戶端對單個(gè)文件和文件夾的使用具有一致性,非常方便。但在C中就不一樣了,而且在不同的平臺下使用方法也不同。在Linux下實(shí)現(xiàn)該功能就非常方便,因?yàn)樽詭в蠥PI庫,幾個(gè)函數(shù)用起來得心應(yīng)手(雖然有些小問題,后面說),在Windows下實(shí)現(xiàn)就不是那么方便,雖然也有自己的API,但用法有些晦澀難懂,因?yàn)闆]有封裝起來,需要自己一步一步進(jìn)行操作,因?yàn)橛玫氖荳indows API庫函數(shù)所以如果對Windows編程不熟悉的話,照搬網(wǎng)上的代碼錯(cuò)了也不易調(diào)試。為此,我把這些操作都封裝成類似Linux下的庫函數(shù),一方面簡化透明了操作,另一方面(也許更重要)就是移植性,這樣將包含該功能的程序從Windows上移植到Linux下就無需改動(dòng)代碼了(刪掉實(shí)現(xiàn)封裝的文件,因?yàn)長inux下自帶了),當(dāng)然從Linux下移植到Windows下同樣方便(增加實(shí)現(xiàn)封裝的文件即可),這就是所謂的OCP原則吧(開放封閉原則,具體見: 程序員該有的藝術(shù)氣質(zhì)—SOLID原則 )。好了,首先看下Linux下是如何實(shí)現(xiàn)這個(gè)功能的。
一、Linux下遍歷目錄的方法
?Linux下實(shí)現(xiàn)目錄操作的API函數(shù)都在頭文件dirent.h中,截取部分該文件內(nèi)容如下:
/* * structure describing an open directory. */ typedef struct _dirdesc { int dd_fd; /* * file descriptor associated with directory */ long dd_loc; /* * offset in current buffer */ long dd_size; /* * amount of data returned by getdirentries */ char *dd_buf; /* * data buffer */ int dd_len; /* * size of data buffer */ long dd_seek; /* * magic cookie returned by getdirentries */ long dd_rewind; /* * magic cookie for rewinding */ int dd_flags; /* * flags for readdir */ struct pthread_mutex *dd_lock; /* * lock */ struct _telldir *dd_td; /* * telldir position recording */ } DIR; typedef void * DIR; DIR *opendir( const char * ); DIR *fdopendir( int ); struct dirent * readdir(DIR * );
void?? ? seekdir(DIR *, long);
long?? ? telldir(DIR *); void rewinddir(DIR * ); int closedir(DIR *);
struct dirent { long d_ino; /* inode number */ off_t d_off; /* offset to this dirent */ unsigned short d_reclen; /* length of this d_name */ unsigned char d_type; /* the type of d_name */ char d_name[ 1 ]; /* file name (null-terminated) */ };
關(guān)鍵部分就是 DIR 這個(gè)結(jié)構(gòu)體的定義,包括文件描述符、緩沖區(qū)偏移、大小、緩沖區(qū)內(nèi)容等,下面定義的就是具體的目錄操作函數(shù)了,有打開目錄、讀目錄、重置讀取位置、關(guān)閉目錄等,這里我所需要的就是打開、讀和關(guān)閉這三個(gè)最基本的目錄操作,下面是使用例子:
#include <stdio.h> #include <stdlib.h> #include < string .h> #include <dirent.h> #define MAX_LEN 65535 int main( void ) { DIR * dir; struct dirent * ptr; char * flow[MAX_LEN]; int num = 0 , i = 0 ; if ((dir=opendir( ". /data " )) == NULL) { perror( " Open dir error... " ); exit( 1 ); } // readdir() return next enter point of directory dir while ((ptr=readdir(dir)) != NULL) { flow[num ++] = ptr-> d_name; // printf("%s\n", flow[num - 1]); } for (i = 0 ; i < num; i++ ) { printf( " %s\n " , flow[i]); } closedir(dir); }
運(yùn)行結(jié)果如下:
?
一看這結(jié)果就不對,輸出的都是同一個(gè)文件名(最后一個(gè)文件的文件名), 哪里出了問題呢?將代碼中 // ?printf("%s\n", flow[num - 1]); 這行注釋去掉再運(yùn)行,發(fā)現(xiàn)注釋處輸出的是正確的,兩者都是輸出的flow數(shù)組元素怎么結(jié)果不一樣呢?經(jīng)過調(diào)試發(fā)現(xiàn)是 flow[num ++] = ptr-> d_name; 這句代碼的問題,因?yàn)檫@是引用拷貝(地址拷貝),所有的flow元素全部指向同一個(gè)對象ptr-> d_name ,雖然ptr-> d_name 對象每次的內(nèi)容不同(也就是前面正確輸出的原因),但所有內(nèi)容都共享一個(gè)地址,用一個(gè)簡單的圖說明就是:
當(dāng)然這個(gè)問題也比較好解決,也是比較常見的問題,用字符串拷貝或內(nèi)存拷貝就行了,給flow每個(gè)元素重新申請一塊內(nèi)存。
#include <stdio.h> #include <stdlib.h> #include < string .h> #include <dirent.h> #define MAX_LEN 65535 int main( void ) { DIR * dir; struct dirent * ptr; char * flow[MAX_LEN]; int num = 0 , i = 0 ; if ((dir=opendir( " ./data " )) == NULL) { perror( " Open dir error... " ); exit( 1 ); } // readdir() return next enter point of directory dir while ((ptr=readdir(dir)) != NULL) { flow[num] = ( char *)malloc( sizeof ( char )); strcpy(flow[num], ptr -> d_name); num ++ ; } for (i = 0 ; i < num; i++ ) { printf( " %s\n " , flow[i]); } closedir(dir); }
?最終結(jié)果就正確了。
二、Windows下遍歷目錄的方法
?在Windows下就比較麻煩了,所要用到的函數(shù)都在windows.h中,Windows編程本來就比較繁瑣,下面就不一一介紹所用到的函數(shù)了,直接給出封裝的過程。
1. 首先模擬Linux下自帶的頭文件dirent.h
不同的是DIR中去掉了一些不需要的屬性,及只定義了三個(gè)我所需要的操作(按需定義)。
// dirent.h
#ifndef _SYS_DIRENT_H #define _SYS_DIRENT_H typedef struct _dirdesc { int dd_fd; / * * file descriptor associated with directory */ long dd_loc; /* * offset in current buffer */ long dd_size; /* * amount of data returned by getdirentries */ char *dd_buf; /* * data buffer */ int dd_len; /* * size of data buffer */ long dd_seek; /* * magic cookie returned by getdirentries */ } DIR; # define __dirfd(dp) ((dp) -> dd_fd) DIR *opendir ( const char * ); struct dirent *readdir (DIR * ); void rewinddir (DIR * ); int closedir (DIR * ); #include <sys/types.h> struct dirent { long d_ino; /* inode number */ off_t d_off; /* offset to this dirent */ unsigned short d_reclen; /* length of this d_name */ unsigned char d_type; /* the type of d_name */ char d_name[ 1 ]; /* file name (null-terminated) */ }; #endif
?
2. 三個(gè)目錄操作函數(shù)的實(shí)現(xiàn)
當(dāng)然這是最關(guān)鍵的部分,我不知道Linux下是怎么實(shí)現(xiàn)的(找了下沒找到),Windows下實(shí)現(xiàn)如下,主要是FindFirstFile()和FindNextFile()這兩個(gè)Windows函數(shù),對Windows編程不精,也不好解釋什么,需要搞明白為啥這樣實(shí)現(xiàn)請上網(wǎng)搜或MSDN。
// dirent.c
#include <stdio.h> #include <windows.h> #include " dirent.h " static HANDLE hFind; DIR *opendir( const char * name) { DIR * dir; WIN32_FIND_DATA FindData; char namebuf[ 512 ]; sprintf(namebuf, " %s\\*.* " ,name); hFind = FindFirstFile(namebuf, & FindData ); if (hFind == INVALID_HANDLE_VALUE) { printf( " FindFirstFile failed (%d)\n " , GetLastError()); return 0 ; } dir = (DIR *)malloc( sizeof (DIR)); if (! dir) { printf( " DIR memory allocate fail\n " ); return 0 ; } memset(dir, 0 , sizeof (DIR)); dir ->dd_fd = 0 ; // simulate return return dir; } struct dirent *readdir(DIR * d) { int i; static struct dirent dirent; BOOL bf; WIN32_FIND_DATA FileData; if (! d) { return 0 ; } bf = FindNextFile(hFind,& FileData); // fail or end if (! bf) { return 0 ; } for (i = 0 ; i < 256 ; i++ ) { dirent.d_name[i] = FileData.cFileName[i]; if (FileData.cFileName[i] == ' \0 ' ) break ; } dirent.d_reclen = i; dirent.d_reclen = FileData.nFileSizeLow; // check there is file or directory if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { dirent.d_type = 2 ; } else { dirent.d_type = 1 ; } return (& dirent); } int closedir(DIR * d) { if (!d) return - 1 ; hFind = 0 ; free(d); return 0 ; }
?
3. 使用方法
與Linux下使用一模一樣,不需要改動(dòng)一句代碼就可應(yīng)用,但卻發(fā)現(xiàn)了與Linux下自帶實(shí)現(xiàn)同樣的問題,即也是引用拷貝,如下。
?因?yàn)檫@是我們自己實(shí)現(xiàn)的代碼,所以字符串拷貝不是最佳解決方案,修改原實(shí)現(xiàn)代碼才是最好的方法,當(dāng)然如果是為了可移植性,就不需要改動(dòng)了,就用字符串拷貝這樣代碼到Linux下就不需要改動(dòng)了。下面看如何修改原實(shí)現(xiàn)解決:
a. 首先定位問題,可以很明顯的知道是readdir這個(gè)函數(shù)的問題;
b. 然后找出問題根源,通過前面的分析可知問題的根源在于每次ptr->d_name使用的是同一內(nèi)存地址,即ptr地址不變,而ptr是readdir返回的struct dirent指針,所以問題的根源在于readdir返回的dirent結(jié)構(gòu)體地址問題,從上面代碼中可以看到static struct dirent dirent; 這句代碼,其中dirent的地址就是返回的地址,注意到dirent被定義為static,大家都知道C中static聲明的變量調(diào)用一次后地址就不變了,存在靜態(tài)存儲區(qū),也就是每次readdir返回的地址都是不變的,但指向的內(nèi)容每次都被覆寫,這就是問題所在;
c. 最后解決問題,知道問題根源后,問題就比較容易解決了,就是每次給dirent重新申請內(nèi)存,看如下我的做法,注意我這里不能簡單的 struct dirent *dirent = (struct dirent *)malloc(sizeof(struct dirent) )就結(jié)束了,看前面dirent結(jié)構(gòu)體定義中char d_name[1];這里我只給d_name一個(gè)內(nèi)存空間,顯然不夠,所以也要給它申請內(nèi)存,我這里是按需申請內(nèi)存,如果定義為char d_name[256];這樣的就不需要了(一般文件名不是太長吧)。
struct dirent *readdir(DIR * d) { int i; BOOL bf; WIN32_FIND_DATA FileData; if (! d) { return 0 ; } bf =FindNextFile(hFind,& FileData); // fail or end if (! bf) { return 0 ; } struct dirent *dirent = ( struct dirent *)malloc( sizeof ( struct dirent)+ sizeof (FileData.cFileName)); for (i = 0 ; i < 256 ; i++ ) { dirent ->d_name[i] = FileData.cFileName[i]; if (FileData.cFileName[i] == ' \0 ' ) break ; } dirent ->d_reclen = i; dirent ->d_reclen = FileData.nFileSizeLow; // check there is file or directory if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { dirent ->d_type = 2 ; } else { dirent ->d_type = 1 ; } return dirent; }
?最終Windows運(yùn)行結(jié)果如下:
PS:不知道這里大家有沒有注意一個(gè)很小的細(xì)節(jié),就是輸出的不同(用的是一個(gè)相同的目錄結(jié)構(gòu)),Linux下輸出了當(dāng)前目錄.和上層目錄..而Windows下只輸出了上層目錄..,當(dāng)然這沒關(guān)系,因?yàn)槲乙闹皇窍旅娴奈募纯伞K,終于完成了,中間找bug花了不少時(shí)間,嘿嘿~~~
?
參考資料:
http://blog.csdn.net/lindabell/article/details/8181866
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
