微内核、宏内核、内核模块、printk

一、微内核和宏内核

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

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

二、内核模块

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

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

加载内核模块到内核:insmod led_drv.ko

从内核卸载内核模块:rmmod led_drv

若查看当前的内核模块是否加载成功,使用命令: lsmod

驱动是安装在内存中正在运行的内核上

C语言应用程序内核模块
运行空间用户空间内核空间
入口mainmodule_init函数指定
出口-module_exit函数指定
编译gccMakefile
运行./直接运行insmod
退出exitrmmod

三、内核模块code

参考内核源码:

\kernel\drivers\watchdog\wdt.c

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

static int __init wdt_init(void)//入口函数
{
    return 0;
}
static void __exit wdt_exit(void)//出口函数
{
    
}
module_init(wdt_init);//驱动入口 insmod
module_exit(wdt_exit);//驱动出口 rmmod
MODULE_AUTHOR("Alan Cox");//作者信息
MODULE_DESCRIPTION("Driver for ISA ICS watchdog cards (WDT500/501)");//模块功能说明
MODULE_LICENSE("GPL");//许可证:驱动遵循GPL协议

__init的初始化函数,往往只是被调用一次,在调用完成后,该函数不应该再被调用。所以它占用的内存必须得释放,只要该函数加上__init关键字就能够达到这个目的。

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

在kernel\arch\um\include\shared\init.h文件中

#define __init		__section(.init.text)
#define __exit		__section(.exit.text)

这个标志符和函数声明放在一起,表示gcc编译器在编译的时候需要把这个函数放.text.init、.exit.text section中

__ection属性专门用于确定标记了该对象的存储位置。 ELF二进制格式将目标文件排列为命名部分,使用这样的属性允许程序员更精确地指定标记目标的信息将放置在目标对象中的位置

四、Makefile

参考文档:

kernel/Documentation/kbuild/makefiles.txt

kernel/Documentation/kbuild/modules.txt

	The kbuild Makefile specifies object files for vmlinux
	in the $(obj-y) lists.  These lists depend on the kernel
	configuration.

	Kbuild compiles all the $(obj-y) files.  It then calls
	"$(LD) -r" to merge these files into one built-in.o file.
	built-in.o is later linked into vmlinux by the parent Makefile.

	The order of files in $(obj-y) is significant.  Duplicates in
	the lists are allowed: the first instance will be linked into
	built-in.o and succeeding instances will be ignored.

	Link order is significant, because certain functions
	(module_init() / __initcall) will be called during boot in the
	order they appear. So keep in mind that changing the link
	order may e.g. change the order in which your SCSI
	controllers are detected, and thus your disks are renumbered.
	$(obj-m) specify object files which are built as loadable
	kernel modules.
	A module may be built from one source file or several source
	files. In the case of one source file, the kbuild makefile
	simply adds the file to $(obj-m).

obj-y:将代码编译进内核
obj-m:生成内核模块

obj-m +=led_drv.o
KERNEL_DIR :=linux/kernel
CROSS_COMPILE :=linux/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

obj-m+=led_drv.o

make的第一阶段将源程序编译为目标文件led_drv.o

make的第二阶段将led_drv.ko编译成一个模块,即led_drv.ko。

KERNEL_DIR :=linux/kernel

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

CROSS_COMPILE:=linux/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi-

交叉编译工具,内核使用4.8版本进行编译

PWD:=$(shell pwd)

当前内核模块源码路径

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

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

注意

make命令对路径是很敏感的,不能有中文或空格出现

注:+=,?=, := 区别

= 是最基本的赋值

:= 是覆盖之前的值

?= 是如果没有被赋值过就赋予等号后面的值

+= 是添加等号后面的值

五、prink

\kernel\include\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			*/

“<3>” 错误级
"<4>"警告级
"<7>"调试级

查看prink打印优先级

cat /proc/sys/kernel/printk
#7 7 1 7

