linux 内核模块入门

内核模块可以动态地被安装到内核,从而扩展内核的功能,使用内核模块时不需要重新编译内核。内核模块常用的场景是驱动,随着芯片种类的增加,硬件种类的增加,这些芯片或者硬件(比如网卡) 的驱动可以以模块的方式进行开发,这样可以在需要的时候加载对应的模块,不使用的时候卸载模块,不至于因为芯片或硬件的种类的快速增加而导致内核镜像的膨胀。

使用 lsmod 可以看到当前系统中安装的内核模块。字符设备,块设备,网络设备,文件系统等都可以通过内核模块的方式来安装。

(1)e1000 是网卡驱动模块,网卡驱动经常以内核模块的方式安装

(2)vsock 是一个字符设备,也是通过内核模块的方式来安装

root@wyl-virtual-machine:/home/wyl/mod/hello2# lsmod
Module                  Size  Used by
isofs                  49152  1
rfcomm                 81920  4
bnep                   24576  2
vmw_vsock_vmci_transport    32768  2
vsock                  40960  3 vmw_vsock_vmci_transport
nls_iso8859_1          16384  1
binfmt_misc            24576  1
snd_ens1371            28672  2
snd_ac97_codec        131072  1 snd_ens1371
gameport               20480  1 snd_ens1371
ac97_bus               16384  1 snd_ac97_codec
snd_pcm               110592  2 snd_ac97_codec,snd_ens1371
btusb                  57344  0
btrtl                  24576  1 btusb
btbcm                  16384  1 btusb
snd_seq_midi           20480  0
snd_seq_midi_event     16384  1 snd_seq_midi
snd_rawmidi            36864  2 snd_seq_midi,snd_ens1371
btintel                24576  1 btusb
intel_rapl_msr         20480  0
intel_rapl_common      24576  1 intel_rapl_msr
crct10dif_pclmul       16384  1
ghash_clmulni_intel    16384  0
aesni_intel           372736  0
bluetooth             557056  27 btrtl,btintel,btbcm,bnep,btusb,rfcomm
crypto_simd            16384  1 aesni_intel
cryptd                 24576  2 crypto_simd,ghash_clmulni_intel
snd_seq                73728  2 snd_seq_midi,snd_seq_midi_event
ecdh_generic           16384  1 bluetooth
ecc                    28672  1 ecdh_generic
glue_helper            16384  1 aesni_intel
snd_seq_device         16384  3 snd_seq,snd_seq_midi,snd_rawmidi
snd_timer              36864  2 snd_seq,snd_pcm
rapl                   20480  0
snd                    90112  11 snd_seq,snd_seq_device,snd_timer,snd_ac97_codec,snd_pcm,snd_rawmidi,snd_ens1371
soundcore              16384  1 snd
vmw_balloon            24576  0
joydev                 24576  0
input_leds             16384  0
serio_raw              20480  0
vmw_vmci               69632  2 vmw_balloon,vmw_vsock_vmci_transport
mac_hid                16384  0
sch_fq_codel           20480  2
vmwgfx                299008  5
ttm                   106496  1 vmwgfx
drm_kms_helper        184320  1 vmwgfx
fb_sys_fops            16384  1 drm_kms_helper
syscopyarea            16384  1 drm_kms_helper
sysfillrect            16384  1 drm_kms_helper
sysimgblt              16384  1 drm_kms_helper
parport_pc             40960  0
ppdev                  24576  0
lp                     20480  0
parport                53248  3 parport_pc,lp,ppdev
ramoops                28672  0
drm                   495616  8 vmwgfx,drm_kms_helper,ttm
efi_pstore             16384  0
reed_solomon           24576  1 ramoops
ip_tables              32768  0
x_tables               40960  1 ip_tables
autofs4                45056  2
crc32_pclmul           16384  0
psmouse               155648  0
e1000                 147456  0
mptspi                 24576  2
mptscsih               40960  1 mptspi
ahci                   40960  1
mptbase                94208  2 mptspi,mptscsih
libahci                36864  1 ahci
scsi_transport_spi     32768  1 mptspi
i2c_piix4              28672  0
pata_acpi              16384  0
hid_generic            16384  0
usbhid                 57344  0
hid                   131072  2 usbhid,hid_generic

