内核模块

1.为什么使用内核模块

宏内核:简单来说,就是把很多东西都集成进内核,例如linux内核,除了最基本的进程、线程管理、内存管理外,文件系统,驱动,网络协议等等都在内核里面。优点是效率高。缺点是稳定性差,开发过程中的bug经常会导致整个系统挂掉。做驱动开发的应该经常有按电源键强行关机的经历。

微内核:内核中只有最基本的调度、内存管理。驱动、文件系统等都是用户态的守护进程去实现的。优点是超级稳定,驱动等的错误只会导致相应进程死掉,不会导致整个系统都崩溃,做驱动开发时,发现错误,只需要kill掉进程,修正后重启进程就行了,比较方便。缺点是效率低。

Linux是一个宏内核,也就是说,Linux内核运行在单独的内核地址空间。不过,Linux汲取了微内核的精华:其引以为豪的是模块化设计、抢占式内核、支持内核线程以及动态装载内核模块的能力。不仅如此,Linux还避免其微内核设计上性能损失的缺陷,让所有事情都运行在内核态,直接调用函数,无需消息传递。至今,Linux是模块化的、多线程的以及内核本身可调度的操作系统,实用主义再次占了上风。

同时内核模块的这一特点也有助于减小内核镜像文件的体积,自然也就减少了内核所占的内存空间(因为整个内核镜像将会被加载到内存中运行)。不必把所有的驱动都编译内核,而是以模块的形式单独编译驱动程序,这是基于不是所有的驱动都会同时工作原理。因为不是所有的硬件都要同时接入系统,比如一个USB无线网卡。讨论完内核模块的这些特性后,我们正式开始编写模块程序。

在这里插入图片描述

2.内核模块是什么?

驱动程序在内核中,都是独立的模块,例如led驱动、蜂鸣器驱动,它们驱动之间没有相互的联系,可以通过应用程序将两个驱动联系在一起,例如以下的代码,led驱动和蜂鸣器驱动各自都是一个独立的模块(module)。

int main(int argc, char **argv)
{
    fd_led  = open(/dev/led_drv”,O_WRONLY);
    fd_beep = open(/dev/beep_drv”,O_WRONLY);

    .......
}

内核模块编译成功之后,会输出*.ko(kernel object)文件。

加载内核模块到内核:insmod led_drv.ko
从内核卸载内核模块:rmmod led_drv
若查看当前的内核模块是否加载成功,使用lsmod命令。

注意:
驱动是安装在内存中正在运行的内核上。
在这里插入图片描述

3.设计一个简单的内核模块

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

//入口函数
static int __init imx6ull_led_init(void)
{
	printk("imx6ull led init\n");

	return 0;
}


//出口函数
static void __exit imx6ull_led_exit(void)
{
	printk("imx6ull led exit\n");
}

//驱动程序的入口:insmod led_drv.ko调用module_init,module_init又会去调用 imx6ull_led_init。
module_init( imx6ull_led_init);

//驱动程序的出口:rmmod led_drv调用module_exit,module_exit又会去调用imx6ull_led_exit。
module_exit(imx6ull_led_exit)


//模块描述
MODULE_AUTHOR("xx@163.com");		//作者信息
MODULE_DESCRIPTION("imx6ull_led_exit led driver");		//模块功能说明
MODULE_LICENSE("GPL");				//许可证:驱动遵循GPL协议

linux内核是节约内存操作系统的典范,任何可能节约下来的内存操作都不会放过它。关于__init的初始化函数,往往只是被调用一次,在调用完成后,该函数不应该再被调用。所以它占用的内存必须得释放,只要该函数加上__init关键字就能够达到这个目的。

__exit也是用于修饰清除退出函数,和__init的效果也是一样,被调用之后,就会释放内存。

