Linux内核入门(七)—— 必要的编译知识

[Linux驱动开发七] pktgen模块的编译以及Linux内核版本切换 本篇文章首先对Linux下的文件目录各功能进行介绍,其次对insmod和modprobe两个命令进行了对比,然后分析了Linux内核源码(如Pktgen等模块源码)的下载与编译,最后讲解了如何进行Linux内核的切换。 阅读详情

 

所有的内核代码,基本都包含了include/linux/compile.h这个文件,所以它是基础,涵盖了分析内核所需要的一些列编译知识,本博就分析分析这个文件里的代码:

#ifndef __LINUX_COMPILER_H
#define __LINUX_COMPILER_H

#ifndef __ASSEMBLY__

首先印入眼帘的是对__ASSEMBLY__这个宏的判断,这个变量实际是在编译汇编代码的时候,由编译器使用-D这样的参数加进去的,gcc会把这个宏定义为1。用在这里,是因为汇编代码里,不会用到类似于__user这样的属性(关于 __user这样的属性是怎么回子事,本博后面会提到),因为这样的属性是在定义函数参数的时候加的,这样避免不必要的宏在编译汇编代码时候的引用。

#ifdef __CHECKER__

接下来是一个对__CHECKER__这个宏的判断,这里需要讲的东西比较多,是本博的重点。
        
当编译内核代码的时候,使用make C=1或C=2的时候,会调用一个叫Sparse的工具,这个工具对内核代码进行检查,怎么检查呢,就是靠对那些声明过Sparse这个工具所能识别的特性的内核函数或是变量进行检查。在调用Sparse这个工具的同时,在Sparse代码里,会加上#define __CHECKER__ 1的字样。换句话说,就是,如果使用Sparse对代码进行检查,那么内核代码就会定义__CHECKER__宏,否则就不定义。具体解释请访问:http://linux.die.net/man/1/sparse

例如:
# define __user  __attribute__((noderef, address_space(1)))
这个宏是重点,用来检查是否属于用户空间!这里就能看出来,类似于__attribute__((noderef, address_space(1)))这样的属性就是Sparse这个工具所能识别的了。
   
其他的那些个属性是用来检查什么的呢,我一个个地做介绍。
__user 这个特性,即__attribute__((noderef, address_space(1))),是用来修饰一个变量的,这个变量必须是非解除参考(__attribute__((noderef))——no dereference)的,即这个变量地址必须是有效的,而且变量所在的地址空间必须是1(__attribute__((address_space(1)))),即用户程序空间的。这里Sparse工具把程序空间分成了3个部分,0表示normal space,即普通地址空间,对内核代码来说,当然就是内核空间地址了。1表示用户地址空间,这个不用多讲,还有一个2,表示是设备地址映射空间,例如硬件设备的寄存器在内核里所映射的地址空间。

所以在内核函数里,有一个copy_to_user的函数(我们在系统调用博文中会详细介绍),函数的参数定义就使用了这种方式。当然,这种特性检查,只有当机器上安装了Sparse这个工具,而且进行了编译的时候调用,才能起作用的。


# define __kernel /* default address space */

根据定义,就是检查是否处于内核空间。其为默认的地址空间,即0,我想定义成__attribute__((noderef, address_space(0)))也是没有问题的。


# define __safe  __attribute__((safe))

这个定义在sparse里也有,内核代码是在2.6.6-rc1版本变到2.6.6-rc2的时候被Linus加入的,原因是这样的:

有人发现在代码编译的时候,编译器对变量的检查有些苛刻,导致代码在编译的时候老是出问题(我这里没有去检查是编译不通过还是有警告信息,因为现在的编译器已经不是当年的编译器了,代码也不是当年的代码)。比如说这样一个例子,
 int test( struct a * a, struct b * b, struct c * c ) {
  return a->a + b->b + c->c;
  }