内核模块也是学习内核的一个工具,linux 内核中使用 EXPORT_SYMBOL 导出的符号,在内核模块中都可以使用,包括进程调度相关的,线程间同步相关,文件系统相关的很多函数,都可以在内核模块中使用,基于此可以通过内核模块来对这些内核子系统进行学习。比如你想学习内核中怎么创建一个内核线程,内核中的自旋锁怎么使用,都可以通过内核模块的方式来学习。

1 最简内核模块

如下是一个简单的内核模块,源代码 hello.c 和编译脚本 Makefile,将两者放在同一个文件夹下,执行 make 即可编译出内核模块。

在 hello_init() 和 hello_exit() 函数中都调用了 dump_stack() 函数。dump_stack() 是内核中很好使用的函数,可以用来打印当前的调用栈。

hello.c

// 定义了 MODULE_AUTHOR(), MODULE_LICENSE(), MODULE_DESCRIPTION() 这些宏
// 以及 module_init() 和 module_exit()
#include <linux/module.h>

// 定义了 __init 和 __exit
#include <linux/init.h>


// 模块初始化函数
static int __init hello_init(void){
    printk("hello init\n");
    dump_stack();
    return 0;
}

// 模块退出函数
static void __exit hello_exit(void){
    printk("hello exit\n");
    dump_stack();
}



// 声明模块的初始化函数
module_init(hello_init);

// 声明模块的退出函数
// 入口函数主要做一些资源的申请和初始化的工作
// 出口函数主要做一些资源的释放工作
// 入口和出口是软件设计中经常会涉及到的思想
// 比如 c++ 对象中的构造函数和析构函数,也可以称作一个对象的入口和出口
module_exit(hello_exit);


MODULE_AUTHOR("wx2");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("hello kernel module");
MODULE_ALIAS("hello kernel module");

Makefile:

# 指定编译的目标文件
# 目标文件是 hello.o,那么编译过程自动找到源文件 hello.c 并编译
obj-m += hello.o


# 内核源码头文件目录
# 编译的时候进入内核源码头文件目录,调用该目录下的 Makefile
# 最终会调用我们自己的模块源码目录,用 M 来表示
# 下文中的 M=$(shell pwd)
KDIR = /lib/modules/$(shell uname -r)/build

all:
        make -C $(KDIR)  M=$(shell pwd) modules
clean:
        make -C $(KDIR)  M=$(shell pwd) clean

编译之后生成的 hello.ko 即内核模块,执行 insmod hello.ko 可以加载内核模块,执行 rmmod hello 可以卸载模块。通过 dmesg 可以查看模块函数中的打印。内核函数 dump_stack() 可以打印调用栈,在函数 hello_init() 和 hello_exit() 中都可以调用 dump_stack() 来查看调用栈。加载内核模块时,调用系统调用 init_module(),卸载内核模块时调用 delete_module(),在这两个系统调用中分别调用内核初始化函数 hello_init() 和内核退出函数 hello_exit()。

