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> 并且 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 中学习静态断言。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux内核编程是一种针对Linux操作系统内核进行开发的技术。它不仅仅涉及到Linux内核的理论知识,还需要具备相应的编程技术以及对计算机系统的深入了解。 Linux内核编程的主要目的是为Linux操作系统添加新的特性或修复现有特性的bug。为了实现这些目标,需要掌握C语言等编程语言,并且了解Linux操作系统的内部工作原理。 Linux内核编程主要包括以下内容: 1. 内核模块编程内核模块是一种可以动态加载到Linux内核中的软件,它可以添加新的功能或修改现有功能。通过编写内核模块可以实现对设备驱动程序的开发、文件系统的修改等。 2. 设备驱动程序开发:设备驱动程序是将硬件设备和操作系统连接起来的软件。Linux内核提供了一系列的接口函数和数据结构,用于开发设备驱动程序。掌握设备驱动程序开发技术可以实现对设备的管理和控制。 3. 文件系统的修改:Linux内核支持多种文件系统,但有时候需要根据特定的需求对文件系统进行修改。文件系统的修改需要深入了解文件系统的工作原理和相关的数据结构。 4. 虚拟文件系统(VFS):VFS是Linux内核中用于管理文件的核心模块之一。掌握VFS的开发技术可以实现对文件系统的自定义扩展。 总之,Linux内核编程是一项复杂而有挑战性的技术,需要具备扎实的编程基础和对操作系统原理的深入理解。掌握这项技术可以为Linux操作系统添加新的功能或改进现有功能,提高系统的性能和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值