Build and Run Modules [LDD3 02]

本文介绍了Linux内核模块的基本概念,包括模块与应用程序的区别、用户空间与内核空间的差异、并发性以及编译加载过程。重点讨论了模块的加载(insmod、modprobe、rmmod)及其依赖处理、版本兼容性和平台适应性。此外,还阐述了内核符号表、初始化与清理函数、错误处理以及模块参数的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Table of Contents

Module Driver Sample

Kernel Modules Versus Applications

User Space and Kernel Space

Concurrency in the Kernel

Compile and Loading

Compiling Modules

Module Driver Load and Unload

Load

Unload

Version Depedency

Platform Dependency

The Kernel Symbol Table

Preliminaries

Initialization and Shutdown

The Cleanup Function

Error Handling During Initialization

Module-Loading Races

Module Parameters

Doing It in User Space


Module Driver Sample


模块化是Linux kernel driver的立身之本,几乎所有的device driver都是module driver,在kernel启动的时候动态加载。直接拿书里的例子看:

#include <linux/init.h> 
#include <linux/module.h> 
MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)
{
    printk(KERN_ALERT "Hello, world\n");
    return 0;
}
     
static void hello_exit(void)
{
    printk(KERN_ALERT "Goodbye, cruel world\n");
}

module_init(hello_init);
module_exit(hello_exit);

上面的这个sample code只是简单展示module driver在安装卸载时会调用的函数。

无论多么复杂的device driver,都是这个骨架,module driver的init和exit,就是driver和kernel接触的第一次和最后一次。module driver在init做完以后,其实就不干活了,躺在那里睡大觉。那么什么时候干活呢?driver的运行是基于event-driven模型的,就是说要有event过来,driver才会有事情做。

这很合理,我写了一个USB 设备的驱动,比如键盘,如果没有人使用键盘,那键盘驱动干啥活。虽然如此,在init的时候,driver也有很多事情可以做,主要是初始化相关的,比如注册设备,设置中断ISR,初始化设备等等。等初始化做完以后,device和driver的信息kernel就会记录下来,一旦有人使用了你的设备,驱动就会被调用。

如果driver被卸载,那么driver自己就要把init中申请的资源按序释放,这个非常重要,因为module driver的一大好处就是动态的加载和卸载,不用重启系统。如果init/exit没有做好,那driver很有可能出问题,甚至把kernel搞死。

来一张module driver被insmod以后在kernel中的描述图:

Kernel Modules Versus Applications


kernel mode driver和应用程序有很大的不同:

1. kernel mode driver无一例外都是事件驱动的,依赖于user mode或者kernel mode事件来触发操作,应用程序有的是,有的不是。

2. 应用程序的资源可以晚一点释放,甚至在退出时也不主动释放,因为OS会在进程结束的时候自动回收进程占用过得资源。而kenrel mode driver则必须在退出时谨慎的释放所有占用的资源。

3. 应用程序可以调用别的库中的函数,比如调用libc库中实现的函数;而module driver只能调用kernel中export出来的函数。(有些module driver可以主动export一些function出来给别的module用)

4. kernel 中没有float运算的支持,只能通过软件的方式来做。

5. 错误处理不同。应用程序出错,最多应用程序被kill,对系统和别的应用程序没有影响;如果kernel driver出了问题,至少当前的process挂了,严重点就是kernel panic,整个系统死掉,只能重启。

说到第五点,就要讨论user  space和kernel space了。

User Space and Kernel Space

学过操作系统的人应该都了解,应用程序都运行在user space,kernel(包括所有的kernel driver)都运行在kernel space。操作系统的职责之一就是对计算机进行抽象,并提供统一的资源访问方式给用户。在用户程序访问资源的同时,OS要对各个用户进行隔离,也要对资源进行保护。user space和kernel space这两种space会对应CPU不同的运行等级,现代CPU的运行等级有好几级,kernel code运行在最高等级,可以访问一切资源;user code运行在最低等级,只能访问有限的资源,而且对hardware的访问,都需要经过kernel。这种等级的限制,可以把user space的不可靠对系统造成的影响降到最低。另外,两种space也对应了两种不同的地址空间,user space对应了比较大的虚拟地址空间,比如32位上,每个应用程序在linux系统中有4G的地址空间,user space占了3G,kernel space只有高地址的1G,所有应用程序的高地址的这1G是共享的,意思就是每个人虚拟地址空间的1G内容都是一样的,都是当前正在运行的kernel,只有剩下的3G虚拟地址空间是每个应用程序私有的。