加载内核模块,调用栈:
[ 1096.663388] hello init
[ 1096.663391] CPU: 1 PID: 3992 Comm: insmod Tainted: G           OE     5.4.0-174-generic #193-Ubuntu
[ 1096.663392] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 07/22/2020
[ 1096.663393] Call Trace:
[ 1096.663398]  dump_stack+0x6d/0x8b
[ 1096.663400]  ? 0xffffffffc0798000
[ 1096.663402]  hello_init+0x1a/0x1000 [hello]
[ 1096.663405]  do_one_initcall+0x4a/0x200
[ 1096.663407]  ? _cond_resched+0x19/0x30
[ 1096.663409]  ? kmem_cache_alloc_trace+0x177/0x240
[ 1096.663411]  do_init_module+0x52/0x240
[ 1096.663412]  load_module+0x128d/0x13d0
[ 1096.663416]  __do_sys_finit_module+0xbe/0x120
[ 1096.663418]  ? __do_sys_finit_module+0xbe/0x120
[ 1096.663420]  __x64_sys_finit_module+0x1a/0x20
[ 1096.663422]  do_syscall_64+0x57/0x190
[ 1096.663423]  entry_SYSCALL_64_after_hwframe+0x5c/0xc1
[ 1096.663425] RIP: 0033:0x7f5fe720195d
[ 1096.663427] Code: 00 c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d 03 35 0d 00 f7 d8 64 89 01 48
[ 1096.663427] RSP: 002b:00007ffcd09b7498 EFLAGS: 00000246 ORIG_RAX: 0000000000000139
[ 1096.663429] RAX: ffffffffffffffda RBX: 000055e84e680790 RCX: 00007f5fe720195d
[ 1096.663430] RDX: 0000000000000000 RSI: 000055e84e1d9358 RDI: 0000000000000003
[ 1096.663430] RBP: 0000000000000000 R08: 0000000000000000 R09: 00007f5fe72d8580
[ 1096.663431] R10: 0000000000000003 R11: 0000000000000246 R12: 000055e84e1d9358
[ 1096.663431] R13: 0000000000000000 R14: 000055e84e680760 R15: 0000000000000000


卸载内核模块,调用栈:
[ 1100.852877] hello exit
[ 1100.852881] CPU: 2 PID: 3994 Comm: rmmod Tainted: G           OE     5.4.0-174-generic #193-Ubuntu
[ 1100.852882] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 07/22/2020
[ 1100.852882] Call Trace:
[ 1100.852888]  dump_stack+0x6d/0x8b
[ 1100.852892]  hello_exit+0x15/0x1000 [hello]
[ 1100.852895]  __x64_sys_delete_module+0x147/0x2b0
[ 1100.852897]  ? exit_to_usermode_loop+0xea/0x160
[ 1100.852899]  do_syscall_64+0x57/0x190
[ 1100.852901]  entry_SYSCALL_64_after_hwframe+0x5c/0xc1
[ 1100.852903] RIP: 0033:0x7fdca32a0c8b
[ 1100.852904] Code: 73 01 c3 48 8b 0d 05 c2 0c 00 f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa b8 b0 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d d5 c1 0c 00 f7 d8 64 89 01 48
[ 1100.852905] RSP: 002b:00007ffcd7b4af08 EFLAGS: 00000206 ORIG_RAX: 00000000000000b0
[ 1100.852906] RAX: ffffffffffffffda RBX: 00005556c8282760 RCX: 00007fdca32a0c8b
[ 1100.852907] RDX: 000000000000000a RSI: 0000000000000800 RDI: 00005556c82827c8
[ 1100.852908] RBP: 00007ffcd7b4af68 R08: 0000000000000000 R09: 0000000000000000
[ 1100.852908] R10: 00007fdca331cac0 R11: 0000000000000206 R12: 00007ffcd7b4b140
[ 1100.852909] R13: 00007ffcd7b4c79b R14: 00005556c82822a0 R15: 00005556c8282760

2 带参内核模块

内核模块也可以带参数,参数是用户和内核模块通信的一种方式。内核模块中的参数,会在模块文件夹中生成对应的文件,用户可以通过 echo xxx > param_path 改变模块中的参数,进而改变模块的行为。 

对于一个可执行的单元来说,往往都是可以传入参数的,通过参数来指定执行体中的行为。函数调用有入参;对于一个可执行程序来说,main() 函数可以有入参;我们在创建一个线程的时候,也可以有入参。内核模块作为一个执行体,也可以指定参数,其它的传参方式本质上都是在函数调用的时候传参,内核模块中的参数使用方式与函数传参是有区别的。

