从本文开始,开始分析kernel部分的测试用例,该部分测试用例大部分为C语言编写,因此会穿插加入一定的C语言或unix环境编程的知识。

abort

设计说明

测试策略

Fork child. Child出现abort,检查返回状态

限制

Core file大小的限制必须大于0

abort

函数名: abort

功 能: 异常终止一个进程

用 法: void abort(void);

abort()是使异常程序终止,同时发送SIGABRT信号给调用进程。

#include <stdlib.h>
void abort(void);
该函数不返回

此函数将SIGABRT信号发送给调用进程(进程不应忽略此信号).ISO C规定,调用abort将向主机环境递送一个未成功终止的通知,其方法是调用raise(SIGABRT)函数。 ISO C要求若捕捉此信号而且相应信号处理程序返回,abort仍不会返回其调用者。如果捕捉到此信号,则信号处理程序不能返回的唯一方法是它调用exit、_exit、_Exit、longjmp或sigloogjmp.POSIX.1也说明 abort并不理会进程对此信号的阻塞和忽略。

让进程捕捉SIGABRT的意图是:在进程终止之前由其执行所需的清理操作。如果进程并不在信号处理程序中终止自己,POSIX.1声明当信号处理程序返回时,abort终止该进程。

ISO C针对此函数的规范将下列问题留由实现决定: 是否要冲洗输出流以及是否删除临时文件。POSIX.1的要求更进一步,要求如果abort调用终止进程,则它对所有打开标准I/O流的效果应当与进程终止前对每个流调用fclose相同。

系统V早期的版本中,abort函数产生SIGIOT信号。更进一步,进程忽略此信号,或者捕捉它并从信号处理程序返回都是可能的,在返回情况下,abort返回到它的调用者。
4.3BSD产生SIGILL信号。在此之前,该函数解除对此信号的阻塞,将其配置恢复为SIG_DFL (终止并构造core文件)。这阻止一个进程忽略或捕捉此信号。
SVR4在产生此信号之前关闭所有I/O流。在另一方面,4.3+BSD则不做此操作。对于保护性的程序设计,如果希望刷新标准I/O流,则在调用abort之前要做这种操作。在err_dump函数中实现了这一点
因为大多数UNIX tmpfile(临时文件)的实现在创建该文件之后立即调用unlink,所以ANSI C关于临时文件的警告通常与我们无关。

abort的POSIX.1实现

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

void
abort(void)    /*POSIX.1风格的abort()实现*/
{
    sigset_t    mask;
    struct sigaction   action;
    /*
     * Caller can't igore SIGABRT, if so reset to default.
     */
    sigaction(SIGABRT, NULL, &action);
    if (action.sa_handler == SIG_IGN) {
        action.sa_handler == SIG_DFL;
        sigaction(SIGABRT, &action, NULL);
    }
    if (action.sa_handler == SIG_DFL)
       fflush(NULL)   /* flush all open stdio stream */

    /*
     * Caller can't block SIGABRT; make sure it's unblocked.
     */
     sigfillset(&mask)
     sigdelset(&mask, SIGABRT); /* mask has only SIGABRT turned off */
     sigprocmask(SIG_SETMASK, &mask, NULL);
     kill(getpid(), SIGABRT); /* send the signal */

     /*
      * if we're here, process caught SIGABRT and returned.
      */

     fflush(NULL); /*flush all open stdio streams*/
     action.sa_handler = SIG_DFL;
     sigaction(SIGABRT, &action, NULL); /* reset to default */
     sigprocmask(SIG_SETMASK, &mask, NULL); /* just in case ...*/
     kill(getpid(), SIGABRT); /* and one more time*/
     exit(1); /*this should never be executed...*/

说明:首先查看是否将执行默认动作,若是则冲洗所有标准I/O流。这并不等价于对所有打开的流调用fclose(因为只冲洗,并不关闭它们),但是当进程终止时,系统会关闭所有打开的文件。如果进程捕捉此信号并返回, 那么因为进程产生了更多的输出,所以再一次冲洗所有的流。不进行冲洗处理的唯一条件是如果进程捕捉此信号,然后调用_exit或_Exit.这种情况下,内存中任何未冲洗的标准I/O缓冲区都被丢弃。我们假定捕捉此信号,而且_exit或_Exit的调用者并不想要冲洗缓冲区。

应用例子:

#include <stdio.h>
#include <stdlib.h>

void main(void)
{
    FILE *stream;
    if ((stream = fopen("NOSUCHF.ILE", "r")) == NULL )
    {
        perror("Couldn't open file");
        abort();
    }
}
else
    fclose(stream);
}

