小心dstat的磁盘统计BUG

最近用 dstat 看系统 IO,发现磁盘读取远高于预期,在代码里摸爬了好久,实在找不出代码有什么问题;

为了排除了系统 cache 预取的可能,先禁用 cache 预取 (详见此文

echo 0 > /sys/block/sda/queue/read_ahead_kb

还找了个测试脚本(详见此贴):

FILENAME=/tmp/test.tmp
BLOCKSIZE=1M
BLOCKCOUNT=100

echo ‘— Write —‘
dd if=/dev/zero of=$FILENAME bs=$BLOCKSIZE count=$BLOCKCOUNT conv=fsync

echo 3 > /proc/sys/vm/drop_caches
echo 0 > /proc/sys/vm/drop_caches

echo ‘—Read —‘
dd if=$FILENAME of=/dev/zero bs=$BLOCKSIZE count=$BLOCKCOUNT

rm -f $FILENAME

测试结果发现同样会有问题,看来自己的程序问题和系统 cache 原因都被排除了,
在绝望中换用 iostat 和 vmstat 观察,发现结果和 dstat 不同。

经过一番研究,终于发现,原来是 dstat 的 BUG,导致统计值翻倍,此 BUG 在 0.7.0 以后版本中已得到修复,而我用的是在 CentOS5 中用 yum 装的 0.6.6 版。

the problem likely is that /proc/diskstats in newer kernels also
provide proper stats for partitions, which it doesn’t do on older kernels:

[dag@rhun ~]$ cat /proc/diskstats | grep sda
8 0 sda 196482 22091 3600800 1524365 304150 614172 7355412 45110870 0 1321463 46646670
8 1 sda1 1033 10862 2 4
8 2 sda2 215888 3587367 919426 7355408
8 3 sda3 1199 1411 0 0
8 4 sda4 427 872 0 0

另外 iostat 和 vmstat 也让很人郁闷,这两个工具默认显示的是 block 数而非 IO 的字节数,而且两个工具所使用的 block size 并不一致。

iostat 默认的 block size是 512 bytes,详见man page;使用 iostat 可以加 -k 或 -m 直接显示字节数。

Blk_read/s
Indicate the amount of data read from the device expressed in a number of blocks per second. Blocks are equivalent to sectors  with  2.4  kernels  and
newer and therefore have a size of 512 bytes. With older kernels, a block is of indeterminate size.

vmstat 默认的 block size 是 1024 bytes, 详见此文


VBoxHeadlessTray 让 VirtualBox 隐藏在系统托盘

VirtualBox很好用,可惜原生不支持像 VMWare 哪样在后台运行。即使使用 headless 模式也会有个残留的 console 窗口,很讨厌。
VirtualBox 论坛里找到这个不错的工具,解决了我的烦恼,希望也能解决你的:)
http://www.toptensoftware.com/VBoxHeadlessTray/

编译libpcap中的几个概念(flex/bison/ar/ranlib)

Flex

  词法分析器生成器

  flex (fast lexical analyser generator) 是 Lex 的另一个替代品。它经常和自由软件 Bison语法分析器生成器 一起使用。Flex 最初由 Vern Paxson 于 1987 年用 C语言 写成。

  Flex 手册里对 Flex 描述如下:

  “flex是一个生成扫描器的工具,能够识别文本中的词法模式。flex读入给定的输入文件, 如果没有给定文件名的话,则从标准输入读取,从而获得一个关于需要生成的扫描器的描述。此描述叫做 规则,由正则表达式和 C代码对组成。flex 的输出是一个 C 代码文件——lex.yy.c——其中定义了 yylex() 函数。编译输出文件并且和 -lfl库链接生成一个可执行文件。当运行可执行文件的时候,它分析输入文件,为每一个正则表达式寻找匹配。当发现一个匹配时,它执行与此正则表达式相关 的 C代码。”

   一个相似的,用 C++语言 的词法分析器生成器是 flex++,包含在 flex 软件包里。

   Flex 不是 GNU 工程,但是 GNU 为 Flex 写了手册。

Bison

  GNU bison 是属于 GNU 项目的一个语法分析器生成器。Bison 把一个关于“向前查看 从左到右 最右”(LALR) 上下文无关文法的描述转化成可以分析该文法的 CC++ 程序。它也可以为二义文法生成 “通用的 从左到右 最右” (GLR)语法分析器。

  Bison 基本上与Yacc兼容,并且在 Yacc 之上进行了改进。它经常和Flex(一个自动的词法分析器生成器)一起使用。

   此软件的源代码是可自由获得的,在GPL下发布。

 

ar

1、ar基本用法

ar命令可以用来创建、修改库,也可以从库中提出单个模块。库是一单独的文件,里面包含了按照特定的结构组织起来的其它的一些文件(称做此库文件的member)。原始文件的内容、模式、时间戳、属主、组等属性都保留在库文件中。

下面是ar命令的格式:
ar[-][abcfilNoPsSuvV][membername][count]archivefiles…

例如我们可以用ar rvl ibtest.a hello.o hello1.o来生成一个库,库名字是test,链接时可以用-ltest链接。该库中存放了两个模块hello.o和hello1.o。选项前可以有 ‘-’字符,也可以没有。下面我们来看看命令的操作选项和任选项。现在我们把部分称为操作选项,而[abcfilNoPsSuvV]部分称为任选项。

中的操作选项在命令中只能并且必须使用其中一个,它们的含义如下:
d:从库中删除模块。按模块原来的文件名指定要删除的模块。如果使用了任选项v则列出被删除的每个模块。

m:该操作是在一个库中移动成员。当库中如果有若干模块有相同的符号定义(如函数定义),则成员的位置顺序很重要。如果没有指定任选项,任何指定的成员将移到库的最后。也可以使用’a’,’b’,或’I’任选项移动到指定的位置。

p:显示库中指定的成员到标准输出。如果指定任选项v,则在输出成员的内容前,将显示成员的名字。如果没有指定成员的名字,所有库中的文件将显示出来。

q:快速追加。增加新模块到库的结尾处。并不检查是否需要替换。’a’,’b’,或’I’任选项对此操作没有影响,模块总是追加的库的结尾处。如果使用了任选项v则列出每个模块。这时,库的符号表没有更新,可以用’ars’或ranlib来更新库的符号表索引。

r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置。

t:显示库的模块表清单。一般只显示模块名。

x:从库中提取一个成员。如果不指定要提取的模块,则提取库中所有的模块。

下面在看看可与操作选项结合使用的任选项:
a:在库的一个已经存在的成员后面增加一个新的文件。如果使用任选项a,则应该为命令行中membername参数指定一个已经存在的成员名。

b:在库的一个已经存在的成员前面增加一个新的文件。如果使用任选项b,则应该为命令行中membername参数指定一个已经存在的成员名。

c:创建一个库。不管库是否存在,都将创建。

f:在库中截短指定的名字。缺省情况下,文件名的长度是不受限制的,可以使用此参数将文件名截短,以保证与其它系统的兼容。

i:在库的一个已经存在的成员前面增加一个新的文件。如果使用任选项i,则应该为命令行中membername参数指定一个已经存在的成员名(类似任选项b)。

l:暂未使用

N:与count参数一起使用,在库中有多个相同的文件名时指定提取或输出的个数。

