Linux内核编程:实现断言

断言

定义

断言是非常常见的,其语义就是判断某个条件,如果不为真,就执行一段非常规的动作,一般为程序立马结束运行。

分类

断言分为动态和静态的。动态断言也叫运行时断言,即在程序运行的使用,由比较指令来判断条件;而静态断言是在代码的编译过程中,通过编译器对常量表达式的计算来判断条件成立与否的,即一般不会生成额外的代码。

实现

静态断言

  1. 一般我们在没有编译器特性支撑的情况下,可以使用C语言的语法语义的正确性来实现,最常规的是根据常量表达式的真假来定义一个负数大小的数组。比如:
#define static_assert(cond) \
do { if (!cond) (void)sizeof(char[-1]); } while(0)

cond 必须要为常量表达式,即非运行时就可以确定结果的表达式。
当断言失败时将出现下面的信息:

../../include/utils/compiler.h:247:20: error: size of unnamed array is negative
  1. 在有编译器特性支持的的情况下,使用 __attribuite__((error(message))) 来描述一个不存在的函数,当编译时发现调用了此函数就打印 message 并终止编译,一般来说,函数的存在与否必须要等到链接时才可知的,但GNU的编译器支持这个特性。比如:
#define static_assert(cond) \
do { if (!cond) { extern void __static_assert_func(void) ___attribute__("static assert failed"); __static_assert_func(); } } while(0)

同样 cond 必须要为常量表达式,即非运行时就可以确定结果的表达式。
当断言失败时将出现下面的信息:

../../include/utils/compiler.h:247:20: error: call to ‘__static_assert_func’ declared with attribute error: static assert failed

内核使用 BUILD_BUG_ON_XXX() 来作为前缀定义一系列的静态断言,比如在定义一个可以配置的哈希表时,为了加快哈希桶的定位,一般都是用 2^n 大小的桶,并用哈希值与其掩码来计算桶下标,即: hash_value & (bucket_size -1) 。为了防止非 2^n 大小的哈希表出现,定义 BUILD_BUG_ON_NOT_POWER_OF_2() 来快速判断大小。
这些宏都最终都是由下列宏实现 :

#define GCC_VERSION (__GNUC__ * 10000		\
		     + __GNUC_MINOR__ * 100	\
		     + __GNUC_PATCHLEVEL__)

#if GCC_VERSION >= 40800
# define __compiletime_error(message) __attribute__((error(message)))
#endif

#ifndef __compiletime_error
# define __compiletime_error(message)
#endif

# define __compiletime_error_fallback(condition) \
	do { ((void)sizeof(char[1 - 2 * condition])); } while (0)
	
#if defined(__OPTIMIZE__)
# define __compiletime_assert(condition, msg, prefix, suffix)		\
	do {								\
		bool __cond = !(condition);				\
		extern void prefix ## suffix(void) __compiletime_error(msg); \
		if (__cond)						\
			prefix ## suffix();				\
		__compiletime_error_fallback(__cond);			\
	} while (0)
#else
# define __compiletime_assert(condition, msg, prefix, suffix) \
	__compiletime_error_fallback(!(condition))
#endif

#define _compiletime_assert(condition, msg, prefix, suffix) \
	__compiletime_assert(condition, msg, prefix, suffix)

#define compiletime_assert(condition, msg) \
	_compiletime_assert(condition, msg, __compiletime_assert_, __LINE__)

#define BUILD_BUG_ON(condition) \
	BUILD_BUG_ON_MSG(condition, "BUILD_BUG_ON failed: " #condition)

#define BUILD_BUG_ON_NOT_POWER_OF_2(n)			\
	BUILD_BUG_ON((n) == 0 || (((n) & ((n) - 1)) != 0))

上面的定义做了一些改动,你可以直接用于自己的应用层程序。 __OPTIMIZE__ 表示在使用 -O 并且 n 大于0时,编译器会定义此宏,并且要GCC编译器特性来断言时,必须要使用较新的版本,我在 macOS 上测试时,GCC就不支持 __attribuite__((error(message))) 这个扩展。

动态断言

刚才我们提示过,动态的断言将使用指令,完成断言,那么聪明的内核在条件不成立时执行一个CPU不认识的指令来触发一个指令异常,并被异常子系统捕获,最终会执行宕机处理。

#define BUG() __asm__ __volatile__("ud2\n")

我们在应用层编程时,直接使用 assert.h 头文件中的 assert(cond) 就可以了。

结束语

内核的断言有许多的代码技巧,你在掌握了基本原理后,可以在所有的 bug.h 中学习动态断言,以及在 compiler.hbuild_bug.h 中学习静态断言。

你可能感兴趣的:(Linux内核)