既然应用程序需要通过kernel访问硬件资源,那程序执行过程中必然有从user space转到kernel space的过程,怎么发生的呢?答案是两种情况:系统调用和硬件中断。

系统调用发生时,用户空间的代码通过系统调用进到了kernel space,同一个process的执行特权等级被提高到了最高等级,可以访问一切资源;硬件中断发生时,kernel会暂停当前CPU的process,并把CPU的使用权交给对应的ISR,中断处理完以后恢复现场。其实系统调用也是中断的一种,是一种软中断,和各种各样的调试器,如gdb等设置断点的原理类似,这里就不展开了。

Concurrency in the Kernel

下面很重要的一个概念,是kernel中的并发。

最开始做device driver的时候,其实根本意识不到并发这个事情,因为device driver是kernel按需调用的,我只要按照kernel的要求实现好接口,driver里面实现好业务的逻辑就可以了,脑子里并没有并发或者竞争条件这种事情。

kernel是一个非常庞大的系统,每个driver在把自己交给kernel的时候就已经分散到kernel的各个子系统之中,随时会被任何程序调用,因此写driver的时候除了要实现好业务逻辑以外,还要清楚的认识到driver运行环境的复杂性。以char device为例,一般支持open/close/read/write/mmap等操作,这些操作是由user mode应用程序发起的,程序调用了open,driver对应的probe会被调用;程序调用了close,driver的close会被调用等,driver本身不再是一个整体,而是由user mode发起的事件所驱动,并且由kernel适时的调用,因此driver什么时候被调用,调用的那个函数,都是user mode和kerne决定的,driver本身无法控制。想明白这点,就比较容易理解并发了,既然是事件驱动,那事件有可能不止一个,因为同一个时刻可能有多个程序使用你的设备,每个程序都在执行你的代码。

并发在很多情况下都会发生,比如:

1. linux本身支持多个process同时运行,这就是上文讲的,同时有多个程序访问你的设备,也就有多个进程调用你的驱动。

2. ISR,中断随时可能发生,也许你的driver正在处理用户态程序的请求,而这时设备中断发生了,那么driver的中断处理程序就会被调用,CPU转去执行driver的中断处理函数。

3. kernel里的timer等,定时器触发,也会调用你的driver,这种情况发生在driver设置了timer function,并且timer被触发的情况下。

正是因为存在这么多并发的可能性,所以driver要可重入(reentrant)。意思就是,driver被多个process同时调用,但是没有副作用。这就要求driver developer在coding的时候,脑子里要有这个意识,function有可能被多个程序同时调用,那么这些function里就要做好全局数据的保护,kernel的锁机制可以用来保证自己的数据不会因为同时访问而损坏,要知道kernel是允许抢占的,CPU可能在任何时候被人抢占,等回来时,说不定数据已经被人修改,所以一定要用锁做好数据保护。

kernel中提供了一种直接访问当前CPU上正在执行的task的方法——current指针,current指针指向struct task_struct结构体,记录了当前CPU上正在执行的task的所有信息,因为多核CPU上可以同时执行多个task,所以这个指针是per-CPU的,也就是只能获取当前CPU上的task信息。而它的实现,也是和架构相关的,kernel已经做好了封装,kernel的subsystem或者driver可以直接通过current指针获取,不用考虑架构相关的东西。

到这里的时候,这本书提到了应用程序调用栈和kernel调用栈的问题。应用程序的栈可以很大,因此可以在stack上申请很多memory;但是对于kernel,stack就很小了,之前我写driver,碰到stack超过1K就不行的情况。说到调用栈,就想到了函数调用过程中的栈变化,这个留到以后专门写一篇文章讲一讲。

Compile and Loading


Compiling Modules

driver的compile没啥可讲的,主要是Makefile。in-tree和out-of-tree,这两种方式的编译还是有区别的。in-tree就是source code放在kernel的source code里面,这种方式除了需要driver自己的Makefile,还有driver所在目录的Kconfig和Makefile。以drm gpu为例,除了把driver的source code放在drivers/gpu/drm目录下外,还需要修改drm目录下的Kconfig和Makfile,保证kernel在build的时候能够找到driver的路径。out-of-tree就是driver的code在kernel code以外,build driver的时候需要指定kernel的build路径,以及设置module路径为当前driver路径。另外,说到driver的makefile,要提下obj-m或者obj-y这个东西,m表明driver按照module方式build,y则把driver做成built-in,说白了m build出来的driver是以ko的方式存在,加载以后通过lsmod可以看到driver;y build出来的driver在kernel image里,没有ko,在系统起来以后,用lsmod看不到driver。这个值,如果是在out-of-tree,则一定是m;如果是在in-tree,应该使用变量(自己driver里的CONFIG_XXXX),这样可以通过menuconfig可以控制build方式。

