tag 标签: LinuxAPI

相关博文
  • 热度 2
    2023-12-13 21:46
    666 次阅读|
    0 个评论
    【Linux API 揭秘】container_of函数详解 Linux Version:6.6 Author:Donge Github: linux-api-insides 1、container_of函数介绍 container_of可以说是内核中使用最为频繁的一个函数了,简单来说,它的主要作用就是根据我们结构体中的已知的成员变量的地址,来寻求该结构体的首地址,直接看图,更容易理解。 image-20231212195328080 下面我们看看linux是如何实现的吧 2、container_of函数实现 /** *container_of-castamemberofastructureouttothecontainingstructure *@ptr:thepointertothemember. *@type:thetypeofthecontainerstructthisisembeddedin. *@member:thenameofthememberwithinthestruct. * *WARNING:anyconstqualifierof@ptrislost. */ # define container_of(ptr,type,member)({\ void*__mptr=(void*)(ptr);\ member)||\ __same_type(*(ptr),void),\ "pointertypemismatchincontainer_of()" );\ ((type*)(__mptr-offsetof(type,member)));}) 函数名称 :container_of 文件位置 : include/linux/container_of.h 该函数里面包括了一些封装好的宏定义以及函数,比如:static_assert、__same_type、offsetof,以及一些指针的特殊用法,比如:(type *)0),下面我们一一拆解来看。 image-20231213140920353 2.1 static_assert /** *static_assert-checkintegerconstantexpressionatbuildtime * *static_assert()isawrapperfortheC11_Static_assert,witha *littlemacromagictomakethemessageoptional(defaultingtothe *stringificationofthetestedexpression). * *ContrarytoBUILD_BUG_ON(),static_assert()canbeusedatglobal *scope,butrequirestheexpressiontobeanintegerconstant *expression(i.e.,itisnotenoughthat__builtin_constant_p()is *trueforexpr). * *AlsonotethatBUILD_BUG_ON()failsthebuildiftheconditionis *true,whilestatic_assert()failsthebuildiftheexpressionis *false. */ # define static_assert(expr,...)__static_assert(expr,##__VA_ARGS__,#expr) # define __static_assert(expr,msg,...)_Static_assert(expr,msg) 函数名称 :static_assert 文件位置 : include/linux/build_bug.h 函数解析 :该宏定义主要用来 在编译时检查常量表达式,如果表达式为假,编译将失败,并打印传入的报错信息 expr:该参数表示传入进来的常量表达式 ...:表示编译失败后,要打印的错误信息 _Static_assert:C11中引入的关键字,用于判断表达式expr并打印错误信息msg。 在container_of函数中,主要用来断言判断 static_assert ( __same_type(*(ptr),((type*) 0 member)||__same_type(*(ptr), void ), "pointertypemismatchincontainer_of()" ); 2.2 __same_type /*Aretwotypes/varsthesametype(ignoringqualifiers)?*/ # ifndef __same_type # define __same_type(a,b)__builtin_types_compatible_p(typeof(a),typeof(b)) # endif 函数名称 :__same_type 文件位置 : include/linux/compiler.h 函数解析 : 该宏定义用于检查两个变量是否是同种类型 __builtin_types_compatible_p:gcc的内建函数,判断两个参数的类型是否一致,如果是则返回1 typeof:gcc的关键字,用于获取变量的类型信息 member),需要先弄明白(type *)0的含义。 更多干货可见: 高级工程师聚集地 ,助力大家更上一层楼! 2.3 (type *)0 (type *)0,该如何理解这个表达式呢? 首先,type是我们传入进来的结构体类型,比如上面讲到的struct test,而这里所做的 可以理解为强制类型转换 :(struct test *)addr。 addr可以表示内存空间的任意的地址,我们在强制转换后,默认后面一片的内存空间存储的是该数据结构。 image-20231213144714508 而(type *)0的作用,也就是默认将0地址处的内存空间,转换为该数据类型。 image-20231213144912371 member,就是获取我们结构体的成员对象。 member:是一种常见的技巧, 用于直接获取结构体type的成员member的类型,而不需要定义一个type类型的对象 。 2.4 offsetof # ifndef offsetof # define MEMBER) # endif 函数名称 :offsetof 文件位置 : include/linux/stddef.h 函数解析 : 该宏定义用于获取结构体中指定的成员,距离该结构体偏移量。 image-20231213152249395 TYPE:表示结构体的类型 MEMBER:表示指定的结构体成员 __builtin_offsetof:gcc内置函数,直接返回偏移量。 在新的linux源码中,直接引用了gcc内置的函数,而在老的内核源码中,该偏移量的实现方式如下: # define MEMBER) 同样用到了((TYPE *)addr),上面我们知道 MEMBER:表示获取该结构体的成员 MEMBER):加了一个&,表示地址,取该成员的内存地址。 MEMBER)就相当于0x00000010+size MEMBER)就相当于size 到这里,我们对container_of函数内部涉及的相关知识了然于胸,下面我们再来看container_of,简直容易到起飞。 2.5 container_of # define container_of(ptr,type,member)({\ void*__mptr=(void*)(ptr);\ member)||\ __same_type(*(ptr),void),\ "pointertypemismatchincontainer_of()" );\ ((type*)(__mptr-offsetof(type,member)));}) static_assert:断言信息,避免我们传入的参数类型不对,而做的编译检查处理,直接忽略。 #definecontainer_of(ptr,type,member)({\ void*__mptr=(void*)(ptr);\ (( type *)(__mptr-offsetof( type ,member)));}) offsetof(type, member):计算的是结构体中的成员的偏移量,这里称为size (__mptr - offsetof(type, member)):也就是根据我们已知的成员变量地址,计算出来结构体的首地址 ((type *)(__mptr - offsetof(type, member))):最后强制转换为(type *),结构体指针。 比如,我们已知的结构体成员的地址为0xffff0000,计算之后如下: image-20231213151416841 3、总结 linux内核中,小小的一个函数,内部包括的技巧如此之多:static_assert、__same_type、(type *)0、offsetof。 了解完内部完整的实现手法之后,我们也可以手码一个container_of了 :) image-20231119211155587
  • 热度 4
    2023-11-27 08:45
    1092 次阅读|
    0 个评论
    【Linux API 揭秘】module_init与module_exit Linux Version:6.6 Author:Donge Github: linux-api-insides 1、函数作用 module_init和module_exit是驱动中最常用的两个接口,主要用来注册、注销设备驱动程序。 并且这两个接口的实现机制是一样的,我们先以module_init为切入点分析。 2、module_init函数解析 2.1 module_init # ifndef MODULE /** *module_init()-driverinitializationentrypoint *@x:functiontoberunatkernelboottimeormoduleinsertion * *module_init()willeitherbecalledduringdo_initcalls()(if *builtin)oratmoduleinsertiontime(ifamodule).Therecanonly *beonepermodule. */ # define module_init(x)__initcall(x); ...... # else /*MODULE*/ ...... /*Eachmodulemustuseonemodule_init().*/ # define module_init(initfn)\ staticinlineinitcall_t__maybe_unused__inittest(void)\ {returninitfn;}\ intinit_module(void)__copy(initfn)\ __attribute__((alias(#initfn)));\ ___ADDRESSABLE(init_module,__initdata); ...... # endif 函数名称 :module_init 文件位置 : include/linux/module.h 函数解析 : 在Linux内核中,驱动程序可以以两种方式存在:内建(Builtin)和模块(Module)。内建驱动就是在编译时,直接编译进内核镜像中;而模块驱动则是在内核运行过程中动态加载卸载的。 module_init函数的定义位置有两处,使用MODULE宏作为判断依据。MODULE是一个预处理器宏,仅当该驱动作为模块驱动时,编译的时候会加入MODULE的定义。 这里难免会有疑问:为什么会有两套实现呢? 其实,当模块被编译进内核时,代码是存放在内存的.init字段,该字段在内核代码初始化后,就会被释放掉了,所以当可动态加载模块需要加载时,就需要重新定义了。 2.1.1 模块方式 当驱动作为可加载模块时,MODULE宏被定义,我们简单分析一下相关代码 # define module_init(initfn)\ staticinlineinitcall_t__maybe_unused__inittest(void)\ {returninitfn;}\ intinit_module(void)__copy(initfn)\ __attribute__((alias(#initfn)));\ ___ADDRESSABLE(init_module,__initdata); static inline initcall_t __maybe_unused __inittest(void) { return initfn; }:一个内联函数,返回传入的initfn函数。 __maybe_unused :编译器指令,用于告诉编译器,该函数可能不会使用,以避免编译器产生警告信息。 int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));:init_module函数的声明 __copy(initfn):编译器指令,也就是将我们的initfn函数代码复制到init_module中, __attribute__((alias(#initfn))):编译器指令,将init_module函数符号的别名设置为initfn。 ___ADDRESSABLE(init_module, __initdata);:一个宏定义,主要用于将init_module函数的地址放入__initdata段,这样,当模块被加载时,init_module函数的地址就可以被找到并调用。 总的来说,如果是可加载的ko模块,module_init宏主要定义了init_module函数,并且将该函数与initfn函数关联起来,使得当模块被加载时,初始化函数可以被正确地调用。 2.1.2 内建方式 当模块编译进内核时,MODULE宏未被定义,所以走下面流程 # define module_init(x)__initcall(x); 2.2 __initcall # define __initcall(fn)device_initcall(fn) # define device_initcall(fn)__define_initcall(fn,6) # define __define_initcall(fn,id)___define_initcall(fn,id,.initcall##id) # define ___define_initcall(fn,id,__sec)\ __unique_initcall(fn,id,__sec,__initcall_id(fn)) # define __unique_initcall(fn,id,__sec,__iid)\ ____define_initcall(fn,\ __initcall_stub(fn,__iid,id),\ __initcall_name(initcall,__iid,id),\ __initcall_section(__sec,__iid)) # define ____define_initcall(fn,__unused,__name,__sec)\ staticinitcall_t__name__used\ __attribute__((__section__(__sec)))=fn; # define __initcall_stub(fn,__iid,id)fn /*Format: __ _ _ */ # define __initcall_id(fn)\ __PASTE(__KBUILD_MODNAME,\ __PASTE(__,\ __PASTE(__COUNTER__,\ __PASTE(_,\ __PASTE(__LINE__,\ __PASTE(_,fn)))))) /*Format:__ __ */ # define __initcall_name(prefix,__iid,id)\ __PASTE(__,\ __PASTE(prefix,\ __PASTE(__,\ __PASTE(__iid,id)))) # define __initcall_section(__sec,__iid)\ #__sec ".init" /*Indirectmacrosrequiredforexpandedargumentpasting,eg.__LINE__.*/ # define ___PASTE(a,b)a##b # define __PASTE(a,b)___PASTE(a,b) 函数名称 :__initcall 文件位置 : include/linux/init.h 函数解析 :设备驱动初始化函数 2.2.1 代码调用流程 module_init(fn) __initcall(fn) device_initcall(fn) __define_initcall(fn, 6 ) ___define_initcall(fn,id,__sec) __initcall_id(fn) __unique_initcall(fn,id,__sec,__iid) ____define_initcall(fn,__unused,__name,__sec) __initcall_stub(fn,__iid,id) __initcall_name(prefix,__iid,id) __initcall_section(__sec,__iid) ____define_initcall(fn,__unused,__name,__sec) 进行函数分析前,我们先要明白#和##的概念 2.2.2 #和##的作用 符号 作用 举例 ## ##符号 可以是连接的意思 例如 __initcall_##fn##id 为__initcall_fnid那么,fn = test_init,id = 6时,__initcall##fn##id 为 __initcall_test_init6 # #符号 可以是 字符串化的意思 例如 #id 为 "id",id=6 时,#id 为"6" 更多干货可见: 高级工程师聚集地 ,助力大家更上一层楼! 2.2.3 函数解析 下面分析理解比较有难度的函数 # define device_initcall(fn)__define_initcall(fn,6) # define __define_initcall(fn,id)___define_initcall(fn,id,.initcall##id) .initcall##id:通过##来拼接两个字符串:.initcall6 # define ___define_initcall(fn,id,__sec)\ __unique_initcall(fn,id,__sec,__initcall_id(fn)) /*Format: __ _ _ */ # define __initcall_id(fn)\ __PASTE(__KBUILD_MODNAME,\ __PASTE(__,\ __PASTE(__COUNTER__,\ __PASTE(_,\ __PASTE(__LINE__,\ __PASTE(_,fn)))))) /*Indirectmacrosrequiredforexpandedargumentpasting,eg.__LINE__.*/ # define ___PASTE(a,b)a##b # define __PASTE(a,b)___PASTE(a,b) ___PASTE:拼接两个字符串 __initcall_id: 它用于生成一个唯一的标识符,这个标识符用于标记初始化函数 。 __KBUILD_MODNAME:当前正在编译的模块的名称 __COUNTER__:一个每次使用都会递增计数器,用于确保生成名称的唯一性 __LINE__:当前代码的行号 # define __unique_initcall(fn,id,__sec,__iid)\ ____define_initcall(fn,\ __initcall_stub(fn,__iid,id),\ __initcall_name(initcall,__iid,id),\ __initcall_section(__sec,__iid)) # define ____define_initcall(fn,__unused,__name,__sec)\ staticinitcall_t__name__used\ __attribute__((__section__(__sec)))=fn; # define __initcall_stub(fn,__iid,id)fn /*Format:__ __ */ # define __initcall_name(prefix,__iid,id)\ __PASTE(__,\ __PASTE(prefix,\ __PASTE(__,\ __PASTE(__iid,id)))) # define __initcall_section(__sec,__iid)\ #__sec ".init" __unique_initcall:调用____define_initcall,关键实现部分 ____define_initcall:定义一个名为 __name 的 initcall_t 类型的静态变量,并将其初始化为 fn,并放入特定的__sec段中。 __initcall_stub:表示唯一的函数名fn __initcall_name:表示一个唯一的变量名 __initcall_section: 生成一个唯一的段名。 #__sec ".init":将两个字符串拼接起来,比如:__sec=.initcall6,拼接后的段为:.initcall6.init,该段为最终存储的段。 字段通过链接器链接起来,形成一个列表进行统一管理。 这些字段我们可以在arch/arm/kernel/vmlinux.lds中查看。 ...... __initcall6_start=.;KEEP(*(.initcall6.init))KEEP(*(.initcall6s.init)) ...... 3、module_exit函数解析 module_exit和module_init的实现机制几乎没有差别,下面就简单介绍一下。 3.1 module_exit # ifndef MODULE /** *module_exit()-driverexitentrypoint *@x:functiontoberunwhendriverisremoved * *module_exit()willwrapthedriverclean-upcode *withcleanup_module()whenusedwithrmmodwhen *thedriverisamodule.Ifthedriverisstatically *compiledintothekernel,module_exit()hasnoeffect. *Therecanonlybeonepermodule. */ # define module_exit(x)__exitcall(x); ...... # else /*MODULE*/ ...... /*Thisisonlyrequiredifyouwanttobeunloadable.*/ # define module_exit(exitfn)\ staticinlineexitcall_t__maybe_unused__exittest(void)\ {returnexitfn;}\ voidcleanup_module(void)__copy(exitfn)\ __attribute__((alias(#exitfn)));\ ___ADDRESSABLE(cleanup_module,__exitdata); ...... # endif 函数名称 :module_exit 文件位置 : include/linux/module.h 3.1.1 模块方式 作为模块方式,与module_init的实现方式一样,定义cleanup_module与exitfn函数相关联,存放在__exitdata段内。 3.1.2 内建方式 当模块编译进内核时,MODULE宏未被定义,所以走下面流程 # define module_exit(x)__exitcall(x); 3.2 __exitcall # define __exitcall(fn)\ staticexitcall_t__exitcall_##fn__exit_call=fn # define __exit_call__used__section( ".exitcall.exit" ) 函数名称 :__initcall 文件位置 : include/linux/init.h 函数解析 :设备驱动卸载函数 __exitcall_##fn:定义一个新的 exitcall_t 类型的静态变量,并赋值为fn __exit_call:__used __section(".exitcall.exit"),定义该函数存储的段 4、扩展 还记得__define_initcall的定义吗? # define pure_initcall(fn)__define_initcall(fn,0) # define core_initcall(fn)__define_initcall(fn,1) # define core_initcall_sync(fn)__define_initcall(fn,1s) # define postcore_initcall(fn)__define_initcall(fn,2) # define postcore_initcall_sync(fn)__define_initcall(fn,2s) # define arch_initcall(fn)__define_initcall(fn,3) # define arch_initcall_sync(fn)__define_initcall(fn,3s) # define subsys_initcall(fn)__define_initcall(fn,4) # define subsys_initcall_sync(fn)__define_initcall(fn,4s) # define fs_initcall(fn)__define_initcall(fn,5) # define fs_initcall_sync(fn)__define_initcall(fn,5s) # define rootfs_initcall(fn)__define_initcall(fn,rootfs) # define device_initcall(fn)__define_initcall(fn,6) # define device_initcall_sync(fn)__define_initcall(fn,6s) # define late_initcall(fn)__define_initcall(fn,7) # define late_initcall_sync(fn)__define_initcall(fn,7s) # define __initcall(fn)device_initcall(fn) 不同的宏定义,被赋予了不同的调用等级,最后将不同的驱动初始化函数统一汇总到__initcallx_start字段统一管理,形成一个有序的列表。 这样,我们在内核中,按照顺序遍历这个列表,最后执行对应的模块初始化函数fn即可实现驱动的初始化。