编译运行一下,看一下执行情况片段:

execve("./test.o", ["./test.o"], [/* 97 vars */]) = 0

open("NOSUCHF.ILE", O_RDONLY)           = -1 ENOENT (No such file or directory)

dup(2)                                  = 3

fcntl(3, F_GETFL)                       = 0x8402 (flags O_RDWR|O_APPEND|O_LARGEFILE)

fstat(3, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 3), ...}) = 0

mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd18aa86000

write(3, "Couldn't open file: No such file"..., 46Couldn't open file: No such file or directory

) = 46

close(3)                                = 0

munmap(0x7fd18aa86000, 4096)            = 0

rt_sigprocmask(SIG_UNBLOCK, [ABRT], NULL, 8) = 0

gettid()                                = 18489

tgkill(18489, 18489, SIGABRT)           = 0

SIGABRT {si_signo=SIGABRT, si_code=SI_TKILL, si_pid=18489, si_uid=0} ---

+++ killed by SIGABRT +++

已放弃

abort测试代码说明

代码大体结构

|-   macro                                                                                                           
||     NUM                                                                                                     
||     MIN_RLIMIT_CORE                                                                                                                           
|                                                                                                                         
|-   variable                                                                                                                            
||     TCID                                                                                                           
||     TST_TOTAL                                                                                                                 
|                                                                                                      
|-   function                                                                                                    
||     main                                                                                         
||     setup                                                              
||     cleanup                                                                 
||     do_child                                                                                    
||     instress

先看一下头文件:

#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h>

#include "test.h"
#include "safe_macros.h"

什么是头文件呢?

在C语言家族程序中,头文件被大量使用。一般而言,每个C++/C程序通常由头文件(header files)和定义文件(definition files)组成。头文件作为一种包含功能函数、数据接口声明的载体文件,主要用于保存程序的声明(declaration),而定义文件用于保存程序的实现 (implementation)

让我们来看,经典程序 “Hello world!”  定义文件名“First.c” main() { printf(“Hello world!”); }

<sample-1>

看看上面的程序,没有.h文件,因为程序太简单,没有需要保存的声明。

文件名 First.c 变形

printStr()
{
    printf(“Hello world!”);
}
main()
{
printStr()
}

<sample-2>

还是没有, 那就让我们把这个程序再稍微改动一下.

文件名 First.c

main()
{
    printStr()
}

printStr()
{
    printf(“Hello world!”);
}

<sample-3>

sample3和sample2是不同的,pritStr()函数定义的顺序不同。sample3是编译不通过的。这里面涉及作用域的问题。在这里只讲述与.h文件相关的顶层作用域。 顶层作用域就是从声明点延伸到源程序文本结束, 就printStr()这个函数来说,他没有单独的声明,只有定义,那么就从他定义的行开始,到first.c文件结束。sample2中,printStr的定义在main函数前面,所以作用域覆盖main函数,而sample3则没有。 这种情况怎么办呢? 有两种方法 ,一个pritStr函数定义到main函数之前,那就让我们来看另一个例子,让我们看看这个方法是不是在任何时候都会起作用. 文件名 First.c

play2()
{
  ……………….
  play1()
  ………………..
}
play1()
{
    ……………………..
    play2()
    ……………………
}
main()
{
    play1()
}

<sample-4>

函数嵌套, 那么play1和play2这两个函数哪个放到前面呢?

这时就需要我们来使用第二种方法,使用声明.

文件名 First.c

play1();
play2();
play2()
{
    ……………….
    play1()
    ………………..
}
play1()
{
    ……………………..
    play2()
    ……………………
);
}
main()
{
    play1()
}

<sample-5>

