IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux:从min宏开始入手LKM -> 正文阅读

[系统运维]Linux:从min宏开始入手LKM

引言

本学期开了陈老师的OS课程,作业比较丰富。对于我这种放飞自我的人来说,作业是“选做 = 不做,必做 = 乱做”的,但是点进去任务的页面,稍微看了一眼觉得挺感兴趣的,也有一点点自己的小问题,决定记录一下。

任务如下:

任务详情
搭建Linux环境,编写Linux内核模块,二选一或者都做
1.编写Linux内核模块,并求最大数,参考,https://mp.weixin.qq.com/s/0ZVNbTj7fGs8Gi03vj541A和 https://mp.weixin.qq.com/s/Kvle_fgr4rj5Jt4VDUmKHA
2.编写Linux内核模块,增加一个新的系统调用,参考https://www.cnblogs.com/wangzahngjun/p/4992045.html

我在这里选择的是任务一来做。
关于max宏的第一版本,Linux内核中max()宏的奥妙何在?(一) 已经写的比较到位了,我就不赘述了,大家可以先从师哥的这篇文章开始看起。

文章末尾放出一张邮件列表的截图,我将从邮件中一些没有提到的点说起。
在这里插入图片描述
首先可以看到有这么一句话:

Our old “min()” had the int ernal variables called “min1” and"nin2",which is crazy too.

为什么说很“crazy”?让我们接着往下看。

第一版的“疯狂”之处

第一版的宏确实用“ ernal variables”解决了多次自增的缺点,并且妙用了GCC扩展特性typeof和statement list(听说这两点也快被加到新的C标准中啦),但是很显然如果当变量名重复时,就会出现ub(Undefined Behaviour)。

让我们试试吧!

#include <stdio.h>

#define min(x, y)                  \
  ({                               \
    typeof(x) _min1 = (x);         \
    typeof(y) _min2 = (y);         \
    (void)(&_min1 == &_min2);      \
    _min1 < _min2 ? _min1 : _min2; \
  })

int main(int argc, char *argv[]) {
  int _min1 = 0, _min2 = 1;
  printf("min(_min1, _min2) is %d\n", min(_min1, _min2));
  return 0;
}

不过运行后我们就会发现结果居然每次都不一样!
任取三次感受一下:

min(_min1, _min2) is 22057
min(_min1, _min2) is -561487744
min(_min1, _min2) is -1579712384

预处理后看看咋回事:

?  lkm git:(master) ? gcc -E max-v1.c | tail

# 2 "max-v1.c" 2
# 11 "max-v1.c"

# 11 "max-v1.c"
int main(int argc, char *argv[]) {
  int _min1 = 0, _min2 = 1;
  printf("min(_min1, _min2) is %d\n", ({ typeof(_min1) _min1 = (_min1); typeof(_min2) _min2 = (_min2); (void)(&_min1 == &_min2); _min1 < _min2 ? _min1 : _min2; }));
  return 0;
}

格式化后更好看一些:

  printf("min(_min1, _min2) is %d\n", ({
           typeof(_min1) _min1 = (_min1);
           typeof(_min2) _min2 = (_min2);
           (void)(&_min1 == &_min2);
           _min1 < _min2 ? _min1 : _min2;
         }));

问题就出在这里:

typeof(_min1) _min1 = (_min1);
typeof(_min2) _min2 = (_min2);

这就相当于int x= x,由于x是local variable(也就是邮件中强调的“ernal variables”),它的值是随机的,不过好在它并不会修改main函数中_min1和_min2的值,这是由作用域决定的。

__LINE__的弊端