该printk文件总共有4个值。

7:控制台打印级别,默认的消息日志级别优先级高于该值,消息就能够被打印到控制台。

7:默认的消息日志级别,例如printk(“hello\n”);

1:最低的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级)

7:缺省的控制台日志级别:控制台日志级别的缺省值

基本打印格式

/kernel/Documentation/printk-formats.txt

If variable is of Type,		use printk format specifier:
---------------------------------------------------------
		int			%d or %x
		unsigned int		%u or %x
		long			%ld or %lx
		unsigned long		%lu or %lx
		long long		%lld or %llx
		unsigned long long	%llu or %llx
		size_t			%zu or %zx
		ssize_t			%zd or %zx

修改printk的打印优先级

echo 7 3 1 7 > /proc/sys/kernel/printk 
cat /proc/sys/kernel/printk
#7       3       1       7
#include <linux/kernel.h>
printk("<3>""hello world\n");
printk(KERN_ERR"hello world\n");

六、内核模块参数

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

module_param与module_param_array宏定义的使用

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

name:变量的名字

type:变量或数组元素的类型

nump:保存数组元素个数的指针,可选。默认写NULL。

perm:在sysfs文件系统中对应的文件的权限属性,决定哪些用户能够传递哪些参数,如果该用户权限过低,则无法通过命令行传递参数给该内核模块。

#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 myled_init(void)
{

	printk("myled 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 myled_exit(void)
{
	printk("myled exit\n");
}

module_init(myled_init);//驱动程序的入口:insmod led_drv.ko
module_exit(myled_exit);//驱动程序的出口:rmmod led_drv

MODULE_LICENSE("GPL");				//许可证:驱动遵循GPL协议

​ make编译模块

obj-m +=led_drv.o
KERNEL_DIR :=linux/kernel
CROSS_COMPILE :=linux/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
	$(MAKE) clean
clean:
	rm  *.o *.order .*.cmd  *.mod.c *.symvers .tmp_versions -rf

​ 加载模块

insmod led_drv.ko baud=115200 port=1,2,3,4 name="tcom"

​ 查看参数

ls -l /sys/module/led_drv/parameters/
#total 0
#-rw-r--r--    1 root     root          4096 Dec  1 02:06 baud
#-rw-r--r--    1 root     root          4096 Dec  1 02:06 name
#-rw-r--r--    1 root     root          4096 Dec  1 02:06 port

七、内核符号表–全局共享函数接口与变量

1.内核符号表

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

注:

符号:指的是函数、全局变量的地址及名字。

2.用到的宏定义EXPORT_SYMBOL、EXPORT_SYMBOL_GPL

EXPORT_SYMBOL(符号名):导出的符号可以给其他模块使用。

EXPORT_SYMBOL_GPL(符号名):导出的符号只能让符合GPL协议的模块才能使用。

​ 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);
MODULE_LICENSE("GPL");				//许可证:驱动遵循GPL协议
#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 myled_init(void)
{

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

	return 0;
}
//出口函数
static void __exit myled_exit(void)
{
	printk("myled exit\n");
}

module_init(myled_init);//驱动程序的入口:insmod led_drv.ko
module_exit(myled_exit);//驱动程序的出口:rmmod led_drv
MODULE_LICENSE("GPL");				//许可证:驱动遵循GPL协议

​ 加载模块

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

grep -r myadd /proc/kallsyms 
#cd125000 t myadd        [sum]

八、多个源文件组合为内核模块

有些时候不想通过符号表全局共享函数与全局变量,可以使用以下方法实现多个文件编译,实现局部访问。
\kernel\Documentation\kbuild\makefiles.txt
在这里插入图片描述

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 :=linux/kernel
CROSS_COMPILE :=linux/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
	$(MAKE) clean
clean:
	rm  *.o *.order .*.cmd  *.mod.c *.symvers .tmp_versions -rf
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yengi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值