一个大型的软件项目,可能有几千个,上万个play, 而不只是play1,play2这么简单, 这样就可能有N个类似 play1(); play2(); 这样的声明, 这个时候就需要我们想办法把这样的play1(); play2(); 也另行管理, 而不是把他放在.c文件中, 于是.h文件出现了.

文件名 First.h

play1();
play2();

文件名 First.C

#include “first.h”
play2()
{
    ……………….
    play1()
    ………………..
}
play1()
{
    ……………………..
    play2()
    ……………………
);
}
main()
{
play1()
}

<sample-6>

如在second.c中还有一个函数需要调用first.c文件中的play1函数, 如何实现呢?

Sencond.h 文件

play1();

sencond.c文件

***()
{
    …………….
    Play();
    ……………….
}

<sample-7>

在sencond.h文件内声明play1函数,怎么能调用到first.c文件中的哪个play1函数中呢? 是不是搞错了,没有搞错, 这里涉及到c语言的另一个特性:存储类说明符. C语言的存储类说明符有以下几个, 我来列表说明一下

说明符 用法
Auto 只在块内变量声明中被允许, 表示变量具有本地生存期.
Extern 出现在顶层或块的外部变量函数与变量声明中,表示声明的对象具有静态生存期, 连接程序知道其名字.
Static 可以放在函数与变量声明中. 在函数定义时, 其只用于指定函数名,而不将函数导出到连接程序. 在函数声明中,表示其后面会有定义声明的函数, 存储类为static. 在数据声明中, 总是表示定义的声明不导出到连接程序.

无疑, 在sample7中的second.h和first.h中,需要我们用extern标志符来修饰play1函数的声明,这样,play1()函数就可以被导出到连接程序, 也就是实现了无论在first.c文件中调用,还是在second.c文件中调用,连接程序都会很聪明的按照我们的意愿,把他连接到first.c文件中的play1函数的定义上去, 而不必我们在second.c文件中也要再写一个一样的play1函数.

但随之有一个小问题, 在sample7中,我们并没有用extern标志符来修饰play1啊, 这里涉及到另一个问题, C语言中有默认的存储类标志符. C99中规定, 所有顶层的默认存储类标志符都是extern .

那么我们如何来区分哪个头文件中的声明在其对应的.c文件中有定义,而哪个又没有呢? 这也许不是必须的,因为无论在哪个文件中定义,聪明的连接程序都会义无返顾的帮我们找到,并导出到连接程序, 但我觉得他确实必要的.

因为我们需要知道这个函数的具体内容是什么,有什么功能, 有了新需求后我也许要修改他, 我需要在短时间内能找到这个函数的定义, 那么我来介绍一下在C语言中一个人为的规范:

在.h文件中声明的函数,如果在其对应的.c文件中有定义,那么我们在声明这个函数时,不使用extern修饰符, 如果反之,则必须显示使用extern修饰符.

这样,在C语言的.h文件中,我们会看到两种类型的函数声明. 带extern的,还不带extern的, 简单明了,一个是引用外部函数,一个是自己声明并定义的函数. 最终如下: Sencond.h 文件

Extern play1();

那么多都是针对函数的,而实际上.h文件却不是为函数所专用的,还有全局变量.

在大型项目中,对全局变量的使用不可避免, 比如,在first.c中需要使用一个全局变量G_test, 那么我们可以在first.h中,定义 TPYE G_test. 与对函数的使用类似, 在second.c中我们的开发人员发现他也需要使用这个全局变量, 而且要与first.c中一样的那个, 如何处理? 对,我们可以仿照函数中的处理方法, 在second.h中再次声明TPYE G_test, 根据extern的用法,以及c语言中默认的存储类型, 在两个头文件中声明的TPYE G_test,其实其存储类型都是extern, 也就是说不必我们操心, 连接程序会帮助我们处理一切. 但我们又如何区分全局变量哪个是定义声明,哪个是引用声明呢?这个比函数要复杂一些, 一般在C语言中有如下几种模型来区分:

1、 初始化语句模型

