アクセス時間の抽出とコードの再編成を巧みに利用する実践

32060 ワード


説明
本文は主にアクセス時間を巧みに利用してある組み込み製品SDKコードを抽出し再編する実践を紹介する.
 
一つの問題を提起する
現在、製品プラットフォームはコンパイル管理を容易にするため、各モジュールをinclude-sourceディレクトリ構造に組織し、ヘッダファイルとソースファイルをそれぞれ保存することを要求している.しかし、チップメーカーが提供するSDKは、subdir 1(.c,.h)...subdirN(.c,.h)...Makefileのような多くのサブディレクトリに機能別に分類され、ヘッダファイルとソースファイルを各サブディレクトリに一括して格納する.
また、メーカーSDKは、さまざまな管理シーンをサポートし、オプションスイッチで異なるディレクトリとファイルをコンパイルします.製品ハードウェアの定板後にシーンの固定を管理すると、他のシーンに関連するコードは不要になります.
そのため,メーカーSDKディレクトリを巡回し,使用しているファイルを抽出してinclude-sourceディレクトリ構造に再編成する必要がある.
 
二ソリューション
再編成の要件に基づいて、4つのソリューションがあります.
2.1方案一
一部の製品では、メーカーSDKをinclude-sourceディレクトリ構造に再編成しています.従って、既存の製品を再編成したSDKに対して、新規のSDKから必要なファイルを取り出し、再編成後に再コンパイルし、コンパイル結果に応じて必要な修正と調整を行うことができる.
明らかに、このスキームは効率が低く(特に異なるシーンで使用される同名ファイルを選別する場合)、安全ではない(SDKの新規コンテンツが漏れている可能性がある).
2.2方案二
メーカーSDKをコンパイルした後、コンパイル出力に出てくるファイルを解析(使用していることを示す)、手動またはスクリプト抽出後に再編成します.
このシナリオの解析はやや難しく、重複するファイル(特にネストされたヘッダファイルにコンパイル警告が発生した場合)を考慮する必要があります.
2.3方案三
モジュールのコード量を2%に削減する実践」を参照して、未使用条件コンパイルブランチの考え方を削除し、本明細書で提起した問題を解決するために少し修正すればよい.
その原理は以下の通りである.
1)includeとsourceディレクトリを作成します.
2)SDKの処理対象コード(すべてのソースファイルとヘッダファイル)の最初の行にgcc拡張プリコンパイラヘッダwarningを挿入する.
3)処理対象コードをコンパイルしてgccコンパイル出力を取得し分析する.
4)コンパイル結果における「#warning」警告に係るファイルは、使用中であることを示し、コンパイルヘッダを削除した後、ソースファイルとヘッダファイルタイプ別にincludeディレクトリとsourceディレクトリにそれぞれコピーすることができる.
ここで、手順1と3は手作業で行い、手順2と4は「実践」で提供されたPythonスクリプトを少し修正すれば実現できます.
2.3方案四
SDKをコンパイルした後、各ファイルのアクセス時間*をディレクトリを巡って読み込み、指定された時間(今回コンパイルされたことを示す)に合致するファイルを抽出して再編成する.「指定された時間」の形式は、「Mon Aug 18 09:59:46 2014」のように、通常、正確な日または時刻のコンパイル時間に設定されます.
このスキームの実現は簡単であり、適用もスキーム3より便利であるため、本論文の好ましい実現とする.
 
トリプルコード実装
このセクションでは、前節で説明したシナリオ4(ソースファイル名をDirMover.cと仮定)を実装します.必要なヘッダファイルは次のとおりです.
1 #include <string.h>
2 #include <time.h> //ctime
3 #include <errno.h>
4 #include <unistd.h> //getcwd
5 #include <sys/stat.h>
6 #include <dirent.h>

まず、グローバル・データのセットを定義します.
1 #define DIR_LEN        512  //          
2 #define DIR_FILE_LEN   1024 //                
3 
4 //              Include-Source    
5 typedef struct{
6     char szIncDir[DIR_LEN];  //Include  
7     char szSrcDir[DIR_LEN];  //Source  
8 }T_DIR_TREE;
9 char gszCmpTime[sizeof("Mon Aug 18 09:59:46 2014")] = {0};

