1. linux设备驱动程序入门

1.1 驱动程序概念

设备驱动程序,就是驱使设备按照用户的预期进行工作的软件,它是应用程序与设备沟通的桥梁。

设备驱动程序主要负责硬件设备的参数配置、数据读写与中断处理。linux运行空间分为内核空间和用户空间。这两个控件各自运行在不同的级别,无法相互访问。

linux内核为应用层提供了系统调用接口,应用程序运行在用户态,而设备驱动程序是操作系统的一部分,运行在内核态。

应用程序要控制硬件设备,首先系统调用访问内核,内核层调用驱动程序来访问设备。
在这里插入图片描述
linux大部分驱动程序是以内核模块的形式编写的。内核模块是Linux内核向外部提供的一个接口,称为动态可加载内核模块(LKM)。

内核模块可以被单独编译,在运行时被链接到内核作为内核的一部分在内核空间运行。

1.2 驱动程序的加载方式

有两种:

  • 直接编译进linux内核,启动时就加载
  • 采用内核模块方式

1.3 编写可加载模块

linux内核模块必须包含两个接口

module_init(void_init func);		//模块初始化接口
module_exit(void_exit func);		// 模块卸载接口

加载一个内核模块的命令是insmod

#insmod modulename.ko

卸载一个内核模块的命令是rmmod

#rmmod modulename

现在来写一个最简单的内核模块

static int demo_module_init(void)
{
	printk("demo module init\n");
	return 0;
}

static int demo_module_exit(void)
{
	printk("demo module exit\n");
	return 0;
}

// module access
module_init(demo_module_init);
module_exit(demo_module_exit);
MODULE_DESCRIPTION("simple module");
MODULE_LICENSE("GPL");

模块运行在内核态,不能使用用户态的printf函数。
编写一个makefile文件如下:

AR = ar
ARCH = arm
CC = arm-none-linux-gnueabi-gcc
DEBFLAGS = -O2
obj-m	:= smodule.o
KERNELDIR ?= /root/fgj/linux-4.5.2
PWD 	:= $(shell_pwd)
modules:
		$(MAKE) -C $(KERNELDIR) M =$(PWD) LDDINC=$(PWD)/../include_modules
clean:
	rm -rf *.o *~ core.depend .*.cmd *.ko *.mod.c .tmp_ersions		

执行make后生成smodule.ko

uname -r 用来得到内核版本号。

uname -r

1.4 带参数的可加载模块

宏MODULE_PARAM(var, type, right)用于向模块传递命令行参数。

示例代码

static int itype = 0;
module_param(itype, int , 0);
static int btype = 0;
module_param(btype, int , 0);
static char ctype = 0;
module_param(ctype, int , 0);
static char *stype = 0;
module_param(stype, int , 0);
// module init
static int __init demo_module_init(void)
{
	printk("simple module init\n");
	printk("itype=%d\n",itype);
	printk("btype=%d\n",btype);
	printk("ctype=%d\n",ctype);
	printk("stype=%s\n",stype);
}

// module remove
static int __init demo_module_exit(void)
{
	printk("simple module exit\n");
}

module_init(demo_module_init);
module_exit(demo_module_exxit);

1.5 内核依赖

linux内核模块间可以相互引用一些符号(包括函数和变量),符号必须到处才能被引用。内核使用宏定义EXPORT_SYMBOL导出。

// smodule_dep.c
int fun_dep(void){...}
EXPORT_SYMBOL(fun_dep)

// smodule.c
extern int fun_dep(void)
static int __init_demo_init(void)
{
	fun_dep();
	return 0;
}

写一个makefile

CC = arm-none-linux-gnueabi-gcc
DEBFLAGS = -O2
obj-m := smodule.o smodule_dep.o
KERNELDIR ?= /root/fgj/linux-4.5.2
PWD := (shell_pwd)
modules:
	$(MAKE) -C $(KERNELDIR ) M=$(PWD) LDDNC=$9PWD)/../include_modules
clean:
	rm -rf *.o *~ core.depend *.cmd *.ko *.mod.c tmp_versions

因此先安装smodule_dep.ko才能smodule.ko。

1.6 printk等级

内核打印函数printk可以设置打印信息等级。printk打印等级设置如下:

int console_printk[4] = {
	CONSOLE_LOGLEVEL_DEFAULT,	// 控制台日志级别
	MESSAGE_LOGLEVEL_DEFAULT,	// 默认消息日志级别
	CONSOLE_LOGLEVEL_MIN,			// 最小的控制台日志级别
	CONSOLE_LOGLEVEL_DEFAULT,	// 默认控制台日志级别
}

#define console_loglevel(console_printk[0])
#define default_message_loglevel(console_printk[1])
#define mininum_console_loglevel(console_printk[2])
#define default_console_loglevel(console_printk[3])

下面是一个带等级控制的printk示例:

printk(KERN_INFO "kernel print %d\n", value);

可以通过/proc/sys/kernel/printk文件动态调整printk打印等级。

[root@]$	cat printk
7 4 1 7

这四个值就是console_printk的四个值。
现在我们来调整

[root@]$	echo 6 4 1 7>/proc/sys/lkernel/printk

1.7 设备驱动程序类别

linux包含如下三类设备驱动程序。

  1. 字符设备
  2. 块设备
  3. 网络设备

每一个字符设备或块设备在文件系统中都有一个特殊设备文件与之对应,这个文件是设备节点。

[root@]$	ls -l |more

第一个字母为c表示字符设备,为b是块设备。第4列是主设备号,第5列是次设备号。

另外字符设备与块设备也可以通过/proc/devices文件查看

[root@]$	cat devices
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