顶层声明中,存在初始化语句是,表示这个声明是定义声明,其他声明是引用声明。C语言的所有文件之中,只能有一个定义声明。 按照这个模型,我们可以在first.h中定义如下TPYE G_test=1;那么就确定在first中的是定义声明,在其他的所有声明都是引用声明。

2、 省略存储类型说明

在这个模型中,所有引用声明要显示的包括存储类extern, 而每个外部变量的唯一定义声明中省略存储类说明符。 这个与我们对函数的处理方法类似,不再举例说明。

这里还有一个需要说明,数组全局变量。

在声明定义时,定义数组如下: int G_glob[100];

在另一个文件中引用声明如下: int * G_glob;

在vc中,是可以编译通过的, 这种情况大家都比较模糊并且需要注意,数组与指针类似,但并不等于说对数组的声明起变量就是指针。 上面所说的的程序在运行时发现了问题,在引用声明的那个文件中,使用这个指针时总是提示内存访问错误,原来我们的连接程序并不把指针与数组等同,连接时,也不把他们当做同一个定义,而是认为是不相关的两个定义,当然会出现错误。正确的使用方法是在引用声明中声明如下:

int G_glob[10];

并且最好再加上一个extern,更加明了。

extern int G_glob[10];

另外需要说明的是,在引用声明中由于不需要涉及到内存分配,可以简化如下,这样在需要对全局变量的长度进行修改时,不用把所有的引用声明也全部修改了。

extern int G_glob[];

接下来,看一下linux环境编程头文件常用的有哪些?

linux常用头文件

POSIX标准定义的头文件

| 文件名 | 内容 |
| ------- | --------
| dirent.h |       目录项
| fcntl.h  |       文件控制
| fnmatch.h |   文件名匹配类型
| glob.h   |  路径名模式匹配类型
| grp.h    |     组文件
| <netdb.h>  |   网络数据库操作
| <pwd.h>    |     口令文件
| <regex.h>   |  正则表达式
| <tar.h>    |     TAR归档值
| <termios.h> |    终端I/O
| <unistd.h>  |   符号常量
| <utime.h>   |  文件时间
| <wordexp.h>  |   字符扩展类型
| <arpa/inet.h>  |   INTERNET定义
| <net/if.h>  |   套接字本地接口
| <netinet/in.h>   |  INTERNET地址族
| <netinet/tcp.h>  |   传输控制协议定义
| <sys/mman.h>  |   内存管理声明
| <sys/select.h>   |  Select函数
| <sys/socket.h> |    套接字借口
| <sys/stat.h>    | 文件状态
| <sys/times.h> |    进程时间
| <sys/types.h>   |  基本系统数据类型
| <sys/un.h> |    UNIX域套接字定义
| <sys/utsname.h>   |  系统名
| <sys/wait.h>   |  进程控制

POSIX定义的XSI扩展头文件

| 文件名 | 内容 |
| ------- | --------
| <cpio.h> |    cpio归档值 
| <dlfcn.h> |   动态链接
| <fmtmsg.h> |   消息显示结构
| <ftw.h>  |      文件树漫游
| <iconv.h> |   代码集转换使用程序
| <langinfo.h> |   语言信息常量
| <libgen.h> |   模式匹配函数定义
| <monetary.h>  |  货币类型
| <ndbm.h>  |  数据库操作
| <nl_types.h> |   消息类别
| <poll.h> |   轮询函数
| <search.h>|     搜索表
| <strings.h> |    字符串操作
| <syslog.h> |    系统出错日志记录
| <ucontext.h>  |   用户上下文
| <ulimit.h> |    用户限制
| <utmpx.h>   |  用户帐户数据库

| <sys/ipc.h> |    IPC(命名管道)
| <sys/msg.h>  |   消息队列
| <sys/resource.h>| 资源操作
| <sys/sem.h>  |   信号量
| <sys/shm.h>   |  共享存储
| <sys/statvfs.h>   |  文件系统信息
| <sys/time.h>   |  时间类型
| <sys/timeb.h>  |   附加的日期和时间定义
| <sys/uio.h>    | 矢量I/O操作

POSIX定义的可选头文件