o:当提取成员时,保留成员的原始数据。如果不指定该任选项,则提取出的模块的时间将标为提取出的时间。

P:进行文件名匹配时使用全路径名。ar在创建库时不能使用全路径名(这样的库文件不符合POSIX标准),但是有些工具可以。

s:写入一个目标文件索引到库中,或者更新一个存在的目标文件索引。甚至对于没有任何变化的库也作该动作。对一个库做ars等同于对该库做ranlib。

S:不创建目标文件索引,这在创建较大的库时能加快时间。

u:一般说来,命令arr…插入所有列出的文件到库中,如果你只想插入列出文件中那些比库中同名文件新的文件,就可以使用该任选项。该任选项只用于r操作选项。

v:该选项用来显示执行操作选项的附加信息。

V:显示ar的版本。

 

ranlib

ranlib [   -t ] [   -X {32|64|32_64}] Archive …

描述
ranlib 命令将每个 Archive 库转换到随机库。随机库是一个包含符号表的归档库。

如果给出了 -t 选项,ranlib 命令只提到归档而不会修改它们。复制一个归档之后,或者为了避免 ld 命令显示关于过期符号表的错误消息而使用 make 命令的 -t 选项的时候,这是很有用的。

标志

-t 提到指定的归档而不修改它们。
-X mode 指定 ranlib 要检查的目标文件的类型。mode 必须是下列之一:
32
仅仅处理 32 位的目标文件
64
仅仅处理 64 位的目标文件
32_64
处理 32 位和 64 位的目标文件
缺省是处理 32 位目标文件(忽略 64 位的目标文件)。mode 也能与 OBJECT_MODE 环境变量一起设置。例如,OBJECT_MODE=64 使得 ranlib 处理任何的 64 位目标文件而忽略 32 位目标文件。-X 标志覆盖了 OBJECT_MODE 变量。

gotopt_long()

帖两篇getopt_long参考文章。
 
其一:getopt_long()函数的作用
http://hi.baidu.com/houhl/blog/item/dcbe702c459520ee8a1399de.html
2007-07-26 09:47
Linux系统下,需要大量的命令行选项,如果自己手动解析他们的话实在是有违软件复用的思想,不过还好,GNU C library留给我们一个解析命令行的接口(X/Open规范),好好使用它可以使你的程序改观不少。

使用getopt_long()需要引入头文件

#include <getopt.h>

     现在我们使用一个例子来说明它的使用。

一个应用程序需要如下的短选项和长选项。

      短选项               长选项                           作用

      -h                      –help                           输出程序命令行参数说明然后退出
      -o filename        –output filename      给定输出文件名
      -v                      –version                       显示程序当前版本后退后

为了使用getopt_long函数,我们需要先确定两个结构:

1.一个字符串,包括所需要的短选项字符,如果选项后有参数,字符后加一个":"符号。本例中,这个字符串应该为"ho:v"。(因为-o后面有参数filename,所以字符后面要加":")

2.一个包含长选项字符串的结构体数组,每一个结构体包含4个域,第一个域为长选项字符串,第二个域是一个标识,只能为0或1,分别代表没有、有。 第三个域永远为NULL。第四个域为对应的短选项字符串。结构体数组的最后一个元素全部为NULL和0,标识结束。在本例中,它应该像一下的样子:

     const struct option long_options[] = {
         { "help",        0, NULL, ‘h’ },
         { "output",      1, NULL, ‘o’ },
         { "version", 0, NULL, ‘v’ },
         { NULL,          0, NULL, 0}
      };

调用时需要把main的两个参数argc和argv以及上述两个数据结构传给getopt_long。
每次调用getopt_long,它会解析一个符号,返回相应的短选项字符,如果解析完毕返回-1。所以需要使用一个循环来处理所有的参数,而相应的循环 里会使用switch语句进行选择。如果getopt_long遇到一个无效的选项字符,它会打印一个错误消息并且返回’?’,很多程序会打印出帮助信息 并且中止运行;当getopt_long解析到一个长选项并且发现后面没有参数则返回’:’,表示缺乏参数。当处理一个参数时,全局变量optarg指向 下一个要处理的变量。当getopt_long处理完所有的选项后,全局变量optind指向第一个未知的选项索引。

这一个例子代码为下:

 

//编译使用gcc -o getopt_long getopt_long.c
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>

/*程序的名字*/
const char* program_name;

/* 打印程序参数 */
void print_usage (FILE* stream, int exit_code)
{
fprintf (stream, "Usage: %s options [ inputfile … ]n", program_name);
fprintf (stream, " -h –help                       显示这个帮助信息.n"
                             " -o –output filename 将输出定位到文件.n"
                             " -v –version                  打印版本信息.n");
exit (exit_code);
}

/* 主程序 */
int main (int argc, char* argv[])
{
int next_option;//下一个要处理的参数符号
int haveargv = 0;//是否有我们要的正确参数,一个标识
           
/* 包含短选项字符的字符串,注意这里的‘:’ */
          
const char* const short_options = "ho:v";
              
/* 标识长选项和对应的短选项的数组 */
              
const struct option long_options[] = {
                   { "help",        0, NULL, ‘h’ },
                   { "output",      1, NULL, ‘o’ },
                   { "version", 0, NULL, ‘v’ },
                   { NULL,         0, NULL, 0     }};//最后一个元素标识为NULL
                    
    /* 此参数用于承放指定的参数,默认为空 */
const char* output_filename = NULL;
/* 一个标志,是否显示版本号 */
int verbose = 0;

/* argv[0]始终指向可执行的文件文件名 */
                             
program_name = argv[0];
    
do
{
    next_option = getopt_long (argc, argv, short_options, long_options, NULL);
    switch (next_option)
    {
     case ‘h’:     /* -h or –help */    
       haveargv = 1;
       print_usage (stdout, 0);
     case ‘o’:     /* -o or –output */
         /* 此时optarg指向–output后的filename */
      output_filename = optarg;
      haveargv = 1;
      break;
     case ‘v’:     /* -v or    –version */
      verbose = 1;
      haveargv = 1;
      break;
     case ‘:’:     /* 缺乏长选项内容 */
      break;
     case ‘?’:     /* 出现一个未指定的参数*/
      print_usage (stderr, 1);
     case -1:      /* 处理完毕后返回-1 */
             if (!haveargv)
             {
                   print_usage (stderr, 1);
             }
      break;
     default:      /* 未指定的参数出现,出错处理 */
      print_usage (stderr, 1);
                                  break;
    }
}while (next_option !=-1);
                            
if (verbose)
{
    int i;
    for (i = optind; i < argc; ++i)
    printf ("Argument: %sn", argv[i]);
}                           
                        
return 0;
}

 

其二: getopt 函数
http://hi.baidu.com/jenny_tat/blog/item/31487718802b7db14aedbcce.html

函数定义:
#include
int getopt(int argc, char * const argv[],
        const char *optstring);

extern char *optarg;
extern int optind, opterr, optopt;

#define _GNU_SOURCE
#include

int getopt_long(int argc, char * const argv[],
        const char *optstring,
        const struct option *longopts,
        int *longindex);

int getopt_long_only(int argc, char * const argv[],
        const char *optstring,
        const struct option *longopts,
        int *longindex);

