第5讲 Linux驱动编写之“内核模块”操作

第5讲、驱动编写之内核模块

1、内核模块的编写

2、内核模块的编译

  * 静态编译
  * 动态编译

3、模块加载和卸载命令

4、内核模块传参

知识回顾:

1.1 在内核中添加编译选项
make menuconfig		//调用了Kconfig文件
make uImage			//调用了Makefile
make modules		//调用了Makefile
1.2、文件系统

基本概念

1.3 根文件系统制作
  1. 目录结构

  2. 系统启动时需要的文件

    • init进程(PID:1)
    • bin文件(shell命令和shell)
    • lib库
    • etc下的配置文件:(inittab 、init.d/rcS、profile、fstab、passwd、group、shadow)
  3. 制作文件系统工具:BusyBox

    • bin下的所有命令
    • linuxrc进程(init进程)
    • etc下的配置文件模板
1.4 根文件系统挂载
  1. NFS挂载

    • 服务器上NFS的服务器配置
    • 网络畅通(ping)
    • 内核支持NFS client功能
    • uboot通过bootargs给内核传递参数,内核根据bootargs的参数,进行根文件系统的挂载
      root=/dev/nfs nfsroot=/home/edu/rootfs rw ip=10.9.21.100:10.9.21.50:10.9.21.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0,115200
  2. 制作根文件系统镜像包

  • 利用Ext4工具包mkfs_ext4.tar.gz

  • make_ext4fs制作ext4文件系统镜像

    tar xvf mkfs_ext4.tar.gz
    cp make_ext4fs /bin/make_ext4fs
    sudo chmod 777 $HOME/rootfs/ -R
    
  • 制作ext4镜像

    make_ext4fs -s -l 314572800 -a root -L linux gtk.img /home/edu/rootfs
    
  • 生成镜像包后,将镜像包拷贝到eMMC中,然后从eMMC中加载根文件系统。

    setenv bootargs root=/dev/mmcblk0p8 rw rootfstype=ext4 init=/linucrc lcd=wy070ml tp=gslx680
    saveenv
    

5.1、内核模块的编写

从内核源码的drivers目录中的驱动C文件中找到共同的函数:

  1. 分析模块初始化宏的调用逻辑

    module_init(X) 
    	#define module_init(x)	__initcall(x)	//在iclude/linux/init.h中申明
    		#define __initcall(fn) device_initcall(fn)
    			#define device_initcall(fn) __define_initcall("6",fn,6)
    

    __define_initcall(level,fn,id)的定义为:

    #define __define_initcall(level,fn,id)\
    	static initcall_t _initcall_##fn##id __used\
        	__attribute__((__section__(".initcall" level ".init"))) = fn
    

    所以:

    __define_initcall("6",fn,6)就等于
    static initcall_t _initcall_fn6 __used __attribute__((__section__(".initcall" 6 ".init"))) = fn
    

    结论:

    模块初始化宏module_init(fn)作用就是将fn这个函数地址,编译的时候统一放到init段中,在内核启动的时候或模块加载的时候被执行!

    同理:模块卸载函数module_exit(fn)的作用就是将fn这个函数地址,编译的时候统一放到init段中,在模块被移除的时候被调用执行。

/*linux/init.h*/
/*linux/module.h*/

//内核模块所必须的三个要素:
typedef int(*initcall_t)(void);
typedef void(*exitcall_t)(void);
MODULE_LICENSE("GPL")

  1. 分析模块声明宏的调用逻辑

可有可无的声明:

MODULE_LICENSE()一般必须得有!!!

7 /*
 98  * The following license idents are currently accepted as indicating free
 99  * software modules
100  *
101  *  "GPL"               [GNU Public License v2 or later]
102  *  "GPL v2"            [GNU Public License v2]
103  *  "GPL and additional rights" [GNU Public License v2 rights and more]
104  *  "Dual BSD/GPL"          [GNU Public License v2
105  *                   or BSD license choice]
106  *  "Dual MIT/GPL"          [GNU Public License v2
107  *                   or MIT license choice]
108  *  "Dual MPL/GPL"          [GNU Public License v2
109  *                   or Mozilla license choice]
110  *
111  * The following other idents are available
112  *
113  *  "Proprietary"           [Non free products]
114  *
115  * There are dual licensed components, but when running with Linux it is the
116  * GPL that is relevant so this is a non issue. Similarly LGPL linked with GPL
117  * is a GPL combined work.
118  *
119  * This exists for several reasons
120  * 1.   So modinfo can show license info for users wanting to vet their setup 
121  *  is free
122  * 2.   So the community can ignore bug reports including proprietary modules
123  * 3.   So vendors can do likewise based on their own policies
124  */
#define MODULE_LICENSE(_license) MODULE_INFO(license,_license)

