驱动模块传参和导出符号表

配置Ubuntu上的NFS


$sudo apt-get install nfs-kernel-server nfs-common

配置


$sudo vim /etc/exports
#添加
#/home/pi/project/rootfs/ *(rw,sync,no_subtree_check,no_root_squash)

#/dir   :共享的目录
#*     :指定哪些用户可以访问
#       * 所有可以ping同该主机的用户
#       192.168.1.* 指定网段,在该网段中的用户可以挂载
#       192.168.1.12 只有该用户能挂载
#(ro,sync,no_root_squash): 权限
#ro : 只读
#rw : 读写
#sync : 同步
#no_root_squash: 不降低root用户的权限
#其他选项man 5 exports 查看

#Ubuntu 18.10默认的NFS不支持NFS2协议,需要手动添加协议支持
$/etc/default/nfs-kernel-server
#添加
RPCNFSDOPTS="--nfs-version 2,3,4 --debug --syslog"

在开发板上挂载NFS


#重启NFS服务
$sudo service nfs-kernel-server restart

#挂载NFS
$sudo mount -t nfs 192.168.2.100:/home/pi/nfs/ /home/pi/nfs/
#查看
$ls /mnt
卸载NFS
$umount /mnt

内核驱动Makefile

编译单文件驱动

KERNELDIR := /home/pi/kernel/linux
COMPILER := arm-linux-gnueabihf-
PWD := $(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) moudles ARCH=arm CROSS_COMPILE=$(COMPILER)
#make -C 的作用是切换目录(切换进入内核源码目录,执行里面的makefile文件)
#make -M 的作用是指定编译模块的路径
clean:
make -C $(KERNELDIR) M=$(PWD) clean ARCH=arm CROSS_COMPILE=$(COMPILER)
install:
cp *.ko /home/pi/nfs
obj-m := chrdev.o
#obj-y 是编译进内核
#obj-m 是编译成驱动模块.ko

编译多文件驱动

KERNELDIR := /home/pi/kernel/linux
COMPILER := arm-linux-gnueabihf-
PWD := $(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) moudles ARCH=arm CROSS_COMPILE=$(COMPILER)
clean:
rm -f *.o *.mod.c *.ko *.symvers *.order *.makers *.cmd
install:
cp *.ko /home/pi/nfs
obj-m := hello.o          #-m 生成模块:hello.o生成hello.ko
hello-y := chrdev.o add.o #-y 添加依赖:将chrdev.o add.o 编译进hello.o

驱动入口三要素

入口函数

static init __init driver_init(void)
{
return 0;
}

出口函数

static void __exit driver_exit(void)
{

}

告诉内核驱动的出入口函数地址

module_init(driver_init);
module_exit(driver_exit);

GPL许可证

MODULE_LICENSE("GPL");	//linux/module.h头文件

创建索引文件

$cd kernel #进入内核源码根目录

$make tags #创建索引文件,普通项目创建索引的方法Ctags -R,但是内核中这样做可能会不完整

$ vi -t __init #搜索代码

内核模块的一些基本操作

基本模块命令

# 安装(args 传参可加可不加)
sudo insmod xxx.ko args

# 查看已安装的模块
lsmod

# 卸载模块
sudo rmmod xxx

在内核中打印语句

printk(KERN_INFO "%s,%s,%d", __func__, __FILE__, __LINE__);	 /* printk 的用法*/

/* 内核中的打印级别,数字越小级别越高,只有消息级别高于终端的级别打印的信息才会在终端上显示
   printk如果不加KERN_LEVEL则会使用默认打印级别,可在/proc/sys/lernel/printk 中修改*/

#define KERN_EMERG  KERN_SOH "0"    /* system is unusable */
#define KERN_ALERT  KERN_SOH "1"    /* action must be taken immediately */
#define KERN_CRIT   KERN_SOH "2"    /* critical conditions */
#define KERN_ERR    KERN_SOH "3"    /* error conditions */                       
#define KERN_WARNING    KERN_SOH "4"    /* warning conditions */
#define KERN_NOTICE KERN_SOH "5"    /* normal but significant condition */
#define KERN_INFO   KERN_SOH "6"    /* informational */
#define KERN_DEBUG  KERN_SOH "7"    /* debug-level messages */
#define KERN_DEFAULT    KERN_SOH "d"    /* the default kernel loglevel */

查看和修改打印级别

#查看打印级别
cat /proc/sys/kernel/printk
   4	      4	          1			   7
终端级别  内核消息默认级别 终端的最高级别  终端的最低级别

#Ubuntu下的终端默认不显示内核打印信息,需要按Ctrl+Alt+[F1~F6]切换进入虚拟终端

#修改打印级别,需要切换到root,只能使用echo命令修改
su root
echo 4 3 1 7 > /proc/sys/kernel/printk

查看所有打印信息dmesg

sudo dmesg #查看所有打印信息

sudo dmesg -C #清理所有打印信息