4.内核模块与应用程序区别

  • 内核模块有入口也有出口,入口是由module_init指定,出口是由module_exit指定。应用程序只有入口,该入口为main函数。
  • 内核模块运行在内核空间,因此得到很高的权限,但是稍有不当,直接导致内核崩溃。应用程序运行在用户空间,权限是比较低,不能够直接访问硬件,但是应用程序崩溃只影响到自己,不会影响到内核。
  • 内核模块只能使用内核源码提供的函数(路径:内核源码/include),不能使用标准C的函数,例如只能使用printk来打印信息,不能使用printf来打印。
  • 内核模块是一个独立的模块,各个模块之间没有直接的联系,符合软件工程的设计——高内聚低耦合。
  • 编译的时候,使用Makefile,因为要指定内核源码路径、交叉编译器路径、内核模块路径等信息。
  • 内核模块指定源码的版本(IMX6ULL内核源码)必须跟目标平台的内核版本(例如IMX6ULL开发板)必须一致,否则无法加载该驱动。

如何去查看目标平台的内核版本

root@ATK-IMX6U:~# uname -r
4.1.15-gb8ddbbc

  • 虽然printk和printf函数非常相似,但是通常printk函数不支持浮点数,虽然能够编译成功,但是最终运行却得不到我们想要的结果。
static int __init imx6ull_led_init(void)
{
	float pi=3.14;
	float a=2.14;
	printk("imx6ull led init,%f\n",pi/a);

	return 0;
}
[root@ATK-IMX6U]#insmod led_drv.ko
[ 2731.460000] imx6ull led init,%f
  • 整个内核空间的调用链上只有4KB或8KB的栈,相对于应用程序来说是非常小,如果需要大内存的空间,需要使用专门的函数进行动态分配——kmalloc、zmalloc、vmalloc

5.Makefile

1.关于Makefile的编写,可以阅读Documentation/kbuild/modules.txt,包含很多编译内核模块的操作步骤:

......................
--- 2.1 Command Syntax
	The command to build an external module is:
		$ make -C <path_to_kernel_src> M=$PWD
......................

2.内容如下:

KERNELDIR := /home/gec/IMX6ULL
CURRENT_PATH := $(shell pwd)

obj-m :=led_drv.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
obj-m+=led_drv.o

make的第一阶段将源程序编译为目标文件led_drv.o
make的第二阶段将led_drv.o编译成一个模块,即led_drv.ko。

KERNELDIR :=/home/gec/IMX6ULL

内核源码路径:查找编译所需的头文件、函数原型、Makefile…

交叉编译工具,内核使用什么编译器版本进行编译,内核模块最好也是跟内核使用相同的编译工具(这里已提前设置,就不需要再指定)

PWD:=$(shell pwd)

当前内核模块源码路径

$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

使用make命令的时候,传递多个参数,并调用内核源码下的Makefie文件,使用该Makefile文件中的工具,将led_drv.o文件编译为一个内核模块led_drv.ko。

$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

相当于:rm *.o .order ..cmd *.mod.c *.symvers .tmp_versions -rf
删除过程文件及其他文件。

注:+=,?=, := 区别
= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值

3.编译:

gec@ubuntu:~/1_chrdevbase$ make
make -C /home/gec/IMX6ULL M=/home/gec/1_chrdevbase modules
make[1]: Entering directory '/home/gec/IMX6ULL'
  CC [M]  /home/gec/1_chrdevbase/chrdevbase.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/gec/1_chrdevbase/chrdevbase.mod.o
  LD [M]  /home/gec/1_chrdevbase/chrdevbase.ko
make[1]: Leaving directory '/home/gec/IMX6ULL'

4. 查看模块信息
使用modinfo命令,可以查看到模块详细信息。

root@ATK-IMX6U:/mnt# modinfo led.ko
filename:       /mnt/led.ko
author:         zuozhongkai
license:        GPL
srcversion:     597E1DDC8A372707B8FD0DE
depends:
vermagic:       4.1.15 SMP preempt mod_unload modversions ARMv7 p2v8

注意:
通过modinfo命令也能够知道内核模块的内核源码版本——4.1.15,可以发现跟IMX6ULL开发板运行的内核版本是一致,只要是一致,该内核才能进行加载。

5. 演示操作