这个编译的时候会有问题,因为没有检查参数是否为空,就直接进行调用。但是呢,在内核里,有好多函数,当它们被调用的时候,这些个参数必定不为空,所以根本用不着去对这些个参数进行非空的检查,所以就增加了一个__safe的属性,如果这样声明变量,
 int test( struct a * __safe a, struct b * __safe b, struct c * __safe c ) {
  return a->a + b->b + c->c;
  }

编译就没有问题了。

不过到目前为止,在现在的代码里没有发现有使用__safe这个定义的地方,不知道是不是编译器现在已经支持这种特殊的情况了,所以就不用再加这样的代码了。

# define __force __attribute__((force))

表示所定义的变量类型是可以做强制类型转换的,在进行Sparse分析的时候,是不用报告警信息的。

# define __nocast __attribute__((nocast))

这里表示这个变量的参数类型与实际参数类型一定得对得上才行,要不就在Sparse的时候生产告警信息。

# define __iomem __attribute__((noderef, address_space(2)))

这个定义与__user一样,不过这里的变量地址需要在设备地址映射空间。

# define __acquires(x) __attribute__((context(x,0,1)))
# define __releases(x) __attribute__((context(x,1,0)))

这是一对相互关联的函数定义,第一句表示参数x在执行之前,引用计数必须为0,执行后,引用计数必须为1,第二句则正好相反,这个定义是用在修饰函数定义的变量的。

# define __acquire(x) __context__(x,1)
# define __release(x) __context__(x,-1)

这是一对相互关联的函数定义,第一句表示要增加变量x的计数,增加量为1,第二句则正好相反,这个是用来函数执行的过程中。

以上四句如果在代码中出现了不平衡的状况,那么在Sparse的检测中就会报警。当然,Sparse的检测只是一个手段,而且是静态检查代码的手段,所以它的帮助有限,有可能把正确的认为是错误的而发出告警。要是对以上四句的意思还是不太了解的话,请在源代码里搜一下相关符号的用法就能知道了。这第一组与第二组,在本质上,是没什么区别的,只是使用的位置上,有所区别罢了。

# define __cond_lock(x,c) ((c) ? ({ __acquire(x); 1; }) : 0)

这句话的意思就是条件锁。当c这个值不为0时,则让计数值加1,并返回值为1。不过这里我有一个疑问,有一个__cond_lock定义,但没有定义相应的__cond_unlock,那么在变量的释放上,就没办法做到一致。而且spin_trylock()这个函数的定义,它就用了 __cond_lock,而且里面又用了_spin_trylock函数,在_spin_trylock函数里,再经过几次调用,就会使用到 __acquire函数,这样的话,相当于一个操作,就进行了两次计算,会导致Sparse的检测出现告警信息,经过写代码进行实验,验证了我的判断,确实会出现告警信息,如果我写两遍unlock指令,就没有告警信息了,但这是与程序的运行是不一致的。


extern void __chk_user_ptr(const volatile void __user *);
extern void __chk_io_ptr(const volatile void __iomem *);

这两句比较有意思。只是定义了函数,但是代码里没有函数的实现。这样做的目的,就是在进行Sparse的时候,让Sparse给代码做必要的参数类型检查,在实际的编译过程中,并不需要这两个函数的实现。

#define notrace __attribute__((no_instrument_function))

这一句,是定义了一个属性,这个属性可以用来修饰一个函数,指定这个函数不被跟踪。在gcc编译器里面,实现了一个非常强大的功能,如果在编译的时候把一个相应的选择项打开,那么就可以在执行完程序的时候,用一些工具来显示整个函数被调用的过程,这样就不需要让程序员手动在所有的函数里一点点添加能显示函数被调用过程的语句,这样耗时耗力,还容易出错。那么对应在应用程序方面,可以使用Graphviz这个工具来进行显示,至于使用说明与软件实现的原理可以自己在网上查一查,很容易查到。对应于内核,因为内核一直是在运行阶段,所以就不能使用这套东西了,内核是在自己的内部实现了一个ftrace的机制,编译内核的时候,如果打开这个选项,那么通过挂载一个debugfs的文件系统来进行相应内容的显示,具体的操作步骤,可以参看内核源码所带的文档。那上面说了这么多,与notrace这个属性有什么关系呢?因为在进行函数调用流程的显示过程中,是使用了两个特殊的函数的,当函数被调用与函数被执行完返回之前,都会分别调用这两个特别的函数。如果不把这两个函数的函数指定为不被跟踪的属性,那么整个跟踪的过程 就会陷入一个无限循环当中。