Module Driver Load and Unload

Load

driver load通过insmod/modprobe的方式,unload用rmmod。在insmod的过程中,需要把module driver里引用的kernel function的symbol处理掉,通过查找kernel的symbol table来实现,有点类似用户态程序编译时的ld过程,但又有区别,kernel的symbol resolve不会修改ko文件,而是在memory中修改driver引用的kernel symbol。在insmod的时候,可以为module driver指定必要的参数,从而可以在load的时候控制module driver 的行为,这种方式成为load-time configuration,与此对应的还有compile-time configuration和runtime configuration。compile-time的configuration指的是通过编译宏来控制module driver的行为,而runtime configuration则复杂一些,需要通过sysfs的方式来实现,比如echo一个值到driver创建出来的sysfs,driver通过读取sysfs的值来实现控制等。

这里还提了一下kernel是如果insmod module driver的,这个需要依赖一个系统调用,kernel中对应的函数是sys_init_module,这个函数会为module driver通过vmalloc 方式分配内存,并把module driver的text(代码段)copy到分配的内存中,然后通过kernel的symbol table来解析module driver中引用的kernel符号,之后开始调用module driver定义的module init函数开始执行。

除了insmod,还有一个命令可以把module driver load进kernel,这就是modprobe,这个命令和insmod类似,二者的区别在于:modprobe会根据当前module引用的symbol,查找依赖的其他module,如果他们当前不在kernel中,就把他们也load进来。modprobe会根据系统中设置的环境变量,去某些固定的路径下所有这些被依赖的module,如果找不到被依赖的symbol,那么module driver就会load失败,是打印unresolve symbol。

Unload

module driver的unload可以使用rmmod,需要注意的是,在rmmod的时候,kernel会检查这个module是否in use,比如是否有被打开的fd,或者module本身禁止unload,这样的话module driver都不会被rmmod。

另外一个命令lsmod,可以查看当前系统中被load module driver。通过/proc/modules以及/sys/modules也可以达到同样的效果。

Version Depedency

这里首先就强调了一点:module driver必须要为target kernel重新编译。原因就在于,每一版kernel都在发生变化,因此module driver依赖的data structure,interface,或者kernel的行为都会发生变化,这就会导致module可能在适配多个版本的kernel时出现问题,因此必须为每一版需要运行driver的kernel编译moduel driver。

所以module driver的版本号要和kernel的版本号一致,为了保证这一点,kernel在load module driver之前,都会检查module driver的版本和当前的kernel是否一致,不一致就不会被load进来。那么module driver中的版本号是怎么来的呢?答案是build的时候产生的,module driver在build的时候会链接kernel的vermagic.o,里面就会保存build 的时候用的kernel信息,这就是driver的版本号。通过load时候的版本号检查,kernel就能保证module driver的版本和当前的kernel兼容。

正因为如此,module driver在发布时,都要针对各个版本的kernel做编译并测试,保证能在这个版本的kernel上正常运行。这就需要用到很多的#ifdef等宏,来对kernel的版本做检查,常用的有以下几个:

UTS_RELEASE
    字符串形式,代表了kernel tree的版本,如“2.6.10”
LINUX_VERSION_CODE 
    代表了kernel版本的二进制表示,如2.6.10对应的就是132618(i.e.,0x02060a)。
KERNEL_VERSION(major,minor,release)
    这个宏可以用来把kernel的version转换成整形数表示,如KERNEL_VERSION(2,6,10)就是132618。在module driver用的比较多,用来检查运行的kernel是否是指定的某个kernel版本。

在真实的module driver中,可能有很多地方需要对kernel version做检查,如果在code中大量使用这种宏,code看起来会一团糟。正确的方式是把这些检查封装起来,统一放在头文件中,在code中需要对版本做检查时,只需要调用封装的函数就好了,这样code会更加容易阅读,也更鲁棒。

Platform Dependency

不同的平台有它自己的特性,比如指令集等等,kernel中有很多针对这些架构的优化。module driver也需要知道这些,不同的架构上driver也许可以使用不同的特性。这里就涉及到另外的module driver检查,也是和之前版本检查的方式一样,在vermagic.o里包含了处理器或者架构相关的配置信息,这些信息要和kernel的相匹配才可以,否则也会load失败。