getopt()函数是用来解析命令行参数的。这里,主要解释getopt_long()。

    getopt_long()的头两参数,argc和argv分别是传递给main()的参数的个数和参数数组(和main()的argc和argv是一个概念)。

    getopt_long()中,optstring是一个字符串,表示可以接受的参数。例如,"a:b:cd",表示可以接受的参数是a,b,c,d,其中,a和b参数后面

跟有更多的参数值。(例如:-a host –b name)

    getopt_long()中,参数longopts,其实是一个结构的实例:
struct option {
const char *name;
   
//name表示的是长参数名
int has_arg;
    //has_arg有3个值,no_argument(或者是0),表示该参数后面不跟参数值
    //   required_argument(或者是1),表示该参数后面一定要跟个参数值
    //   optional_argument(或者是2),表示该参数后面可以跟,也可以不跟参数值
int *flag;
    //用来决定,getopt_long()的返回值到底是什么。如果flag是null,则函数会返回与该项option匹配的val值
int val;
    //和flag联合决定返回值
}

给个例子:

struct option long_options[] = {
{"a123",       required_argument,      0, ‘a’},
{"c123",       no_argument,            0, ‘c’},
}

现在,如果命令行的参数是-a 123,那么调用getopt_long()将返回字符’a’,并且将字符串123由optarg返回(注意注意!字符串123由optarg带

回!optarg不需要定义,在getopt.h中已经有定义)
那么,如果命令行参数是-c,那么调用getopt_long()将返回字符’c’,而此时,optarg是null。

最后,当getopt_long()将命令行所有参数全部解析完成后,返回-1。

看来,我说的有点混乱,那么,看个例子,我相信,代码最能说明问题:

#include
#include
#include
#include

int main( int argc, char **argv )
{

struct option long_options[] = {
   {"a123",       required_argument,      0, ‘a’},
   {"c123",       no_argument,            0, ‘c’},
}
int opt;

printf("starting… ");

while((opt = getopt_long(argc, argv, "a:c", long_options, NULL)) != -1)
{
switch (opt)
{
case ‘a’:
    printf("It’s a! ");
    printf("string of a:%s ",optarg);
break;
    
case ‘c’:
    printf("It’s c! ");
break;
    
default:
    printf("You should look for help! ");
    exit(1);
break;            
}
}
printf("end… ");
return 0;
}

编译后,假设生成a.out,可以试验一下。
./a.out -a hello -c
输出:
starting…
It’s a!
string of a:hello
It’s c!
end…

Linux命令行程序设计

Linux下很多程序甚至那些具有图形用户界面(graphical user interface,GUI)的程序,都能接受和处理命令行选项。对于某些程序,这是与其他程序或用户进行交互的主要手段。具有可靠的复杂命令行参数处理 机制,会使得您的应用程序更好、更有用。不过很多开发人员都将其宝贵的时间花在了编写自己的命令行解析器,却不使用 getopt(),而后者是一个专门设计来减轻命令行处理负担的库函数。

1、命令行参数

命令行程序设计的首要任务是解析命令行参数,GUI派的程序员很少关心这个。这里,我们对参数(argument)采用了一种比较通俗的定义:命令行上除命令名之外的字符串。参数由多项构成,项与项之间用空白符彼此隔开。
参数进一步分为选项和操作数。选项用于修改程序的默认行为或为程序提供信息,比较老的约定是以短划线开头。选项后可以跟随一些参数,称为选项参数。剩下的就是操作数了。

2、POSIX约定

POSIX表示可移植操作系统接 口:Portable Operating System Interface,电气和电子工程师协会(Institute of Electrical and Electronics Engineers,IEEE)最初开发 POSIX 标准,是为了提高 UNIX 环境下应用程序的可移植性。然而,POSIX 并不局限于 UNIX。许多其它的操作系统,例如 DEC OpenVMS 和 Microsoft Windows NT,都支持 POSIX 标准。

下面是POSIX标准中关于程序名、参数的约定:

  • 程序名不宜少于2个字符且不多于9个字符;
  • 程序名应只包含小写字母和阿拉伯数字;
  • 选项名应该是单字符活单数字,且以短横‘-‘为前綴;
  • 多个不需要选项参数的选项,可以合并。(譬如:foo -a -b -c —->foo -abc)
  • 选项与其参数之间用空白符隔开;
  • 选项参数不可选。
  • 若选项参数有多值,要将其并未一个字串传进来。譬如:myprog -u "arnold,joe,jane"。这种情况下,需要自己解决这些参数的分离问题。
  • 选项应该在操作数出现之前出现。
  • 特殊参数‘–‘指明所有参数都结束了,其后任何参数都认为是操作数。
  • 选项如何排列没有什么关系,但对互相排斥的选项,如果一个选项的操作结果覆盖其他选项的操作结果时,最后一个选项起作用;如果选项重复,则顺序处理。
  • 允许操作数的顺序影响程序行为,但需要作文档说明。
  • 读写指定文件的程序应该将单个参数’-‘作为有意义的标准输入或输出来对待。

当然许多标准从恒许未遵守以上约定,主要是历史兼容问题,因为标准出现之前,就已经存在N多程序了。

3、GNU长选项

GNU鼓励程序员使用–help、–verbose等形式的长选项。这些选项不仅不与POSIX约定冲突,而且容易记忆,另外也提供了在所有GNU工具之间保持一致性的机会。GNU长选项有自己的约定:

  • 对于已经遵循POSIX约定的GNU程序,每个短选项都有一个对应的长选项。
  • 额外针对GNU的长选项不需要对应的短选项,仅仅推荐要有。
  • 长选项可以缩写成保持惟一性的最短的字串。
  • 选项参数与长选项之间或通过空白字符活通过一个’=’来分隔。
  • 选项参数是可选的(只对短选项有效)。
  • 长选项允许以一个短横线为前缀。

4、基本的命令行处理技术

C程序通过argc和argv参数访问它的命令行参数。argc是整型数,表示参数的个数(包括命令名)。main()函数的定义方式有两种,区别仅在于argv如何定义:

int main(int argc, char *argv[])
{
   ……
}
int main(int argc, char **argv)
{
   ……
}

当 C 运行时库的程序启动代码调用您的 main() 时,已经对命令行进行了处理。argc 参数包含参数的计数值,而 argv 包含指向这些参数的指针数组。argv[0]是程序名。

一个很简单的命令行处理技术的例子是echo程序,它可以将参数输出到标准设备上,用空格符隔开,最后换行。若命令行第一个参数为-n,那么就不会换行。

清单1:

#include <stdio.h>

int main(int argc, char **argv)
{
    int i, nflg;

    nflg = 0;
    if(argc > 1 && argv[1][0] == ‘-‘ && argv[1][1] == ‘n’){

        nflg++;

        argc–;

        argv++;

    }
    for(i=1; i<argc; i++){
        fputs(argv[i], stdout);
        if(i < argc-1)
            putchar(‘ ‘);
    }
    if(nflg == 0)
        putchar(‘n’);

    return 0;
}

上面代码中,加亮区域仔细研究一下,会发现很有趣。

echo程序中,对于命令行参数的解析是手动实现的。很久以前,Unix支持小组为了简化对于命令行参数的解析,开发了getopt()函数,同时提供了几个外部变量,使得编写遵守POSIX的代码变得更加容易了。

5、命令行参数解析函数 —— getopt()

getopt()函数声明如下:

#include <unistd.h>

int getopt(int argc, char * const argv[], const char *optstring);

extern char *optarg;
extern int optind, opterr, optopt;

该函数的argc和argv参数通常直接从main()的参数直接传递而来。optstring是选项字母组成的字串。如果该字串里的任一字符后面有冒号,那么这个选项就要求有选项参数。

当给定getopt()命令参数的数量 (argc)、指向这些参数的数组 (argv) 和选项字串 (optstring) 后,getopt() 将返回第一个选项,并设置一些全局变量。使用相同的参数再次调用该函数时,它将返回下一个选项,并设置相应的全局变量。如果不再有可识别的选项,将返回 -1,此任务就完成了。

getopt() 所设置的全局变量包括:

  • char *optarg——当前选项参数字串(如果有)。
  • int optind——argv的当前索引值。当getopt()在while循环中使用时,循环结束后,剩下的字串视为操作数,在argv[optind]至argv[argc-1]中可以找到。
  • int opterr——这个变量非零时,getopt()函数为“无效选项”和“缺少参数选项,并输出其错误信息。
  • int optopt——当发现无效选项字符之时,getopt()函数或返回’?’字符,或返回’:’字符,并且optopt包含了所发现的无效选项字符。

下面就用getopt()来写个小程序,体验一下命令行解析的快乐。

程序描述:

程序名:opt_parse_demo

选项:

  • -n —— 显示我的名字。
  • -g —— 显示我女朋友的名字。
  • -l —— 带参数的选项.

清单2:

#include <stdio.h>
#include <unistd.h>

int main (int argc, char **argv)
{
    int oc;                     /*选项字符 */
    char *b_opt_arg;            /*选项参数字串 */

    while((oc = getopt(argc, argv, "ngl:")) != -1)
    {
        switch(oc)
        {
            case ‘n’:
                printf("My name is Lyong.n");
                break;
            case ‘g’:
                printf("Her name is Xxiong.n");
                break;
            case ‘l’:
                b_opt_arg = optarg;
                printf("Our love is %sn", optarg);
                break;
        }
    }
   return 0;
}

运行结果:

$ ./opt_parse_demo -n
My name is Lyong.
$ ./opt_parse_demo -g
Her name is Xxiong.
$ ./opt_parse_demo -l forever
Our love is forever
$ ./opt_parse_demo -ngl forever
My name is Lyong.
Her name is Xxiong.
Our love is forever

6、改变getopt()对错误命令行参数信息的输出行为

不正确的调用程序在所难免,这种错误要么是命令行选项无效,要么是缺少选项参数。正常情况下,getopt()会为这两种情况输出自己的出错信息,并且返回’?’。为了验证此事,可以修改一下上面的清单2中的代码。

清单3:

#include <stdio.h>
#include <unistd.h>

int main (int argc, char **argv)
{
    int oc;                     /*选项字符 */
    char *b_opt_arg;            /*选项参数字串 */

    while((oc = getopt(argc, argv, "ngl:")) != -1)
    {
        switch(oc)
        {
            case ‘n’:
                printf("My name is Lyong.n");
                break;
             case ‘g’:
                printf("Her name is Xxiong.n");
                break;
            case ‘l’:
                b_opt_arg = optarg;
                printf("Our love is %sn", optarg);
                break;
            case ‘?’:
                printf("arguments error!n");
                break;
        }
    }
    return 0;
}

输入一个错误的命令行,结果如下:

$ ./opt_parse_demo -l
./opt_parse_demo: option requires an argument — l
arguments error!

很多时候,我们不希望输出任何错误信息,或更希望输出自己定义的错误信息。可以采用以下两种方法来更改getopt()函数的出错信息输出行为:

  1. 在调用getopt()之前,将opterr设置为0,这样就可以在getopt()函数发现错误的时候强制它不输出任何消息。
  2. 如果optstring参数的第一个字符是冒号,那么getopt()函数就会保持沉默,并根据错误情况返回不同字符,如下:
    • “无效选项” —— getopt()返回’?’,并且optopt包含了无效选项字符(这是正常的行为)。
    • “缺少选项参数” —— getopt()返回’:’,如果optstring的第一个字符不是冒号,那么getopt()返回’?’,这会使得这种情况不能与无效选项的情况区分开。

多说无益,动手测试一下。

清单4:

#include <stdio.h>
#include <unistd.h>

int main (int argc, char **argv)
{
    int oc;                     /*选项字符 */
    char ec;                             /*无效的选项字符*/
    char *b_opt_arg;            /*选项参数字串 */

    while((oc = getopt(argc, argv, ":ngl:")) != -1)
    {
        switch(oc)
        {
            case ‘n’:
                printf("My name is Lyong.n");
                break;
             case ‘g’:
                printf("Her name is Xxiong.n");
                break;
            case ‘l’:
                b_opt_arg = optarg;
                printf("Our love is %sn", optarg);
                break;
            case ‘?’:
                ec = (char)optopt;
                printf("无效的选项字符 ‘ %c ‘!n", ec);
                break;
            case ‘:’:
                printf("缺少选项参数!n");
                break;
        }
    }
    return 0;
}

测试结果:

$ ./opt_parse_demo -a
无效的选项字符 ‘ a ‘!
$ ./opt_parse_demo -l
缺少选项参数!

7、GNU提供的getopt()函数的特点

上 面所设计的getopt()函数是UNIX支持小组提供的,其执行时一碰到不以’-‘开始的命令行参数就停止寻找选项。而GNU提供的getopt()函 数与之不同,它会扫描整个命令行来寻找选项。当调用GNU getopt()函数并处理命令行参数的时候,它重新排列argv中的元素,这样当重排结束时,所有选项都被移动到前面并且那些继续检查 argv[optind]至argv[argc-1]中剩余参数的代码仍正常工作,但在任何情况下,碰到特殊参数’–‘就结束对选项的扫描。

可以输入一个乱序的命令行,查看opt_parse_demo的输出:

$ ./opt_parse_demo -l forever a b c d -g -n
Our love is forever
Her name is Xxiong.
My name is Lyong.

GNU getopt()第二个特点是可以在optstring中使用特殊的首字符改变getopt()的默认行为:

  • optstring[0] = ‘+’,这样就与UNIX支持小组提供的getopt()很相近了。
  • optstring[0] = ‘-‘,会在optarg中得到命令行中的每个参数。
  • 以上两种情况下,’:’可以作为第二个字符使用。

GNU getopt()第三个特点是optstring中的选项字符后面接两个冒号,就允许该选项有可选的选项参数。在选项参数不存在的情况下,GNU getopt()返回选项字符并将optarg设置为NULL。

8、GNU长选项命令行解析

20 世纪 90 年代,UNIX 应用程序开始支持长选项,即一对短横线、一个描述性选项名称,还可以包含一个使用等号连接到选项的参数。

GNU提供了getopt-long()和getopt-long-only()函数支持长选项的命令行解析,其中,后者的长选项字串是以一个短横线开始的,而非一对短横线。

getopt_long() 是同时支持长选项和短选项的 getopt() 版本。下面是它们的声明:

#include <getopt.h>

int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex);

int getopt_long_only(int argc, char * const argv[],const char *optstring,const struct option *longopts, int *longindex);
 