模块参数相关的宏声明在文件 include/linux/moduleparam.h,主要包括以下 3 个, 分别声明一个普通参数,一个数组参数,一个带回调的参数

module_param(name, type, perm)

module_param_array(name, type, nump, perm)

module_param_cb(name, ops, arg, perm)

name: 变量名

type: 变量类型,支持常见的 short,int, long 等常见的基本数据类型,另外还支持以下几种

bool: 可以赋值为 0 或者非 0 的数,如果赋值为 0,则 cat 这个变量会显示 N,赋值为非 0,则 cat 这个变量会显示 Y;另外, bool 还可以赋值为 y/n, Y/N

invbool: 这种类型也可以出现在 module_param 的第二个形参中,只不过使用 cat 查看值的时候显示的值是和初始化的值相反的,如果初始化赋值为 0 则显示 Y,初始化赋值为 1,则显示 N

hexint: 当在 module_param 中传入这个数据类型时,则变量显示为十六进制

参数类型和实际类型不一致时,编译会报错

perm: 参数的读写以及可执行权限,如果传入 0,那么该参数在模块参数路径下看不到

模块参数路径是 /sys/module/参数名/parameters/,安装模块之后,在这个路径下就能看到模块中定义的参数。常见的权限组合是 S_IWUSR | S_IRUSR,即用户可读可写,参数不能设置可执行权限,如果设置了可执行权限,在加载模块时会打印错误信息,提示参数权限无效。

#define S_IRUSR 00400 文件所有者可读

#define S_IWUSR 00200 文件所有者可写

#define S_IXUSR 00100 文件所有者可执行

#define S_IRGRP 00040 与文件所有者同组的用户可读

#define S_IWGRP 00020

#define S_IXGRP 00010

nump: 这个参数指定数组的元素个数, 对于已经声明了数组长度的数组,该配置无效,即如果声明了数组长度是 4,但是这个参数是 2,那么仍然可以向数组中写 4 个数据

ops: 结构体,主要两个成员是 set 和 get,分别是设置变量的值或者获取变量的值,这两个函数可以使用内核默认的,也可以自己定义,一般 set 使用自己定义的函数,这样方便在回调函数里实现自己的逻辑,get 使用内核默认的函数。cb 参数可以给我们开发者更大的灵活性和自由度。

struct kernel_param_ops {
    /* How the ops should behave */
    unsigned int flags;
    /* Returns 0, or -errno.  arg is in kp->arg. */
    int (*set)(const char *val, const struct kernel_param *kp);
    /* Returns length written or -errno.  Buffer is 4k (ie. be short!) */
    int (*get)(char *buffer, const struct kernel_param *kp);
    /* Optional function to free kp->arg when module unloaded. */
    void (*free)(void *arg);
};

arg: 变量的地址

2.1 示例

如下模块代码中有 4 个参数,这 4 个参数都有用户读写权限。在模块中创建了一个线程,该线程每隔 1s 打印一次 4 个参数的值。模块使用 insmod 安装之后,可以使用 echo 命令修改变量的值,参数修改的结果,可以通过 dmesg 查看线程的打印来确认。

echo 10 > /sys/module/hello/parameters/param_int

echo "hello" > /sys/module/hello/parameters/param_charp

echo 10 > /sys/module/hello/parameters/param_cb_int

echo 1,2,3,4 > /sys/module/hello/parameters/param_array_int

#include <linux/delay.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/moduleparam.h>

// int 类型的参数
int param_int;

// 数组参数
int param_array_int[4];

// 字符指针参数
char *param_charp;

// 回调参数
int param_cb_int = 0;


// 声明参数 param_int 
module_param(param_int, int, S_IWUSR | S_IRUSR);
// 声明字符指针参数,param_charp
module_param(param_charp, charp, S_IWUSR | S_IRUSR);
// 声明数组参数 param_array_init
module_param_array(param_array_int, int, NULL, S_IWUSR | S_IRUSR);

static struct task_struct *test_kthread = NULL;