再編成する前にincludeとsourceディレクトリを作成する必要があります.MakeCleanDir()関数は、空のディレクトリを作成するために使用されます.ディレクトリがすでに存在し、ファイルが含まれている場合は、既存のファイルを削除します.
 1 int MakeCleanDir(const char *pszDir, mode_t dwMode)
 2 {
 3     if(0 == mkdir(pszDir, dwMode))
 4         return 0;
 5 
 6     if(errno != EEXIST)
 7     {
 8         fprintf(stderr, "Cannot make directory: %s(%s)!
", pszDir, strerror(errno)); 9 return -1; 10 } 11 12 char szCopyCmd[DIR_FILE_LEN] = {0}; 13 snprintf(szCopyCmd, sizeof(szCopyCmd), "rm -rf %s/*", pszDir); 14 system(szCopyCmd); // 15 return 0; 16 }

実装を簡略化するために、削除操作はrmコマンドを直接呼び出す.同様の考慮に基づいて、後でファイルをコピーするときもcpコマンドを直接呼び出す.
再編成ディレクトリを作成したら、TraverseDirectory()関数を呼び出して元のSDKディレクトリを巡ります.
 1 typedef int (*TravFileFunc)(char *pszAbsFile, struct stat *ptFileStatus, void* pvTravInfo);
 2 int TraverseDirectory(const char *pszCurDir, void* pvTravInfo, TravFileFunc fpTravFile)
 3 {
 4     DIR *pDir = opendir(pszCurDir);
 5     if(NULL == pDir){
 6        fprintf(stderr, "Cannot open directory: %s!
", pszCurDir); 7 return -1; 8 } 9 10 struct dirent *pFileEntry = NULL; 11 while((pFileEntry = readdir(pDir)) != NULL){ 12 // ., .. , 13 if(0 == strncmp(pFileEntry->d_name, ".", 1)) 14 continue; 15 16 struct stat tFileStatus; // 17 char szAbsFile[DIR_FILE_LEN] = {0}; // 18 sprintf(szAbsFile, "%s/%s", pszCurDir, pFileEntry->d_name); 19 if(stat(szAbsFile, &tFileStatus) != 0){ 20 fprintf(stderr, "Call stat error(%s)!
", strerror(errno)); 21 return -1; 22 } 23 24 if(S_ISDIR(tFileStatus.st_mode)) // 25 { 26 TraverseDirectory(szAbsFile, pvTravInfo, fpTravFile); 27 continue; 28 } 29 30 if(fpTravFile(szAbsFile, &tFileStatus, pvTravInfo) != 0) 31 break; 32 } 33 34 closedir(pDir); 35 return 0; 36 }

汎用性を求めるために、TravFileFuncコールバック関数ポインタでファイル操作を実行します.ここでコールバック関数は、現在のファイルの戻り時間をチェックし、必要に応じたファイルをincludeディレクトリとsourceディレクトリにそれぞれコピーするMoveFile()関数です.
 1 int MoveFile(char *pszAbsFile, struct stat *ptFileStatus, void* pvTravInfo)
 2 {
 3     printf("Current file: %s(atime: %s).
", pszAbsFile, ctime(&ptFileStatus->st_atime)); 4 5 // Include Source 6 T_DIR_TREE *ptNewDirTree = (T_DIR_TREE *)pvTravInfo; 7 char *pSlashPos = strrchr(pszAbsFile, '/'); // '/' 8 *pSlashPos = '\0'; // 9 if(!strcmp(pszAbsFile, ptNewDirTree->szIncDir) || 10 !strcmp(pszAbsFile, ptNewDirTree->szSrcDir)) 11 return 0; 12 *pSlashPos = '/'; // 13 14 // DirMover.c 15 char *pszFileName = pSlashPos + 1; //basename(pszAbsFile); 16 if(!strcmp(pszFileName, __FILE__)) 17 return 0; 18 19 // 20 if(strncmp(ctime(&ptFileStatus->st_atime), gszCmpTime, strlen(gszCmpTime))) 21 return 0; 22 23 char szCopyCmd[DIR_FILE_LEN] = {0}; 24 char szAbsNewFile[DIR_FILE_LEN] = {0}; 25 char szSuffix[12] = {0}; 26 sscanf(pszFileName, "%*[^.].%c", szSuffix); 27 if('h' == szSuffix[0]) // .h, 28 sprintf(szAbsNewFile, "%s/%s", ptNewDirTree->szIncDir, pszFileName); 29 else if('c' == szSuffix[0]) // .c, 30 sprintf(szAbsNewFile, "%s/%s", ptNewDirTree->szSrcDir, pszFileName); 31 else 32 { 33 printf("Unknown extension of file: %s!
", pszAbsFile); 34 return 0; 35 } 36 snprintf(szCopyCmd, sizeof(szCopyCmd), "cp -p %s %s", pszAbsFile, szAbsNewFile); 37 system(szCopyCmd); // Include , Source 38 39 return 0; 40 }

MoveFile()関数では、まずIncludeとSourceディレクトリをスキップします.このステップには冗長性がある(ファイル操作のたびに判断を実行する必要がある)が、コールバック関数としてはそれ以外に方法がない.
最後にmain()関数の内容は次のとおりです.
 1 int main(int dwArgc, char *pArgv[])
 2 {
 3     int dwRet = -1;
 4 
 5     if(dwArgc != 2)
 6     {
 7         fprintf(stderr, "Usage: %s ['TimetobeCompared']
" 8 " ['TC']Substring of time string(Format='Mon Aug 18 09:59:46 2014')
" 9 " e.g. %s 'Mon Aug 18' -->" 10 " Match files whose access time is 2014-8-18
", pArgv[0], pArgv[0]); 11 return -1; 12 } 13 14 T_DIR_TREE tDirTree = {{0}}; 15 char *pszCurDir = getcwd(NULL, DIR_LEN); 16 snprintf(tDirTree.szIncDir, sizeof(tDirTree.szIncDir), "%s/include", pszCurDir); 17 18 dwRet = MakeCleanDir(tDirTree.szIncDir, S_IRWXU); // Include 19 if(dwRet != 0) 20 return -1; 21 22 snprintf(tDirTree.szSrcDir, sizeof(tDirTree.szSrcDir), "%s/source", pszCurDir); 23 dwRet = MakeCleanDir(tDirTree.szSrcDir, S_IRWXU); // Source 24 if(dwRet != 0) 25 return -1; 26 27 strcpy(gszCmpTime, pArgv[1]); 28 dwRet = TraverseDirectory(pszCurDir, &tDirTree, MoveFile); 29 printf("Rearrange Directory %s!
", dwRet?"Incorrectly":"Successfully"); 30 31 return dwRet; 32 }

 
四効果検証
コード再編成は、次の手順で行います.
1)前節で示したコードをコンパイルし,実行可能ファイル(仮定名はDirMover)を生成する.
2)このファイルをSDKコードルートの下に置く.すなわち、subdir 1(.c,.h)...subdirN(.c,.h)...Makefile...DirMoverである.
3)SDKコードをコンパイルする.SDKコードの作成時間が当日より早い場合、「指定時間」は当日に設定でき、フォーマットは「Mon Aug 18」のようになる.そうでなければ、SDKコードの作成時間が以前の場合、dateコマンドで現在の時間を表示したり、statコマンドでコンパイルする必要があるファイルへのアクセス時間を表示したりして、「指定時間」の精度を調整することができます.
4)実行./DirMover'Mon Aug 18 17'のようなコマンドは、SDKを使用したファイル再編成で自動的に生成されるinclude-sourceディレクトリに配置することができます.
なお、ステップ3は、他のステップよりも前に完了してもよい.この場合、「指定時間」は、必ずコンパイルされるファイルへのアクセス時間によって異なります.
なお、「Mon Aug 18 17」は、Shellによってコマンドラインパラメータとして認識されるように、単一引用符または二重引用符で囲まなければならない.
もちろん、DirMover.c内でtime()関数を呼び出してtime_を取得することもできるtフォーマットのシステムの現在の時間は、ファイルのatime値と直接比較されます.コマンドラインには、数字を入力して時間精度を指定し、コード内で精度要求に応じて現在の時間を調整します(3600で割って時間精度に変換します).これにより、使いやすさは向上しますが、柔軟性は低下します.
 
五注釈
Unixシステムは、次の表に示すように、ファイルごとに3つの時間属性を維持します.
時間のプロパティ
説明
さようかんすう
ls(-l)例
st_atime
ファイルデータの最後のアクセス時間
creat/open(O_CREAT), exec, mkfifo, mknod, pipe, utime, read
ls -lu file
st_mtime
ファイルデータの最終変更時間
creat/open(O_CREAT|O_TRUNC), mkfifo, mknod, pipe, utime, truncate/ftruncate, write
ls -l file
st_ctime
iノード状態の最終変更時間
chmod/fchmod, chown/fchown, creat/open(O_CREAT), link/unlink, mkfifo, mknod, pipe, remove, rename, truncate/ftruncate, utime, write
ls -lc file
次の3つの時間の違いを詳しく説明します.
1)アクセス時間(access time):ファイル内のデータの最近のアクセス時間(last accessed time)を表す.ファイルを読み込んだり実行したりすると、システムプロセスで直接使用されたり、cat/moreなどのコマンドやスクリプトで間接的に使用されたりして、この時間が更新されます.lsコマンドとstatコマンドでは、ファイルへのアクセス時間は変更されません.
2)変更時間(modification time):ファイル内のデータの最近の変更時間(last modified time)を表します.ファイルの内容を変更すると、ファイルにデータを書き込むと更新されます.
3)変更時間(change time):ファイルiノード状態の最近の変更時間(last i-node's status changed time)を示す.この時間は、ファイルのアクセス権、所有者、リンク数などが変更されると更新されます.iノードのすべての情報は、ファイルの実際の内容とは別に格納されるため、ファイルの内容は変更されません.もちろん、ファイルの内容を変更すると、変更時間と変更時間が変わります.
新しいファイルを作成すると、アクセス時間、変更時間、変更時間の3つが一致します.ファイルを読み込むとアクセス時間が更新されますが、変更時間と変更時間は変わりません(ファイル自体やファイルに関する情報は変わりません).さらに、システムは、1つのiノードに対する最後のアクセス時間を保存しないため、accessおよびstat関数は、この3つの時間のいずれかを変更しない. 
Windowsファイルには、作成時間、変更時間、アクセス時間の3つの時間プロパティがあります.Unixにはファイル作成時間の概念はありません.ファイルの作成後に内容が変更されていない場合、変更時間は作成時間と同じです.ファイルの作成後にステータスが変更されていない場合、変更時間は作成時間と同じです.ファイル作成後にコンテンツが読み込まれていない場合は、アクセス時間は作成時間と同じです.しかし、通常、ファイルが変更されたかどうか、読んだかどうか、その状態が変更されたかどうかを判断することは困難であるため、保存ファイルの作成時間を実現するために、融通のきく方法が必要である. 
パラメータ-o noatimeは、mountファイルシステムをマウントするときに使用し、システムを閉じてatimeの特性を更新できます.これにより、ファイルにアクセスしたときにatimeは更新されません.つまり、同等のファイルの作成時間です.また、ファイルの読み取り操作が頻繁なシステムでは、atimeの更新にかかるオーバーヘッドが大きいため、noatimeプロパティを使用すると、ファイルの読み取りと書き込みのパフォーマンスが改善されます.
しかし、一部のプログラムはatimeに基づいていくつかの判断と操作を行う必要があるため、Linux 2.6.29以降はrelatimeプロパティがデフォルトで統合されます.このプロパティを使用してファイルシステムをマウントすると、mtimeがatimeよりも更新された場合にのみatimeが更新されます(この場合、atimeはmtimeと同じです).
ファイルを表示する3つの時間のコマンドは、次のとおりです.
1)lsコマンド
lsコマンドは、3つの時間値のうちの1つのソートで表示されます.システムのデフォルト(-lまたは-tオプションを使用)は、ファイル変更時間の前後にソートされ、-uオプションはアクセス時間順にソートされ、-cオプションはステータス時間の変更順にソートされます.
--timeおよび--full-timeオプションを使用して、より詳細な時間情報を表示します.ただし、--timeの値はatimeまたはctimeで、このオプションを指定しない場合はデフォルトで変更時間です.
また、3つの時間値をフォーマットして出力できます.「%AY-%Am-%Ad%AH:%AM:%AS」(アクセス時間)などの文字列をフォーマットし、文字列の文字「A」を「T」または「C」に変更すると、変更時間と変更時間をフォーマットできます.
1  [wangxiaoyuan_@localhost SDK573]$ ls -lu Makefile
2 -rwxr--r-- 1 wangxiaoyuan_ users 4963 Aug 20 09:48 Makefile
3  [wangxiaoyuan_@localhost SDK573]$ ls -l --time=atime --full-time Makefile
4 -rwxr--r-- 1 wangxiaoyuan_ users 4963 2014-08-20 09:48:43.000000000 +0800 Makefile
5 [wangxiaoyuan_@localhost SDK573]$ find . -name  Makefile -printf "%AY-%Am-%Ad %AH:%AM:%AS"
6 2014-08-20 09:48:43(    )

2)statコマンド
1 [wangxiaoyuan_@localhost SDK573]$ stat Makefile
2   File: `Makefile'
3   Size: 4963            Blocks: 16         IO Block: 4096   regular file
4 Device: 811h/2065d      Inode: 25640989    Links: 1
5 Access: (0744/-rwxr--r--)  Uid: (  540/wangxiaoyuan_)   Gid: (  100/   users)
6 Access: 2014-08-20 09:48:43.000000000 +0800
7 Modify: 2014-07-08 18:29:04.000000000 +0800
8 Change: 2014-08-19 12:14:41.000000000 +0800

現在のディレクトリのすべてのファイルの時間情報はstat*コマンドで表示できます.