Linux内核中的巧妙实现

common.h
#ifndef __COMMON_H_
#define __COMMON_H_

/**
* int64_t数的输出:%lld ;
* uint64_t数的输出:%llu ;
* uint64_t数十六进制输出:%llx ;
*/
#include <stdint.h>		/* 引入uintx_t类型*/
#include <stdbool.h>	/* 引入bool(true/false)类型*/

// 若x、y不是同一种数据类型,编译时会报warning!
#define MIN(x, y) ({ \
	typeof(x) _min1__ = (x); \
	typeof(y) _min2__ = (y); \
	(void) (&_min1__ == &_min2__); \
	_min1__ < _min2__ ? _min1__ : _min2__; })

#define MAX(x, y) ({ \
	typeof(x) _max1__ = (x); \
	typeof(y) _max2__ = (y); \
	(void) (&_max1__ == &_max2__); \
	_max1__ > _max2__ ? _max1__ : _max2__; })

#define MIN3(x, y, z) MIN((typeof(x))MIN(x, y), z)
#define MAX3(x, y, z) MAX((typeof(x))MAX(x, y), z)

/*
 * ..and if you can't take the strict
 * types, you can specify one yourself.
 *
 * Or not use min/max/clamp at all, of course.
 */
#define MIN_T(type, x, y) ({			\
	type _min1__ = (x);			\
	type _min2__ = (y);			\
	_min1__ < _min2__ ? _min1__ : _min2__; })

#define MAX_T(type, x, y) ({			\
	type _max1__ = (x);			\
	type _max2__ = (y);			\
	_max1__ > _max2__ ? _max1__ : _max2__; })

 /**
 * swap - swap values of @a and @b
 * @a: first value
 * @b: second value
 */
#define SWAP(a, b) \
	do { typeof(a) _tmp__ = (a); (a) = (b); (b) = _tmp__; } while (0)

/**
 * ARRAY_SIZE - get the number of elements in array @arr
 * @arr: array to be sized
 */
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

/* 判断x是否是以a对齐的*/
//#define IS_ALIGNED(x, a)		(((x) & ((typeof(x))(a) - 1)) == 0)
#define IS_ALIGNED(x, a)		(((x) & ((typeof(x))(a) - 1)) == 0 ? true : false)

/*
 * abs() handles unsigned and signed longs, ints, shorts and chars.  For all
 * input types abs() returns a signed long.
 * abs() should not be used for 64-bit types (s64, u64, long long) - use abs64()
 * for those.
 */
#define ABS(x) ({						\
		long ret;					\
		if (sizeof(x) == sizeof(long)) {		\
			long _x__ = (x);				\
			ret = (_x__ < 0) ? -_x__ : _x__;		\
		} else {					\
			int _x__ = (x);				\
			ret = (_x__ < 0) ? -_x__ : _x__;		\
		}						\
		ret;						\
	})

/* 向free()传递一个NULL参数不会产生任何效果*/
#define SAFE_FREE(p) do{free(p); p=NULL;} while(0)

/**
* 这条语句的目的就是获取TYPE类型中MEMBER成员的相对偏移地址
* 在C标准库中(stddef.h)也可以找到offsetof;由于在内核空间中没有标准C库,因此就出现了下面的宏定义
*/
//#define OFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/* 在64bit系统中,size_t为无符号的64bit整型*/
#define OFFSETOF(TYPE, MEMBER) ((uint64_t) &((TYPE *)0)->MEMBER)
/* 在32bit系统中,size_t为无符号的32bit整型*/
//#define OFFSETOF(TYPE, MEMBER) ((uint32_t) &((TYPE *)0)->MEMBER)

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:	the pointer to the member.
 * @type:	the type of the container struct this is embedded in.
 * @member:	the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({			\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - OFFSETOF(type,member) );})



#endif


GNU C 和 ANSI C

