Linux内核中max()宏的奥妙何在?(一)

1、max()宏那点事

在Linux内核中,有这样四个比较大小的函数,如下:

max(x,y) //两个数求最大值
min(x,y) //两个数求最小值
max3(x,y,z) //三个数求最大值
min3(x,y,z) //三个数求最小值

以Linux内核源码linux-3.18.34为例,它被定义在kernel.h中,它位于:

linux-3.18.34/include/linux/kernel.h

我们先来思考这样一个问题,如果让你写一个max(x,y)的宏,你会怎么写?
有同学说,不就是两个数比较大小求最小值的宏嘛,一行代码解决战斗,瞧瞧我的吧,于是乎出现了这样一个宏:

#define max(a,b) ((a)>(b)?(a):(b))

这样一个宏看似很精简,很完美,其实,问题很大,不信看下面的例子:

#include<stdio.h>
#define max(a,b) ((a)>(b)?(a):(b))
int main()
{int x = 1, y = 2;printf("max=%d\n", max(x++, y++));printf("x = %d, y = %d\n", x, y);
}

执行完max(x++,y++),我们期望max的值为2,x的值为2,y的值为3。但实际的结果呢,有图有真相。

执行完max(x++,y++)后,max的值为3,x的值为2,y的值为4, 和我们想的不一样! 为什么会这样呢?我们又该如何解决这样的问题呢?
问题不大,GCC他来了,他来了,他带着GCC中的({statement list})和typeof()扩展走来了。

  • ({statement list})是一个表达式,逗号表达式类似,但是功能更强,({语句1;语句2;语句3;})中可以包含有多条语句(可以是变量定义、复杂的控制语句),该表达式的值为statement list中最后一条语句的值。
  • typeof()的功能是取变量类型。 typeof(x)是获取x的类型,typeof(x) _a = (x)就是定义一个x类型的变量 _a,并把x的数值赋值给它。如x是int型的5,那么typeof(x) _a = (x),就相当于int _a=5。

那么GCC的扩展在内核中是如何巧妙使用的呢,下面我们就结合内核中的max()宏的源码来一探究竟,先看下linux-3.18.34内核中有哪些常见的求最值的宏:

/** min()/max()/clamp() macros that also do* strict type-checking.. See the* "unnecessary" pointer comparison.*/
#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)

由此观之,大同小异,下面我们以max(x,y)宏为例,进行深入探究:

#define max(x, y) ({             \typeof(x) _max1 = (x);            \typeof(y) _max2 = (y);            \(void) (&_max1 == &_max2);       \_max1 > _max2 ? _max1 : _max2; })

首先,它的结构是这样({语句1;语句2;语句3;语句4;}),根据GCC的扩展特性,这个表达式最终的值应该是语句4的值。

语句1typeof(x) _max1 = (x);是定义了一个x类型的局部变量_max1,并把x的数值赋值给了_max1。

语句2typeof(y) _max2 = (y);是定义了一个y类型的局部变量_max2,并把x的数值赋值给了_max2。

语句3(void) (&_max1 == &_max2);对于程序的执行是没有任何作用的,它的作用在于判断两个数的类型是否相同,如果类型不同,就会在编译过程中抛出一个警告。因为x和y的类型不一样,其指针类型也会不一样,两个不一样的指针类型进行比较操作,会抛出一个编译警告。如char * x; int * y, 然后x==y, 这个判断因为一个是char * 一个是int *,所以gcc在编译时会产生一个warning,这样可以避免一些潜在的错误发生。

语句4_max1 > _max2 ? _max1 : _max2;才是最终求最大值的核心语句,那为什么要大费周章这样写呢?

在这个宏定义中,先根据x和y的类型生成了两个局部变量_max1和_max2,之后判断其类型,比较其大小,返回较大的一个,这样就保证了宏参数只会被执行一次,避免了之前实验中出现的错误结果。要将x和y重新定义为_max1和_max2是为了避免输入参数和宏定义内部使用的局部变量重名,重名会导致在宏定义的语句块外层同名变量被内层变量作用而出现错误,这也就是前面提到的为什么会执行两次y++而出现错误的原因了。