#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

这两句是一对对应关系。__builtin_expect(expr, c)这个函数是新版gcc支持的,它是用来作代码优化的,用来告诉编译器,expr的期,非常有可能是c,这样在gcc生成对应的汇编代码的时候,会把相应的可能执行的代码都放在一起,这样能少执行代码的跳转。为什么这样能提高CPU的执行效率呢?

因为CPU在执行的时候,都是有预先取指令的机制的,把将要执行的指令取出一部分出来准备执行。CPU不知道程序的逻辑,所以都是从可程序程序里挨着取的,如果这个时候,能不做跳转,则CPU预先取出的指令都可以接着使用,反之,则预先取出来的指令都是没有用的。还有个问题是需要注意的,在__builtin_expect的定义中,以前的版本是没有!!这个符号的,这个符号的作用其实就是负负得正,为什么要这样做呢?就是为了保证非零的x的值,后来都为1,如果为零的0值,后来都为0,仅此而已。

#ifndef barrier
# define barrier() __memory_barrier()
#endif
这里表示如果没有定义barrier函数,则定义barrier()函数为__memory_barrier()。但在内核代码里,是会包含 compiler-gcc.h这个文件的,所以在这个文件里,定义barrier()为__asm__ __volatile__("": : :"memory")。

barrier翻译成中文就是屏障的意思,为什么要一个屏障呢?这是因为CPU在执行的过程中,为了优化指令,可能会对部分指令以它自己认为最优的方式进行执行,这个执行的顺序并不一定是按照程序在源码内写的顺序。编译器也有可能在生成二进制指令的时候,也进行一些优化。这样就有可能在多CPU,多线程或是互斥锁的执行中遇到问题。那么这个内存屏障可以看作是一条线,内存屏障用在这里,就是为了保证屏障以上的操作,不会影响到屏障以下的操作。

然后再看看这个屏障怎么实现的。__asm__表示后面的东西都是汇编指令,当然,这是一种在C语言中嵌入汇编的方法,语法有其特殊性,我在这里只讲跟这条指令有关的。__volatile__表示不对此处的汇编指令做优化,这样就会保证这里代码的正确性。""表示这里是个空指令,那么既然是空指令,则所对应的指令所需要的输入与输出都没有。在gcc中规定,如果以这种方式嵌入汇编,如果输出没有,则需要两个冒号来代替输出操作数的位置,所以需要加两个::,这时的指令就为"" : :。然后再加上为分隔输入而加入的冒号,再加上空的输入,即为"" : : :。后面的memory是gcc中的一个特殊的语法,加上它,gcc编译器则会产生一个动作,这个动作使gcc不保留在寄存器内内存的值,并且对相应的内存不会做存储与加载的优化处理,这个动作不产生额外的代码,这个行为是由gcc编译器来保证完成的。

#ifndef RELOC_HIDE
# define RELOC_HIDE(ptr, off)     /
  ({ unsigned long __ptr;     /
     __ptr = (unsigned long) (ptr);    /
    (typeof(ptr)) (__ptr + (off)); })
#endif

接下来好多定义都没有实现,可以看一看注释就知道了,所以这里就不多说了。唉,不过再插一句,__deprecated属性的实现是为deprecated。

#define noinline_for_stack noinline

#ifndef __always_inline
#define __always_inline inline
#endif

这里noinline与inline属性是两个对立的属性,从词面的意思就非常好理解了。

#ifndef __cold
#define __cold
#endif