/*Author(s),use"Name "or just"Name",formultiple
 *authors use multipleMODULE_AUTHOR()statements/lines.
 */
#define MODULE_AUTHOR(_author) MODULE_INFO(author,_author)

/* What your module does. */
#define MODULE_DESCRIPTION(_description) MODULE_INFO(description,_description)

检查其调用逻辑:

#define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)	//./module.h:92:
	#define __MODULE_INFO(tag, name, info)\
		static const char __module_cat(name,__LINE__)[]\
		__used __attribute__((section(".modinfo"), unused, aligned(1)))\
		= __stringify(tag) "=" info

结论

  作者传入的信息最后都被存入到一个数组中去了。

  1. 编写内核模块的模板:
#include 
#include

static int __init demo_init(void)
{
	printk("---%s---%s---%d---\n",__FILE__,__func__,__LINE___);
	return 0;
}


static void __exit demo_exit(void)
{
	printk("---%s---%s---%d---\n",__FILE__,__func__,__LINE___);
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

5.2 内核模块的编译

image-20220319103251548

5.2.1、静态编译
  • 定义:将驱动直接编译进内核,与内核形成一个整体运行,生命周期从内核启动开始动内核运行结束
  • **方法:**在内核源码树中编译(配置成*之后,执行make uImage生成内核镜像,模块便加入到镜像文件中去了)
5.2.2、动态编译
  1. 内部编译:

    • 定义:以模块的形式编译,在内核运行以后,通过insmod命令加载到内核里,和内核一起运行,通过rmmod卸载命令卸载模块。

    • 优点:使用灵活,适合开发过程中使用

    • 方法:内部编译:配置成’M’之后,执行make modules命令后,以模块的方式编译出来,

      第5讲 Linux驱动编写之“内核模块”操作_第1张图片

  2. 外部编译

    • 利用Makefile文件实现外部编译,具体使用方法可以查看帮助文档——kernel-XXX/Documentation/kbuild/modules.txt
    • 编辑好makefile文件后,当前目录下执行命令make之后,就可以看到.ko格式的模块文件了。
    • 而后执行insmod mod_name.ko就可以加载进内核,执行rmmod mod_name就可以卸载模块了。
    • 执行dmesg命令,可以查看模块的加载卸载情况。
  • #Makefile文件
    
    # 1.首先定义内核源码路径
    #KDIR:=/lib/modules/$(shell uname-r)/build #x86架构
    KDIR:=/home/edu/SAMBA_SHARE/BK2101/Driver/03_kernel/kernel-3.4.39 #armx架构
    
    # 2. 然后定义自己定义的模块源码路径
    PWD:=$(shell pwd)
    
    # 3.添加自己模块的目标文件名
    obj-m += demo.o
    
    # 4.添加编译目标
    modules:
    	make -C $(KDIR) M=$(PWD) modules
    
    clean:
    	make -C $(KDIR) M=$(PWD) clean
    

    第5讲 Linux驱动编写之“内核模块”操作_第2张图片


    5.3、模块加载和卸载命令
    • 加载:insmodxxx.ko
    • 卸载:rmmodxxx

    5.4、内核模块传参
    • 头文件的位置:通用头文件一般位于/include/linux/下;体系结构相关的位于/arch/[arm/x86]/include/下。
    • 我们这个模块传参函数是通用的,所以在/include/linux/moduleparam.h下。
    5.4.1 基本变量的传参
    /*module_param-typesafe helperfora module/cmdline parameter
     *@name:the variable to alter,and exposed parameter name.
     *@type:the type of the parameter
     *@perm:visibility in sys fs; 在sys目录下创建文件时用的文件权限。
     *
     *@value becomes the module parameter,or(prefixed byKBUILD_MODNAMEand a
     *".")the kernel commandline parameter.Note that-is changed to _,so
     *the user can use"foo-bar=1"evenforvariable"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 thatifit
     *is writable,you may need to use kparam_block_sysfs_write()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:forconvenience many standard types are providedbut
     *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,values0/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)
    

    举例说明:

    #include 
    #include 
    
    int num=10;
    char *strp = "default";
    module_param(num, int, 0644);
    module_param(strp, charp, 0644);
    
    static int __init demo_init(void)
    {
    	printk(KERN_INFO"---num=%d---\n", num);
        printk(KERN_INFO"---strp=%s---\n", strp);
    	printk(KERN_INFO"---%s---%s---%d--\n", __FILE__, __func__, __LINE__ );
    	return 0;
    }
    
    static void __exit demo_exit(void)
    {
    	printk(KERN_INFO"---%s---%s---%d--\n", __FILE__, __func__, __LINE__ );
    }
    
    module_init(demo_init);
    module_exit(demo_exit);
    MODULE_LICENSE("GPL");
    
    insmod demo.ko num=21 strp="leon"
    

    第5讲 Linux驱动编写之“内核模块”操作_第3张图片

5.4.2 字符串变量的传参
/**
 *module_param_string - a char array parameter
 *@name:	the name of the parameter //参数名
 *@string:	the string variable //字符串变量的名字
 *@len:		the maximum length of the string,incl.terminator
 *@perm:	visibility in sys fs.
 *
 *This actually copies the string when it's set(unlike type charp).
 *@len is usually just sizeof(string).
 */
//一般我们将参数名name(用在命令行中给)和变量名string(用在代码中)都设置为一样;
//其涵义就是name从命令行中获取参数值,而后回来赋值给string
#define module_param_string(name,string,len,perm)

//insmod demo.ko name="I am a string!"
//回到代码中后,string = name;
5.4.3 数组变量的传参
/**
 *module_param_array-a parameter which is an arrayofsome type
 *@name:	the name of the array variable
 *@type:	the type,aspermodule_param()
 *@nump:	optional pointer filled in with the number written //数组元素个数
 *@perm:	visibility in sys fs
 *
 *Input and output areascomma-separated values. Commas inside values
 *don't workproperly(eg.an arrayofcharp).
 *
 *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)

//insmod demo.ko name=val1,val2,...
5.5、符号表导出

符号表文件:

image-20220319104622847

//fn为需要导出的函数名,之后其它模块便可以调用fn了
#include 

EXPORT_SYMBOL(fn);	
EXPORT_SYMBOL_GPL(fn);

注意:

  • 当模块之间相互间需要引用变量或函数的时候,需要使用符号导出,且需要按依赖关系顺序(被导出函数所在模块要先加载)加载,卸载的时候按逆顺序卸载。
  • 模块生成的符号表在当前目录下的Module.symvers文件里;
  • 若某模块需要使用刚才模块导出的符号时,需要将该符号表文件复制到模块所在目录里才能编译成功。
5.6、printk
printk(打印级别"",...);

#define KERN_EMERG "<0>"	/* system is unusable */
#define KERN_ALERT "<1>"	/* action must be taken immediately */
#define KERN_CRIT  "<2>"	/* critical conditions */
#define KERN_ERR   "<3>"	/* error conditions */
#define KERN_WARNING "<4>"	/* warning conditions */
#define KERN_NOTICE "<5>"	/* normal but significant condition */
#define KERN_INFO  "<6>"	/* informational */
#define KERN_DEBUG "<7>"	/* debug-level messages */
  • 打印级别和控制台相关:

    /proc/sys/kernel/printk文件指示了当前系统的打印级别配置,即凡是比控制台打印级别高的(数字上小的打印级别高)都可以显示,否则只能通过dmesg命令查看了:

    控制台打印级别	 默认级别	系统支持的最高打印级别	最低打印级别
    7				 7				1			 			7
    
5.7、模块操作相关命令
lsmod	-查看内核加载的模块
insmod	-加载模块
rmmod	-卸载模块
modprob 

你可能感兴趣的:(Linux3.4.2驱动开发,内核模块的编译安装等操作,Linux驱动)