The Kernel Symbol Table


这一章还提到了kernel的symbol table,以及module stack的概念。

上面说了,module driver在被insmod进来的时候,需要resolve其中的symbol,这些symbol主要是kernel自身的interface,或者全局变量,如何resolve这些symbol呢?kernel在运行时有一个全局的symbol table,里面记录着这些interface和变量的地址,module driver resolve symbol的过程就是查找这些interface和变量地址的过程。如果module driver自己也export一些symbol(interface和变量),这些symbol也会变成kernel symbol table的一部分,可以被别的module发现和引用。kernel提供了宏来帮助module driver export自己的symbol:

EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name); //只允许遵循GPL license的module调用这个symbol

要注意的是,这两个宏要在module driver global的位置使用,不能在任何的函数里使用。因为kernel会把它展开,并定义特殊的全局变量,在build module driver的时候把它存储在ELF特殊的区域,这样kernel在load module的时候就可以找到module export的symbol了。

所谓的module stack,意思就是module driver之间的依赖,A 依赖B, B依赖C,ABC就形成了一个module stack。其实现在复杂一些的device,都具有多个功能,这些功能需要依赖于kernel的其他的module,层层依赖,这就形成了一个module stack。比如GPU,GPU都是挂在PCI总线上,依赖于kernel的drm/kms等module driver,这样一个完成的GPU driver运行起来,就依赖于kernel的很多module driver,形成了一个庞大的stack。一个简单的例子如下:

之前提高的modprobe命令,就适用于这样的module stack场景,它可以通过查找帮助module driver把stack上的所有的module都load进来。自己在开发device driver的时候,也可以通过这种方式,把driver分成几个独立的module,然后通过相互的依赖关系组成一个driver stack,每一个module都可以做的尽量简单,这样可以方便开发,提高效率。而每一个module都可以export自己的symbol供其他的module使用,类似用户态程序的interface,每一个module做到高内聚低耦合。

Preliminaries

kernel在load module driver的时候,要求module driver遵循一些规定,比如下面这些:

#include <linux/module.h>
#include <linux/init.h>

MODULE_LICENSE  //license that module driver follow
MODULE_AUTHOR //author of this module driver
MODULE_DESCRIPTION  //human read-able description about this module driver
MODULE_VERSION   //version of this module driver
MODULE_ALIAS   //module another name
MODULE_DEVICE_TABLE  //tell user space which device this driver support

Initialization and Shutdown


module的初始化过程中,会把driver所有的functionality注册进去,这样user application就有机会使用。初始化的接口类似下面这种:

static int __init initialization_function(void) {
    /* Initialization code here */
}
module_init(initialization_function);

module driver的初始化函数一般声明为static,以限制初始化函数的可见范围(仅限当前的文件内),这个没有明文规定,只是一种习惯。初始化函数中的__init是告诉kernel,这个函数只会在初始化的过程中使用,module loader在初始化完成以后会把初始化函数占用的内存释放掉,这是一种优化。类似的,__initdata这个是告诉kernel,当前的数据只在初始化的过程中使用,初始化完成以后就可以它占用的内存回收。注意,千万不要把初始化以后还会用的函数或者数据声明为__init和__initdata,理由显而易见。另外,还有两个类似的是__devinit和__devinitdata,当设备不支持hotplug时,分别等同于__init和__initdata,如果支持hotplug,就不一样了。

还有两个特殊的宏,module_init和module_exit。module_init宏会在生成object文件中添加一个特殊的section,里面记录了初始化函数的位置,从而使得kernel能够找到module driver的初始化函数位置,如果没有通过module_init定义初始化函数,driver的初始化函数将无法被kernel调用。

The Cleanup Function

绝大部分module driver都有一个exit函数,在module driver被从kernel remove之前做一些clean up的工作,用法类似如下:

static void __exit cleanup_function(void) {
         /* Cleanup code here */
}
module_exit(cleanup_function);

exit函数没有任何返回值。函数定义中,__exit是告诉kernel,当前的函数只会在module exit的时候被调用,这样compiler就把它放到ELF中一个特殊的section之中。此外,exit的函数只能在module driver unload,或者系统关机的时候调用,其他时机的调用都是错误的情况。module_exit这个宏必须使用,否则kernel也无法找到定义的exit函数。

总之,module_init是driver被load的时候被调用,module_exit是在driver被unload或者系统关机的时候调用,如果module driver没有定义module_exit,那么意味着module driver不允许被卸载。