| 文件名 | 内容 |
| ------- | --------
| <aio.h>    |     异步I/O
| <mqueue.h>  |   消息队列
| <pthread.h>   |  线程
| <sched.h>   |  执行调度
| <semaphore.h>  |   信号量
| <spawn.h>    |  实时spawn接口
| <stropts.h>  |   XSI STREAMS接口
| <trace.h>   |   事件跟踪

C/C++头文件一览

C语言

| 文件名 | 内容 |
| ------- | --------
| <assert.h>| 设定插入点
| <ctype.h>  | 字符处理
| <errno.h>  | 定义错误码
| <float.h>  | 浮点数处理
| <iso646.h>   | 对应各种运算符的宏
| <limits.h>  | 定义各种数据类型最值的常量
| <locale.h>  | 定义本地化C函数
| <math.h>   | 定义数学函数
| <setjmp.h> | 异常处理支持
|  <signal.h>   | 信号机制支持
|  <stdarg.h>   | 不定参数列表支持
|  <stddef.h>    | 常用常量
| <stdio.h>  | 定义输入/输出函数
|  <stdlib.h>| 定义杂项函数及内存分配函数
|  <string.h>  | 字符串处理
| <time.h>    | 定义关于时间的函数
| <wchar.h>   | 宽字符处理及输入/输出
| <wctype.h>  | 宽字符分类

传统C++

| 文件名 | 内容 |
| ------- | --------
|  <fstream.h> | 改用<fstream>
| <iomanip.h> | 改用<iomainip>
|  <iostream.h> | 改用<iostream>
| <strstrea.h>  | 该类不再支持,改用<sstream>中的stringstream

标准C++

| 文件名 | 内容 |
| ------- | --------
|  <algorithm>    | 通用算法
| <bitset>    | 位集容器
| <cctype>         | 字符处理
|  <cerrno>     | 定义错误码
|  <cfloat>   | 浮点数处理
| <ciso646>        | 对应各种运算符的宏
|  <climits>    | 定义各种数据类型最值的常量
|  <clocale>    | 定义本地化函数
| <cmath>     | 定义数学函数
| <complex>    | 复数类
| <csignal>       | 信号机制支持
|  <csetjmp>       | 异常处理支持
|  <cstdarg>       | 不定参数列表支持
|  <cstddef>       | 常用常量
| <cstdio>   | 定义输入/输出函数
|  <cstdlib>   | 定义杂项函数及内存分配函数
|  <cstring>  | 字符串处理
| <ctime>   | 定义关于时间的函数
|  <cwchar>  | 宽字符处理及输入/输出
|  <cwctype>   | 宽字符分类
|  <deque>     | STL 双端队列容器
|  <exception>   | 异常处理类
| <fstream>     | 文件输入/输出
|  <al>  | STL 定义运算函数(代替运算符)
|  <limits>     | 定义各种数据类型最值常量
|  <list>      | STL 线性列表容器
|  <locale>        | 本地化特定信息
|  <map>      | STL 映射容器
|  <memory>        | STL通过分配器进行的内存分配
|  <new>           | 动态内存分配
|  <numeric>      | STL常用的数字操作
|  <iomanip>   | 参数化输入/输出
|  <iOS>       | 基本输入/输出支持
|  <iosfwd>    | 输入/输出系统使用的前置声明
|  <iostream>   | 数据流输入/输出
|  <istream>    | 基本输入流
|  <iterator>       | STL迭代器
| <ostream>     | 基本输出流
| <queue>      | STL 队列容器
|  <set>     | STL 集合容器
| <sstream>     | 基于字符串的流
|  <stack>     | STL 堆栈容器
|  <stdexcept>   | 标准异常类
|  <streambuf>    | /底层输入/输出支持
|  <string>    | 字符串类
|  <typeinfo>      | 运行期间类型信息
|  <utility>    | STL 通用模板类
|  <valarray>       | 对包含值的数组的操作
|  <vector>     | STL 动态数组容器

C99增加的部分

| 文件名 | 内容 |
| ------- | --------
|  <complex.h> | 复数处理
|  <fenv.h>    | 浮点环境
|  <inttypes.h>  | 整数格式转换
|  <stdbool.h>   | 布尔环境
|  <stdint.h>   | 整型环境
|  <tgmath.h>   | 通用类型数学宏