从注释中就可以看出来,如果一个函数的属性为__cold,那么编译器就会认为这个函数几乎是不可能被调用的,在进行代码优化的时候,就会考虑到这一点。不过我没有看到在gcc里支持这个属性的说明。

#ifndef __section
# define __section(S) __attribute__ ((__section__(#S)))
#endif

这个比较容易理解了,用来修饰一个函数是放在哪个区域里的,不使用编译器默认的方式。这个区域的名字由定义者自己取,格式就是__section__加上用户输入的参数。

#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
这个函数的定义很有意思,它就是访问这个x参数所对应的东西一次,它是这样做的:先取得这个x的地址,然后把这个地址进行变换,转换成一个指向这个地址类型的指针,然后再取得这个指针所指向的内容。这样就达到了访问一次的目的。

gcc: libc: __ASSEMBLER__; Kernel: __ASSEMBLY__ 这里探讨一下这两个宏定义的作用。 阅读详情

相关推荐

springboot 启动方式 装配流程 自定义starter 文件加载顺序 常见设计模式

springboot 启动方式 装配流程 自定义starter 文件加载顺序 常见设计模式

sv 936

Linux内核入门(七)—— 必要的编译知识

所有的内核代码,基本都包含了include/linux/compile.h这个文件,所以它是基础,涵盖了分析内核所需要的一些列编译知识,本博就分析分析这个文件里的代码:#ifndef __LINUX_COMPILER_H#define __LINUX_COMPILER_H#ifndef __ASSEMBLY__首先印入眼帘的是对__ASSEMBLY__这个宏的判断,这个

云(存储),松(耦合) 6641

经典点云配准算法:迭代最近点算法ICP(Iterative Closest Point)

迭代最近点算法,又名为 ICP(Iterative Closest Point)算法。 (1)ICP算法对初始值的依赖比较大,具体的配准过程中,如何选择初始值是一个问题,可以使用轮式里程计、imu预积分或其它先验的配准结果。 (2)ICP迭代次数过多,实际工程中需要考虑到其实时性的优化问题。 (3)迭代终止的阀值的设置也是需要考虑的点,可以考虑自适应阀值的方法。 (4)关于寻找最近点可以使用kdtree进行临近搜索。

qq_35102059的博客 1万+

linux_compiler内核自定义,(转) Linux内核源码分析——compiler.h分析

Linux的内核源码都会包含文件linux/compile.h,所以先分析该文件内的内容,作为开篇。1 汇编编译时不定义的内容该文件的第一个内容是对宏__ASSEMBLY__的判断,这个宏的作用是避免在进行汇编编译的时候,不定义后续相关内容。这个宏通过在编译器中用-D选项中加入,参数AFLAGS也包含该宏定义。在汇编时编译器会定义__ASSEMBLY__为1。———————————————————...

weixin_35935808的博客 543

ESP32 Arduino学习篇 (一) 引脚和串口的基本使用

简单来讲arduino就是通过源代码控制开发板的信号输入输出从而实现对一些电子元件的控制,例如LED灯,蜂鸣器,按键arduino程序首先来讲arduino程序是基于C语言,而实现类似对于LED灯和蜂鸣器的简单控制只需简单C语言知识即可实现。arduino程序是基于函数的,而在arduino的库有很多函数,我们只需要调用即可。arduino的程序主体是**setup()函数和loop()**函数以及其它在使用过程中需要构建的函数:setup()函数。

DOF526570的博客 1万+

Cortex-M内核DWT周期计数器:从原理到STM32实战时间测量与性能分析

在嵌入式开发中,代码执行耗时测量是性能优化的基础。除了依赖外设定时器或SysTick,ARM Cortex-M3/M4/M7内核自带的DWT(Data Watchpoint and Tracker)调试组件提供了一种极低侵入性的硬件计时方案。其内部的32位周期计数器CYCCNT跟随内核时钟递增,无需占用额外引脚或定时器资源,即可实现纳秒级分辨率的函数耗时测量、中断响应分析及延时实现。从DWT寄存器配置、周期差值计算到基于SystemCoreClock的微秒换算,开发者可以快速搭建一套轻量级性能分析工具。本文