Error Handling During Initialization

module driver初始化的过程中往往会有资源的分配,有时候资源的分配会因为某些原因失败,module driver就需要对这种出错的情况进行处理,大多数情况下,driver会禁止出错的function,并继续完成初始化;如果出错比较严重,driver的初始化无法继续进行,那么只能把已经成功分配的资源全部释放,做好driver的clean up,并返回error。kernel中,对错误的处理往往使用goto语句完成:

int __init my_init_function(void) {
    int err;
    /* registration takes a pointer and a name */
    err = register_this(ptr1, "skull");
    if (err) goto fail_this;

    err = register_that(ptr2, "skull");
    if (err) goto fail_that;

    err = register_those(ptr3, "skull");
    if (err) goto fail_those;

    return 0; /* success */

fail_those:
    unregister_that(ptr2, "skull");   
fail_that:
    unregister_this(ptr1, "skull");
fail_this:
    return err; /* propagate the error */
}

如上面的例子所示,当错误发生时,要把之前分配成功的资源释放掉。除了使用goto语句,也可以用driver自己的cleanup函数来做,只是相对于上面的goto方式,使用driver自己的cleanup会需要更多的code,费更多的CPU时间。driver cleanup示例如下:

void __exit my_cleanup_function(void) {
    unregister_those(ptr3, "skull");
    unregister_that(ptr2, "skull");
    unregister_this(ptr1, "skull");
    return;
}

更合理的错误处理方式是这样的:

struct something *item1;
struct somethingelse *item2;
int stuff_ok;

void my_cleanup(void) {
    if (item1)
        release_thing(item1);
    if (item2)
        release_thing2(item2);

    if (stuff_ok)
        unregister_stuff( );
        return; 
}

int __init my_init(void) {
    int err = -ENOMEM;

    item1 = allocate_thing(arguments);
    item2 = allocate_thing2(arguments2);
    if (!item2 || !item2)
        goto fail;

    err = register_stuff(item1, item2);
    if (!err)
        stuff_ok = 1;
    else
      goto fail;

    return 0; /* success */

fail: 
    my_cleanup( );
    return err;
}

上面的处理方式更加灵活,而且即便driver有很多资源,也可以使用这种易于维护和管理的方式来释放。

此外,关于出错时候的返回值,kernel都有自己的定义,driver应该根据出错的原因,合理的返回错误值,这样user mode可以根据driver的返回值知道具体的错误原因。kernel自己的error number定义都在<linux/errno.h>里。

Module-Loading Races

在module driver初始化的过程中,要考虑竞争的发生。比如driver在注册的过程中,某些function可能注册完马上就会被人使用,这时候就要充分考虑在注册的时候,该function依赖的所有资源是否ready,如果function不完整就注册进去,极有可能导致kernel不稳定,所以一定要在所有资源都ready的情况下再注册这个function;另外,考虑错误的处理,在错误发生时,如果之前注册成功的function已经有人在使用,那么driver的初始化要必须做完,如果此时做driver的cleanup,也极有可能导致kernel不稳定。

Module Parameters

insmod module driver的时候可以通过module parameter的方式给module driver传递参数,如果是insmod,就要手动指定参数;如果是modprobe,kernel就会自动从/etc/modprobe.conf下查找对应的参数。通过这些参数,module driver可以在load的时刻动态的控制行为。

在module driver能够得到或者解析参数之前,需要告诉kernel自己支持哪些参数,这就需要通过module_param这个宏来实现。这个宏接受三个参数:参数名,参数类型,以及对应的权限mask(是参数对应的sysfs权限)。

这个宏也要求放在任何函数之外,不能放在函数里面,因为也是展开成变量的形式,如果在函数里,就变成了局部变量,kernel无法读取。一个简单的例子:

static char *whom = "world";
static int howmany = 1;

module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);

kernel中支持所有参数类型如下:bool,invbool(inverse bool),charp(char pointer,memory allocated for user string),int ,long, short, uint, ulong, ushort,以及array parameter。如果要声明一个array parameter,使用如下方式:

//name,参数的名字
//type,参数的类型
//num,array中参数的个数
//perm,权限
module_param_array(name,type,num,perm);

另外,kernel也支持自定义的参数类型,具体可以参考moduleparam.h中的细节。driver中定义的这些参数,都必须要有default值,kernel只有在insmod时提供了参数的情况下才会给参数复制,这样driver可以通过对比参数的当前值和默认值,知道user 是否设置了参数。

