• 巧妙利用访问时间提取和重组代码的实践


    说明

         本文主要介绍巧妙利用访问时间提取和重组某嵌入式产品SDK代码的实践。

    一  问题提出

         目前产品平台为便于编译管理,要求各模块组织为include-source目录结构,分别存放头文件和源文件。但芯片厂家提供的SDK按功能划分为众多子目录,如subdir1(.c,.h)…subdirN(.c,.h)…Makefile,并将头文件和源文件一并存放在各子目录内。

         此外,厂家SDK支持多种管理场景,通过选项开关编译不同的目录和文件。而产品硬件定板后管理场景固定,其他场景所涉及的代码将不再需要。

         因此,需要遍历厂家SDK目录,提取在用的文件并重组为include-source目录结构。

     

    二  解决方案

         根据重组要求,有四种解决方案。

    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",通常设置为精确到日或时的编译时间。

         该方案实现简单,应用起来也比方案三方便,故作为本文的优选实现。

     

    三  代码实现

         本节将实现上节所述的方案四(假设源文件名为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回调函数指针执行文件操作。此处回调函数原体为MoveFile()函数,该函数检查当前文件的返回时间,并将符合要求的文件分别拷贝至include和source目录。

     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 = '';  //丢弃文件名,留取目录名
     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代码根目录下,即subdir1(.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系统为每个文件维护三个时间属性,其意义如下表所示:

    时间属性

    说明

    作用函数

    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

         以下详述三种时间的区别:

         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节点中的所有信息与文件的实际内容分开存放,故此时并未更改文件内容。当然,修改文件内容时,修改时间和更改时间均会改变。

         创建新文件时,访问时间、修改时间和更改时间三者一致。读取该文件时会更新访问时间,但其修改时间和更改时间并不改变(文件本身及文件相关的信息没有被改变)。此外,系统并不保存对一个i节点的最后一次访问时间,故access和stat函数并不改变这三个时间中的任一个。 

         Windows文件有三种时间属性,即创建时间、修改时间和访问时间。而Unix中没有文件创建时间的概念。若文件创建后内容未曾修改,则修改时间等同创建时间;若文件创建后状态未曾改动,则更改时间等同创建时间;若文件创建后内容未曾读取,则访问时间等同创建时间。但通常难以判断文件是否被改过、读过、其状态是否变过,因此需要变通的方法来实现保留文件创建时间。 

         可在挂载(mount)文件系统时使用参数-o noatime,关闭系统更新atime的特性。这样,访问文件时atime就不会更新,即等同文件的创建时间。此外,在文件读操作很频繁的系统中,atime更新所带来的开销很大,因此使用noatime属性可改善文件读写性能。

         但有些程序需要根据atime进行一些判断和操作,因此Linux2.6.29后默认集成relatime属性。使用该属性挂载文件系统后,仅当mtime比atime时间更新时,才更新atime(此时atime等同mtime)。

         查看文件这三种时间的命令有:

         1) ls命令

         ls命令按三个时间值中的一个排序进行显示。系统默认(使用-l或-t选项)按文件修改时间的先后排序,-u选项使其按访问时间顺序排序,-c选项使其按更改状态时间排序。

         使用--time和--full-time选项可查看更详细的时间信息。其中,--time取值可为atime或ctime,不指定该选项时默认为修改时间。

         此外,可格式化输出三种时间值。格式化字符串形如"%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 *命令查看当前目录所有文件的时间信息。

  • 相关阅读:
    libevent的问题
    mysql homedir迁移
    mysql海量数据的优化
    Spark(Hive) SQL中UDF的使用(Python)【转】
    Spark SQL inferSchema实现原理探微(Python)【转】
    Spark SQL利器:cacheTable/uncacheTable【转】
    Spark使用CombineTextInputFormat缓解小文件过多导致Task数目过多的问题【转】
    ExecutorService-10个要诀和技巧【转】
    漫游Kafka之过期数据清理【转】
    kafka多线程消费及处理和手动提交处理方案设计[转]
  • 原文地址:https://www.cnblogs.com/clover-toeic/p/3924174.html
Copyright © 2020-2023  润新知