2007年11月28日星期三

如何取得系统的闲置(idle)时间

在windows平台上,利用调用GetLastInputInfo()函数得到最后一次输入事件发生的时间,通过计算当前时间与最后输入时间的时间差得到系统闲置时间。

定义:

#define _WIN32_WINNT 0x0500
#include <windows.h>
#define EXPORT __declspec(dllexport)

typedef BOOL (WINAPI *GETLASTINPUTINFO)(LASTINPUTINFO *);
static HMODULE g_user32 = NULL;
static GETLASTINPUTINFO g_GetLastInputInfo = NULL;

初始化:

g_user32 = LoadLibrary("user32.dll");
if (g_user32) {
 g_GetLastInputInfo = (GETLASTINPUTINFO)GetProcAddress(g_user32, "GetLastInputInfo");
}

得到系统闲置时间:

int idle_time = 0;

if (g_GetLastInputInfo != NULL) {
 LASTINPUTINFO lii;
 memset(&lii, 0, sizeof(lii));
 lii.cbSize = sizeof(lii);
 if (g_GetLastInputInfo(&lii)) {
  idle_time = lii.dwTime;
 }
 idle_time = (GetTickCount() - idle_time) / 1000;
}


在Linux下,则是利用X11屏保扩展函数中的相关调用得到系统闲置时间。

定义:

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/scrnsaver.h>
#include <gdk/gdkx.h>
#include <gtk/gtk.h>

得到系统闲置时间:

static XScreenSaverInfo *mit_info = NULL;
int idle_time, event_base, error_base;

gtk_init (NULL, NULL);
if (XScreenSaverQueryExtension(GDK_DISPLAY(), &event_base, &error_base))
{
 if (mit_info == NULL)
  mit_info = XScreenSaverAllocInfo();
 XScreenSaverQueryInfo(GDK_DISPLAY(), GDK_ROOT_WINDOW(), mit_info);
 idle_time = (mit_info->idle) / 1000;
}
else
 idle_time = 0;

lex/yacc系列:lex示例

示例1:将输入中的多个空格或者制表符压缩为一个空格,并且删除每行结尾的空格:
%%
[ \t]+ { putchar(' '); }
[ \t]+$


示例2:在输入文件的每行前面加上行号然后输出:
%{
unsigned yylineno = 1;
%}

%%
^.*\n { printf("%4d: %s", yylineno, yytext); ++yylineno; }

%%
int
main(int argc, char *argv[])
{
 if (argc > 1) {
  FILE *file = fopen(argv[1], "r");
  if (file) {
   yyin = file;
   yylex();
  }
 }
 return 0;
}

代码中的yytext包含匹配字符串。在某些lex的实现中预定义了变量yylineno

lex/yacc系列:在lex中处理多个输入文件

使用lex的文件尾处理程序,可以让程序处理多个输入文件。

yylex()到达文件结尾的时候,它调用yywrap(),该函数返回0或者1。如果返回1,意味着程序完成,没有更多的输入;如果返回值是0,词法分析程序假设yywrap()已经打开了另一个文件,然后继续从yyin读取数据。默认的yywrap()总是返回1。通过提供自己的yywrap()版本,可以使程序读取命令行传递的所有文件,一次读取一个。

下面是可以处理多个文件的单词计数程序的完整源代码:

%{
unsigned long charCount = 0, wordCount = 0, lineCount = 0;

#undef yywrap
%}

word [^ \t\n]+
eol \n

%%
{word} { ++wordCount; charCount += yyleng; }
{eol} { ++charCount; ++lineCount; }
. ++charCount;

%%
char **fileList;
unsigned nFiles;
unsigned currentFile = 0;
unsigned long totalCC = 0;
unsigned long totalWC = 0;
unsigned long totalLC = 0;

int
main(int argc, char *argv[])
{
 fileList = argv + 1;
 nFiles = argc - 1;

 if (nFiles == 1) {
  FILE *file;

  currentFile = 1;
  file = fopen(argv[1], "r");
  if (!file) {
   fprintf(stderr, "could not open file %s\n", argv[1]);
   exit(1);
  }
  yyin = file;
 }

 if (nFiles > 1)
  yywrap();

 yylex();

 if (nFiles > 1) {
  printf("%8lu %8lu %8lu %s\n", lineCount, wordCount, charCount,
         fileList[currentFile - 1]);
  totalCC += charCount;
  totalWC += wordCount;
  totalLC += lineCount;
  printf("%8lu %8lu %8lu total\n", totalLC, totalWC, totalCC);
 } else
  printf("%8lu %8lu %8lu\n", lineCount, wordCount, charCount);

 return 0;
}