And our __UNIQUE_D( macro is garbage anyway,since it falls back cnthe line rumber,which doesn’t really work for macros aryway.But wehave proper macros for both clang and gcc,so maybe we should ignorethe broken fallback.

garbage这词都用上了,不愧是你linus。原因出在“it falls back cnthe line rumber”。我在这里中找到了类似的实现。

# define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __LINE__)

这个宏想必也是耳熟能详的,在不少自定义的debug函数中我们可以看到它的身影,预处理时,编译器会自动把它替换成当前行号。但是完全依赖行号,当多个源文件对应行行号一致,在链接时(编译不会)会报错。

最新版本

我在Github上找到了目前Linux内核中某些文件正在使用的版本,链接在下方给出。根据这些源文件,我们提取出相关内容并简单写了一个小例子进行讨论。

#include <stdio.h>

#define __min(t1, t2, min1, min2, x, y) \
  ({                                    \
    t1 min1 = (x);                      \
    t2 min2 = (y);                      \
    (void)(&min1 == &min2);             \
    min1 < min2 ? min1 : min2;          \
  })

#define ___PASTE(a, b) a##b
#define __PASTE(a, b) ___PASTE(a, b)

#define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__)

#define min(x, y) \
  __min(typeof(x), typeof(y), __UNIQUE_ID(min1_), __UNIQUE_ID(min2_), x, y)

#define min_t(type, x, y) \
  __min(type, type, __UNIQUE_ID(min1_), __UNIQUE_ID(min2_), x, y)

int main(int argc, char *argv[]) {
  int smaller = 0, bigger = 1;
  printf("min(bigger, smaller) is %s\n",
         min(bigger, smaller) == smaller ? "smaller" : "bigger");
  printf("min_t(int, bigger, smaller) is %s\n",
         min_t(int, bigger, smaller) == smaller ? "smaller" : "bigger");
  return 0;
}

输出是符合预期的:

min(bigger, smaller) is smaller
min_t(int, bigger, smaller) is smaller

这版跟上面的变化不大,主要是__LINE__换成了__COUNTER__来避免上面那种情况。
我们看到这里实际上有两个宏,区别在于min_t多了一个type参数,可能是用于在编码时明确指定比较类型,使代码更清晰吧。它和min实际上都调用了__min。

__PASTE和___PASTE

初看的时候我觉得还挺奇怪的,为什么一个连接字符串的宏还要分成两层来做,macro __min实际上类似于一个xxx_helper、或者do_xxx函数,是有用的,那么这里的macro PASTE到底有什么用呢?

首先我们试一下没有___PASTE会发生什么?
一个简单的例子:

#include <stdio.h>

#define __PASTE(a, b) a##b

int main(int argc, char *argv[]) {
  // result: __PASTE(a, b) c;
  __PASTE(__PASTE(a, b), c);
  return 0;
}

查看预处理后的结果:

?  lkm git:(master) ? gcc -E __PASTE.c | tail
__PASTE.c:7:23: error: pasting ")" and "c" does not give a valid preprocessing token
    7 |   __PASTE(__PASTE(a, b), c);
      |                       ^
__PASTE.c:3:23: note: in definition of macro ‘__PASTE’
    3 | #define __PASTE(a, b) a##b
      |                       ^




# 5 "__PASTE.c"
int main(int argc, char *argv[]) {

  __PASTE(a, b) c;
  return 0;
}

可以看到,__PASTE(__PASTE(a, b), c)并不会展开为abc,而是__PASTE(a, b) c,为什么呢?

我们需要先了解一下C语言的宏展开规则:

Macro arguments are completely macro-expanded before they are substituted into a macro body, unless they are stringified or pasted with other tokens. After substitution, the entire macro body, including the substituted arguments, is scanned again for macros to be expanded. The result is that the arguments are scanned twice to expand macro calls in them.

有看到一句更清晰的解释:

An occurrence of a parameter in a function-like macro, unless it is the operand of # or ##, is expanded before substituting it and rescanning the whole for further expansion.

其中,expanded before substituting我们可以称之为pre-expanded。

__PASTE(__PASTE(a, b), c) -> __PASTE(a, b) c。

而当我们这么写:

#define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__)

展开顺序应该是:

__UNIQUE_ID(prefix) ->
__PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__) ->
__PASTE(___PASTE(__UNIQUE_ID_, prefix), __COUNTER__) ->
__PASTE(__UNIQUE_ID_##prefix, __COUNTER__) ->
__PASTE(__UNIQUE_ID_prefix, __COUNTER__) ->
___PASTE(__UNIQUE_ID_prefix, __COUNTER__) ->
__UNIQUE_ID_prefix##__COUNTER__ ->
__UNIQUE_ID_prefix__COUNTER__

防止不恰当的pre-expanded,这就是为什么要有两重PASTE宏的原因。顺带一提,我个人觉得这个宏形参的名称应该取名suffix更为恰当,毕竟是“后缀”嘛。

自定义LKM

这步基本上其他博客都有,我们就照葫芦画瓢(把别人文章拿来改改),不过我没有成功。

make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules

问题锦集

/usr/src/下为空

用包管理器下载对应的linux-header即可。

无法进入到对应目录,原因比较简单:

?  min-lkm git:(master) ? ls /usr/src/
linux-headers-5.4.0-72/  linux-source-5.4.0/  linux-source-5.4.0.tar.bz2@
?  min-lkm git:(master) ? uname -r
5.4.72-microsoft-standard-WSL2

所以说,WSL还是会碰到一些小坑的。

/bin/sh: 1: flex: not found

安装flex即可,bison也同理。

sudo apt-get install flex
sudo apt-get install bison

Configuration file “.config” not found!

生成配置文件即可,注意后面一串东西是要加上的,这样才能找到Makefile。

sudo make menuconfig -C /usr/src/linux-headers-5.4.0-72/ SUBDIRS=$PWD modules

The present kernel configuration has modules disabled.

在这里插入图片描述这是因为忘了开启LKM支持,进入menuconfig选中这项再保存即可。

No rule to make target ‘arch/x86/entry/syscalls/syscall_32.tbl’, needed by ‘arch/x86/include/generated/asm/syscalls_32.h’. Stop.

目前卡在这步,原因不详。
在这里插入图片描述

Reference

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-09-12 13:32:23  更:2021-09-12 13:33:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 15:08:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码