getopt_long() 的前三个参数与上面的getopt()相同,第4个参数是指向option结构的数组,option结构被称为“长选项表”。longindex参数如果 没有设置为NULL,那么它就指向一个变量,这个变量会被赋值为寻找到的长选项在longopts中的索引值,这可以用于错误诊断。

option结构在getopt.h中的声明如下:

struct option{
    const char *name;
    int has_arg;
    int *flag;
    int val;
};

对结构中的各元素解释如下:

const char *name

这是选项名,前面没有短横线。譬如"help"、"verbose"之类。

int has_arg

描述了选项是否有选项参数。如果有,是哪种类型的参数,此时,它的值一定是下表中的一个。
符号常量  数值  含义 
no_argument  0 选项没有参数 
required_argument 1 选项需要参数
optional_argument 2 选项参数可选 

int *flag

如 果这个指针为NULL,那么getopt_long()返回该结构val字段中的数值。如果该指针不为NULL,getopt_long()会使得它所指 向的变量中填入val字段中的数值,并且getopt_long()返回0。如果flag不是NULL,但未发现长选项,那么它所指向的变量的数值不变。

int val

这 个值是发现了长选项时的返回值,或者flag不是NULL时载入*flag中的值。典型情况下,若flag不是NULL,那么val是个真/假值,譬如1 或0;另一方面,如果flag是NULL,那么val通常是字符常量,若长选项与短选项一致,那么该字符常量应该与optstring中出现的这个选项的 参数相同。

每个长选项在长选项表中都有一个单独条目,该条目里需要填入正确的数值。数组中最后的元素的值应该全是0。数组不需要排序,getopt_long()会进行线性搜索。但是,根据长名字来排序会使程序员读起来更容易。

以上所说的flag和val的用法看上去有点混乱,但它们很有实用价值,因此有必要搞透彻了。

大部分时候,程序员会根据getopt_long()发现的选项,在选项处理过程中要设置一些标记变量,譬如在使用getopt()时,经常做出如下的程序格式:

int do_name, do_gf_name, do_love; /*标记变量*/
char *b_opt_arg;

while((c = getopt(argc, argv, ":ngl:")) != -1)
{
    switch (c){
    case ‘n’:
        do_name = 1;
    case ‘g’:
        do_gf_name = 1;
        break;
        break;
    case ‘l’:
        b_opt_arg = optarg;
    ……
    }
}

当 flag不为NULL时,getopt_long*()会为你设置标记变量。也就是说上面的代码中,关于选项’n’、’l’的处理,只是设置一些标记,如 果flag不为NULL,时,getopt_long()可以自动为各选项所对应的标记变量设置标记,这样就能够将上面的switch语句中的两种种情况 减少到了一种。下面给出一个长选项表以及相应处理代码的例子。

清单5:

#include <stdio.h>
#include <getopt.h>

int do_name, do_gf_name;
char *l_opt_arg;

struct option longopts[] = {
    { "name",        no_argument,            &do_name,        1    },
    { "gf_name",    no_argument,            &do_gf_name,    1    },
    { "love",        required_argument,    NULL,                ‘l’    },
    {     0,    0,    0,    0},
};

int main(int argc, char *argv[])
{
    int c;
   
    while((c = getopt_long(argc, argv, ":l:", longopts, NULL)) != -1){
        switch (c){
        case ‘l’:
            l_opt_arg = optarg;
            printf("Our love is %s!n", l_opt_arg);
            break;
        case 0:
            printf("getopt_long()设置变量 : do_name = %dn", do_name);
            printf("getopt_long()设置变量 : do_gf_name = %dn", do_gf_name);
            break;
        }
    }
    return 0;
}

在进行测试之前,再来回顾一下有关option结构中的指针flag的说明吧。

如果这个指针为NULL,那么getopt_long()返回该结构val字段中的数值。如果该指针不为NULL,getopt_long()会使得它所 指向的变量中填入val字段中的数值,并且getopt_long()返回0。如果flag不是NULL,但未发现长选项,那么它所指向的变量的数值不 变。

下面测试一下:

$ ./long_opt_demo –name
getopt_long()设置变量 : do_name = 1
getopt_long()设置变量 : do_gf_name = 0

$ ./long_opt_demo –gf_name
getopt_long()设置变量 : do_name = 0
getopt_long()设置变量 : do_gf_name = 1

$ ./long_opt_demo –love forever
Our love is forever!

$ ./long_opt_demo -l forever
Our love is forever!
 

测试过后,应该有所感触了。关于flag和val的讨论到此为止。下面总结一下get_long()的各种返回值的含义:

返回值    含 义
0      getopt_long()设置一个标志,它的值与option结构中的val字段的值一样
1 每碰到一个命令行参数,optarg都会记录它
‘?’ 无效选项
‘:’ 缺少选项参数
‘x’ 选项字符’x’
-1 选项解析结束

从实用的角度来说,我们更期望每个长选项都对应一个短选项,这种情况下,在option结构中,只要将flag设置为NULL,并将val设置为长选项所对应的短选项字符即可。譬如上面清单5中的程序,我们修改如下。

清单6:

#include <stdio.h>
#include <getopt.h>

int do_name, do_gf_name;
char *l_opt_arg;

struct option longopts[] = {
    { "name",        no_argument,            NULL,                ‘n’    },
    { "gf_name",    no_argument,            NULL,                ‘g’    },
    { "love",        required_argument,    NULL,                ‘l’    },
    {     0,    0,    0,    0},
};

int main(int argc, char *argv[])
{
    int c;
    
    while((c = getopt_long(argc, argv, ":l:", longopts, NULL)) != -1){
        switch (c){
        case ‘n’:
            printf("My name is LYR.n");
            break;
        case ‘g’:
            printf("Her name is BX.n");
            break;
        case ‘l’:
            l_opt_arg = optarg;
            printf("Our love is %s!n", l_opt_arg);
            break;
        }
    }
    return 0;
}

测试结果如下:

$ ./long_opt_demo –name –gf_name –love forever
My name is LYR.
Her name is BX.
Our love is forever!

$ ./long_opt_demo -ng -l forever
My name is LYR.
Her name is BX.
Our love is forever!

9、在LINUX之外的系统平台上使用GNU getopt()或getopt_long()