驱动模块传参

/**  * module_param - typesafe helper for a module/cmdline parameter                 * @value:  要更改的变量和暴露的参数名称。
  * @type: 参数的类型。
  * @perm: 在sysfs中的权限
  *
  * @value becomes the module parameter, or (prefixed by KBUILD_MODNAME and a
  * ".") the kernel commandline parameter.  Note that - is changed to _, so
  * the user can use "foo-bar=1" even for variable "foo_bar".
  *
  * @perm is 0 if the the variable is not to appear in sysfs, or 0444
  * for world-readable, 0644 for root-writable, etc.  Note that if it
  * is writable, you may need to use kernel_param_lock() around
  * accesses (esp. charp, which can be kfreed when it changes).
  *
  * The @type is simply pasted to refer to a param_ops_##type and a
  * param_check_##type: for convenience many standard types are provided but
  * you can create your own by defining those variables.
  *
  * Standard types are:
  *  byte, short, ushort, int, uint, long, ulong
  *  charp: a character pointer
  *  bool: a bool, values 0/1, y/n, Y/N.
  *  invbool: the above, only sense-reversed (N = true).
  */
 #define module_param(name, type, perm)              \
     module_param_named(name, name, type, perm)
--------------------------------------------------------
/* One for each parameter, describing how to use it.  Some files do
 * multiple of these per line, so can't just use MODULE_INFO. 
 * 模块传参描述
 * @_parm:参数名
 * @desc:描述的字段
 */
#define MODULE_PARM_DESC(_parm, desc) \                                        		__MODULE_INFO(parm, _parm, #_parm ":" desc)
----------------------------------------------------------
 /**
  * module_param_array - a parameter which is an array of some type
  * @name: 数组名
  * @type: 数组类型
  * @nump: 传入数组元素数量的指针
  * @perm: 在sysfs中的权限
  *
  * Input and output are as comma-separated values.  Commas inside values
  * don't work properly (eg. an array of charp).
  *
  * ARRAY_SIZE(@name) is used to determine the number of elements in the
  * array, so the definition must be visible.
  */
 #define module_param_array(name, type, nump, perm)      \                       
     module_param_array_named(name, name, type, nump, perm)
