- 驱动基础知识
- 经过测试,发现,modprobe默认会去/lib, /lib/modules/ , 指定路径,这三个地方去找对应的xxx.ko文件
- 在下一章会发现,modprobe寻找依赖时,会先尝试将当前驱动加载(此时加载不上),然后将依赖加载,在将当前驱动加载
- 以下是转载的,找不到原创了
在搞Linux驱动移植/开发的时候,对于编译出来的驱动可以选择手动insmod,但是感觉很土:1. 需要指定路径; 2. 如果碰到存在依赖的,就丑陋不堪了。 - include
- if LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0)
//旧版本代码
#else
// 新版本代码 - endif
- 总线相关技术
驱动基础知识
内核相关网址
https://elixir.bootlin.com/linux/latest/source #linux内核源码在线
https://elinux.org/Main_Page #elinux
https://www.kernel.org/ #kernel下载
https://github.com/torvalds/linux # kernel git
文章内容按照 kernel 4.14开始学习
搭建本地环境
相关工具
tftp
nfs
minicom
vim+.vimrc插件+.vim插件
ctags/cscope / SI IED工具
ssh
aarch-linux-gcc
u-boot-tools
device-tree-compiler
libncurses5
ARM等编译器下载和安装
官网下载安装,aarch-linux-gcc -v查看需要的版本
https://www.linaro.org/downloads/
搭建开发板环境
固化UBOOT
解压源代码,
make xxx_config,make ARCH=xxx CROSS_COMPILE=arm-xxxx-即可生成u-boot.bin,
启动kernel
官方提供源代码,
cp arch/xx/configs/xxx_deconfig .config 或者make xxx_defconfig
make menuconfig 修改 ->查看内核是否支持ARM平台、是否支持当前处理器、是否支持当前开发板
system type->
ARM
CPU(s5pv210)
board(cw210)
system type ->
->ARM system type(Samsung EXYNOS)
->board selection 对应UBOOT id号
make ARCH=xxx CROSS_COMPILE=arm-linux- zImage -j8
挂接文件系统
体力活,去多学习,熟练。有机会阅读busybox,这部分代码挺好 init.c开始看起。
设置启动参数
setenv bootcmd 加载并引导启动内核{网络加载,本地flash,本地sd卡启动等}
setenv bootargs 启动参数,UBOOT告诉内核挂接根文件系统信息
setenv bootcmd tftp 50008000 zImage;bootm 50008000
如nfs启动
setenv bootargs root=/dev/nfs nfsroot=主机ip:主机NFS共享目录
ip=板子IP:主机IP:网关:子网掩码::eth0
init=/linuxrc console=ttySAC0,115200
setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/opt/rootfs init=/linuxrc console=ttySAC0,115200
ip=192.168.1.110:192.168.1.8:192.168.1.1:255.255.255.0::eth0
如果提示:Root-NFS:Server returned error -13 while mounting /opt/rootfs 表示服务没有开启。
ipaddr=xxx 注:这个IP为uboot时使用,bootargs在启动时用
saveenv
注:
这部分也可以在内核中配置
CONFIG_CMDLINE="root=/dev/nfs rw nfsroot=192.168.1.222:/home/baiy/train/ITOP-learn/nfs-fs/system ip=192.168.1.230:192.168.1.222:192.168.1.1:255.255.255.0:iTOP-nfs:eth0 :off rootfstype=ext4 init=/linuxrc console=ttySAC2,115200"
2.5 系统起来后,就可以搞驱动和应用了。
2.6 熟悉Makefile 和Kconfig的使用
案例:现在需要写一个LM77温度传感器的配置信息,在Make menuconfig中找到LM77的配置信息,怎么找源文件?
通过源文件找配置项?这部分必须熟练。
内核模块
驱动的本质:1.对硬件工作的封装 2.给用户提供可访问的操作方法
驱动的分类: 无操作系统(裸板和单片机),应用程序直接调用函数即可。 驱动就是函数封装。
有操作系统(Linux,RTOS)应用程序通过系统间接调用函数即可。驱动是系统调用。
open->sys_open->drv_open,只需要完成drv_open和open即可。
Linux内核驱动的层次:
printf(“hello, world\n”)—->write———>filesystem——>uart_drivers—->硬件输出
Linux驱动
3.1.1 驱动的入口函数 和 出口函数
#include
#include
static int init xxx_init(void)
{
printk(“xx_init:%s\n”,FUNCTION);
return 0;//成功返回0,失败返回错误码
}
static void exit xxx_exit(void)
{
printk(“xxx_exit:%s\n”,FUNCTION);
return;
}
module_init(xxx_init);
module_exit(xxx_exit); //修饰入口和出口函数
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“baiy baiyang0223@163.com“); //作者信息,选填
MODULE_DESCRIPTION(“This is a test driver”); //描述信息,选填
MODULE_VERSION(“1.0.0.0”); //版本描述,选填
3.1.2 入口函数和出口函数什么时候被执行
当内核初始化时或者动态加载驱动模块时,执行驱动入口函数。 初始化工作。
当系统复位或者卸载驱动模块时调用驱动出口函数。扫尾工作。
注释:可通过request_module(module_name);函数去加载模块
3.2 Linux内核驱动模块编译
3.2.1 静态编译:
将驱动和内核绑定在一起,写入到Makefile中,将驱动和内核都压缩在zImage中
做法:
1.Kconfig和Makefile
Kconfig中找到配置项 config SENSORS_LM77。
通过此信息得到Makefile使用的配置项:CONFIG_SENSORS_LM77。
找到对应的配置项存在的内容,也就找到对应的源文件。
2.将驱动放在内核源代码中,
cp xxx.c driver/char/
修改Kconfig和Makefile
Kconfig中加入
config HELLO_WORLD
#depend on //依赖某个驱动
default y
help
This is first drive
Makefile中加入
obj-${CONFIG_HELLO_WORLD} += hello.o
make zImage
查看.config和xxx.o文件是否生成。
重启
3.2.2 动态编译1:内核模块。
配置项写为三态,选为模块即可,最后make modules
make menuconfig 将*变成M
make zImage 去除方法1中的静态编译
make modules 编译模块,生成.ko文件,不产生依赖
make modules_install INSTALL_MOD_PATH=/opt/ //安装模块,并产生依赖关系,生成一个lib目录,包含了之间依赖关系。
cp /opt/lib/modules /rootfs/lib/ -frd 即可。 //安装模块后会生成/opt/lib/modules 文件,拷贝到/lib/下边即可
烧录zImage重启。
动态编译1每次修改只需要 make modules 重新编译,将对应.ko拷贝到设备上加载即可。不需要重新启动系统。
共性:都得去修改Kconfig和Makefile,且放到内核编译。
3.2.3 动态编译2:内核模块,将驱动源码和内核源码进行分离。
前两种方式:代码都在内核目录下,都需要修改Kconfig和Makefile
内核目录
driver/
char
helloworld.c 在内核代码中,
可以在外部编写,跳转到模块中
#Makefile:
If KERNELRELEASE is defined, we’ve been invoked from the
kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)
#这部分第二次执行
obj-m := hello.o
# Otherwise we were called directly from the command
# line; invoke the kernel build system.
else
#这部分第一次执行
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
#install: //加不加伪目标都可以。这里可以加入modinstall信息
# $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install INSTALL_MOD_PATH=/opt/ // 创建模块依赖关系
endif
clean:
rm -rf .ko ..ko.cmd .mod.c .mod.o ..mod.o.cmd _.o .*.o.cmd modules.order Module.symvers .tmp_versions
当make的目标为all时,-C $(KDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。
当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容,其为kbuild语法的语句, 指明模块源码中各文件的依赖关系,以及要生成的目标模块名。
所以也可以跳转到内核,然后
make M=/home/baiy/code/driver-test/test01 modules,一样。
注:如果这里模块有多个文件,可以:
如何将多个.c生成一个.ko?
a.c和b.c生成d.ko
Makefile中
obj-m := d.o
d-objs = a.o b.o
或者
obj-m := ${MODNAME}.ko
${MODNAME}-objs += helloworld1.o
${MODNAME}-objs += helloworld2.o
因为同一个驱动,所以无依赖关系了
模块间的依赖关系
baiy@baiy-ThinkPad-X230:iTop4412_Kernel_3.0$ make modules_install INSTALL_MOD_PATH=/home/baiy/code/mod_install/
INSTALL crypto/ansi_cprng.ko
INSTALL drivers/char/baiyang_hello1.ko
INSTALL drivers/media/video/gspca/gspca_main.ko
INSTALL drivers/mtk_wcn_combo/common/mtk_hif_sdio.ko
INSTALL drivers/mtk_wcn_combo/common/mtk_stp_bt.ko
INSTALL drivers/mtk_wcn_combo/common/mtk_stp_gps.ko
INSTALL drivers/mtk_wcn_combo/common/mtk_stp_uart.ko
INSTALL drivers/mtk_wcn_combo/common/mtk_stp_wmt.ko
INSTALL drivers/mtk_wcn_combo/common/mtk_wmt_wifi.ko
INSTALL drivers/mtk_wcn_combo/drv_bt/hci_stp.ko
INSTALL drivers/mtk_wcn_combo/drv_fm/private/mtk_fm_priv.ko
INSTALL drivers/mtk_wcn_combo/drv_fm/public/mt6620_fm_drv.ko
INSTALL drivers/mtk_wcn_combo/drv_wlan/p2p/p2p.ko
INSTALL drivers/mtk_wcn_combo/drv_wlan/wlan/wlan.ko
INSTALL drivers/scsi/scsi_wait_scan.ko
INSTALL net/bluetooth/hidp/hidp.ko
DEPMOD 3.0.15 #生成模块依赖关系
Warning: you may need to install module-init-tools
See http://www.codemonkey.org.uk/docs/post-halloween-2.6.txt
baiy@baiy-ThinkPad-X230:iTop4412_Kernel_3.0$ tree ../mod_install/lib/
../mod_install/lib/
└── modules
└── 3.0.15
├── build -> /home/baiy/code/iTop4412_Kernel_3.0
├── kernel
│ ├── drivers
│ │ ├── char
│ │ │ └── baiyang_hello1.ko
。。。。。。
├── modules.alias
├── modules.alias.bin
├── modules.builtin
├── modules.builtin.bin
├── modules.dep #依赖信息
├── modules.dep.bin
├── modules.devname
├── modules.order
├── modules.softdep
├── modules.symbols
├── modules.symbols.bin
└── source -> /home/baiy/code/iTop4412_Kernel_3.0
注:如果内核有需要的驱动,则直接编译选项选中即可。
3.3 内核模块动态加载
3.3.1 加载模块
modprobe xxx 会默认到/lib/目录下寻找对应.ko文件,并且检测xxx.ko依赖关系,将所有依赖的.ko加载完成后,在加载xxx.ko。
注:
insmod ….ko 这里必须要写.ko的路径,且不会检查依赖关系。如所依赖的模块未加载,则直接出错。
3.3.2 卸载模块
modprobe -r xxx 这个卸载会将依赖一起卸载
rmmod xxx 这个删除模块只删除指定模块,不删除依赖
3.3.3 查看模块
lsmod
modinfo xxx.ko 即可。
注::这几个命令必须会
注:modprobe 加载和卸载时不需要加 .ko,且编译完内核必须进行模块安装且拷贝到/lib目录下
modprobe(选项)(参数)
选项
-a或—all:载入全部的模块;
-c或—show-conf:显示所有模块的设置信息;
-d或—debug:使用排错模式;
-l或—list:显示可用的模块;
-r或—remove:模块闲置不用时,即自动卸载模块;
-t或—type:指定模块类型;
-v或—verbose:执行时显示详细的信息;
-V或—version:显示版本信息;
-help:显示帮助。
参数: 加载或移除的模块名
modinfo [-adhpV][模块文件]
参数:
-a或—author 显示模块开发人员。
-d或—description 显示模块的说明。
-h或—help 显示modinfo的参数使用方法。
-p或—parameters 显示模块所支持的参数。
-V或—version 显示版本信息。
insmod(选项)(参数)
选项
-f:不检查目前kernel版本与模块编译时的kernel版本是否一致,强制将模块载入;
-k:将模块设置为自动卸除;
-m:输出模块的载入信息;
-o<模块名称>:指定模块的名称,可使用模块文件的文件名;
-p:测试模块是否能正确地载入kernel;
-s:将所有信息记录在系统记录文件中;
-v:执行时显示详细的信息;
-x:不要汇出模块的外部符号;
-X:汇出模块所有的外部符号,此为预设置。
-p:测试模块是否能正确地载入kernel;
1.insmod一次只能加载特定的一个设备驱动,且需要驱动的具体地址。写法为:insmod drv.ko
2.modprobe则可以一次将有依赖关系的驱动全部加载到内核。不加驱动的具体地址,但需要在安装文件
系统时是按照make modues_install的方式安装驱动模块的。驱动被安装在/lib/modules/$(uname -r)/…下。写法为:
modprob driver_name
3.modprobe可以解决load module时的依赖关系,比如load moudleA就必须先load mouduleB之类的,它是通过/lib/modules//modules.dep文件来查找依赖关系的。而insmod不能解决依赖问题。
4.modprobe默认会去/lib/下面查找module,而insmod只在给它的参数中去找module(默认在当前目录找)。
这样,有时insmod也有它的有用之处,举个例子吧:有/root/my-mod.ko这个module,cd /root/,然后用insmod my-mod.ko(insmod /root/my-mod.ko)就可以insert这个module了,
经过测试,发现,modprobe默认会去/lib, /lib/modules/ , 指定路径,这三个地方去找对应的xxx.ko文件
在下一章会发现,modprobe寻找依赖时,会先尝试将当前驱动加载(此时加载不上),然后将依赖加载,在将当前驱动加载
lsmod 中used by说明此驱动被哪些驱动依赖,需要调用这个驱动中的函数等;这个命令读取 cat /proc/modules 来显示;
驱动中需要关心 /sys/modules/$modulename/相关部分
/sys/module/hello/
├── coresize
├── holders #持有人,被哪些驱动引用
│ └── param -> ../../param
├── initsize
├── initstate #live 状态
├── notes
├── refcnt #引用计数,当为0时可被卸载
├── sections
│ ├── ksymtab_gpl
│ ├── ksymtab_strings
│ └── __mcount_loc
├── srcversion
├── taint
├── uevent
└── version
root@baiy-ThinkPad-T480:test01# cat /sys/module/hello/refcnt
0
root@baiy-ThinkPad-T480:test01# cat /sys/module/hello/initstate
live
以下是转载的,找不到原创了
在搞Linux驱动移植/开发的时候,对于编译出来的驱动可以选择手动insmod,但是感觉很土:1. 需要指定路径; 2. 如果碰到存在依赖的,就丑陋不堪了。
但是modprobe可以很优雅的解决:直接$ modprobe XX_DRIVER_XX即可。
那么问题来了:modprobe自动加载的时候,如何知道驱动的路径和信息呢?以及,我自己编译的驱动,又如何能够modprobe,而不是山寨的insmod呢?
- modprobe的信息依据
modprobe依赖于/lib/modules/$(uname -r)/modules.dep。而且该文件而不需要手动修改,而使用depmod即可实现自动化操作。
- depmod
depmod执行不依赖于目录,效果是自动查找/lib/modules/$(uname -r)/下的驱动文件,以及分析彼此的依赖关系。
因此,如果使modprobe找得到的话:
STEP 1:添加驱动至/lib/modules/$(uname -r)/XX_PATH_XX
STEP 2:$ depmod //更新modules.dep信息
注意:以及,加载驱动时: $modprobe XX_DRIVER_XX 而不是 XX_DRIVER_XX.ko
注:Linux的代码从3.10以后,修改了很多接口,通过以下宏来隔开,适应多个版本
include
if LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0)
//旧版本代码
#else
// 新版本代码
endif
一、驱动头文件和额外信息
#include
#include
注:内核驱动需要指定一些模块信息
MODULE_LICENSE(“GPL”); //指定模块遵循GPL协议,否则加载时出现告警,必加
否则内核一些遵循GPL的API无法调用。告警:module license “unspecified” taints kernel
MODULE_AUTHOR(“baiy baiyang0223@163.com“); //作者信息,选填
MODULE_DESCRIPTION(“This is a test driver”); //描述信息,选填
MODULE_VERSION(“1.0.0.0”); //版本描述,选填
可通过modinfo查看。 modinfo xxx.ko
二、内核符号的导出
允许内核模块导出模块中的函数或者变量,供其他内核模块引用:
也就是内核中某个驱动的变量或者函数,给另一个驱动使用
EXPORT_SYMBOL(“符号名称”);
EXPORT_SYMBOL_GPL(“符号名称”); //这里的符号名称可以是变量名也可以是函数名
EXPORT_SYMBOL_GPL:导出的符号只能被拥有GPL许可支持的内核模块导出。
其次,头文件需要声明对应的符号, extern 函数原型/变量
说白了,在标C的基础上多了个EXPORT_SYMBOL_GPL(“符号名称”);
注:
把mod_a的Module.symvers放到mod_b的当前路径,从而编译mod_b,符号信息会自动连接进去.
或者在mod_b的makefile中使用KBUILD_EXTRA_SYMBOLS指定mod_a的Module.symvers, 如:
KBUILD_EXTRA_SYMBOLS=/mod_a/Module.symvers
编译mod_b时,搜索Module.symvers的路径是:
1, kernel source path, e.g. /usr/src/kernels/linux-2.6.28.10
2, makefile中M=所指定的路径, 它等效于变量KBUILD_EXTMOD的值
3, 变量KBUILD_EXTRA_SYMBOLS的值
注:
也就是几种方法:
1.编写的多个驱动在同一个目录。
2.当前驱动中加入KBUILD_EXTRA_SYMBOLS=/mod_a/Module.symvers 指定依赖驱动的符号库
尝试用modprobe加载模块
问题:如何想使用modprobe,必须先去安装,但这时候已经驱动内核分离,如何install?
Makefile中
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install INSTALL_MOD_PATH=/opt/
先删除以前驱动的依赖,rm -rf /opt/lib/*,然后将安装的mod拷贝进去。
强调:调用GPL的文件必须要支持GPL协议
三、如何将多个.c生成一个.ko?
a.c和b.c生成d.ko
Makefile中
obj-m := d.o
d-objs = a.o b.o
或者
obj-m := ${MODNAME}.ko
${MODNAME}-objs += helloworld1.o
${MODNAME}-objs += helloworld2.o
因为同一个驱动,所以无依赖关系了
内核的模块参数
使用用法
对于已经知道咋用的,直接看demo程序即可。
内核的模块参数DEMO-dmatest : 中各种花里胡哨的使用了内核的模块参数。
内核模块参数就像C语言的argc和argv,作用是调用程序时传入
int main(int argc,char * argv[])
{
printf("argc = %d,argv[0] = %s\n",argc, argv[0]);
int data = stroul(argv[1],NULL,10);
return 0;
}
./a.out 1 //作用:可在执行时动态修改程序某个
内核模块的参数:在外部定义一系列全局变量,可以在加载时设置这些变量的值,也可以在程序运行中通过sysfs去d动态修改。
模块参数声明:通过module_param修饰后,可在加载驱动时或以后,可动态修改变量值。
内核模块的作用: 可动态修改驱动中的值。
定义一个变量
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
支持数据的类型
bool
invbool
charp chat指针,
short
ushort
int
uint
long 长整型
ulong 无符号长整型
static int data = 2; //然后驱动加载时如何设置别的值?或修改值。
作用:可动态修改驱动中某个值
用module_param 去修饰
可参考:module_param()函数
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
module_param(name,type,perm); // 非数组类型
name:参数名称
type:参数数据类型
perm:用户对模块的操作权限
module_param_array(name,type,nump,type) // 数组类型
nump:数组元素个数的指针,如果数组参数在加载时设置,该值为加载时设置的数据个数,
不允许传递比数据允许个数更多的值。
如:数组元素个数为10,但如果只想修改前5(必须<=10)个,参数可以传递5所对应变量的地址
如果nump为NULL,则默认为为数组元素个数。一般都是NULL。
MODULE_PARM_DESC(_parm,desc); // 模块参数描述声明,可通过modinfo查看。
parm:待增加描述内容的模块参数,也就是变量名
desc:对模块参数的描述声明
模块参数用户操作权限
S_I[R/W/X]USR
S_I[R/W/X]GRP
S_I[R/W/X]OTH
实际使用基本都是8进制数,如0666。如果权限是0,则就是读写执行都可
/sys/module/${modulename}/parameters目录 // 模块参数对应的文件系统位置:
模块参数赋值:
定义变量时初始化
模块加载时初始化
直接修改模块参数文件内容
高逼格玩法1:在内核的模块参数DEMO-dmatest驱动中也看到过这样变态的修饰:
static char test_channel[20];
module_param_string(channel, test_channel, sizeof(test_channel),
S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(channel, "Bus ID of the channel to test (default: any)");
原型:module_param_string(name, string, len, perm);
参数:
,name:既是用户看到的参数名,又是模块内接受参数的变量。 在用户空间可见名称
,string:是内部的变量名, 实际变量名
,nump:以string命名的buffer大小(可以小于buffer的大小,但是没有意义)
,perm:指定了在sysfs中相应文件的访问权限;
高逼格玩法2:在内核的模块参数DEMO-dmatest驱动中竟然可以玩参数回调方法:
可以参考:module_param_call 方法
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
static int dmatest_run_set(const char *val, const struct kernel_param *kp);
static int dmatest_run_get(char *val, const struct kernel_param *kp);
static const struct kernel_param_ops run_ops = {
.set = dmatest_run_set,
.get = dmatest_run_get,
};
static bool dmatest_run;
module_param_cb(run, &run_ops, &dmatest_run, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(run, "Run the test (default: false)");
在回调接口中,通过以下内核空间获取值的方法
extern int param_set_byte(const char *val, const struct kernel_param *kp);
extern int param_get_byte(char *buffer, const struct kernel_param *kp);
#define param_check_byte(name, p) __param_check(name, p, unsigned char)
extern const struct kernel_param_ops param_ops_short;
extern int param_set_short(const char *val, const struct kernel_param *kp);
extern int param_get_short(char *buffer, const struct kernel_param *kp);
#define param_check_short(name, p) __param_check(name, p, short)
extern const struct kernel_param_ops param_ops_ushort;
extern int param_set_ushort(const char *val, const struct kernel_param *kp);
extern int param_get_ushort(char *buffer, const struct kernel_param *kp);
#define param_check_ushort(name, p) __param_check(name, p, unsigned short)
extern const struct kernel_param_ops param_ops_int;
extern int param_set_int(const char *val, const struct kernel_param *kp);
extern int param_get_int(char *buffer, const struct kernel_param *kp);
#define param_check_int(name, p) __param_check(name, p, int)
extern const struct kernel_param_ops param_ops_uint;
extern int param_set_uint(const char *val, const struct kernel_param *kp);
extern int param_get_uint(char *buffer, const struct kernel_param *kp);
#define param_check_uint(name, p) __param_check(name, p, unsigned int)
extern const struct kernel_param_ops param_ops_long;
extern int param_set_long(const char *val, const struct kernel_param *kp);
extern int param_get_long(char *buffer, const struct kernel_param *kp);
#define param_check_long(name, p) __param_check(name, p, long)
extern const struct kernel_param_ops param_ops_ulong;
extern int param_set_ulong(const char *val, const struct kernel_param *kp);
extern int param_get_ulong(char *buffer, const struct kernel_param *kp);
#define param_check_ulong(name, p) __param_check(name, p, unsigned long)
extern const struct kernel_param_ops param_ops_ullong;
extern int param_set_ullong(const char *val, const struct kernel_param *kp);
extern int param_get_ullong(char *buffer, const struct kernel_param *kp);
#define param_check_ullong(name, p) __param_check(name, p, unsigned long long)
extern const struct kernel_param_ops param_ops_charp;
extern int param_set_charp(const char *val, const struct kernel_param *kp);
extern int param_get_charp(char *buffer, const struct kernel_param *kp);
extern void param_free_charp(void *arg);
#define param_check_charp(name, p) __param_check(name, p, char *)
/* We used to allow int as well as bool. We're taking that away! */
extern const struct kernel_param_ops param_ops_bool;
extern int param_set_bool(const char *val, const struct kernel_param *kp);
extern int param_get_bool(char *buffer, const struct kernel_param *kp);
内核的随机数
参考:内核随机数产生器
demo: test05-random