Linux上使用GNU C;GNU C对标准C进行了一系列的扩展,以增强标准C的功能。

  1. 语句表达式
    GNU C把包含在括号中的复合语句看成是一个表达式,它可以出现在任何允许表达式的地方。
    为GCC的一个扩展特性,形如({ ... })这样的代码块会被视为一条语句,称作为 语句表达式。其计算结果是{ ... }中最后一条语句的计算结果。
  2. typeof关键字
    typeof(x)可以获得变量x的类型

内核标准实现
  1. 使用宏实现MIN/MAX函数
    Linux内核实现(\linux-5.14.7\include\linux\minmax.h)
/*
 * min()/max()/clamp() macros must accomplish three things:
 *
 * - avoid multiple evaluations of the arguments (so side-effects like
 *   "x++" happen only once) when non-constant.
 * - perform strict type-checking (to generate warnings instead of
 *   nasty runtime surprises). See the "unnecessary" pointer comparison
 *   in __typecheck().
 * - retain result as a constant expressions when called with only
 *   constant expressions (to avoid tripping VLA warnings in stack
 *   allocation usage).
 */
#define __typecheck(x, y) \
	(!!(sizeof((typeof(x) *)1 == (typeof(y) *)1)))

#define __no_side_effects(x, y) \
		(__is_constexpr(x) && __is_constexpr(y))

#define __safe_cmp(x, y) \
		(__typecheck(x, y) && __no_side_effects(x, y))

#define __cmp(x, y, op)	((x) op (y) ? (x) : (y))

#define __cmp_once(x, y, unique_x, unique_y, op) ({	\
		typeof(x) unique_x = (x);		\
		typeof(y) unique_y = (y);		\
		__cmp(unique_x, unique_y, op); })

#define __careful_cmp(x, y, op) \
	__builtin_choose_expr(__safe_cmp(x, y), \
		__cmp(x, y, op), \
		__cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op))

/**
 * min - return minimum of two values of the same or compatible types
 * @x: first value
 * @y: second value
 */
#define min(x, y)	__careful_cmp(x, y, <)

/**
 * max - return maximum of two values of the same or compatible types
 * @x: first value
 * @y: second value
 */
#define max(x, y)	__careful_cmp(x, y, >)


/**
 * swap - swap values of @a and @b
 * @a: first value
 * @b: second value
 */
#define swap(a, b) \
	do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)

最基本的形式:

#define MIN(a, b) ((a) < (b) ? (a) : (b))

存在的问题:
I、若a和b的类型不一样,理论上是无法比较的;上述实现未做类型判断
II、MIN(a++, b),若a<b,则a会自增2次

为解决上面存在的问题,改进版实现如下:

// 若x、y不是同一种数据类型,编译时会报warning!
#define MIN(x, y) ({ \
	typeof(x) _min1__ = (x); \
	typeof(y) _min2__ = (y); \
	(void) (&_min1__ == &_min2__); \
	_min1__ < _min2__ ? _min1__ : _min2__; })

上述宏定义展开后的结果为最后一行返回的结果。注意,这个扩展特性不是所有编译器都有的,如果用VS编译上述代码,是无法通过编译的。
这个宏定义中,先根据x, y的类型生成两个局部变量_min1___min2__,之后比较其大小,返回较小的一个,这样就保证了宏参数只会被执行一次,避免了上述副作用。另外,第4行代码其实是没有实际作用的,其意义在于:若_min1___min2__的类型不一致,比较其地址时编译器会给出一个Warning,这样可以避免一些潜在的错误发生。
存在的问题:
I、当输入参数和宏定义内部使用的局部变量重名时,代码执行会不符合预期
输入参数和宏定义内部使用的局部变量重名,会导致在宏定义的语句块内,外层同名变量的作用域被内层局部变量的作用域所屏蔽。知道问题的原因后解决方法就很清晰了,只要避免重名就可以了,其实上述宏定义中使用_min1___min2__这样的名字也是为了避免重名,然而,靠起特殊的名字这种方法不是那么的优雅,故实际新版的Linux Kernel中使用了编译器产生的唯一名称来解决这一问题,就是最先描述的实现。

结论
输入参数和宏定义内部使用的局部变量(_min1___min2__)重名的情况基本上不会发生,所以使用如下实现即可有效达到我们的目的(\linux-4.0.6\include\linux\kernel.h既是采用的如下实现):