int
yywrap()
{
 FILE *file = 0;

 if ((currentFile != 0) && (nFiles > 1) && (currentFile < nFiles)) {
  printf("%8lu %8lu %8lu %s\n", lineCount, wordCount, charCount,
         fileList[currentFile - 1]);
  totalCC += charCount;
  totalWC += wordCount;
  totalLC += lineCount;
  charCount = wordCount = lineCount = 0;
  fclose(yyin);
 }

 while (fileList[currentFile]) {
  file = fopen(fileList[currentFile], "r");
  ++currentFile;
  if (file) {
   yyin = file;
   break;
  }
  fprintf(stderr, "could not open file %s\n", fileList[currentFile - 1]);
 }

 return (file ? 0 : 1);
}

lex/yacc系列:lex简介

下面结合一个基本的单词计数程序(类似UNIX程序wc)来看一下lex规范的实际结构。

lex规范由三部分组成:定义段、规则段和用户子例程段,各部分由符号“%%”分隔。

第一部分,定义段,处理lex用于词法分析程序中的选项,并且一般建立词法分析程序运行的执行环境。

单词计数示例的定义段如下:

%{
unsigned charCount = 0, wordCount = 0, lineCount = 0;
%}

word [^ \t\n]+
eol \n

被“%{”和“%}”扩住的部分是C代码,它们将被原封不动地拷贝到词法分析程序中,置于输出代码的靠前的部分,因此这里定义的数据可以由规则段中的代码引用。

定义段的最后两行是模式定义,通过这种简单的替换机制,lex中可以方便地定义长的或复杂的模式。示例中的第一个定义提供了单词描述:除了空格、制表符和换行符之外的字符的非空组合;第二个定义描述行结束字符,即换行。

规则段包含指定词法分析程序的模式和动作。每个规则由两部分组成:模式和动作,由空格分开。当lex生成的词法分析程序识别出某个模式时,将执行相应的动作。模式由正则表达式描述。当匹配模式时,lex拥有一套简单的消除歧义的规则:

  1. lex模式只匹配输入字符或字符串一次
  2. lex总是尝试匹配尽可能长的字符串
  3. 当存在同样长度的字符串匹配时,lex使用先定义的规则

如果输入不匹配任何模式,默认动作是将输入拷贝到输出。如果动作为空,则意味着丢弃被匹配的输入。如果动作仅仅是一条竖线“¦”,意思是与下一规则所使用的动作相同。

下面是单词计数示例的规则段:

%%
{word} { ++wordCount; charCount += yyleng; }
{eol} { ++charCount; ++lineCount; }
. ++charCount;

%%是分隔符,标志规则段的开始。在模式指定中,lex将大括号{}中的名字,用定义段中定义的实际的正则表达式代替。示例中还使用了lex的内部变量yyleng,它表示词法分析程序识别的字符串的程度。

在词法分析程序识别了完整单词时,它就增加单词和字符的数目;当识别一个换行时,就增加字符数和行数;如果识别任意其他字符,就增加字符数。对于这个示例程序,它识别的唯一的“其他字符”是空格或制表符,其他的字符匹配第一个正则表达式,被当作一个单词。(由规则3,如果遇到例如“I”这样的单词,它会由第一个规则匹配,而不是由“.”规则匹配。)

lex规范的第三部分是用户子例程段。该部分包含任何有效的C代码,它们将被逐字拷贝到生成的词法分析程序中。一般来说,这一部分包含支持例程。对于这个示例,“支持”代码是主程序:

%%
int
main(void)
{
 yylex();
 printf("%d %d %d\n", lineCount, wordCount, charCount);
 return 0;
}

%%标志用户子例程段的开始。在主程序中,首先调用词法分析程序的入口yylex(),然后调用printf()输出运行结果。上面的示例不接受命令行参数,不打开任何文件,只是使用lex默认,读取标准输入。下面是重新连接lex的输入流的示例:

%%
int
main(int argc, char *argv[])
{
 if (argc > 1) {
  FILE *file;

  file = fopen(argv[1], "r");
  if (!file) {
   fprintf(stderr, "could not open %s\n", argv[1]);
   exit(1);
  }
  yyin = file;
 }

 yylex();
 printf("%d %d %d\n", lineCount, wordCount, charCount);
 return 0;
}

这个示例假设通过命令行参数传递要处理的文件。lex词法分析程序从标准I/O文件yyin中读取输入,所以当需要时,只需要改变yyinyyin的默认值是stdin,所以默认输入源是标准输入。

传统上,lex源文件的后缀为.l。将上面的三个部分保存为源文件mywc2.l,创建可执行程序mywc2,输入下面的命令:

% lex mywc2.l
% cc lex.yy.c -o mywc2 -ll

lex将lex规范译成C源文件,缺省文件名为lex.yy.c,对它进行编译时需要通过“-ll”链接lex库。

lex/yacc系列:正则表达式

正则表达式是一种使用“元(meta)”语言的模式描述,元语言用于描述特定模式。

.匹配除换行符(“\n”)之外的任意单个字符
*匹配前述表达式的零个或多个拷贝
+匹配前述表达式的一个或多个拷贝
?匹配前述表达式的零个或一个拷贝
^表示表达式的第一个字符匹配行首。也用于方括号中的否定。
$表示表达式的最后一个字符匹配行尾,“r$”等同于“r/\n”
\用于转义元字符,并且作为通常C转义序列的一部分,例如,“\n”是换行符,“\*”是星号,“\0”是空字符,“\123”表示8进制数123,“\x2a”表示16进制数2a。
[]匹配括号中任意字符的字符集。如果第一个字符是“^”,含义改变为匹配除括号中的字符以外的任意字符。方括号中的“-”表示一个字符范围,例如,“[0-9]”和“[0123456789]”的含义相同。“¦”或“]”作为“[”后的第一个字符时按字面意思解释,即短划线或方括号。
{}如果括号中包含一个或两个数字,指示前述表达式被允许匹配的次数,例如,A{1,3}表示匹配字母A一次到三次,A{2,}表示匹配字母A两次或以上,A{4}则表示匹配字母A四次。如果包含名称,则表示以该名称作替换。
¦表示“或者”,匹配其中的一个表达式
""字面引述
/只在斜线后面跟有指定的正则表达式时才匹配斜线之前的正则表达式。例如,0/1匹配字符串“01”中的“0”,但是不匹配“0”或“02”中的任何字符。由斜线后的模式所匹配的内容并不被“使用”,会被转变为随后的标记。一个模式中只允许使用一个斜线。
()将一系列正则表达式组成一个新的正则表达式

例1:
表达一个数字:
[0-9]
表示整数的正则表达式:
[0-9]+
上面的表达式要求至少有一个数字。下面的允许没有数字:
[0-9]*
添加一个可选的负数符号:
-?[0-9]+
扩展到表示小数(要求最后一位必须是数字):
[0-9]*\.[0-9]+
注意上述表达式中小数点“.”之前的“\”,使得它只能匹配小数点,而不是任意一个字符。而且这个表达式不能匹配整数。不考虑负号,合并匹配:
([0-9]+)([0-9]*\.[0-9]+)
上述表达式中使用括号“()”分组。加入负号:
-?(([0-9]+)([0-9]*\.[0-9]+))
进一步扩展,允许浮点风格的指数。先定义指数的正则表达式:
[eE][-+]?[0-9]+
表达式允许大写或者小写的字母E,然后是可选的正号或负号,以及一个整数。下面把它们组合在一起,表达一个实数的正则表达式:
-?(([0-9]+)([0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?)
其中的指数部分是可选的。

例2:另一个用于脚本和简单的配置文件常见的正则表达式,匹配以“#”开始的注释:
#.*

例3:下面是匹配引用字符串的正则表达式:
\"[^"\n]*["\n]

lex/yacc系列:简介

lex和yacc用来帮助编写程序,对结构化的输入进行转换


lex的任务是将结构化的输入分割成有意义的单元(通常称为标记,token),这一过程也称为词法分析(lexical analysis,或者简称为lexing)。lex使用正则表达式(regular expression)作为标记描述。


当将输入拆分为标记时,程序通常需要建立标记之间的关系,这个过程称为分析(parsing),即定义程序能够理解的关系的规则列表,也就是语法(grammar)。yacc采用简明的语法描述,产生一个能分析语法的C例程,即分析程序(parser)。