配置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, \ ¶m_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
打印效果
驱动模块传参示例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"(非法值),将空格用下划线代替
驱动模块传参示例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
驱动模块传参示例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
# 参数名 = 传入字符(不需要双引号,不能用空格)
导出符号表
内存地址表
1G
0x4000 0000
2G
0x8000 0000
3G
0xC000 0000
4G
0xFFFF FFFF
【注意】:符号表中的地址不是内存地址。而是编译器生成的一个地址,内核可以通过这个地址找到导出 的变量或者函数入口。
【作用】:可以调用别人写的函数,简化开发和节省代码量。
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,而内核导出的符号表文件也在内核源码顶层目录下。所以编译的时候能直接找到,不需要手动复制。