只 要从GNU程序或GNU C Library(GLIBC)的CVS档案文件中copy源文件即可(http://sourceware.org/glibc/)。所需源文件是 getopt.h、getopt.c和getoptl.c,将这些文件包含在你的项目中。另外,你的项目中最好也将COPYING.LIB文件包含进去, 因为

GNU LGPL(GNU 程序库公共许可证)的内容全部包括在命名为COPYING.LIB 的文件中。没事时,可以阅读一下自由软件的许可协议,感受一下雷锋精神。

注意,在包含所需文件之后,在调用getopt_long()系列函数的源代码中,应该使用#include "getopt.h",而不是#include <getopt.h>,前者会首先在当前目录中寻找getopt.h。

使用Windows平台的兄弟可以试用一下,很希望能在这里反馈一下你的使用情况,谢谢。

10、结论

程 序需要能够快速处理各个选项和参数,且要求不会浪费开发人员的太多时间。在这一点上,无论是GUI(图形用户交互)程序还是CUI(命令行交互)程序,都 是其首要任务,其区别仅在于实现方式的不同。GUI通过菜单、对话框之类的图形控件来完成交互,而CUI使用了纯文本的交互方式。各有利弊吧。在程序开发 中,许多测试程序用CUI来完成是首选方案。

getopt() 函数是一个标准库调用,可允许您使用直接的 while/switch 语句方便地逐个处理命令行参数和检测选项(带或不带附加的参数)。与其类似的 getopt_long() 允许在几乎不进行额外工作的情况下处理更具描述性的长选项,这非常受开发人员的欢迎。

既然已经知道了如何方便地处理命令行选项,现在就可以集中精力改进您的程序的命令行,可以添加长选项支持,或添加之前由于不想向程序添加额 外的命令行选项处理而搁置的任何其他选项。但不要忘记在某处记录您所有的选项和参数,并提供某种类型的内置帮助函数来为健忘的用户提供帮助。

参考资料

kernel_thread 的使用

可以在非内核线程中调用kernel_thread, 但这样创建的线程必须在自己调用daemonize(…)来释放资源, 成为真正的内核线程。

#include <linux/kernel.h>
#include <linux/module.h>

static int noop(void *dummy)
{
int i = 0;
daemonize("mythread");
while(i++ < 5) {
printk("current->mm = %pn", current->mm);
printk("current->active_mm = %pn", current->active_mm);
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(10 * HZ);
}
return 0;
}

static int test_init(void)
{
kernel_thread(noop, NULL, CLONE_KERNEL | SIGCHLD);
return 0;
}

static void test_exit(void) {}
module_init(test_init);
module_exit(test_exit);

”mythread“就是给这个内核线程取的名字, 可以用ps -A来查看。
schedule()用于进程调度, 可以理解为放弃CPU的使用权.

kernel thread可以用kernel_thread创建,但是在执行函数里面必须用daemonize释放资源并挂到init下,还需要用completion等待这一过程的完成。
kthread_create是比较正牌的创建函数,这个不必要调用daemonize,用这个创建的kernel thread都挂在了kthread线程下。

cond_resched()函数

整理关于cond_resched的帖子:
来自http://linux.chinaunix.net/bbs/thread-1056984-1-1.html
 
1楼 发表于 2009-1-8 02:11 
 
// preempt_disable()会使preempt_count加1
#define preempt_disable()
do {
        inc_preempt_count();
        barrier();
} while (0)

//  只检查了PREEMPT_ACTIVE标志
int __sched _cond_resched(void)
{
        if (need_resched() && !(preempt_count() & PREEMPT_ACTIVE) &&
                                        system_state == SYSTEM_RUNNING) {
                __cond_resched();
                return 1;
        }
        return 0;
}

如上代码,假如先调用了preempt_disable使preempt_count加1,显示禁用了内核抢占,
接着调用了某个会调用cond_resched的函数,然后_cond_resched函数只会检查PREEMPT_ACTIVE
标志,对0~7位的抢占计数器视而不见,使得该函数会调用schedule(),发生内核抢占。从而
忽略掉preempt_disable对于内核抢占的影响。

不知道我以上推断是否正确?如果是这样,那为什么要这么设计啊?还请各位指教一下。

 
2楼 发表于 2009-1-8 23:27 
 
首先确认一点,  如果使用了preempt_disable(), 是不允许调用cond_resched的, 如果调用, schedule()应该会打印出错信息.
这个很好理解, cond_resched()的目的是提高系统实时性, 主动放弃cpu供优先级更高的任务使用, 如果调用了preempt_disable(), 不允许抢占, 就不应该调用有cond_resched()的函数.

2.6.28内核, schedule()会在这里捕获这个错误:
asmlinkage void __sched schedule(void)
{
        struct task_struct *prev, *next;
        unsigned long *switch_count;
        struct rq *rq;
        int cpu;

need_resched:
        preempt_disable();
        cpu = smp_processor_id();
        rq = cpu_rq(cpu);
        rcu_qsctr_inc(cpu);
        prev = rq->curr;
        switch_count = &prev->nivcsw;

        release_kernel_lock(prev);
need_resched_nonpreemptible:

        schedule_debug(prev);

static inline void schedule_debug(struct task_struct *prev)
{
       /* 如果preempt值不等于1, 说明出错, 等于1而不是0是因为schedule()中要关闭抢占, 在上面有这句 */
        if (unlikely(in_atomic_preempt_off() && !prev->exit_state))
                __schedule_bug(prev);

怎么解决这个preempt_disable()和cond_resched()的冲突?
preempt_disable()是一个最底层的接口,  如果你要使用这个接口关抢占, 必须要熟悉会调用cond_resched()的函数, 或者知道如何调试这个bug, 这种bug发现和解决应该不难.
一般都是通过mutex, spinlock, 关中断, 关软中断等方法, 顺带关抢占. 如果是上面的几种情况, 最新的内核提供:
extern int cond_resched_lock(spinlock_t * lock);
extern int cond_resched_softirq(void);
static inline int cond_resched_bkl(void);
来代替cond_resched(), 使得在不同情况下使用更安全.

printk()函数的总结

printk()函数的总结

我们在使用printk()函数中使用日志级别为的是使编程人员在编程过程中自定义地进行信息的输出,更加容易地掌握系统当前的状况。
对程序的调试起到了很重要的作用。
(下文中的日志级别和控制台日志控制级别是一个意思)

printk(日志级别 "消息文本");这里的日志级别通俗的说指的是对文本信息的一种输出范围上的指定。
日志级别一共有8个级别,printk的日志级别定义如下(在linux26/includelinux/kernel.h中):
#defineKERN_EMERG"<0>"/*紧急事件消息,系统崩溃之前提示,表示系统不可用*/
#defineKERN_ALERT"<1>"/*报告消息,表示必须立即采取措施*/
#defineKERN_CRIT"<2>"/*临界条件,通常涉及严重的硬件或软件操作失败*/
#defineKERN_ERR"<3>"/*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/
#defineKERN_WARNING"<4>"/*警告条件,对可能出现问题的情况进行警告*/
#defineKERN_NOTICE"<5>"/*正常但又重要的条件,用于提醒。常用于与安全相关的消息*/
#defineKERN_INFO"<6>"/*提示信息,如驱动程序启动时,打印硬件信息*/
#defineKERN_DEBUG"<7>"/*调试级别的消息*/

没有指定日志级别的printk语句默认采用的级别是 DEFAULT_ MESSAGE_LOGLEVEL(这个默认级别一般为<4>,即与KERN_WARNING在一个级别上),其定义在linux26/kernel/printk.c中可以找到。
下面是一个比较简单的使用
printk(KERN_INFO "INFOn");  //这里可以使用数字代替 KERN_INFO,即可以写成printk(<6> "INFOn");  
在这个格式的定义中,日志级别和信息文本之间不能够使用逗号隔开,因为系统在进行编译的时候,将日志级别转换成字符串于后面的文本信息进行连接。

在对系统输出进行控制时,主要是讨论控制台和伪终端的输情况,以及系统日志等。

下面是控制台日志级别的一些简要的介绍
控制台相应的日志级别定义如下:
#define MINIMUM_CONSOLE_LOGLEVEL  1   /*可以使用的最小日志级别*/
#define DEFAULT_CONSOLE_LOGLEVEL  7 /*比KERN_DEBUG 更重要的消息都被打印*/

int console_printk[4] = {
DEFAULT_CONSOLE_LOGLEVEL,/*控制台日志级别,优先级高于该值的消息将在控制台显示*/
/*默认消息日志级别,printk没定义优先级时,打印这个优先级以上的消息*/
DEFAULT_MESSAGE_LOGLEVEL,
/*最小控制台日志级别,控制台日志级别可被设置的最小值(最高优先级)*/
MINIMUM_CONSOLE_LOGLEVEL,
DEFAULT_CONSOLE_LOGLEVEL,/* 默认的控制台日志级别*/
};
在进行查看的时候,可以使用命令 cat /proc/sys/kernel/printk来查看这四个值
可以通过修改文件/proc/sys/kernel/printk中的第一个值来更改当前的控制台日志级别。

(声明:在下面的模块函数中控制台所使用的日志级别均为KERN_WARNING级别)当日志级别高于console_loglevel(控制台日志级别)时,消息才能在控制台显示出来。
假如我们写了一个如下的模块函数:
1 #include <linux/init.h>
2 #include <linux/module.h>
3 MODULE_LICENSE("Dual BSD/GPL");
4 static int book_init(void)
5 {
6   printk(KERN_EMERG "EMERGn");
7   printk(KERN_ALERT "ALERTn");
8   printk(KERN_CRIT " CRITn");
9   printk(KERN_ERR " ERRn");
10  printk(KERN_WARNING ""WARNINGn");
11   printk(KERN_NOTICE "NOTICEn");
12  printk(KERN_INFO "INFOn");
13  printk(KERN_DEBUG "DEBUGn");
14  return 0;
    }
15static void book_exit(void)
16{
17  printk(KERN_ALERT "Book module exitn");
    }
18  module_init(book_init);
19  module_exit(book_exit);

在控制台(这里指的是虚拟终端  Ctrl+Alt+(F1~F6))加载模块以后,控制台给出的信息为
6~9行中要求输出的信息,我们在伪终端(如果对伪终端不是很清楚可以看相关的内容)上运行命令tail -n 10 /var/log/messages查看日志文件刚才得到的运行记录
可以发现messages中的值为KERN_WARNING级别之后所要求输出到信息值。而如果我们在文件syslog和kern-log中查看系统日志文件,一般情况下可以得到所有的输出信息

Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637057] INFO
Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637063] CRIT
Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637066] WARNING
Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637068] ERR
Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637069] ALERT
Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637070] EMERG
Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637071]  NOTICE
Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637072] DEBUG
(不过在有些机器上运行得到的结果并不是这样的)
即一般情况下,syslog和kern.log两个文件中记录的内容从编程这个角度来看是基本一致的。
在目录/var/log/下有一下四个文件可以查看日志
syslog ,kern.log,messages ,DEBUG 。   
syslog和kern.log一般情况下可以得到所有的系统输出值,而messages得到的是比控制台日志级别低的输出值,DEBUG得到的仅仅是DEBUG级别的
输出值。
一般情况下,优先级高于控制台日志级别的消息将被打印到控制台。优先级低于控制台日志级别的消息将被打印到messages日志文件中,而在伪终端下不打印任何的信息。
我们在进行有关编程的时候,若使用到printk()这个函数,一般查看信息是在messages和虚拟终端下进行查看,而对于syslog和kern.log下是用来检验所有信息的输出情况。