最后就是参数的perm这个东西,它可以控制谁能通过sysfs访问参数。如果设置为0,就不会为这个参数设置sysfs入口,否则就会在/sys/module下为这个参数创建sysfs,并根据perm的值设置相应权限。比如S_IRUGO,表示谁都能读,但是没有人可以写;S_IRUGO|S_IWUSR表示只有root用户可以写参数的值;值得注意的是,通过sysfs的方式修改参数的值,kernel不会主动通知module driver,这就要求module driver必须监视参数的值,这样一旦被修改就可以马上知道。所以,除非driver真有必要,否则一般不设置sysfs。

Doing It in User Space

这里讨论就是,driver到底在user space实现,还是在kernel mode实现。其实各有各的好处。

在user space实现driver的优点:

1. 可以使用libc函数库,这样driver实现起来会更容易;

2. debug起来更容易,debug kernel是很麻烦的一件事;

3. 如果driver出了问题,直接kill掉就行,kernel中一旦出了问题就可能导致kernel panic,整个系统就会死掉;

4. user space 的memory是可以被swap出去的,这样如果driver不常使用,就可以被swap出去,从而省RAM;

5. user space的driver,可以对device进行并发的访问;

6. 如果是闭源的driver,在user space实现更好,不用考虑各种license的问题,以及kernel interface的变化。

其实已经有类似的user driver,比如libusb,以及X server。通常来说,要实现user space的driver,都会创建一个server,所有对device的访问,都经过这个server来实现,这样可以解决并发问题。当然,user space driver也有缺点:

1. user space没有办法解决中断问题,因为user space无法接收到中断;

2. 只有map了/dev/mem才能做DMA,而且只有特权用户才可以做;

3. 访问I/O port也会受限;

4. 会有较大的dealy,因为可能涉及到context的switch操作,这个很费时;

5. 一旦user space driver被swap到disk,那么响应时间会大大延长,即便通过mlock的方式不被swap,也会浪费很多内存,并且mlock也需要特权用户才可以;

6. 有些device driver只能在kernel中做,包括但不限于网络设备和block设备。

 

最后附上本章的keyword list:

insmod

modprobe

rmmod

User-space utilities that load modules into the running kernels and remove them.

#include <linux/init.h>

module_init(init_function);

module_exit(cleanup_function);

Macros that designate a module’s initialization and cleanup functions.

_ _init
_ _initdata

_ _exit
_ _exitdata

Markers for functions(__init and__exit)and data(__initdata and__exitdata) that are only used at module initialization or cleanup time. Items marked for ini- tialization may be discarded once initialization completes; the exit items may be discarded if module unloading has not been configured into the kernel. These markers work by causing the relevant objects to be placed in a special ELF sec- tion in the executable file.

#include <linux/sched.h>

One of the most important header files. This file contains definitions of much of the kernel API used by the driver, including functions for sleeping and numer- ous variable declarations.

struct task_struct *current;

The current process.

current->pid

current->comm

The process ID and command name for the current process.

obj-m

A makefile symbol used by the kernel build system to determine which modules should be built in the current directory.

/sys/module

/proc/modules

/sys/module is a sysfs directory hierarchy containing information on currently- loaded modules. /proc/modules is the older, single-file version of that informa- tion. Entries contain the module name, the amount of memory each module occupies, and the usage count. Extra strings are appended to each line to specify flags that are currently active for the module.

vermagic.o

An object file from the kernel source directory that describes the environment a module was built for.

#include <linux/module.h>

Required header. It must be included by a module source.

#include <linux/version.h>

A header file containing information on the version of the kernel being built.

LINUX_VERSION_CODE
Integer macro, useful to #ifdef version dependencies.

EXPORT_SYMBOL (symbol);
EXPORT_SYMBOL_GPL (symbol);

Macro used to export a symbol to the kernel. The second form exports without using versioning information, and the third limits the export to GPL-licensed modules.

MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);

Place documentation on the module in the object file.

module_init(init_function);
module_exit(exit_function);

Macros that declare a module’s initialization and cleanup functions.

#include <linux/moduleparam.h>

module_param(variable, type, perm);

Macro that creates a module parameter that can be adjusted by the user when the module is loaded (or at boot time for built-in code). The type can be one of bool, charp, int, invbool, long, short, ushort, uint, ulong, or intarray.

#include <linux/kernel.h>
int printk(const char * fmt, ...);

The analogue of printf for kernel code.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值