内核驱动原理
首先从一张图认识一下内核驱动,有没有很熟悉的5大Linux模块(进程管理、内存管理、文件系统、设备管理、网络管理),而今天需要了解的内核驱动就在其中……
1、用户态的应用程序调用 C库的 open、read、write、close等接口API函数
2、通过system_call陷入内核态调用虚拟文件系统VFS的system_open、system_read、system_write、system_close等接口
3、通过设备号从驱动链上查找指定的驱动
4、执行指定驱动定义的file_operations中对应的API接口
5、通过API发送具体的指令到硬件设备
内核驱动与裸机驱动
有内核开发 | 裸机开发 | |
---|---|---|
功能 | 进程管理、内存管理、文件管理、驱动管理 网络管理 | 无 |
开发模式 | 通过驱动程序+应用程序的模式操作硬件完成任务 | 烧入什么程序,只能跑这一个程序,直接通过烧入程序控制硬件 |
安全性 | 应用程序不直接操纵硬件更安全 | 应用程序直接访问硬件,不安全 |
开发难度 | 复杂 | 简单 |
运行方式
1)将驱动编译进内核中,当Linux内核启动时会自动运行驱动程序
将xxx.ko文件放到/lib/modules/‘unamer’/extra/目录下,
运行depmod -a命令生成新的modules.alias、modules.dep等文件
修改 etc/modules文件,添加xxx模块名
2)将驱动编译成模块,在内核启动后使用insmod或modprobe命令加载驱动模块
加载模块 insmod xxx.ko
卸载模块 rmmod xxx
查看模块 lsmod |grep xxx
查看信息 modinfo xxx
linux内核与用户数据交互
- procfs(进程文件系统)
1)占用少量的内存,挂载在 /proc目录下,为用户态和内核态之间的的交互搭建了一个桥梁
2)用户态读写 /proc下的文件,就是读写内核相关的配置参数。
3)当应用程序读取某个 /proc 文件时,内核才会去注册这个文件,然后再调用一组内核函数来处理,将相应的内核参数拷贝到用户态空间,这样用户读这个文件就可以获取到内核的信息。
//创建节点
proc_create(const char *name, mode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops);
proc_create_data(const char *name, mode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops, void *data);
//删除节点:
remove_proc_entry(const char *name, struct proc_dir_entry *parent);
- sysfs (基于内核的文件系统)
1)作用是将内核信息以文件的方式提供给用户程序使用
2)sysfs 不仅可以从内核空间读取设备和驱动程序的信息,也可以对设备和驱动进行配置
3)sysfs 有两组接口,一组针对内核,用干将设备映射到文件系统中,另一组针对用户程序,用于读取或操作这些设备
4)创建一个最简单的sysfs文件至少需要创建一个kobject结构(内核对象)和sysfs文件属性(对象属性)
内核层次 | 用户层次 |
---|---|
内核对象(kobject) | 目录 |
对象属性(attribute) | 文件 |
对象关系(relationship) | 链接(Symbolic Link) |
//动态创建一个kobject结构并注册到sysfs
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
//创建sysfs文件
int sysfs_create_group(struct kobject *kobject, const struct attribute_group *group);
- udev (与用户态相关的驱动设备管理机制)
1)当底层设备发生插拔的时候,底层驱动通过netlink发送事件(uevent)给udev后台程序
2)udev的守护进程侦听来自内核的uevent,并在上层做相应的设备节点创建、命名、权限控制,即添加或删除/dev下的设备文件
3)udev必须要有sysfs的支持
sysfs是建立在内核基础上的文件系统,建立在内核对象kobject的基础上,把链接在系统上的设备和总线组织成一个分级的文件,可以由用户空间获取,也可以向用户空间导出内核数据结构以及它们的属性。
过程描述
1)在内核空间,当系统启动加载驱动或设备发生热插拔的时候,驱动自身需要做相应的硬件探测方面的工作
2)探测到设备后,会去加载相应的设备驱动
3)在sysfs下创建添加内核对象,会调用到kobject_add()来完成该内核对象的添加注册
4)调用kobject_uevent()来通知系统该对象已经添加进来了。
kobject_uevent()函数是uevent的关键函数,它将通过netlink socket把对象相应的信息,属性等发给上层用户空间
通过配置文件/etc/udev/udev.conf查找规则文件目录/etc/udev/rules.d/下的.rules文件
学习思路
模块类型 | 区别 |
---|---|
字符设备驱动 | 传输字符 |
块设备驱动 | 传输块 |
网络设备驱动 | 根据socket |
杂项设备驱动 |
linux驱动模块大概有30-50个,但是常用的就10来种,搞懂常用的几个,项目基本够用。
$ cat /lib/modules/$(uname -r)/modules.builtin #查看linux内置驱动模块
模块 | 模块名 | 作用 |
---|---|---|
gpio | gpio.ko | |
UART | ||
I2C | ||
SPI | ||
USB | ||
LCD | ||
WIFI/bluetooth | ||
Ethernet | ||
Power |
大多数驱动设备都用了2个以上的驱动模块,比如摄像头就用了MIPI+I2C两种驱动,4/5G 用了USB+虚拟串口驱动.
嵌入式Makefile模板
CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET ?= bsp
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
INCDIRS := imx6ul \
bsp/clk \
bsp/led \
bsp/delay
SRCDIRS := project \
bsp/clk \
bsp/led \
bsp/delay
INCLUDE := $(patsubst %, -I %, $(INCDIRS))
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
SFILENDIR := $(notdir $(SFILES))
CFILENDIR := $(notdir $(CFILES))
SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
OBJS := $(SOBJS) $(COBJS)
VPATH := $(SRCDIRS)
.PHONY: clean
$(TARGET).bin : $(OBJS)
$(LD) -Timx6ul.lds -o $(TARGET).elf $^
$(OBJCOPY) -O binary -S $(TARGET).elf $@
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
$(SOBJS) : obj/%.o : %.S
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
$(COBJS) : obj/%.o : %.c
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
clean:
rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)