weixin_34216614的博客 183

linux kernel的开始

现在学习看linux内核代码,一开始看一头雾水,不知道从那里开始,偶尔听一个同学说起内核就是一个while(1)循环,在看TPC/IP 内核协议栈时,经常看到内核的回调机制,结合自己看代码的经验,对内核结构猜测:内核是一个模型,这个模型具有良好的分层结构,在不同的模块里通过回调init完成注册,在推出时通过注册回调exit完成退出。       在这里,内核使用了面向对象的思想。在我们讨论编程语言

chituhuan的专栏 526

阿里巴巴达摩院人工智能训练师(高级)

阿里巴巴达摩院人工智能训练师(高级)

huang0612123的博客 1万+

linux内核编译入门,Linux内核入门(七)—— 必要的编译知识

Linux内核入门(七)—— 必要的编译知识所有的内核代码,基本都包含了include/linux/compile.h这个文件,所以它是基础,涵盖了分析内核所需要的一些列编译知识,本博就分析分析这个文件里的代码:#ifndef __LINUX_COMPILER_H#define __LINUX_COMPILER_H#ifndef __ASSEMBLY__首先印入眼帘的是对__ASSEMBLY__这...

weixin_39704246的博客 243

RK3506开发板实战:5分钟搞定开源鸿蒙OpenHarmony5.1+星闪功能配置

本文提供了一份基于RK3506开发板快速配置开源鸿蒙OpenHarmony 5.1并启用星闪功能的实战指南。文章详细介绍了从开发环境搭建、系统镜像编译烧录,到星闪驱动加载验证、应用开发及性能调优的全流程,帮助开发者高效上手,将新一代短距无线通信技术应用于物联网项目。

time3的博客 653

一张图讲透银行个人外汇业务逻辑和实操

个人外汇业务主要分为结售汇和外币兑换两类,前者满足日常跨境需求,后者因投机属性多数银行已暂停。业务核心是利用汇率波动实现货币兑换,满足支付或投资需求。交易涉及客户分类、主流币种、多种渠道和汇率计算规则,需区分直接/间接标价法判断汇率走势。银行通过"货币兑换"科目核算损益,采用期末折算确认盈亏。监管要求包括5万美元年度额度、合法渠道、真实性原则及大额申报等。随着跨境金融数字化发展,掌握基础逻辑和合规要求是安全参与外汇业务的关键。

银行信息系统应用架构详解 288

linux 内核编译

本文介绍了Linux内核编译的详细步骤,包括环境准备、配置选项、编译过程及安装方法,是学习和实践内核编译的指南。

mx_jun的博客 9556

基于多智能体强化学习的轨道追逃博弈方法复现

本文提出了一种基于多智能体深度强化学习(MADDPG)的空间轨道追逃博弈方法。通过构建Actor-Critic网络架构,采用集中训练分布式执行的策略,使卫星群能够自主协调完成对非合作目标的围捕任务。代码实现了经验回放缓冲区、噪声探索机制和网络参数更新等核心功能,通过奖励函数设计平衡最短时间、燃料消耗和碰撞规避等目标。仿真结果表明该方法能有效利用数量优势,涌现出智能协同行为,为解决复杂空间博弈问题提供了新思路。

huanghm88的专栏 336

Linux内核及内核编译

使用make menuconfig时,配置工具首先分析与体系结构对应的/arch/xxx/Kconfig文件,xxx为传入的ARCH参数,/arch/xxx/Kconfig文件中除本身包含一些与体系结构相关的配置项和配置菜单外,还通过source语句引入一系列Kconfig文件。如果需要几个并发执行的任务,可以启动内核线程,这些线程没有用户空间。内核空间和用户空间用来区分程序执行的两种不同的状态,他们使用不同的地址空间,Linux中只能通过系统调用和硬件中断完成从用户空间到内核空间的控制转移。

weixin_45241469的博客 4587

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

【Linux】Linux内核编译与入门