int notify_param(const char *val, const struct kernel_param *kp) {
  int res = param_set_int(val, kp);
  printk("cb param addr: %p,  arg in kp: %p\n", &param_cb_int, kp->arg);
  if (res == 0) {
    printk(KERN_INFO "call back function called...\n");
    printk(KERN_INFO "new value of param_cb_int = %d\n", param_cb_int);
    return 0;
  }
  return -1;
}

// call back 参数的 set 和 get 函数
// get 函数使用系统定义的,set 函数使用自定义的函数
// 当然对于 int 类型的参数, set 函数也可以使用系统定义的函数 param_set_int
const struct kernel_param_ops my_param_ops = {
    .set = &notify_param,
    .get = &param_get_int,
};

module_param_cb(param_cb_int, &my_param_ops, &param_cb_int, S_IWUSR | S_IRUSR);

static int thread_hello(void *data) {
  int i = 0;
  int print_seq = 0;
  while (!kthread_should_stop()) {
    msleep(1000);
    printk("---------------- %d ----------------\n", print_seq++);
    printk(KERN_INFO "param_int = %d\n", param_int);
    printk(KERN_INFO "param_cb_int = %d\n", param_cb_int);
    printk(KERN_INFO "param_charp = %s, string addr = %p\n", param_charp, (void *)param_charp);

    for (i = 0; i < (sizeof param_array_int / sizeof(int)); i++) {
      printk(KERN_INFO "param_array_int[%d] = %d\n", i, param_array_int[i]);
    }
  }
  return 0;
}

static int __init hello_init(void) {
  int i = 0;
  test_kthread = kthread_run(thread_hello, NULL, "hello_test");
  printk(KERN_INFO "param_int = %d  \n", param_int);
  printk(KERN_INFO "param_cb_int = %d  \n", param_cb_int);
  printk(KERN_INFO "param_charp = %s \n", param_charp);

  for (i = 0; i < (sizeof param_array_int / sizeof(int)); i++) {
    printk(KERN_INFO "param_array_int[%d] = %d\n", i, param_array_int[i]);
  }
  printk(KERN_INFO "kernel module inserted successfully...\n");
  return 0;
}