加载内核模块

root@ATK-IMX6U:/mnt# insmod led.ko

使用lsmod命令显示当前内核已经加载的内核模块,该命令等同于cat /proc/modules操作

root@ATK-IMX6U:/mnt# lsmod
Module                  Size  Used by
led                     1791  0
mx6s_capture           14876  0
ov5640_camera          19965  0

root@ATK-IMX6U:/mnt# cat /proc/modules
led 1791 0 - Live 0x7f011000 (O)
mx6s_capture 14876 0 - Live 0x7f009000
ov5640_camera 19965 0 - Live 0x7f000000

关键内容
第一列:模块的名称,led
第二列:模块占用内存的大小,1791
第三列:表示依赖该模块的个数,当前为0;若有一个程序调用会自加1;若有一个模块依赖该模块,也会自加1。
第四列:模块的运行状态,live。
第五列:模块的加载地址,0x7f011000

5.卸载的内核模块

root@ATK-IMX6U:/mnt# rmmod led.ko

注意:
如果加载内核模块或者卸载内核模块没有打印信息,有可能是printk函数的优先级是低于控制台的优先级无法进行打印,这个时候可以使用dmesg来检查打印信息,该打印信息是直接输出到日志文件。

6.printk函数

1.在驱动开发当中,我们不能使用printf函数,只能使用printk函数,使用方法会跟printf函数相像,但是也有点不同。

参考内核的一些驱动源码

#include <linux/kernel.h>

使用方法:

printk(KERN_INFO "## delay: %d\n", readl(base_addr + S3C_ADCDLY));

printk(KERN_WARNING" %d is already reserved for TouchScreen\n",
				adc_port);
printk(KERN_ERR "cannot register miscdev on minor=%d (%d)\n",
			ADC_MINOR, ret);

2.printk函数打印优先级的宏定义,在<linux/printk.h>当中能够找到

#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	

中文版:

#define KERN_EMERG              "<0>"    /* 致命级:紧急事件消息,系统崩溃之前提示,表示系统不可用   */
#define KERN_ALERT              "<1>"    /* 警戒级:报告消息,表示必须采取措施                 */
#define KERN_CRIT               "<2>"    /* 临界级:临界条件,通常涉及严重的硬件或软件操作失败   */
#define KERN_ERR                "<3>"    /* 错误级:错误条件,驱动程序常用KERN_ERR来报告硬件错误 */
#define KERN_WARNING            "<4>"    /* 警告级:警告条件,对可能出现问题的情况进行警告   */
#define KERN_NOTICE             "<5>"    /* 注意级:正常但又重要的条件,用于提醒             */
#define KERN_INFO               "<6>"    /* 通知级:提示信息,如驱动程序启动时,打印硬件信息   */
#define KERN_DEBUG              "<7>"    /* 调试级:调试级别的信息 

3.查看printk的打印优先级

root@ATK-IMX6U:/mnt# cat /proc/sys/kernel/printk
7       4       1       7

该printk文件总共有4个值。

7:控制台打印级别,默认的消息日志级别优先级高于该值,消息就能够被打印到控制台。
4:默认的消息日志级别,例如printk(“imx6ull led drvier init\n”); == printk("<4>"“imx6ull led drvier init\n”);
1:最低的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级)
7:缺省的控制台日志级别:控制台日志级别的缺省值

4.修改printk的打印优先级

1)第一种方法:直接修改printk文件

root@ATK-IMX6U:/mnt# echo 7 3 1 7 > /proc/sys/kernel/printk
root@ATK-IMX6U:/mnt# cat /proc/sys/kernel/printk
7       3       1       7

2)第二种方法:在printk函数添加优先级

printk("<3>"“imx6ull led drvier init\n”);

也可以写为
printk(KERN_ERR"imx6ull led drvier init\n");

7.内核模块参数

1.概述

比如编写一个串口驱动,想要在串口驱动加载的时候,波特率能够由命令行参数设定,就像运行普通的应用程序的时候,通过命令行参数来传递信息一样,应用程序示例如下:

#./udp 192.168.1.100
int main(int argc,char **argv)
{
    argv[0]
    argv[1]
    ............
}

模块参数允许用户在加载模块的时候,通过命令行指令参数值,内核支持的参数:bool、invbool(反转bool值)、charp(字符串指针)、short、int、long、ushort、uint、ulong类型,这些类型可以对应于整型、数组、字符串。

2.module_param与module_param_array宏定义的使用

module_param(name,type,perm)
module_param_array(name,type,nump,perm)

name:变量的名字
type:变量或数组元素的类型
nump:保存数组元素个数的指针,可选。默认写NULL。
perm:在sysfs文件系统中对应的文件的权限属性,决定哪些用户能够传递哪些参数,如果该用户权限过低,则无法通过命令行传递参数给该内核模块。

3.示例源码

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

static int baud = 9600;       //baud变量
static int port[4]={0,1,2,3}; //port数组
static int port_cnt=0;        //记录module_param_array传递数组的长度
static char *name="vcom";     //name变量

//通过以下宏定义来接收命令行的参数
module_param(baud,int,0644); 			//rw- r-- r--
module_param_array(port,int,&port_cnt,0644);	//rw- r-- r--
module_param(name,charp,0644);			//rw- r-- r--

//入口函数
static int __init imx6ull_led_init(void)
{

	printk("imx6ull led init\n");
	
	printk("baud=%d\n",baud);
	printk("port=%d %d %d %d ,port_cnt=%d\n",port[0],port[1],port[2],port[3],port_cnt);
	printk("name=%s\n",name);

	return 0;
}


//出口函数
static void __exit imx6ull_led_exit(void)
{
	printk("imx6ull led exit\n");
}

//驱动程序的入口:insmod led_drv.ko调用module_init,module_init又会去调用gec6818_led_init。
module_init(imx6ull_led_init);

//驱动程序的出口:rmmod led_drv调用module_exit,module_exit又会去调用gec6818_led_exit。
module_exit(imx6ull_led_exit)


//模块描述
MODULE_AUTHOR("stephenwen88@163.com");		//作者信息
MODULE_DESCRIPTION("imx6ull led driver");		//模块功能说明
MODULE_LICENSE("GPL");				//许可证:驱动遵循GPL协议

演示效果:
1)加载内核模块的时候,填写相应的参数

root@ATK-IMX6U:/mnt# insmod led.ko baud=115200 port=1,2,3,4 name="tcom"
[ 3201.074680] imx6ull led init
[ 3201.077594] baud=115200
[ 3201.080048] port=1 2 3 4 ,port_cnt=4
[ 3201.087332] name=tcom

2)加载内核模块后,能够在/sys/module/led/parameters/目录下看到对参数的访问权限。

root@ATK-IMX6U:/mnt# ls -l /sys/module/led/parameters/
total 0
-rw-r--r-- 1 root root 4096 Aug 14 13:38 baud
-rw-r--r-- 1 root root 4096 Aug 14 13:38 name
-rw-r--r-- 1 root root 4096 Aug 14 13:38 port
root@ATK-IMX6U:/mnt#

8.编译多个内核模块

在Makefile添加对应的语句就能能够实现多个模块的编译,例如当前demo包含了led_drv.c和sum.c文件,对应的Makefile修改如下:

KERNELDIR := /home/gec/IMX6ULL
CURRENT_PATH := $(shell pwd)

//注意是+=   不是:=
obj-m +=led_drv.o
obj-m +=sum.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

9.内核符号表——全局共享函数接口与变量

1.内核符号表
内核符号表是记录了内核中所有的符号(函数、全局变量)的地址及名字,这个符号表被嵌入到内核镜像中,使得内核可以在运行过程中随时获得一个符号地址对应的符号名。

注:
符号:指的是函数、全局变量的地址及名字。
在这里插入图片描述
2.用到的宏定义

EXPORT_SYMBOL(符号名):导出的符号可以给其他模块使用。
EXPORT_SYMBOL_GPL(符号名):导出的符号只能让符合GPL协议的模块才能使用。