Linux信号列表

我们运行如下命令,可看到Linux支持的信号列表:

~$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD
18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO
30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1
36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5
40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9
44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13
52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9
56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5
60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1
64) SIGRTMAX

列表中,编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。

下面我们对编号小于SIGRTMIN的信号进行讨论。

1) SIGHUP
本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。

登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和 后台进程组,一般都属于这个 Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进 程组和后台有终端输出的进程就会中止。不过可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略它,这样就算退出了Linux登录,wget也 能继续下载。

此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。

2) SIGINT
程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。

3) SIGQUIT
和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。

4) SIGILL
执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。

5) SIGTRAP
由断点指令或其它trap指令产生. 由debugger使用。

6) SIGABRT
调用abort函数生成的信号。

7) SIGBUS
非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。

8) SIGFPE
在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。

9) SIGKILL
用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。

10) SIGUSR1
留给用户使用

11) SIGSEGV
试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.

12) SIGUSR2
留给用户使用

13) SIGPIPE
管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。

14) SIGALRM
时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.

15) SIGTERM
程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。

17) SIGCHLD
子进程结束时, 父进程会收到这个信号。

如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这 时的子进程称为僵尸进程。这种情 况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程 来接管)。

18) SIGCONT
让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符

19) SIGSTOP
停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.

20) SIGTSTP
停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号

21) SIGTTIN
当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.

22) SIGTTOU
类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.

23) SIGURG
有”紧急”数据或out-of-band数据到达socket时产生.

24) SIGXCPU
超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。

25) SIGXFSZ
当进程企图扩大文件以至于超过文件大小资源限制。

26) SIGVTALRM
虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.

27) SIGPROF
类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.

28) SIGWINCH
窗口大小改变时发出.

29) SIGIO
文件描述符准备就绪, 可以开始进行输入/输出操作.

30) SIGPWR
Power failure

31) SIGSYS
非法的系统调用。

在以上列出的信号中,程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP
不能恢复至默认动作的信号有:SIGILL,SIGTRAP
默认会导致进程流产的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
默认会导致进程退出的信号有:SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
默认会导致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH

此外,SIGIO在SVR4是退出,在4.3BSD中是忽略;SIGCONT在进程挂起时是继续,否则是忽略,不能被阻塞。

基于Xenomai的实时Linux分析与研究

作者:南京航空航天大学 王荣 游有鹏 张少坤

引 言
   
随 着嵌入式设备的快速发展,嵌入式设备的功能和灵活性要求越来越高,很多嵌入式设备中都开始使用操作系统。由于工作的特殊性,很多嵌入式设备要求系统对外部 事件的中断响应必须在事先设定的时限范围内完成,使系统具有可预测性,而通用的桌面操作系统大都是非实时或者是软实时的,无法满足需求,因此就必须使用实 时操作系统(Real—Time Operating System,RTOS)。
    实时操作系统是一个可以在有限确定的时间内,对异步输入进行处理并输出的信息系统。一个高性能的实时操作系统应具备良好的综合性能,包括系统体系结构、基本系统功能支持(如内存和中断管理)、APl支持和稳定性等。
    实时系统又有软实时系统(soft real—time system)和硬实时系统(hard real—time system)之分。软实时系统是指那些在系统负荷较重时,允许发生错过时限(deadline)的情况而且不会造成太大危害的系统,如电视会议系统;而 硬实时系统是指那些对每个任务的调度时间要求非常严格的系统,如果不满足时间限制的要求,则会给系统带来毁灭性的后果。比如数控机床的进给控制系统,在规 定时间内进给系统必须达到预定的位置,否则无法保证加工零件的精度,甚至无法完成加工。
    在嵌入式系统领域,实时系统的核心是实时操作系统。目前已有很多商业实时操作系统,著名的有WindRiver公司的VxWorks,其他的有QNX、 pSOS+等。它们的优点是具有非常好的稳定性、可靠性和实时性,但是一般价格昂贵且互不兼容,而且源代码作为商业秘密而不公开。与之相反,GPL协议下 的Linux操作系统则为开发者在前人基础上进行更深入的研究提供了可能。目前,具有代表性的Linux内核实时性研究项目有RT—Linux、 RTAI、Xenomai等。