回到abort01的测试上来。先看一下执行情况:

abort01     0  TINFO  :  Adjusting RLIMIT_CORE to 1048576                                                                                            
abort01     1  TPASS  :  abort dumped core                                                                                                           
abort01     2  TPASS  :  abort raised SIGIOT                                                                                                         
abort01     3  TPASS  :  abort dumped core                                                                                                           
abort01     4  TPASS  :  abort raised SIGIOT                                                                                                         
abort01     5  TPASS  :  abort dumped core                                                                                                           
abort01     6  TPASS  :  abort raised SIGIOT

程序后台执行片段

chdir("/tmp/aboeHLyFk")                 = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fb05157a9d0) = 19587
wait4(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGABRT && WCOREDUMP(s)}], 0, NULL) = 19587
SIGCHLD {si_signo=SIGCHLD, si_code=CLD_DUMPED, si_pid=19587, si_uid=0, si_status=SIGABRT, si_utime=0, si_stime=0} ---
wait4(-1, 0x7ffd43788f30, 0, NULL)      = -1 ECHILD (No child processes)
write(1, "abort01     1  TPASS  :  abort d"..., 43abort01     1  TPASS  :  abort dumped core
) = 43
write(1, "abort01     2  TPASS  :  abort r"..., 45abort01     2  TPASS  :  abort raised SIGIOT
) = 45
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fb05157a9d0) = 19588
wait4(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGABRT && WCOREDUMP(s)}], 0, NULL) = 19588
SIGCHLD {si_signo=SIGCHLD, si_code=CLD_DUMPED, si_pid=19588, si_uid=0, si_status=SIGABRT, si_utime=0, si_stime=0} ---
wait4(-1, 0x7ffd43788f30, 0, NULL)      = -1 ECHILD (No child processes)  
write(1, "abort01     3  TPASS  :  abort d"..., 43abort01     3  TPASS  :  abort dumped core
) = 43
write(1, "abort01     4  TPASS  :  abort r"..., 45abort01     4  TPASS  :  abort raised SIGIOT
) = 45
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fb05157a9d0) = 19589
wait4(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGABRT && WCOREDUMP(s)}], 0, NULL) = 19589
SIGCHLD {si_signo=SIGCHLD, si_code=CLD_DUMPED, si_pid=19589, si_uid=0, si_status=SIGABRT, si_utime=0, si_stime=0} ---
wait4(-1, 0x7ffd43788f30, 0, NULL)      = -1 ECHILD (No child processes)
write(1, "abort01     5  TPASS  :  abort d"..., 43abort01     5  TPASS  :  abort dumped core
) = 43
write(1, "abort01     6  TPASS  :  abort r"..., 45abort01     6  TPASS  :  abort raised SIGIOT
) = 45
unlink("core")

从测试log来看,abort01主要进行abort"dumped core"、和"raised SIGIOT"两种测试,并且测试了3遍。0为setup打印信息。

#define NUM 3 /*定义fork进程的数量*/
char *TCID;         /* tescase的名字*/
int TST_TOTAL;          /* testcases数量 */

主要函数

static void setup(void);
static void cleanup(void);                                                                                            
static void do_child();                                                                                               
static int instress();
int main(int argc, char *argv[])

分别分析这几个函数

setup

#define MIN_RLIMIT_CORE (1024 * 1024)
static void setup(void)
{
    struct rlimit rlim;
    SAFE_GETRLIMIT(NULL, RLIMIT_CORE, &rlim);

    if (rlim.rlim_cur < MIN_RLIMIT_CORE) {
        test_resm(TINFO, "Adjusting RLIMIT_CORE to %i", MIN_RLIMIT_CORE);
        rlim.rlim_cur = MIN_RLIMIT_CORE;
        SAFE_GETRLIMIT(NULL, RLIMIT_CORE, &rlim);
    }
    tst_tmpdir()
}

setup函数的目的是为了调整RLIMIT_CORE的大小。在Linux系统中,Resouce limit指在一个进程的执行过程中,它所能得到的资源的限制,比如进程的core file的最大值,虚拟内存的最大值等。Resouce limit的大小可以直接影响进程的执行状况。其有两个最重要的概念:soft limit 和 hard limit。