---------------------------------------------------------------
/**
  * module_param_string - a char array parameter
  * @name: 参数名(字符数组名)
  * @string: 字符数组名
  * @len: 字符串的最大长度
  * @perm: 在sysfs中的权限
  *
  * This actually copies the string when it's set (unlike type charp).
  * @len is usually just sizeof(string).
  */
 #define module_param_string(name, string, len, perm)            \               
     static const struct kparam_string __param_string_##name     \
         = { len, string };                  \
     __module_param_call(MODULE_PARAM_PREFIX, name,          \
                 &param_ops_string,              \
                 .str = &__param_string_##name, perm, -1, 0);\
     __MODULE_PARM_TYPE(name, "string")

驱动模块传参示例1:单个参数

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

//定义默认值
int arg = 100;

//接收传参
module_param(arg, int, 0664); //权限最大只能是0775否则编译报错
/* 将会在/sys/module/xxx驱动模块名称/paramters/ 出现对应的参数名称的文件
 * 这个文件的权限就就是module_param中的权限,同时也可以对这个文件写数据来在
 * 驱动安装完成之后向驱动模块传值 */

//传参描述
MODULE_PARM_DESC(a, "this is chrdev parameter, default=100,span 0-199");

static int __init chrdev_init(void) {
    printk(KERN_EMERG "hello kernel!\n");
    printk("param = %d\n", arg);
    printk(KERN_EMERG "%s, %s, %d\n", __FILE__, __func__, __LINE__);
    return 0;
}

static void __exit chrdev_exit(void) {
    printk(KERN_EMERG "%s, %s, %d\n",__FILE__, __func__, __LINE__);
}

module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_LICENSE("GPL");

查看驱动模块信息

modinfo xxx.ko

打印效果

image-20200405162121508

驱动模块传参示例2:多个参数

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

//定义默认值
int val = 100;
char chr = ' ';
char *p = "defualt";

//接收传参
module_param(val, int, 0664);
module_param(chr, byte, 0664);
module_param(p, charp, 0664);

//传参描述
MODULE_PARM_DESC(val, "val is this module parameter, default=100,span 0-199");
MODULE_PARM_DESC(chr, "chr is this module parameter, default=' '");
MODULE_PARM_DESC(val, "p is this module parameter, default=\"default\"");

static int __init chrdev_init(void) {
    printk(KERN_EMERG "hello kernel!\n");
    printk(KERN_EMERG "param val = %d\n", val);
    printk(KERN_EMERG "param chr= %c\n", chr);
    printk(KERN_EMERG "param p = %s\n", p);
    printk(KERN_EMERG "%s, %s, %d\n", __FILE__, __func__, __LINE__);
    return 0;
}

static void __exit chrdev_exit(void) {
    printk(KERN_EMERG "%s, %s, %d\n",__FILE__, __func__, __LINE__);
}

module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_LICENSE("GPL");

多个值同时传入

sudo insmod chrdev.ko val=99 chr=67 p="pypyn.com_blog"
# 参数名 = 传入值
# 字符传传入的适合注意空格,默认不识别空格,比如"hello world"(非法值),将空格用下划线代替
image-20200405173124525

驱动模块传参示例3:数组

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

//定义默认值
int val = 100;
char chr = ' ';
char *p = "defualt";
int arr[10]={0,};
int lenth;

//接收传参
module_param(val, int, 0664); //权限最大只能是0775否则编译报错
module_param(chr, byte, 0664);
module_param(p, charp, 0664);
module_param_array(arr, int, &lenth, 0664); //注意长度需要取地址

//传参描述
MODULE_PARM_DESC(val, "val is this module parameter, default=100,span 0-199");
MODULE_PARM_DESC(chr, "chr is this module parameter, default=' '");
MODULE_PARM_DESC(val, "p is this module parameter, default=\"default\"");
MODULE_PARM_DESC(arr, "arr is int arr[10] array, default={0,}");

static int __init chrdev_init(void) {
    int i=0;
    printk(KERN_EMERG "hello kernel!\n");
    printk(KERN_EMERG "param = %d\n", val);
    printk(KERN_EMERG "%c\n", chr);
    printk(KERN_EMERG "%s\n", p);

    for(i=0; i<lenth; i++){
        printk(KERN_EMERG "arr[%d]=%d\n", i, arr[i]);
    }
    
    printk(KERN_EMERG "%s, %s, %d\n", __FILE__, __func__, __LINE__);
    return 0;
}

static void __exit chrdev_exit(void) {
    printk(KERN_EMERG "%s, %s, %d\n",__FILE__, __func__, __LINE__);
}

module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_LICENSE("GPL");

多个值同时传入

sudo insmod chrdev.ko val=99 chr=67 p="pypyn.com_blog" arr=14,3,2,1,0
# 参数名 = 传入值
# 字符传传入的适合注意空格,默认不识别空格,比如"hello world"(非法值),将空格用下划线代替
# 数组=1,2,3
image-20200405223442072

驱动模块传参示例4:字符数组

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

//定义默认值
char str[64]={0,};

//接收传参
module_param_string(str, str, 64-1, 0664);

//传参描述
MODULE_PARM_DESC(string, "str is this string array, default=NULL");

static int __init chrdev_init(void) {
    int i=0;
    printk(KERN_EMERG "%s\n", str);    
    printk(KERN_EMERG "%s, %s, %d\n", __FILE__, __func__, __LINE__);
    return 0;
}

static void __exit chrdev_exit(void) {
    printk(KERN_EMERG "%s, %s, %d\n",__FILE__, __func__, __LINE__);
}

module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_LICENSE("GPL");

多个值同时传入

sudo insmod chrdev.ko str=xiaoyang
# 参数名 = 传入字符(不需要双引号,不能用空格)
image-20200405225517903

导出符号表

内存地址表


1G0x4000 0000
2G0x8000 0000
3G0xC000 0000
4G0xFFFF FFFF
image-20200406152224552

【注意】:符号表中的地址不是内存地址。而是编译器生成的一个地址,内核可以通过这个地址找到导出 的变量或者函数入口。

【作用】:可以调用别人写的函数,简化开发和节省代码量。

1、导出:在A模块的C源码中添加


EXPORT_SYMBOL_GPL(function_name); /* 可导出函数,变量*/

编译这个驱动模块,导出的符号表在 Module.symvers 文件中

2、添加:在B模块中添加从别的模块导出的内容


/* 在源码中声名导入的函数名或者变量 */
extern int function_name(int arg...);
extern int dat;

拷贝A模块中的 Module.symvers 到B模块的根目录,执行make

3、注意事项


1、Module.symvers 符号表文件在执行make clean 之后将会被删除。所以需要注意如果执行了clean就需要将符号表文件重新复制。

2、安装和卸载模块的时候需要先安装导出符号表的模块,再安装导入符号表的模块。卸载模块的时候需要先卸载导入符号表的模块,再卸载导出符号表的模块。

4、导出内核中的函数的符号表


1、在内核源码或已经存在内核驱动源码中添加

EXPORT_SYMBOL(function_name); /* 不带GPL会将函数入口地址放到另外一个段中(内存地址)*/

2、重新编译内核后,导出的符号表将会保存在内核顶层目录下的Module.symvers 文件中。在B模块中添加后编译就不需要再把符号表文件拷贝过来了。因为我们编译内核的Makefile就是依赖内核源码顶层目录下的Makefile,而内核导出的符号表文件也在内核源码顶层目录下。所以编译的时候能直接找到,不需要手动复制。

发表评论

您的电子邮箱地址不会被公开。