// 若x、y不是同一种数据类型,编译时会报warning!
#define MIN(x, y) ({ \
	typeof(x) _min1__ = (x); \
	typeof(y) _min2__ = (y); \
	(void) (&_min1__ == &_min2__); \
	_min1__ < _min2__ ? _min1__ : _min2__; })

测试代码:

#include <stdio.h>

#define MIN(x, y) ({ \
	typeof(x) _min1__ = (x); \
	typeof(y) _min2__ = (y); \
	(void) (&_min1__ == &_min2__); \
	_min1__ < _min2__ ? _min1__ : _min2__; })
     
void main(void)
{
    int a = 10;
    float b = 9.8;
    
    printf("\t\t\t MIN=%u \n", MIN(a, b));
}

结果:
在这里插入图片描述

  1. 通过结构体成员获取结构体指针container_of
    Linux内核实现(\linux-5.14.7\include\linux\kernel.h)
/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:	the pointer to the member.
 * @type:	the type of the container struct this is embedded in.
 * @member:	the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({				\
	void *__mptr = (void *)(ptr);					\
	BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&	\
			 !__same_type(*(ptr), void),			\
			 "pointer type mismatch in container_of()");	\
	((type *)(__mptr - offsetof(type, member))); })

结论
使用如下版本即可实现(\linux-4.0.6\include\linux\kernel.h 和 linux-4.0.6\include\linux\stddef.h):

/**
* 这条语句的目的就是获取TYPE类型中MEMBER成员的相对偏移地址;
* 在C标准库中(stddef.h)也可以找到offsetof;由于在内核空间中没有标准C库,因此就出现了下面的宏定义;
*/
#define OFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:	the pointer to the member.
 * @type:	the type of the container struct this is embedded in.
 * @member:	the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({			\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - OFFSETOF(type,member) );})

测试代码:

#include <stdio.h>

#define OFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

#define container_of(ptr, type, member) ({			\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - OFFSETOF(type,member) );})
    
void main(void)
{
    struct test{
        int a;
        char b;
        unsigned int c;
        unsigned char d;
    };
    
    struct test tmp = {5, 'x', 2, 1};
    struct test *ptmp = NULL;
    
    ptmp = container_of(&tmp.c, struct test, c);
    
    printf("a=%d, b=%c, c=%d, d=%d \n", ptmp->a, ptmp->b, ptmp->c, ptmp->d);
}

结果:
在这里插入图片描述

  1. 内核kfifo
    kfifo最终版(参考linux-5.14.7)

  2. 取余%转化为与&
    若b为2的n次幂,则a%b可转化为a&(b-1)。 与& 的效率比 取余% 高。

  3. 宏定义中的do{…}while(0)的使用
    举例:

if (NULL != p)
	SAFE_FREE(p);
else
	...

#define SAFE_FREE(p) do{free(p); p=NULL;} while(0)

会被展开为:

if (NULL != p)
	do {
		free(p); 
		p=NULL;
	} while(0);
else
	...

若不用do{…}while(0):

#define SAFE_FREE(p) {free(p); p=NULL;}

会被展开为:

if (NULL != p)
{
	free(p); 
	p=NULL;
}
;
else
	...	

这样会造成else悬挂,else分支没有对应的if了,编译无法提供。

  1. __func__
    C99已支持__func__宏,意味着字符串,可使用%s print出。

  2. 宏定义中typeof((inp) + 1) __tmp = (inp); 保证传入的inp为指针类型
    使用typeof((fifo) + 1) __tmp = (fifo);
    保证传入的fifo为指针类型(在编译阶段即可检查出来,而不是程序运行后发生奇奇怪怪的错误)
    宏定义实现的函数和真正的函数的最大的区别为:真正的函数在编译时会做参数类型检查,若调用函数的入参类型不对,编译阶段就会报错;使用宏定义是实现不了参数类型检查的。所以得使用诸如上述的特殊技巧进行编译阶段的检查。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值