3.示例代码
sum.c重点代码

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

static unsigned int g_count=0x100000;

static int myadd(int a,int b)
{
	
	return (a+b);
	
}


EXPORT_SYMBOL_GPL(myadd);
EXPORT_SYMBOL_GPL(g_count);

.............

led.drv.c重点代码

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

extern int myadd(int a,int b);
extern unsigned int g_count;

//入口函数
static int __init imx6ull_led_init(void)
{

	printk("imx6ull led init\n");
	
	printk("myadd(10+110)=%d",myadd(10,110));

	return 0;
}
.........

加载与卸载模块

加载模块的时候要分先后顺序,由于led_drv.ko依赖于sum.ko文件,因此先加载sum.ko文件,再加载led_drv.ko。

[root@GEC6818 /]#insmod sum.ko
[ 1109.544000] sum init

[root@GEC6818 /]#insmod led_drv.ko
[ 1116.872000] myadd(10+110)=120
[ 1117.022000] gec6818 led init

加载成功后,能够在内核符号表找到myadd函数,说明了该函数已经嵌入了内核符号表。

[root@GEC6818 /]#grep -r myadd /proc/kallsyms 
bf018000 t myadd        [sum]

卸载模块的时候要分先后顺序,由于led_drv.ko依赖于sum.ko文件,因此先卸载led_drv.ko文件,再卸载sum.ko。

[root@GEC6818 /]#rmmod led_drv
gec6818 led exit

[root@GEC6818 /]#rmmod sum
[ 1212.821000] sum exit

两个模块存在依赖关系,sum内核模块依赖led_drv,显示被1个内核模块依赖,显示如下

[root@GEC6818 /]#lsmod
led_drv 793 0 - Live 0xbf03c000 (O)
sum 926 1 led_drv, Live 0xbf034000 (O)

5.错误整理
1)如果在sum.c中删除”EXPORT_SYMBOL_GPL(myadd)”该代码,然后led_drv.c直接调用myadd函数,编译错误如下:

make ARCH=arm CROSS_COMPILE=/home/gec/6818GEC/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi- -C /home/gec/6818GEC/kernel M=/mnt/hgfs/share/driver/module/demo4 modules
make[1]: Entering directory '/home/gec/6818GEC/kernel'
  CC [M]  /mnt/hgfs/share/driver/module/demo4/led_drv.o
  CC [M]  /mnt/hgfs/share/driver/module/demo4/sum.o
  Building modules, stage 2.
  MODPOST 2 modules
WARNING: "myadd" [/mnt/hgfs/share/driver/module/demo4/led_drv.ko] undefined!
  CC      /mnt/hgfs/share/driver/module/demo4/led_drv.mod.o
  LD [M]  /mnt/hgfs/share/driver/module/demo4/led_drv.ko
  CC      /mnt/hgfs/share/driver/module/demo4/sum.mod.o
  LD [M]  /mnt/hgfs/share/driver/module/demo4/sum.ko
make[1]: Leaving directory '/home/gec/6818GEC/kernel'

2)在加载驱动的时候,先加载led_drv.ko,会出现以下错误,原因在于led_drv.ko依赖sum.ko的符号,所以无法加载成功,只能先加载sum.ko,再加载led_drv.ko。

[ 1219.032000] led_drv: Unknown symbol myadd (err 0)
insmod: can't insert 'led_drv.ko': unknown symbol in module or invalid parameter

3)在卸载驱动的时候,先卸载sum.ko,会出现以下错误,因为led_drv.ko引用了sum.ko提供的符号,所以无法被卸载成功,只能先卸载led_drv.ko,再卸载sum.ko。

[root@GEC6818 /]#rmmod sum
rmmod: remove 'sum': Resource temporarily unavailable

4)如果当前内核模块没有添加许可证,那么无论是编译还是加载驱动的时候,都会有警告信息。

警告信息如下:

root@ubuntu:/mnt/hgfs/share/driver/module/demo1# make
make ARCH=arm CROSS_COMPILE=/home/gec/6818GEC/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi- -C /home/gec/6818GEC/kernel M=/mnt/hgfs/share/driver/module/demo1 modules
make[1]: Entering directory '/home/gec/6818GEC/kernel'
  CC [M]  /mnt/hgfs/share/driver/module/demo1/led_drv.o
  Building modules, stage 2.
  MODPOST 1 modules
WARNING: modpost: missing MODULE_LICENSE() in /mnt/hgfs/share/driver/module/demo1/led_drv.o
see include/linux/module.h for more information
  CC      /mnt/hgfs/share/driver/module/demo1/led_drv.mod.o
  LD [M]  /mnt/hgfs/share/driver/module/demo1/led_drv.ko
make[1]: Leaving directory '/home/gec/6818GEC/kernel'

加载内核模块的警告信息:

[root@GEC6818 /]#insmod led_drv.ko
[   87.372000] led_drv: module license 'unspecified' taints kernel.   //模块许可证“未指定”污染内核。
[   87.372000] Disabling lock debugging due to kernel taint           //由于内核污染而禁用调试
[   87.379000] gec6818 led init

10.多个源文件组合为内核模块

有些时候不想通过符号表全局共享函数与全局变量,可以使用以下方法实现多个文件编译,实现局部访问。

led.c关键代码

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

extern int myadd(int a,int b);
extern unsigned int g_count;

//入口函数
static int __init gec6818_led_init(void)
{

	printk("gec6818 led init\n");
	printk("gec6818 led init\n");	
	
	printk("myadd(10+110)=%d",myadd(10,110));
	printk("g_count=%d",g_count);
	
	return 0;
}


//模块描述
MODULE_AUTHOR("stephenwen88@163.com");		//作者信息
MODULE_DESCRIPTION("gec6818 led driver");		//模块功能说明
MODULE_LICENSE("GPL");				//许可证:驱动遵循GPL协议

sum.c关键代码

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

unsigned int g_count=0x100000;

int myadd(int a,int b)
{
	return (a+b);
}

Makefile

在这个Makefile中,obj-m说明最终生成的内核模块目标文件是led_drv.ko,并且它必须依赖两个文件led.c和sum.c。因此,通过模块名加-objs的形式可以定义整个模块所包含的文件。
注意,这里定义的都是与原文件同名的目标文件,详细如下:

obj-m +=led_drv.o
led_drv-objs=led.o sum.o

KERNEL_DIR :=/home/gec/6818GEC/kernel
CROSS_COMPILE :=/home/gec/6818GEC/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi-
PWD:=$(shell pwd)

default:
	$(MAKE) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNEL_DIR) M=$(PWD) modules

clean:
	rm  *.o *.order .*.cmd  *.mod.c *.symvers .tmp_versions -rf

编译输出

gec@ubuntu:/mnt/hgfs/share/module/demo5$ make
make ARCH=arm CROSS_COMPILE=/home/gec/6818GEC/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi- -C /home/gec/6818GEC/kernel M=/mnt/hgfs/share/module/demo5 modules
make[1]: Entering directory '/home/gec/6818GEC/kernel'
  CC [M]  /mnt/hgfs/share/module/demo5/led.o            //编译led.c输出led.o目标文件
  CC [M]  /mnt/hgfs/share/module/demo5/sum.o            //编译sum.c输出sum.o目标文件
  LD [M]  /mnt/hgfs/share/module/demo5/led_drv.o        //将led.o sum.o链接为led_drv.o文件
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /mnt/hgfs/share/module/demo5/led_drv.mod.o
  LD [M]  /mnt/hgfs/share/module/demo5/led_drv.ko
make[1]: Leaving directory '/home/gec/6818GEC/kernel'

操作演示

[root@GEC6818 /]#insmod led.ko
[ 6840.604000] gec6818 led init
[ 6840.604000] gec6818 led init
[ 6840.604000] myadd(10+110)=120
[ 6840.604000] g_count=104857
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值