1 Linux 2.6内核的实时性分析
    相对于老版本内核,Linux 2.6版本的内核结构做了很大的改动,开发者对很多功能模块的代码都进行了重写。最为显著的改进是在影响系统实时性的进程调度方面,包括采用可抢占内核和新的0(1)调度程序。
    但是Linux在最初的设计是用作个人PC或者小型服务器的操作系统,由于设计要求的针对性,导致了Linux无法提供硬实时环境,直接影响了它的硬实时性能。这主要表现在两方面:
    (1)进程调度方式
    Linux 的进程调度采用的是时间片轮转调度策略。不论进程优先级的高低,Linux在某段时间内都会分配给该进程一个时间片运行,也就是说它的设计更注重任务调度 的公平性。这种情况下,就会出现高优先级进程由于其时间片的耗尽而被迫放弃处理器,处理器被没有耗尽时间片的低优先级进程所占用的现象。这样显然无法适用 于实时性要求比较高的系统。
    (2)时钟粒度粗糙
   
在Linux 2.6版本内核中,时钟中断发生的频率范围为50~1 200Hz,周期不小于0.8 ms,而工业上很多的中断周期都在几十μs之内。
    对于上面提到的影响Linux实时性的问题,目前的解决办法主要有2种:
    ①对Linux内核的内部进行实时改造,即直接修改Linux内核的数据结构、调度方式以及中断方式(主要是时钟中断)。
    采用这种方法,实时化改造后的系统实时性较好,但是工作量大,并且可能会造成系统不稳定。最大的缺点是:原本在Linux上运行的设备驱动程序和应用程序不能直接在改进的内核上运行。典型代表有Kurt-Linux。
    ②对Linux内核的外部实时扩展,这种方法通常是采用双内核的办法。具体是在Linux内核和硬件间加入一个硬件抽象层(Hardware Abstract Layer,HAL),系统所有的硬件中断由这个抽象层控制。新创建一个内核专门用来调度实时进程,而普通进程通过原来的Linux内核进行调度。采用此 方法的最大好处在于对Linux的内核改动很小,而且原Linux上的设备驱动程序和应用程序都能顺利地在此实时系统上运行。其代表有RT—Linux、 RTAI和Xenomai。

2 Xenomai原理与应用
2.1 Xenomai简介及其Adeos实现

    Xenomai是一个自由软件项目,提供了一个基于Linux的实时解决方案。它可以提供工业级RTOS的性能,而且完全遵守GNU/Linux自由软件协议。目前最新稳定版本是2.4.5。
    Xenomai项目起始于2001年。从2003年夏天起,Xenomai和RTAI有了两年时间的合作,期间开发了广为人知的RTAI/fusion项 目分支。到2005年,Xenomai项目又重新独立出来。而从2.0.0版本开始,Xenomai在硬件平台的移植就一直是基于Adeos构架来实现 的。
    在基于Adeos的系统中,分为多个域。每个域中独立运行一个操作系统(或者是实现一定功能的程序模块),每个域可以有独立的地址空间和类似于进程、虚拟 内存等的软件抽象层。在各个域下层有一个Adeos通过虚拟中断等方法来调度上面的各个域。在基于Adeos的系统中,存在着A、B、C、D四种类型的交 互,如图1所示。

    A类交互是各个域直接操作硬件设备,包括访问内存等;B类交互指当Adeos接收到硬件中断后,会根据中断来对相应的域进行中断服务;C类交互指当前域内 的操作系统主动向Adeos请求某些服务;D类交互是指Adeos接收硬件产生的中断和异常,同时也可以直接控制硬件。其中,Adeos实现的功能主要包 括中断管道机制(I—Pipe)、域管理模块和域调度模块功能。
2.2 Xenomai用户层实时的实现
    Xenomai除了在内核层利用Adeos实现了硬实时外,它在用户空间也有很好的实时性。在S3C2410平台上,为了实现用户层的实 时,Xenomai实现了一个硬件计数器——Decrementer。这个硬件计数器可以在用户空问里很好地模拟TSC(Time Stamp Counter,时间戳计数器)。
    同时,Xenomai在Linux内核中加入了一个全新的数据结构__ipipe_tscinfo,可以通过此数据结构变量存放用户层需要的数据。该数据结构组成如下:

    在用户层,应用程序通过系统调用可以迅速得到struct_ipipe_tscinfo结构体中的数据。而且为了避免受到缓存的影响,Xenomai将此结构体变量存放在Linux的向量页中。

    内核通过函数_ipipe_mach_get_tscinfo来填充struct_ipipe_tscinfo结构体变量中的各项内容:

   
    其中,info一>typte说明在S3C2410平台上TSC是基于Decrementer硬件计数方式的;info 一>u.dec.counter用来将Decrementer计数器的物理地址设定为0x51000038;info 一>u.dec.mask掩码用来注明使用Dec—rementet。计数器中的特定位;info一>u.dec.tsc指向存放64位 TSC值的区域。
    在Xenomai用户层的实时程序运行时,程序都会通过系统调用得到内核填充好的struct_ipipe_tscinfo结构体变量。具体实现可参考编 译用户层实时程序时用到的,由Xenomai所提的头文件/usr/xenomai/include/asm/syscall.h。
2.3 Xenomai多API构架
    除了提供Linux硬实时,Xenomai的另一个目的是使基于Linux的实时操作系统能提供与传统的工业级实时操作系统(包括VxWorks、 pSOS+、VRTX或者uITRON)功能相同的API。这样,可以让这些操作系统下的应用程序能够很容易地移植到GNU/Linux环境中,同时保持 很好的实时性。
    Xenomai的核心技术表现为使用一个实时微内核(real—time nucleus)来构建这些实时API,也称作“skin”。在实时核复用的基础上,一个skin可以很好地模拟一种实时操作系统的API。它的结构图可以参考图2。

    图2中,Native是Xenomai自带的API,各类API都有着同等的地位,都独立地基于同一个实时微内核。这样做可以让内核的优点被外层所有的 API很好地继承下来。更重要的是,实时微内核提供的服务被外层各种API以不同的方式表现出来,由此可以增强整个系统的强壮性。
    编制实时程序时,在很多实时操作系统上只能在内核层实现;而编制实时内核模块时,会受到内核的限制,比如有些实时内核不支持浮点运算,模块出错时容易使整 个系统挂起,而且内核模块的调试比较困难。Xenomai能够支持较好的用户层实时,这为编制实时性要求不是非常高的实时程序提供了一个有效途径。下面这 个用户层实时例程使用的是Xenomai提供的Native API:


    从程序中可以看出,Xenomai的用户层实时程序的周期可以轻易地设定到μs级,所以它完全可以适用于一般实时性要求的工程应用。

3 总 结
   
本 文首先简单介绍了实时操作系统,分析了Linux 2.6内核实时性能的不足;然后着重介绍了一个Linux实时化的解决方案——Xenomai,分析了Xenomai的Adeos构架基础,简要说明了 Xenomai用户层实时的实现,以及Xenomai支持多种实时操作系统的API的新特点。本文给出的Xenomai的用户层实时例程已经成功地在多个 平台上运行过,表明Xenomai用户程序在多种硬件平台上有很好的移植性。