从本文开始,开始分析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测试分析结束。