那么接下来,我们就编写一个内核模块来实现求最大数,看看内核中的max(x,y)宏的使用效果。

2、内核模块代码

如下代码实现了在Linux内核模块中,两个数比较大小,并输出最大数。

/*********************************************************** Author        : 梁金荣* Email         : Liangjinrong111@163.com* Last modified : 2019-09-20 7:00* Filename      : maxnum.c* Description   : 模块初始化函数中求最大数(使用内核中的代码)* *******************************************************//**
* 必要的头文件
*/
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>/**
* 模块的初始化函数,模块的入口函数,加载模块,需超级用户权限
*/
static int __init lk_maxnum(void)
{int x = 1, y = 2;printf("max=%d\n", max(x++, y++));printf("x = %d, y = %d\n", x, y);return 0;
}/**
* 出口函数,卸载模块,需超级用户权限
*/
static void __exit lk_exit(void)
{printk("The maxnum moudle has exited!\n");
}module_init(lk_maxnum); //内核入口点,调用初始化函数,包含在module.h中
module_exit(lk_exit); //出口点
MODULE_LICENSE("GPL"); //许可证
MODULE_AUTHOR("ljr"); //作者(非必须)
MODULE_DESCRIPTION("max number"); //模块描述(非必须)

3、Makefile文件

如下代码为上述内核模块的Makefile文件:

#产生目标文件
obj-m:=maxnum.o
#定义当前路径
CURRENT_PATH:=$(shell pwd)
#定义内核版本号
LINUX_KERNEL:=$(shell uname -r)
#定义内核源码绝对路径
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
#编译模块
all:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
#清理模块
clean:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

切记,在Makefile文件中,空格是不能乱加的

4、编译、加载、查看、卸载内核模块

(1)编译内核模块

命令如下:

make


如上图所示,生成的.ko文件就是内核模块文件(kernel object),该文件可在内核中加载或卸载。

(2)加载内核模块

加载内核模块时需要使用超级用户权限,且模块名后面必须加上.ko,命令如下:

sudo insmod maxnum.ko

(3)查看内核模块

查看内核模块命令如下:

lsmod


如图,maxnum模块已经加载到内核中了。

(4)卸载内核模块

卸载内核模块时依然需要超级用户权限,注意此时模块名后无需加上.ko,命令如下:

sudo rmmod maxnum

5、查看结果

我们在模块初始化函数中将输出结果打印在系统日志中,查看系统日志命令如下:

dmesg


如图,我们使用内核中的代码,在模块初始化函数中求出了最大数,并将其输出,同时也验证了GCC中({statement list})的扩展和typeof()解决了普通max(x,y)宏会出现结果错误的问题。

嗯?Linux内核中又更新max()宏相关代码了?新提交的代码是什么样子呢?其中又有什么奥妙之处呢?下一篇文章《Linux内核中max()宏的奥妙何在?(二)——大神Linus对这个宏怎么看?》让我们一探究竟!

先贴一张图片,感受一下:

Linux内核中max()宏的奥妙何在?(一)相关推荐

  1. Linux内核中max()宏的奥妙何在?(二)——大神Linus对这个宏怎么看?

    最新max()宏 上回,我们在<Linux内核中max()宏的奥妙何在?(一)>一文中说到,在3.18.34版Linux内核源码中的max()宏,采用了GCC的扩展特性,可以避免一些错误. ...

  2. Linux 内核中的宏定义

    Linux 内核中的宏定义 rtoax 日期 内核版本:linux-5.10.13 注释版代码:https://github.com/Rtoax/linux-5.10.13 __attribute__ ...

  3. linux内核函数 ffs,linux内核中的宏ffs(x)【转】

    linux内核中ffs(x)宏是平台相关的宏,在arm平台,该宏定义在 arch/arm/include/asm/bitops.h #define ffs(x) ({ unsigned long __ ...

  4. linux内核函数 ffs,linux内核中的宏ffs(x)

    转自:https://www.cnblogs.com/fengeryi/p/3449720.html linux内核中ffs(x)宏是平台相关的宏,在arm平台,该宏定义在 arch/arm/incl ...

  5. Linux内核中container_of宏的理解

    对 typeof 的理解: 实际上, typeof 并不是宏定义,它是GCC的关键字,是GCC特有的特性.如果只知道一个变量的名字要得到其类型,并不是宏定义能够完成的,这需要编译时的信息.所以,typ ...

  6. 如何放出Linux内核中的链表大招

    前言 上回,我们说到Linux内核中max()宏的终极奥义,Linux内核链表也不甘示弱,那么接下来,让我们看看Linux内核中的链表大招. 如何放出Linux内核中的链表大招 前言 一.链表简介 ( ...

  7. Linux内核中的常用宏container_of其实很简单【转】

    转自:http://blog.csdn.net/npy_lp/article/details/7010752 开发平台:Ubuntu11.04 编 译器:gcc version 4.5.2 (Ubun ...

  8. 如何在linux内核中读写文件

    在VFS的支持下,用户态进程读写任何类型的文件系统都可以使用read和write着两个系统调用,但是在linux内核中没有这样的系统调用我们如何操作文件呢?我们知道read和write在进入内核态之后 ...

  9. Linux内核中的READ_ONCE和WRITE_ONCE宏

    在Linux内核代码中,经常可以看到读取一个变量时,不是直接读取的,而是需要借助一个叫做READ_ONCE的宏:同样,在写入一个变量的时候,也不是直接赋值的,而是需要借助一个叫做WRITE_ONCE的 ...

最新文章

  1. 《精通Unix下C语言编程与项目实践》目录
  2. win2000输入密码后循环登陆,不能进入用户桌面
  3. mysql存储加速_mysql存储过程加速
  4. 腾讯微博发表带图片的微博
  5. JDK,JRE,JVM三者的关系
  6. 5000字 大数据时代读书笔记_大数据时代 读书笔记
  7. Windows命令行提取日期时间
  8. 内核移植(4)移植yaffs文件系统
  9. 微pe工具箱有linux版吗,微PE工具箱
  10. 广东工业大学通信原理复习笔记第七章数字信号的调制传输(思维导图)含链接和习题
  11. t检验自由度的意义_统计学常用概念:T检验、F检验、卡方检验、P值、自由度...
  12. 麦吉尔大学计算机工程的世界排名,不只是知名大学:麦吉尔大学你需要知道这些!...
  13. WMS系统开发总结-移库管理-下架与上架
  14. iiOS 6 新特性
  15. 软件测试,软件测试练习题
  16. 发运确认后,订单行保持“已挑库”状态 Order Line Remains in Picked (Awaiting Shipping) Status After Ship Confirmation
  17. R语言 RevoScaleR的大规模数据集决策树模型应用案例
  18. 免费领,自动化控制编程入门到开挂学习路径(附教程和软件工具)
  19. 题目 1878: 蓝桥杯2017年第八届真题-青蛙跳杯子
  20. 基于cc2530获取ds18b20温度值

热门文章

  1. 思科认证与华为认证在考题与内容上到底多大差别?
  2. 02-NLP-01-jieba中文处理
  3. 【274天】每日项目总结系列012(2017.11.06)
  4. C# 强制删除文件,解除占用的几点思考
  5. linux-Tcp IP协议栈源码阅读笔记
  6. 转: 用css把图片转为灰色图
  7. javascript获取网页URL地址及参数等
  8. photoshop ps 钢笔工具抠出图 复制出来 方法
  9. linux c 获取系统进程总数
  10. git 常用命令 方法大全