struct rlimit {
rlim_t rlim_cur;
rlim_t rlim_max;
};

是指内核所能支持的资源上限。比如对于RLIMIT_NOFILE(一个进程能打开的最大文件 数,内核默认是1024),soft limit最大也只能达到1024。对于RLIMIT_CORE(core文件的大小,内核不做限制),soft limit最大能是unlimited。hard limit在资源中只是作为soft limit的上限。当你设置hard limit后,你以后设置的soft limit只能小于hard limit。要说明的是,hard limit只针对非特权进程,也就是进程的有效用户ID(effective user ID)不是0的进程。具有特权级别的进程(具有属性CAP_SYS_RESOURCE),soft limit则只有内核上限。

tst_tmpdir函数说明一下,该函数用来为ltp测试程序创建临时目录,目录名case名称+随机字母。

cleanup函数

static void cleanup(void)
{
    unlink("core")
    tst_rmdir();
}

该函数比较简单,用来清理产生的临时文件和软连接。可以做下试验,将main函数中的该函数注释掉,会在tmp下看到产生的core文件。

do_child函数

static void do_child(void)
{
    abort()
    fprintf(stderr, "\tchild - abort failed.\n");
    exit(1);
}

abort的调用,进程退出。

instress函数

static int instress(void)
{
    test_resm(TINFO,
        "System resources may be too low; fork(), select() etc are likely to fail.");
    return 1;
}

系统资源比较低的情况, 出现fork()等调用失败时的处理。也是为兼容UCLINUK测试(如嵌入式系统)。

main函数 控制测试流程和主要的测试代码,以下将重点介绍一些代码片段。

#ifdef WCOREDUMP
    int core;
    core = 0;
#endif

WCOREDUMP(status) 如果孩子进程产生核心转储文件则返回真。这个宏只应该在 WIFSIGNALED 返回真时调用。这个没有在 POSIX.1-2001 里指定并且在一些 UNIX 实现(如 AIX、SunOS)里也没有提供。只在 #ifdef WCOREDUMP ... #endif 内部使用。

#ifdef UCLINUX
    maybe_run_child(&do_child, "");  /* UCLINUX的情况 */
#endif

    setup(); /*调用setup函数*/

    for (i = 0; i < NUM; i++) {   /*连续创建3个进程调用do_child函数*/
        kidpid = FORK_OR_VFORK();
        if (kidpid == 0) {
#ifdef UCLINUX
            if (self_exec(argv[0], "")) {
                if (!instress()) {
                    perror("fork failed");
                    exit(1);
                }
            }
#else
            do_child();  //调用do_child()函数

#endif
        }  
                }
    if (kidpid < 0)
        if (!instress())
            tst_brkm(TBROK | TERRNO, cleanup,
                 "fork failed");
    count = 0;
    while ((child = wait(&status)) > 0)
        count++;
    if (count != 1) {
        tst_brkm(TBROK, cleanup,
             "wrong # children waited on; got %d, expected 1",
             count);
    }
#ifdef WCOREDUMP
        core = WCOREDUMP(status);//WCOFEDUMP情况
#endif
        sig = WTERMSIG(status);

    }
    if (WIFEXITED(status))
        ex = WEXITSTATUS(status);

#ifdef WCOREDUMP
    if (core == 0) {
        tst_brkm(TFAIL, cleanup,
             "Child did not dump core; exit code = %d, "
             "signal = %d", ex, sig);
    } else if (core != -1) {
        tst_resm(TPASS, "abort dumped core"); //core值等于0,则没有正常产生core文件,如果core不等于0且不等于-1,说明正常产生了core文件
    }
#endif
    if (sig == SIGIOT) {
        tst_resm(TPASS, "abort raised SIGIOT");  //发出的信号如果为SIGIOT说明信号触发正常
    } else {
        tst_brkm(TFAIL, cleanup,
             "Child did not raise SIGIOT (%d); exit code = %d, "
             "signal = %d", SIGIOT, ex, sig);
    }

}

abort测试分析结束。

by 李鹏