linux内核编译与入门

DevFrank的博客 3749

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

[OS] 编译 Linux 内核

这些工具的安装是内核编译的基础,因为内核编译涉及到大量复杂的配置和依赖,少了其中任何一个都会导致编译失败。在进行内核模块的加载和测试时,我们还需要构建内核对象。在重启过程中,你需要在引导菜单中选择新编译的内核版本,以确保加载我们新加入的 Kthreads 模块。内核源代码较大,编译过程需要大量的磁盘空间,建议将源代码移动到有足够空间的目录下再继续编译。这个步骤的目的是使用现有内核的配置,确保我们在编译过程中不会遗漏关键的选项。在构建内核对象之后,我们需要将其插入内核进行测试,并在不需要时将其移除。

m0_74331272的博客 1919

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

编译linux内核_在 MacOS 上编译 Linux 内核(2020 edition)

平时使用 MacOS 比较多,因为最近在接触操作系统的知识,就想着用手头的设备编译下 Linux 内核。本次实践仅作为 Linux 内核学习的入门练习。以下是记录。资源准备下载 VirtualBox (用时 20min) VirtualBox6.1.16 platform packages :Downloads – Oracle VM VirtualBox[1]下载 ubuntu 64 ...

weixin_39973492的博客 2842

Linux内核编译入门

编译Linux内核可能听起来有些复杂,但只要你遵循正确的步骤,就能够轻松地完成。在这篇博客中,我们介绍了Linux内核的基本概念和作用,并通过一个简单的示例演示了如何编译Linux内核。希望这能够帮助你更好地理解和掌握Linux内核编译的过程。这篇博客详细介绍了如何编译Linux内核,包括获取内核源代码、配置内核选项、编译内核、安装内核和更新引导配置等步骤。希望这篇文章能够帮助读者理解和掌握Linux内核编译的基本过程。

qq_36733726的博客 814

Linux内核驱动入门 编译环境搭建、编译内核

哎…有时候我都不知道自己是干啥的说是运维吧,docker不会,k8s不会;说是驱动吧,uboot不熟,内核不熟,驱动没写过;什么都是会点皮毛,这篇文章记录一下内核驱动学习记录。(背景:用的正点原子的开发板)

Aruko的博客 1688

如何编译Linux内核

内核,是一个操作系统的核心。它负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。Linux作为一个自由软件,在广大爱好者的支持下,内核版本不断更新。新的内核修订了旧内核的bug,并增加了许多新的特性。如果用户想要使用这些新特性,或想根据自己的系统度身定制一个更高效,更稳定的内核,就需要重新编译内核。本文将以RedHat Linux 6.0(kernel 2.2.5)

cdsnmdl的专栏 7万+

内核编译:缺少了一些必要的配置文件

内核编译:缺少了一些必要的配置文件

xie__jin__cheng的博客 1354

查看linux内核的编译时间,linux内核编译步骤

linux内核编译步骤对于linux新手来说,编译内核相对有一些难度,甚至不知道如何入手,我通过在网上收集这方面的资料,最终编译成功.现在我归纳了一下,写出这一篇还算比较详细的步骤,希望能对各位新手有一些帮助。1、 准备工作(1)整理出系统需要支持的硬件、文件系统类型以及网络协议等内容。(2)建议用命令uname –r 查看一下系统的版本号,如果你的系统版本与将要编译的内核版本一致,建议将/li...

weixin_29498577的博客 4129

Linux入门之内核编译与替换

Linux入门之内核编译与替换——black的专栏 —— washaxiu

black的专栏 9087

Linux内核编译

编译完成以后就会在这个目录下生成一个叫做zImage的文件,zImage 就是我们要用的Linux镜像文件。另外也会在下生成很多.dtb文件,这些.dtb就是设备树文件。

weixin_45205160的博客 1239
上一篇: Linux内核入门(六)—— __attribute_ 机制
下一篇: 进程优先级札记
废言Pro
博客等级 码龄16年 1092粉丝 142原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值