static void __exit hello_exit(void) {
  if (test_kthread) {
    kthread_stop(test_kthread);
  }
  printk(KERN_INFO "Kernel Module Removed Successfully...\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wx2");
MODULE_DESCRIPTION("test module param");
MODULE_VERSION("1.0");
obj-m += hello.o
KDIR = /lib/modules/$(shell uname -r)/build
all:
        make -C $(KDIR)  M=$(shell pwd) modules
clean:
        make -C $(KDIR)  M=$(shell pwd) clean

dmesg 查看打印结果如下,内核模块安装之后,参数默认都是 0;设置参数之后,可以看到打印的参数与设置的参数是一致的。

3 模块间依赖 

假如有两个模块 A 和 B,B 中调用了 A 中的函数,或者使用了 A 中的全局变量,那么就认为 B 依赖 A。

 

3.1 手动指定

如下两个模块 A 和 B,A 中定义了函数 func1,并使用 EXPORT_SYMBOL() 将符号导出,B 中使用 extern 声明了 func1(),并且调用了 func1(),所以说 B 依赖 A。这种情况下,想要安装模块 B,需要先安装模块 A。如果没有安装 A 的情况下就安装 B,那么 B 会安装失败,错误信息 "Unknown symbol in module"

相同的符号只能导出一次,假如有两个模块都导出了符号 func1, 那么加载第二个模块的时候会加载失败,dmesg 中错误信息 "exports duplicate symbol func1 (owned by dep2)"

内核中导出的符号可以在 /proc/kallsyms 中看到,模块中导出的符号,也可以在其中查看到。

// mod_a.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>

int param_int = 0;


module_param(param_int, int, S_IWUSR | S_IRUSR);

int func1(void)
{
    printk("In Func: %s...\n", __func__);
    return 0;
}
// 导出函数符号,在较老的内核版本上,导出的符号可以声明为 static 类型
// 从内核的某个版本之后,被导出的符号不能声明为 static 类型
EXPORT_SYMBOL(func1);

static int __init hello_init(void)
{
    printk("module a init, param_int: %d\n", param_int);
    return 0;
}

static void __exit hello_exit(void)
{
    printk("module a exit\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wx2");
MODULE_DESCRIPTION("module a");
MODULE_VERSION("1.0");

Makefile:

obj-m += mod_a.o

KDIR = /lib/modules/$(shell uname -r)/build

all:
        make -C $(KDIR)  M=$(shell pwd) modules
clean:
        make -C $(KDIR)  M=$(shell pwd) clean

 

// mod_b.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

extern int func1(void);
static int func2(void)
{
    func1();
    printk("in func: %s...\n", __func__);
    return 0;
}

static int __init hello_init(void)
{
    printk("module b init\n");
    func2();
    return 0;
}

static void __exit hello_exit(void)
{
    printk("module b exit\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wx2");
MODULE_DESCRIPTION("module b");
MODULE_VERSION("1.0");

Makefile:

obj-m += mod_b.o

KDIR = /lib/modules/$(shell uname -r)/build
# mod_b 依赖 mod_a 中的符号,那么需要配置 KBUILD_EXTRA_SYMBOLS
# 如果不指定,那么在编译的时候会报错,提示符号找不到
KBUILD_EXTRA_SYMBOLS=/home/wyl/mod/a/Module.symvers


all:
        make -C $(KDIR)  M=$(shell pwd) modules
clean:
        make -C $(KDIR)  M=$(shell pwd) clean

除了上边声明 KBUILD_EXTRA_SYMBOLS 的方式,也可以将 mod_a.c 和 mod_b.c 放到一个文件夹下,然后 Makefile 如下,这样可以一次编译出来两个模块。

obj-m += mod_a.o mod_b.o

KDIR = /lib/modules/$(shell uname -r)/build
 

all:
        make -C $(KDIR)  M=$(shell pwd) modules
clean:
        make -C $(KDIR)  M=$(shell pwd) clean

3.1 depmod 和 modprobe

depmod 可以自动发现模块之间的依赖关系。使用 depmod 之前,需要把内核模块拷贝到 /lib/modules/$(uname -r) 目录下或者这个目录的子目录下。拷贝结果截图如下:

拷贝内核模块之后执行 depmod,便会自动生成模块之间的依赖关系,模块依赖关系保存到了 /lib/modules/$(uname -r)/modules.dep 中。mod_b 依赖 mod_a,在文件中使用冒号来表示,被依赖的模块放在冒号后边。

 内核模块的的 .ko 文件也是 elf 文件格式,在 elf 文件中可以确定一个函数符号是在本文件中定义的还是在其它文件中定义的。depmod 就是通过函数符号是不是在本 .ko 文件中定义来确定模块之间的依赖。

使用 objdump -tT xxx 可以查看一个二进制文件中的符号。如下图所示,在 mod_a.ko 中 func1 显示为 F,表示函数;在 mod_b.ko 中 func1 显示为 UND,表示没有在这个模块中定义。

执行 depmod 之后,就可以使用 modprobe 直接安装 mod_b,modprobe 安装 mod_b 的时候会首先检查依赖关系,发现 mod_b 依赖 mod_a,便会先安装 mod_a 再安装 mod_b。

使用 modprobe 安装模块时,只需要指定模块名即可,不需要指定模块文件的全路径,也不需要在模块名后边带后缀 .ko;而使用 insmod 安装模块时,需要指定模块的全路径,也需要带着模块的后缀 .ko。

使用 depmod 和 modprobe 来管理模块之间的依赖,类似于在 ubuntu 上使用 apt 来安装软件,apt 安装软件的时候也会自动去判断软件的依赖,先安装这个软件包的依赖,最后再安装这个软件包。

3.2 有依赖关系的模块参数

如上边例子所示,mod_b 依赖 mod_a,在使用 modprobe 安装 mod_b 的时候,怎么给 mod_a 传递参数呢。

执行 modprobe mod_b param_int=100,这种方式无法将参数传递给 mod_a。

通过如下方式给 mod_a 传递参数:

① 在目录 /etc/modprobe.d 中添加一个文件,比如 mod_a.conf

② 在 mod_a.conf 中添加如下内容,在执行 moprobe mod_a 的时候便会将 mod_a 中的 param_int 初始化成 10。

options mod_a param_int=10

模块参数配置文件 xxx.conf 可以放在 /etc/modprobe.d 或者 /usr/lib/modprobe.d 中,当两个目录中存在相同的参数时,优先选择 /etc/modprobe.d 中的配置。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/504352.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ONT60 旋转链表 思路分享

题干链接&#xff1a;ONT60 旋转链表 ​ 这道题是反转链表题的pro升级版&#xff0c;但比反转链表略微复杂一些。如果有做过旋转数组那道题&#xff08;链接在这里&#xff1a;https://blog.csdn.net/wyd_333/article/details/126712919&#xff0c;但当时刷这道题的时候我用的…

Linux|centos7-postgresql数据库|yum安装数据库和配置repmgr高可用集群以及repmgr的日常管理工作

一、 前言 postgresql 的yum部署其实还是有点东西的&#xff0c;本文就做一个小小的记录&#xff0c;高可用方面repmgr插件还是非常不错的&#xff0c;但如何部署以及部署后如何使用也是一个难点&#xff0c;因此&#xff0c;也在本文里做一个记录 环境介绍&#xff1a; 第…

【Redis教程0x0A】详解Redis哨兵机制

1. 引言 Redis的哨兵机制是基于主从架构的。 在 Redis 的主从架构中&#xff0c;由于主从模式是读写分离的&#xff0c;如果主节点&#xff08;master&#xff09;挂了&#xff0c;那么将没有主节点来服务客户端的写操作请求&#xff0c;也没有主节点给从节点&#xff08;slav…

java: 错误: 无效的源发行版:17

目录 一、java: 错误: 无效的源发行版&#xff1a;17 报错 原因 解决方法 二、pring-boot-starter-parent下面的版本报红 原因 解决方案 一、java: 错误: 无效的源发行版&#xff1a;17 报错 创建了一个sprintboot项目&#xff0c;运行CommunityApplication时&#xf…

小白从0学习ctf(web安全)

文章目录 前言一、baby lfi&#xff08;bugku-CTF&#xff09;1、简介2、解题思路1、解题前置知识点2、漏洞利用 二、baby lfi 2&#xff08;bugku-CTF&#xff09;1.解题思路1、漏洞利用 三、lfi&#xff08;bugku CTF&#xff09;1、解题思路1、漏洞利用 总结 前言 此文章是…

动态规划刷题(算法竞赛、蓝桥杯)--合唱队形(线性DP)

1、题目链接&#xff1a;[NOIP2004 提高组] 合唱队形 - 洛谷 #include <bits/stdc.h> using namespace std; int n,ans; int a[105],f[105][2];//f[i][2]中2表示正反两个方向int main(){cin>>n;for(int i1;i<n;i){cin>>a[i];}//正方向求最长上升子序列 a[…

HWOD:字符的排序

一、知识点 char的最大值是127&#xff0c;最小值是-128 自己填充的char型数组&#xff0c;以字符串打印&#xff0c;打印之前要手动在末尾加上 \0 二、题目 1、描述 Lily上课时使用字母数字图片教小朋友们学习英语单词&#xff0c;每次都需要把这些图片按照大小&#x…

财务管理系统的设计与实现|Springboot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目持续更新中..... 2024年计算机毕业论文&#xff08;设计&#xff09;学生选题参考合集推荐收藏&#xff08;包含Springboot、jsp、ssmvue等技术项目合集&#xff09; 目录 1. …

Spring Boot单元测试全指南:使用Mockito和AssertJ

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

算法学习15:数论(高斯消元,组合数,卡特兰数)

算法学习15&#xff1a;数论&#xff08;高斯消元&#xff0c;组合数&#xff0c;卡特兰数&#xff09; 文章目录 算法学习15&#xff1a;数论&#xff08;高斯消元&#xff0c;组合数&#xff0c;卡特兰数&#xff09;前言一、高斯消元1.输入一个包含n个方程&#xff0c;n个未…

用vscode仿制小米官网

html内容: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><link rel&quo…

Intellij IDEA 类注释模板设置

1、配置全局USER 在此配置全局USER&#xff0c;用于填充自动生成的注释中的作者author属性。 注释模板中的user参数是默认是获取系统的用户&#xff08;当然注释作者也可以直接写固定值&#xff09;&#xff0c;如果不想和系统用户用同一个信息&#xff0c;可以在IDEA中进行配…

查生意平台联动SFE上海连锁加盟展,呈现口碑招商盛宴

随着中国广告市场规模突破1251亿美元大关&#xff0c;连锁经营企业在其中的营销投放愈发凸显其重要性。查生意&#xff08;www.chasyi.com&#xff09;&#xff0c;作为国内领先的一站式连锁经营口碑评分查询服务平台&#xff0c;携手SFE上海连锁加盟展览会成功举办了一场严选品…

分布式架构商城系统的设计与实现|SpringCloud+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目持续更新中..... 2024年计算机毕业论文&#xff08;设计&#xff09;学生选题参考合集推荐收藏&#xff08;包含Springboot、jsp、ssmvue等技术项目合集&#xff09; 目录 1. …

C++格式化输入和输出

格式化输入与输出 除了条件状态外&#xff0c;每个iostream对象还维护一个格式状态来控制IO如何格式化的细节。 格式状态控制格式化的某些方面&#xff0c;如整型值是几进制、浮点值的精度、一个输出元素的宽度等。 标准库定义了一组操纵符来修改流的格式状态。 一个操纵符…

Vue3+.NET6前后端分离式管理后台实战(十)

1&#xff0c;Vue3.NET6前后端分离式管理后台实战&#xff08;十&#xff09;已经在订阅号发布有兴趣的可以关注一下&#xff01; 感兴趣请关注订阅号谢谢&#xff01; 代码已经上传gitee

树莓派串口读取陀螺仪ky9250(mpu9250)数据

9轴姿态角度传感器&#xff0c;其中ky9250陀螺仪由于自带卡尔曼动态滤波算法方便用户使用。ky9250陀螺仪基本可以在各个平台上进行数据的读取&#xff08;如stm32\arduino\C#\Matlab\树莓\Unity3d\python\ROS\英飞凌\Nvidia jetson linux 等&#xff09; 1、树莓派和ky9250的接…

储能系统--BMS系统中的高压BUCK电路

一、Buck关键器件介绍 1、芯片选型 控制方式分类 优势缺点同步 1&#xff1a;效率高 2&#xff1a;MOS压降低 1&#xff1a;成本高 2&#xff1a;下官驱动复杂 异步 1&#xff1a;成本便宜 2&#xff1a;适合较高的输出电压 1&#xff1a;效率低 按照隔离方式分类 隔离电源…

会员制医疗预约服务管理信息系统的设计与实现|Springboot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目持续更新中..... 2024年计算机毕业论文&#xff08;设计&#xff09;学生选题参考合集推荐收藏&#xff08;包含Springboot、jsp、ssmvue等技术项目合集&#xff09; 1. 系统功能…

基于Givens旋转完成QR分解进而求解实矩阵的逆矩阵

基于Givens旋转完成QR分解进而求解实矩阵的逆矩阵 目录 前言 一、Givens旋转简介 二、Givens旋转解释 三、Givens旋转进行QR分解 四、Givens旋转进行QR分解数值计算例子 五、求逆矩阵 六、MATLAB仿真 七、参考资料 总结 前言 在进行QR分解时&#xff0c;HouseHolder变换…