D1 背景、学习方法和环境搭建
计算机系统结构
程序分类
程序按其运行环境分为:
- 裸机程序:直接运行在对应硬件上的程序
- 应用程序:只能运行在对应操作系统上的程序
计算机系统的层次结构
无操作系统的简单的两层结构
有操作系统的复杂的四层结构
什么是操作系统
狭义的操作系统:给应用程序提供运行环境的裸机程序,也被称为操作系统内核
广义的操作系统:一组软件集合,它包含:
- 最核心的一个裸机程序 ----------内核 (kernel)
- app开发常用的一些功能库(如:C语言标准函数库、线程库、C++标准类库、QT类库等等)
- 一些管理用的特殊app(如桌面、命令行、app包管理器、资源管理器、系统设置、一些常用后台服务程序)
操作系统内核的实现模式
内核:操作系统最核心的那个裸机程序,主要负责硬件资源的驱动和管理。
一个操作系统内核主要包括如下几个子模块:
- 任务管理:多任务支持、任务调度、任务间通讯
- 内存管理:物理内存管理,虚拟内存实现
- 设备驱动:各种外部设备的I/O支持
- 网络协议支持
- 文件系统支持
- 启动管理
两种典型的内核实现模式:
- 单内核(宏内核):所有子模块代码编译到一个比较大的可执行文件(镜像文件)中,各子模块代码共用同一套运行资源,各模块间的交互直接通过函数调用来进行
- 微内核:只将任务管理、内存管理、启动管理最基本的三个子模块编译到一个微型的可执行文件中,其它子模块则各自编译成独立的后台服务程序,这些服务程序与微型内核以及app间主要通过各种IPC(进程间通信(Inter-Process Communication))进行通讯
单内核特点:效率高,稳定性低,扩展性差,安全性高,典型操作系统:UNIX系列、Linux1
微内核特点:效率低,稳定性高,扩展性高,安全性低,典型操作系统:Windows,QNX
提高扩展性:使用服务进程和核心内核结合做系统的方法
微内核:服务进程需要赋予一定权限,因此安全性略差
什么是设备驱动程序
英文:Device Driver
简称:驱动(Driver)
一种添加到操作系统中的特殊程序,主要作用是协助操作系统完成应用程序与对应硬件设备之间数据传送的功能
简言之,设备驱动程序就是操作系统中“驱动”对应硬件设备使之能正常工作的代码。
一个驱动程序主要完成如下工作:
- 初始化设备,让设备做好开始工作的准备
- 读数据:将设备产生的数据传递给上层应用程序
- 写数据:将上层应用程序交付过来的数据传递给设备
- 获取设备信息:协助上层应用程序获取设备的属性、状态信息
- 设置设备信息:让上层应用程序可以决定设备的一些工作属性、模式
- 其它相关操作:如休眠、唤醒、关闭设备等
其中最核心的工作就是设备数据的输入和输出,因此计算机外部设备(外设)也被称为IO设备
环境搭建
一、开发板运行Linux需要的原料
1.1 u-boot-fs4412.bin
开机运行的第一个裸机程序被称为bootloader,主要负责:
- 加载内核可执行文件到内存运行
- 给待运行的内核准备好启动参数
- 加载二进制设备树文件到内存
- 安装系统
u-boot是一个开源的bootloader程序,u-boot-fs4412.bin由其源码编译生成,详情见《系统移植之u-boot移植》课程
1.2 uImage
Linux内核的裸机可执行文件,由Linux源码编译生成,编译过程参见本章第三节,或参见《系统移植之内核移植》课程
1.3 exynos4412-fs4412.dtb
ARM-Linux内核启动、运行过程中需要一些来自各芯片手册的编程依据,该文件专门用于记录这些依据
设备树文件有两种格式:
- .dts、.dtsi:文本形式,便于书写、修改
- .dtb:二进制形式,由.dts文件经专门工具处理后生成
1.4 rootfs.tar.xz
Linux内核运行成功后,需要运行第一个应用程序(即祖先进程)以及后续其它应用程序
而任何应用程序的运行需要各种文件的支持,如:可执行文件、库文件、配置文件、资源文件
这些文件的持久保存和按路径访问需要外存分区特定文件系统的支持
rootfs就是Linux系统根目录所在的分区,其内包含根分区下众多常用app所需的文件
根分区的制作过程请见《系统移植之根文件系统的制作》课程
rootfs.tar.xz文件是根分区打包生成的压缩文件
为了统一起见:
- 大家ubuntu版本统一为ubuntu14.04 32位版,否则有些安装步骤可能有变化
- 大家统一在ubuntu系统登录用户家目录下创建文件夹fs4412,用于存放后面安装环境所用的所有文件,过程如下:
1. cd ~
2. mkdir fs4412
3. 将uImage u-boot-fs4412.bin exynos4412-fs4412.dtb gcc-4.6.4.tar.xz mkimage rootfs.tar.xz sdfuse_q.zip linux-3.14-fordriver.tgz等8个文件传到~/fs4412目录下备用
二、交叉工具链的安装
cd ~/fs4412
sudo tar xvf gcc-4.6.4.tar.xz -C /opt
cd /opt/gcc-4.6.4/bin
pwd
#复制pwd命令的输出结果 ------ 完整的绝对路径
cd ~
vim .bashrc
#在.bashrc文件的最后一行添加:export PATH=$PATH:第6步复制的路径
#保存退出.bashrc
. .bashrc #让第9、10步的修改生效
arm加两次tab键,能看到一坨的arm-none-linux开头的显示则说明安装成功
三、tftp安装
#安装tftp-hpa tftpd-hpa:
sudo apt-get install tftp-hpa tftpd-hpa
sudo mkdir /tftpboot #创建tftp服务端共享目录
sudo chmod -R 777 /tftpboot #修改目录权限
#修改服务端配置文件---tftpd-hpa
sudo vim /etc/default/tftpd-hpa
#文件内容如下:
#RUN_DAEMON="no"
#OPTIONS="-s /tftpboot -c -p -U tftpd"
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/tftpboot"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="-l -c -s"
#运行服务端
sudo service tftpd-hpa restart
#测试
cd /tftpboot
touch xxx
vim xxx #随便输入一些内容后保存退出
cd ~
tftp 127.0.0.1
tftp>get xxx
tftp>q
cat xxx #查看xxx内容为上面输入的内容则表示安装成功,否则安装过程有问题
四、NFS安装
#nfs 安装
sudo apt-get install nfs-kernel-server
#编辑服务端配置文件----/etc/exports
sudo vim /etc/exports
#在其内添加一行,内容如下:
/opt/4412/rootfs *(rw,sync,no_root_squash,no_subtree_check)
#创建挂载点目录并修改挂载点目录的访问权限
sudo mkdir /opt/4412/rootfs -p
sudo chmod 777 /opt/4412/rootfs
#启动NFS服务端(每一次修改/etc/exports都要重启nfs)
sudo service nfs-kernel-server restart
sudo service rpcbind restart
#验证安装是否正确
#在/opt/4412/rootfs下创建一个空文件
cd /opt/4412/rootfs
touch test
sudo mount 127.0.0.1:/opt/4412/rootfs /mnt
#127.0.0.1(这是被挂目录的主机IP)
#ubuntu上NFS服务器上被挂目录的绝对路径/opt/4412/rootfs
#/mnt(挂载的目的地)
ls -l /mnt #如果有test的话就说明ok了
sudo rm /mnt/test
sudo mount /mnt #卸掉挂载的目录
五、制作SD卡启动盘
5.1 方法1:在Linux下制作
一、准备好烧录脚本
cd ~/fs4412
unzip sdfuse_q.zip
cd sdfuse_q
chmod +x *.sh
二、将SD卡插入USB读卡器,并连接到虚拟机
或者
三、烧录
cp ../u-boot-fs4412.bin .
sudo ./mkuboot.sh #烧录
#原理说明
#dd if=u-boot-fs4412.bin of=/dev/sdb seek=1
5.2 方法2:在Windows下制作
ubuntu Linux下执行以下命令制作u-boot-fs4412.img
cd ~/fs4412
mkdir win-sd
cp ./u-boot-fs4412.bin ./win-sd
cd win-sd
dd if=/dev/zero of=sector0 bs=512 count=1
cat sector0 u-boot-fs4412.bin > u-boot-fs4412.img
将u-boot-fs4412.img文件传到windows下,放到一个路径不含任何中文的目录下
windows下解压“SD卡烧写.rar”文件
解压后双击运行其中的Win32DiskImager.exe来烧写u-boot-fs4412.img到SD卡(步骤见下图)
- 将SD卡插入卡槽或者将SD卡插入读卡器后将读卡器插入USB接口
- 运行Win32DiskImager.exe,在上图1处看能不能识别出SD卡的盘符,如不能请检查连接情况
- 点击2处按钮,在出现的窗口里找到并选中u-boot-fs4412.img文件
- 点击3处Write按钮开始烧写并等待完成后安全拔出SD卡
六、串口终端设置
安装TeraTerm串口终端软件,安装过程:一路下一步
将USB转串口线插入电脑USB接口
双击运行TeraTerm选择串口后点击确定:
设置串口通讯参数:
设置字体:
验证串口连接和制作好SD卡:
-
USB转串线9针端连接开发板三个9孔母口的COM2(中间的那个)
-
开发板启动模式开关设置为下图形式
-
开发板插入电源,打开开关,观察串口终端软件界面有没有正常内容显示,没有则认真检查前面的操作
七、u-boot参数设置
串口终端软件界面下,给开发板加电,刚加电时有几秒的倒计时,在倒计时时间内,敲空格键可以进入u-boot命令行
在u-boot命令行下一次执行如下u-boot命令:
u-boot# setenv serverip 192.168.9.16
u-boot# setenv ipaddr 192.168.9.99
u-boot# setenv gatewayip 192.168.9.1
u-boot# setenv bootcmd tftp 41000000 uImage\;tftp 42000000 exynos4412-fs4412.dtb\;bootm 41000000 - 42000000
u-boot# setenv bootargs root=/dev/nfs nfsroot=192.168.9.16:/opt/4412/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=192.168.9.99
u-boot# saveenv
网线连接开发板和主机,验证双方网络是否畅通
主机侧网络设置:
先关闭虚拟ubuntu系统
开发板侧-----串口终端软件界面
u-boot# ping 192.168.9.16
#出现is alive表示网络畅通,否则检查网线连接和网络设置
八、开发板运行Linux
1. 网线连接开发板和主机
2. ubuntu下拷贝uImage、exynos4412-fs4412.dtb两个文件到/tftpboot目录下
cd ~/fs4412
cp uImage exynos4412-fs4412.dtb /tftpboot
3. rootfs.tar.xz解压到/opt/4412
sudo tar xvf rootfs.tar.xz -C /opt/4412
sudo chmod 777 /opt/4412/rootfs
4. 启动tftp服务
sudo service tftpd-hpa restart
5. 开发板加电,观察串口终端软件界面,看能不能进入Linux命令行
九、内核编译
libncurses52
sudo apt-get install libncurses5-dev #如已安装则跳过本步
cd ~/fs4412
sudo cp ./mkimage /sbin
sudo chmod 777 /sbin/mkimage
tar zxvf linux-3.14-fordriver.tgz
cd linux-3.14
make fs4412_defconfig
make uImage -j2 #有代码变更需重新生成uImage时,执行本步骤 -j2 使用两个线程编译,数字一般为处理器核心的2倍
#将在arch/arm/boot目录下生成uImage文件,拷贝uImage到/tftpboot下启动开发板可以验证uImage的正确性
make dtbs #设备树源文件被更改需重新生成dtb文件时,执行本步骤
#将在arch/arm/boot/dts目录下生成exynos4412-fs4412.dtb文件
#拷贝exynos4412-fs4412.dtb到/tftpboot下启动开发板可以验证exynos4412-fs4412.dtb的正确性
D2 内核模块上_编译方法
Linux 采用单内核设计模式,意味着设备驱动程序是内核源码的一部分,用内核模块形式组织新功能的源码
Kconfig 用来配置 make meauconfig的界面
Ubuntu 内核打印信息在指定文件中,开发板可以通过串口展示
lsmod | grop hello(模块名) 查看指定模块
rmmod hello(模块名) 删除模块名
sudo dmesg -C 清除内核的打印信息
一、向内核添加新功能
1.1 静态加载法:
即新功能源码与内核其它代码一起编译进uImage文件内
-
新功能源码与Linux内核源码在同一目录结构下
在linux-3.14/driver/char/目录下编写myhello.c,文件内容如下:
#include <linux/module.h>
#include <linux/kernel.h>
int __init myhello_init(void)
{
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("myhello is running\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
return 0;
}
void __exit myhello_exit(void)
{
printk("myhello will exit\n");
}
MODULE_LICENSE("GPL");
module_init(myhello_init);
module_exit(myhello_exit);
- 给新功能代码配置Kconfig
#进入myhello.c的同级目录
cd ~/fs4412/linux-3.14/drivers/char
vim Kconfig
#39行处添加如下内容:
config MY_HELLO
tristate "This is a hello test"
help
This is a test for kernel new function
- 改写Makefile,纳入新功能代码
#进入myhello.c的同级目录
cd ~/fs4412/linux-3.14/drivers/char
vim Makefile
#拷贝18行,粘贴在下一行,修改成:
obj-$(CONFIG_MY_HELLO) += myhello.o
- make menuconfig 界面里将新功能对应的那项选择成<*>
cd ~/fs4412/linux-3.14
make menuconfig
#make menuconfig如果出错,一般是两个原因:
#1. libncurses5-dev没安装
#2. 命令行界面太小(太矮或太窄或字体太大了)
-
make uImage
-
cp arch/arm/boot/uImage /tftpboot
-
启动开发板观察串口终端中的打印信息
1.2 动态加载法:
即新功能源码与内核其它源码不一起编译,而是独立编译成内核的插件(被称为内核模块)文件.ko,分为两种情况。
a、新功能源码与Linux内核源码在同一目录结构下时
-
给新功能代码配置Kconfig
-
改写Makefile,纳入新功能代码
-
make menuconfig 界面里将新功能对应的那项选择成
-
make uImage
-
cp arch/arm/boot/uImage /tftpboot
-
make modules
make modules会在新功能源码的同级目录下生成相应的同名.ko文件(生成的ko文件只适用于开发板linux)
注意此命令执行前,开发板的内核源码已被编译
b、新功能源码与Linux内核源码不在同一目录结构下时
- cd ~/fs4412
- mkdir mydrivercode
- cd mydrivercode
- cp …/linux-3.14/drivers/char/myhello.c .
- vim Makefile //todo 说明这个Makefile 文件时怎么来的。
- make (生成的ko文件适用于主机ubuntu linux)
- make ARCH=arm (生成的ko文件适用于开发板linux,注意此命令执行前,开发板的内核源码已被编译)
#file命令可以查看指定ko文件适用于哪种平台,用法:
file ko文件
#结果带x86字样的适用于主机ubuntu linux,带arm字样的适用于开发板linux
c、主机ubuntu下使用ko文件
sudo insmod ./???.ko #此处为内核模块文件名,将内核模块插入正在执行的内核中运行 ----- 相当于安装插件
lsmod #查看已被插入的内核模块有哪些,显示的是插入内核后的模块名
sudo rmmod ??? #,此处为插入内核后的模块名,此时将已被插入的内核模块从内核中移除 ----- 相当于卸载插件
sudo dmesg -C #清除内核已打印的信息
dmesg #查看内核的打印信息
d、开发板Linux下使用ko文件
#先将生成的ko文件拷贝到/opt/4412/rootfs目录下:
cp ????/???.ko /opt/4412/rootfs
#在串口终端界面开发板Linux命令行下执行
insmod ./???.ko #将内核模块插入正在执行的内核中运行 ----- 相当于安装插件
lsmod #查看已被插入的内核模块有哪些
rmmod ??? #将已被插入的内核模块从内核中移除 ----- 相当于卸载插件
内核随时打印信息,我们可以在串口终端界面随时看到打印信息,不需要dmesg命令查看打印信息
二、内核模块基础代码解析
Linux内核的插件机制——内核模块
类似于浏览器、eclipse这些软件的插件开发,Linux提供了一种可以向正在运行的内核中插入新的代码段、在代码段不需要继续运行时也可以从内核中移除的机制,这个可以被插入、移除的代码段被称为内核模块。
主要解决:
- 单内核扩展性差的缺点
- 减小内核镜像文件体积,一定程度上节省内存资源
- 提高开发效率
- 不能彻底解决稳定性低的缺点:内核模块代码出错可能会导致整个系统崩溃
内核模块的本质:一段隶属于内核的“动态”代码,与其它内核代码是同一个运行实体,共用同一套运行资源,只是存在形式上是独立的。
#include <linux/module.h> //包含内核编程最常用的函数声明,如printk
#include <linux/kernel.h> //包含模块编程相关的宏定义,如:MODULE_LICENSE
/*该函数在模块被插入进内核时调用,主要作用为新功能做好预备工作
被称为模块的入口函数
__init的作用 :
1. 一个宏,展开后为:__attribute__ ((__section__ (".init.text"))) 实际是gcc的一个特殊链接标记
2. 指示链接器将该函数放置在 .init.text区段
3. 在模块插入时方便内核从ko文件指定位置读取入口函数的指令到特定内存位置
*/
int __init myhello_init(void)
{
/*内核是裸机程序,不可以调用C库中printf函数来打印程序信息,
Linux内核源码自身实现了一个用法与printf差不多的函数,命名为printk (k-kernel)
printk不支持浮点数打印*/
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("myhello is running\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
return 0;
}
/*该函数在模块从内核中被移除时调用,主要作用做些init函数的反操作
被称为模块的出口函数
__exit的作用:
1.一个宏,展开后为:__attribute__ ((__section__ (".exit.text"))) 实际也是gcc的一个特殊链接标记
2.指示链接器将该函数放置在 .exit.text区段
3.在模块插入时方便内核从ko文件指定位置读取出口函数的指令到另一个特定内存位置
*/
void __exit myhello_exit(void)
{
printk("myhello will exit\n");
}
/*
MODULE_LICENSE(字符串常量);
字符串常量内容为源码的许可证协议 可以是"GPL" "GPL v2" "GPL and additional rights" "Dual BSD/GPL" "Dual MIT/GPL" "Dual MPL/GPL"等, "GPL"最常用
其本质也是一个宏,宏体也是一个特殊链接标记,指示链接器在ko文件指定位置说明本模块源码遵循的许可证
在模块插入到内核时,内核会检查新模块的许可证是不是也遵循GPL协议,如果发现不遵循GPL,则在插入模块时打印抱怨信息:
myhello:module license 'unspecified' taints kernel
Disabling lock debugging due to kernel taint
也会导致新模块没法使用一些内核其它模块提供的高级功能
*/
MODULE_LICENSE("GPL");
/*
module_init 宏
1. 用法:module_init(模块入口函数名)
2. 动态加载模块,对应函数被调用
3. 静态加载模块,内核启动过程中对应函数被调用
4. 对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.initcall段),方便系统初始化统一调用。
5. 对于动态加载的模块,由于内核模块的默认入口函数名是init_module,用该宏可以给对应模块入口函数起别名
*/
module_init(myhello_init);
/*
module_exit宏
1.用法:module_exit(模块出口函数名)
2.动态加载的模块在卸载时,对应函数被调用
3.静态加载的模块可以认为在系统退出时,对应函数被调用,实际上对应函数被忽略
4.对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.exitcall段),方便系统必要时统一调用,实际上该宏在静态加载时没有意义,因为静态编译的驱动无法卸载。
5.对于动态加载的模块,由于内核模块的默认出口函数名是cleanup_module,用该宏可以给对应模块出口函数起别名
*/
module_exit(myhello_exit);
模块三要素:入口函数 出口函数 MODULE__LICENSE
三、内核模块的多源文件编程
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= 目标板linux内核源码顶层目录的绝对路径
ROOTFS ?= 目标板根文件系统顶层目录的绝对路径
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules //M不是makefile的选项,是内核根目录下的Makefile中使用的变量。
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else
obj-m += hello.o #多源文件编程需要复制添加该行,eg:obj-m += 下一个模块名.o
endif
Makefile中:
obj-m用来指定模块名,注意模块名加.o而不是.ko
可以用 模块名-objs 变量来指定编译到ko中的所有.o文件名(每个同名的.c文件对应的.o目标文件)
一个目录下的Makefile可以编译多个模块:
添加:obj-m += 下一个模块名.o
四、 内核模块信息宏
MODULE_AUTHOR(字符串常量); //字符串常量内容为模块作者说明
MODULE_DESCRIPTION(字符串常量); //字符串常量内容为模块功能说明
MODULE_ALIAS(字符串常量); //字符串常量内容为模块别名
这些宏用来描述一些当前模块的信息,可选宏
这些宏的本质是定义static字符数组用于存放指定字符串内容,这些字符串内容链接时存放在.modinfo字段,可以用modinfo命令来查看这些模块信息,用法:
modinfo 模块文件名
D3 内核模块下_参数和依赖
一、模块传参
module_param(name,type,perm);//将指定的全局变量设置成模块参数
/*
name:全局变量名
type:
使用符号 实际类型 传参方式
bool bool insmod xxx.ko 变量名=0 或 1
invbool bool insmod xxx.ko 变量名=0 或 1
charp char * insmod xxx.ko 变量名="字符串内容"
short short insmod xxx.ko 变量名=数值
int int insmod xxx.ko 变量名=数值
long long insmod xxx.ko 变量名=数值
ushort unsigned short insmod xxx.ko 变量名=数值
uint unsigned int insmod xxx.ko 变量名=数值
ulong unsigned long insmod xxx.ko 变量名=数值
perm:给对应文件 /sys/module/name/parameters/变量名 指定操作权限,模块传参其他用户一般没有执行权限 perm 设置成0664(八进制)
#define S_IRWXU 00700
#define S_IRUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100
#define S_IRWXG 00070
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010
#define S_IRWXO 00007
#define S_IROTH 00004
#define S_IWOTH 00002 //不要用 编译出错.其他用户不能有写权限
#define S_IXOTH 00001
*/
module_param_array(name,type,&num,perm);
/*
name、type、perm同module_param,type指数组中元素的类型
&num:存放数组大小变量的地址,可以填NULL(确保传参个数不越界)
传参方式 insmod xxx.ko 数组名=元素值0,元素值1,...元素值num-1
*/
可用MODULE_PARAM_DESC宏对每个参数进行作用描述,用法(选用):
MODULE_PARM_DESC(变量名,字符串常量);
字符串常量的内容用来描述对应参数的作用
modinfo可查看这些参数的描述信息
全局变量demo
testparam.c
#include <linux/module.h>
#include <linux/kernel.h>
int gx = 10;
char *gstr = "hello";
int garr[5] = {1,2,3,4,5};
module_param(gx,int,0664);
module_param(gstr,charp,0664);
module_param_array(garr,int,NULL,0664);
int __init test_init(void)
{
int i = 0;
printk("gx=%d\n",gx);
printk("gstr=%s\n",gstr);
for(i=0;i<5;i++){
printk("%d",garr[i]);
}
printk("\n");
return 0;
}
void __exit test_exit(void)
{
printk("test will exit\n");
}
MODULE_LICENSE("GPL");
module_init(test_init);
module_exit(test_exit);
Makefile
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/fs4412/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else
obj-m += myhello.o
obj-m += xyz.o
xyz-objs += test.o func.o
obj-m += testparam.o #对应测试源码
endif
执行,跟参数则可以重新对全局变量赋值,否则输出初始值
二、模块依赖
既然内核模块的代码与其它内核代码共用统一的运行环境,也就是说模块只是存在形式上独立,运行上其实和内核其它源码是一个整体,它们隶属于同一个程序,因此一个模块或内核其它部分源码应该可以使用另一个模块的一些全局特性。
一个模块中这些可以被其它地方使用的名称被称为导出符号,所有导出符号被填在同一个表中这个表被称为符号表。
最常用的可导出全局特性为全局变量和函数
查看符号表的命令:nm
nm查看elf格式的可执行文件或目标文件中包含的符号表,用法:
nm 文件名
: 查看符号表(可以通过man nm查看一些字母含义)
B 没有初始化或初始化为0 的全局变量
D 全局变量
T 函数
R 加了constant 的全局变量
两个用于导出模块中符号名称的宏:
EXPORT_SYMBOL(函数名或全局变量名)
EXPORT_SYMBOL_GPL(函数名或全局变量名) 需要GPL许可证协议验证
使用导出符号的地方,需要对这些符号进行extern声明后才能使用这些符号
B模块使用了A模块导出的符号,此时称B模块依赖于A模块,则:
1. 编译次序:先编译模块A,再编译模块B,当两个模块源码在不同目录时,需要:
i. 先编译导出符号的模块A
ii. 拷贝A模块目录中的Module.symvers到B模块目录
iii. 编译使用符号的模块B。否则编译B模块时有符号未定义错误
2. 加载次序:先插入A模块,再插入B模块,否则B模块插入失败
3. 卸载次序:先卸载B模块,在卸载A模块,否则A模块卸载失败
补充说明:
内核符号表(直接当文本文件查看)
/proc/kallsyms运行时
/boot/System.map编译后
Ubuntu 编译出来后再在sudo 查看 /boot/ System.map-3.13.0-32-generic 是具有相对地址
的内核符号表
在/proc/kallsyms 中是没有相对地址
的符号表
开发板上Linux 内核编译出来后符号表查看方式:
linux@linux:~/fs4412/linux-3.14$ vim System.map
下面这个文件用vim 不好使,因为文件存放的是二进制裸机执行机器码
linux@linux:~/fs4412/linux-3.14$ nm vmlinux
同极目录共享全局变量demo
同级目录a文件
#include <linux/module.h>
#include <linux/kernel.h>
int gx = 19;
EXPORT_SYMBOL(gx);
int __init modulea_init(void)
{
printk("In modele_a init gx=%d\n",gx);
return 0;
}
void __exit modulea_exit(void)
{
printk("modulea will exit\n");
}
MODULE_LICENSE("GPL");
module_init(modulea_init);
module_exit(modulea_exit);
同级目录b文件
#include <linux/module.h>
#include <linux/kernel.h>
extern int gx;
int __init moduleb_init(void)
{
printk("In module_b init gx=%d\n",gx);
return 0;
}
void __exit moduleb_exit(void)
{
printk("moduleb will exit\n");
}
MODULE_LICENSE("GPL");
module_init(moduleb_init);
module_exit(moduleb_exit);
make 文件
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/fs4412/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else
obj-m += modulea.o
obj-m += moduleb.o
endif
运行模块
linux@linux:~/fs4412/linux-3.14/mydrivercode/twomodule$ make
make -C /lib/modules/3.13.0-32-generic/build M=/home/linux/fs4412/linux-3.14/mydrivercode/twomodule modules
make[1]: Entering directory `/usr/src/linux-headers-3.13.0-32-generic'
Building modules, stage 2.
MODPOST 2 modules
make[1]: Leaving directory `/usr/src/linux-headers-3.13.0-32-generic'
linux@linux:~/fs4412/linux-3.14/mydrivercode/twomodule$ sudo insmod modulea.ko
[sudo] password for linux:
linux@linux:~/fs4412/linux-3.14/mydrivercode/twomodule$ sudo insmod moduleb.ko
linux@linux:~/fs4412/linux-3.14/mydrivercode/twomodule$ dmesg
[37528.318267] In modele_a init gx=19
[37539.427462] In module_b init gx=19
非同级目录共享全局变量 todo
i. 先编译导出符号的模块A
ii. 拷贝A模块目录中的Module.symvers到B模块目录
iii. 编译使用符号的模块B。否则编译B模块时有符号未定义错误
三、内核空间和用户空间
为了彻底解决一个应用程序出错不影响系统和其它app的运行,操作系统给每个app一个独立的假想的地址空间,这个假想的地址空间被称为虚拟地址空间(也叫逻辑地址),操作系统也占用其中固定的一部分,32位Linux的虚拟地址空间大小为4G,并将其划分两部分:
-
0~3G 用户空间 :每个应用程序只能使用自己的这份虚拟地址空间
-
3G~4G 内核空间:内核使用的虚拟地址空间,应用程序不能直接使用这份地址空间,但可以通过一些系统调用函数与其中的某些空间进行数据通信
实际内存操作时,需要将虚拟地址映射到实际内存的物理地址,然后才进行实际的内存读写
四、执行流
执行流:有开始有结束总体顺序执行的一段独立代码,又被称为代码上下文3
计算机系统中的执行流的分类:
执行流:
- 任务流–任务上下文(都参与CPU时间片轮转,都有任务五状态:就绪态 运行态 睡眠态 僵死态 暂停态)
- 进程
- 线程
- 内核线程:内核创建的线程
- 应用线程:应用进程创建的线程
- 异常流–异常上下文
- 中断
- 其它异常
应用编程可能涉及到的执行流:
- 进程
- 线程
内核编程可能涉及到的执行流:
- 应用程序自身代码运行在用户空间,处于用户态 ----------------- 用户态app
- 应用程序正在调用系统调用函数,运行在内核空间,处于内核态,即代码是内核代码但处于应用执行流(即属于一个应用进程或应用线程) ---- 内核态app
- 一直运行于内核空间,处于内核态,属于内核内的任务上下文 --------- 内核线程
- 一直运行于内核空间,处于内核态,专门用来处理各种异常 --------- 异常上下文
五、模块编程与应用编程的比较
不同点 | 内核模块 | 应用程序 |
---|---|---|
API来源 | 不能使用任何库函数 | 各种库函数均可以使用 |
运行空间 | 内核空间 | 用户空间 |
运行权限 | 特权模式运行 | 非特权模式运行 |
编译方式 | 静态编译进内核镜像或编译特殊的ko文件 | elf格式的应用程序可执行文件 |
运行方式 | 模块中的函数在需要时被动调用 | 从main开始顺序执行 |
入口函数 | init_module | main |
退出方式 | cleanup_module | main函数返回或调用exit |
浮点支持 | 一般不涉及浮点运算,因此printk不支持浮点数据 | 支持浮点运算,printf可以打印浮点数据 |
并发考虑 | 需要考虑多种执行流并发的竞态情况 | 只需考虑多任务并行的竞态 |
程序出错 | 可能会导致整个系统崩溃 | 只会让自己崩溃 |
六、内核接口头文件查询
大部分API函数包含的头文件在include/linux目录下,因此:
- 首先在include/linux 查询指定函数:grep 名称 ./ -r -n
- 找不到则更大范围的include目录下查询,命令同上
D4 字符设备驱动基础上_基础框架
一、Linux内核对设备的分类
linux的文件种类:
- -:普通文件
文件内容 :
文件名
元信息:创建人、创建时间 - d:目录文件 :有内容
- p:管道文件 :外存中无文件内容,仅代表在内核中通过mkfifo创建好的通信机制
- s:本地socket文件 :没有文件内容,相当于服务器或客户端的网络编程的地址值(IP 加端口号)。
- l:链接文件 :分两种,软链接内容,指向另外一个文件或目录的路径;硬链接, 文件内容和元信息都是同一份,只是文件名是两份,相当于是别名
- c:字符设备:没有文件内容,
- b:块设备4:没有文件内容
Linux内核按驱动程序实现模型框架的不同,将设备分为三类:
- 字符设备:按字节流形式进行数据读写的设备,一般情况下按顺序访问,数据量不大,一般不设缓存。
- 块设备:机械硬盘、固态硬盘等,按整块进行数据读写的设备,最小的块大小为512字节(一个扇区),块的大小必须是扇区的整数倍,Linux系统的块大小一般为4096字节,随机访问,设缓存以提高效率,缓冲设在文件系统里,应用层序无需关心,当普通文件操作。
- 网络设备:网卡和蓝牙等,一切皆文件的特例之一,针对网络数据收发的设备。 etho 是网卡的名字 lo本地环回网卡的名字,都不是文件名。
总体框架图:
二、设备号------内核中同类设备的区分
内核用设备号来区分同类里不同的设备,设备号是一个无符号32位整数,数据类型为dev_t,设备号分为两部分:
- 主设备号:占高12位,用来表示驱动程序相同的一类设备
- 次设备号:占低20位,用来表示被操作的哪个具体设备
应用程序打开一个设备文件时,通过设备号来查找定位内核中管理的设备。
MKDEV宏用来将主设备号和次设备号组合成32位完整的设备号,用法:
dev_t devno;
int major = 251;//主设备号
int minor = 2;//次设备号
devno = MKDEV(major,minor);
MAJOR宏用来从32位设备号中分离出主设备号,用法:
dev_t devno = MKDEV(249,1);
int major = MAJOR(devno);
MINOR宏用来从32位设备号中分离出次设备号,用法:
dev_t devno = MKDEV(249,1);
int minor = MINOR(devno);
如果已知一个设备的主次设备号,应用层指定好设备文件名,那么可以用mknod命令在/dev目录创建代表这个设备的文件,即此后应用程序对此文件的操作就是对其代表的设备操作,mknod用法如下:
@ cd /dev
@ mknod 设备文件名 设备种类(c为字符设备,b为块设备) 主设备号 次设备号 //ubuntu下需加sudo执行
在应用程序中如果要创建设备可以调用系统调用函数mknod,其原型如下:
int mknod(const char *pathname,mode_t mode,dev_t dev);
//pathname:带路径的设备文件名,无路径默认为当前目录,一般都创建在/dev下
//mode:文件权限 位或 S_IFCHR/S_IFBLK
//dev:32位设备号
//返回值:成功为0,失败-1
三、申请和注销设备号
字符驱动开发的第一步是通过模块的入口函数向内核添加本设备驱动的代码框架,主要完成:
- 申请设备号
- 定义、初始化、向内核添加代表本设备的结构体元素
int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:手动分配设备号,先验证设备号是否被占用,如果没有则申请占用该设备号
参数:
from:自己指定的设备号
count:申请的设备数量
name:/proc/devices文件中与该设备对应的名字,方便用户层查询主设备号
返回值:
成功为0,失败负数,绝对值为错误码
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count, const char *name)
功能:动态分配设备号,查询内核里未被占用的设备号,如果找到则占用该设备号
参数:
dev:分配设备号成功后用来存放分配到的设备号
baseminior:起始的次设备号,一般为0
count:申请的设备数量
name:/proc/devices文件中与该设备对应的名字,方便用户层查询主次设备号
返回值:
成功为0,失败负数,绝对值为错误码
分配成功后在/proc/devices 可以查看到申请到主设备号和对应的设备名,mknod时参数可以参考查到的此设备信息
void unregister_chrdev_region(dev_t from, unsigned count)
功能:释放设备号
参数:
from:已成功分配的设备号将被释放
count:申请成功的设备数量
释放后/proc/devices文件对应的记录消失
四、函数指针复习
内存的作用-----用来存放程序运行过程中的
- 数据
- 指令
4.1、 内存四区
堆区 :存放数据,动态申请
栈区:存放数据,局部变量
数据区:存放数据,全局变量
代码区:存放指令
4.2、C语言中内存数据的访问方式
直接访问:通过所在空间名称去访问
间接访问:通过所在空间首地址去访问 *地址值 此时的*为间接访问运算符,堆区数据只能间接访问
4.3、C语言中函数调用方式:
直接调用:通过函数名去调用函数
间接调用:通过函数在代码区所对应的那份空间的首地址去调用
int func(int a,int b)
{
//......
}
int (int a,int b) * pf;//语法错误
int *pf(int a,int b);//函数声明语句
int (*pf)(int a,int b);//定义一个函数指针
pf = &func;//&运算符后面如果是函数名的话可以省略不写
pf = func;
y = func(3,4);//直接调用
y = (*pf)(3,4);//间接调用,*运算符后面如果是函数指针类型则可以省略不写
y = pf(3,4);//间接调用
typedef int myint;
typedef int (*)(int,int) pft;//语法错误
typedef int (*pft)(int,int) ;
pft pt;
4.4、适用场合
前提:当有很多个同类函数待被调用时
A处:知道所有函数名,由此处来决定B处将会调用哪个函数
B处:负责调用A处指定的函数
思考:A处如何告诉B处被调用的是哪个函数呢,无非两个办法:
- 告诉B处函数名,怎么做呢?传字符串----“函数名”? C语言没有对应语法支持
- 告诉B处对应函数在代码区的地址
五、注册字符设备
面向对象是一种编程思想(封装、继承、多态),是编程的一种思维,用C语言也可以实现,只是不如C++,C++提供了一些实现面向对象的新语法,实现起来丝滑。
编写驱动程序的核心思想:编写针对要驱动的设备的各种各样的操作函数。如初始化,读写操作
标准C库,初始化函数的时候只能传递函数指针,C99新语法按指定的成员名称进行初始化
Linux 支持语法版本是C99
struct cdev //kobject 的子类,也是内核基本对象的一种
{
struct kobject kobj;//定义该成员,表示成员所在结构体类型实体是一种内核对象;kobject 是父类,基类,内核基本对象。 一般自己编程,写字符驱动程序用不到,只是概念上要知道这个
**struct module *owner;/*填THIS_MODULE,表示该字符设备从属于哪个内核模块,自己写代码要用到*/
**const struct file_operations *ops;//指向空间存放着的针对该设备的各种操作函数地址,最重要的成员。跟整个字符驱动程序的框架有非常大的关系
struct list_head list;//链表指针域,哈希链表管理设备。
dev_t dev;//设备号
unsigned int count;//设备数量
};
自己定义的结构体中必须有一个成员为 struct cdev cdev,两种方法定义一个设备:
-
直接定义:定义结构体全局变量,用的多
-
动态申请:用的少
struct cdev * cdev_alloc()
void cdev_init(struct cdev *cdev,const struct file_operations *fops); //初始化上边结构体,需要用到struct file_operations *fops 这个对象
struct file_operations
{
struct module *owner; //填THIS_MODULE,表示该结构体对象从属于哪个内核模块
int (*open) (struct inode *, struct file *); //打开设备
int (*release) (struct inode *, struct file *); //关闭设备
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //读设备
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //写设备
loff_t (*llseek) (struct file *, loff_t, int); //定位
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备
unsigned int (*poll) (struct file *, struct poll_table_struct *); //POLL机制,实现多路复用的支持
int (*mmap) (struct file *, struct vm_area_struct *); //映射内核空间到用户层
int (*fasync) (int, struct file *, int); //信号驱动
//......
};
该对象各个函数指针成员
都对应相应的系统调用函数
,应用层通过调用系统函数来间接调用这些函数指针成员指向的设备驱动函数:
一般定义一个struct file_operations类型的全局变量并用自己实现各种操作函数名对其进行初始化
int cdev_add(struct cdev *p,dev_t dev,unsigned int count)
功能:将指定字符设备添加到内核
参数:
p:指向被添加的设备
dev:设备号
count:设备数量,一般填1
void cdev_del(struct cdev *p)
功能:从内核中移除一个字符设备
参数:
p:指向被移除的字符设备
小结:
字符设备驱动开发步骤:
如果设备有自己的一些控制数据
,则定义一个包含struct cdev cdev成员的结构体struct mydev,其它成员根据设备需求,设备简单则直接用struct cdev- 定义一个struct mydev或struct cdev的全局变量来表示本设备;也可以定义一个struct mydev或struct cdev的全局指针(记得在init时动态分配)
- 定义三个全局变量分别来表示主设备号、次设备号、设备数
- 定义一个struct file_operations结构体变量,其owner成员置成THIS_MODULE
- module init函数流程:
a. 申请设备号
b. 如果是全局设备指针则动态分配代表本设备的结构体元素
c. 初始化struct cdev成员
d. 设置struct cdev的owner成员为THIS_MODULE
e. 添加字符设备到内核 - module exit函数:
a. 注销设备号
b. 从内核中移除struct cdev
c. 如果如果是全局设备指针则释放其指向空间 - 编写各个操作函数并将函数名初始化给struct file_operations结构体变量
验证操作步骤:
- 编写驱动代码mychar.c
- make生成ko文件
- insmod内核模块
- 查阅字符设备用到的设备号(主设备号):cat /proc/devices | grep 申请设备号时用的名字
- 创建设备文件(设备节点) : mknod /dev/??? c 上一步查询到的主设备号 代码中指定初始次设备号
- 编写app验证驱动(testmychar_app.c)
- 编译运行app,dmesg命令查看内核打印信息
六、字符设备驱动框架解析
设备的操作函数如果比喻是桩的话(性质类似于设备操作函数的函数,在一些场合被称为桩函数),则:
驱动实现设备操作函数 ----------- 做桩
insmod调用的init函数主要作用 --------- 钉桩
应用层通过系统调用函数间接调用这些设备操作函数 ------- 用桩
rmmod调用的exit函数主要作用 --------- 拔桩
6.1 两个操作函数中常用的结构体说明
//内核中记录文件元信息的结构体,与外存中的文件一一对应,在内核中以链表的形式组织起来,当内核发现某一个应用程序使用外存中的某一个文件,会首先到node 链表中搜索。
struct inode
{
//....
dev_t i_rdev;//设备号。存在外部文件的元信息中
struct cdev *i_cdev;//如果是字符设备才有此成员,创建cdev 对象的时候填充,指向对应设备驱动程序中的加入系统的struct cdev对象。
//....
}
/*
1. 内核中每个该结构体对象对应着一个实际文件,一对一
2. open一个文件时如果内核中该文件对应的inode对象已存在则不再创建,不存在才创建
3. 内核中用此类型对象关联到对此文件的操作函数集(对设备而言就是关联到具体驱动代码)
*/
//读写文件内容过程中用到的一些控制性数据组合而成的对象------文件操作引擎(文件操控器)。
//用很多个数组进行管理
//open 的时候创建,每调用一次创建一个文件的引擎。还要去调用对应驱动程序中的open 函数,对设备做一些启动工作
//用该对象去操作对应文件
struct file
{
//...
mode_t f_mode;//不同用户的操作权限,驱动一般不用
loff_t f_pos;//(常用)position 数据位置指示器,需要控制数据开始读写位置的设备有用,有些文献上称此为指针,但是跟C语言中的指针完全不同
unsigned int f_flags;//(常用)open时的第二个参数flags存放在此,驱动中常用
struct file_operations *f_op;//open时从struct inode中i_cdev的对应成员获得地址,驱动开发中用来协助理解工作原理,内核中使用
void *private_data;//(常用)本次打开文件的私有数据,驱动中常来在几个操作函数间传递共用数据
struct dentry *f_dentry;//驱动中一般不用,除非需要访问对应文件的inode,用法flip->f_dentry->d_inode
int refcnt;//引用计数,保存着该对象地址的位置个数,close时发现refcnt为0才会销毁该struct file对象
//...
};
/*
1. open函数被调用成功一次,则创建一个该对象,因此可以认为一个该类型的对象对应一次指定文件的操作
2. open同一个文件多次,每次open都会创建一个该类型的对象
3. 文件描述符数组中存放的地址指向该类型的对象
4. 每个文件描述符都对应一个struct file对象的地址
*/
6.2 字符设备驱动程序框架分析
驱动实现端
驱动使用端
伪代码
syscall_open函数实现的伪代码:
int syscall_open(const char *filename,int flag)
{
dev_t devno;
struct inode *pnode = NULL;
struct cdev *pcdev = NULL;
struct file *pfile = NULL;
int fd = -1;
/*根据filename在内核中查找该文件对应的struct inode对象地址,找到则pnode指向该对象,未找到则创建新的struct inode对象,pnode指向该对 象,并从文件系统中读取文件的元信息到该对象*/
if(/*未找到对应的struct inode对象*/)
{
/*根据文件种类决定如何进行下面的操作,如果是字符设备则执行如下操作*/
/*从pnode指向对象中得到设备号*/
devno = pnode->i_rdev;
/*用devno在字符设备链表查找对应节点,并将该节点的地址赋值给pcdev*/
/*pcdev赋值给pnode的i_cdev成员*/
pnode->i_cdev = pcdev;
}
/*创建struct file对象,并将该对象的地址赋值给pfile*/
pfile->f_op = pnode->i_cdev->ops;
pfile->f_flags = flag;
/*调用驱动程序的open函数*/
pfile->f_op->open(pnode,pfile,flag);
/*将struct file对象地址填入进程的描述符数组,得到对应位置的下标赋值给fd*/
return fd;
}
syscall_read函数实现的伪代码
int syscall_read(int fd,void *pbuf,int size)
{
struct file *pfile = NULL;
struct file_operations *fops = NULL;
int cnt;
/*将fd作为下标,在进程的描述符数组中获得struct file对象的地址赋值给pfile*/
/*从struct file对象的f_op成员中得到操作函数集对象地址赋值给fops*/
/*从操作函数集对象的read成员得到该设备对应的驱动程序中read函数,并调用之*/
cnt = fops->read(pfile,pbuf,size,&pfile->f_pos);
。。。。
return cnt;
}
6.3 参考原理图
6.4 常用操作函数说明
int (*open) (struct inode *, struct file *); //打开设备
/*
指向函数一般用来对设备进行硬件上的初始化,对于一些简单的设备该函数只需要return 0,对应open系统调用,是open系统调用函数实现过程中调用的函数,
*/
int (*release) (struct inode *, struct file *); //关闭设备
/*
,指向函数一般用来对设备进行硬件上的关闭操作,对于一些简单的设备该函数只需要return 0,对应close系统调用,是close系统调用函数实现过程中调用的函数
*/
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //读设备
/*
指向函数用来将设备产生的数据读到用户空间,对应read系统调用,是read系统调用函数实现过程中调用的函数
*/
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //写设备
/*
指向函数用来将用户空间的数据写进设备,对应write系统调用,是write系统调用函数实现过程中调用的函数
*/
loff_t (*llseek) (struct file *, loff_t, int); //数据操作位置的定位
/*
指向函数用来获取或设置设备数据的开始操作位置(位置指示器),对应lseek系统调用,是lseek系统调用函数实现过程中调用的函数
*/
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备
/*
指向函数用来获取、设置设备一些属性或设备的工作方式等非数据读写操作,对应ioctl系统调用,是ioctl系统调用函数实现过程中调用的函数
*/
unsigned int (*poll) (struct file *, struct poll_table_struct *);//POLL机制,实现对设备的多路复用方式的访问
/*
指向函数用来协助多路复用机制完成对本设备可读、可写数据的监控,对应select、poll、epoll_wait系统调用,是select、poll、epoll_wait系统调用函数实现过程中调用的函数
*/
int (*fasync) (int, struct file *, int); //信号驱动
/*
指向函数用来创建信号驱动机制的引擎,对应fcntl系统调用的FASYNC标记设置,是fcntl系统调用函数FASYNC标记设置过程中调用的函数
*/
七、读操作实现
ssize_t xxx_read(struct file *filp, char __user *pbuf, size_t count, loff_t *ppos);
完成功能:读取设备产生的数据
参数:
filp:指向open产生的struct file类型的对象,表示本次read对应的那次open
pbuf:指向用户空间一块内存,用来保存读到的数据
count:用户期望读取的字节数
ppos:对于需要位置指示器控制的设备操作有用,用来指示读取的起始位置,读完后也需要变更位置指示器的指示位置
返回值:
本次成功读取的字节数,失败返回-1
put_user(x,ptr)
x:char、int类型的简单变量名
/*To 目标地址,这个地址是用户空间的地址;
From 源地址,这个地址是内核空间的地址;
N 将要拷贝的数据的字节数。*/
unsigned long copy_to_user (void __user * to, const void * from, unsigned long n)
成功为返回0,失败非0
八、写操作实现
ssize_t xxx_write (struct file *filp, const char __user *pbuf, size_t count, loff_t *ppos);
完成功能:向设备写入数据
参数:
filp:指向open产生的struct file类型的对象,表示本次write对应的那次open
pbuf:指向用户空间一块内存,用来保存被写的数据
count:用户期望写入的字节数
ppos:对于需要位置指示器控制的设备操作有用,用来指示写入的起始位置,写完后也需要变更位置指示器的指示位置
返回值:
本次成功写入的字节数,失败返回-1
get_user(x,ptr)
x:char、int类型的简单变量名
/*copy_from_user(void *to, const void __user *from, unsigned long n)
1. @*to 将数据拷贝到内核的地址
2. @*from 需要拷贝数据的地址
3. @n 拷贝数据的长度(字节)
3. 也就是将@form地址中的数据拷贝到@to地址中去,拷贝长度是n*/
unsigned long copy_from_user (void * to, const void __user * from, unsigned long n)
成功为返回0,失败非0
九、ioctl操作实现
ioctl()主要用于应用层系统调用
不做读写操作,主要用于获取设备属性。
在计算机中,ioctl(input/output control)是一个专用于设备输入输出操作的系统调用,该调用传入一个跟设备有关的请求码,系统调用的功能完全取决于请求码。举个例子,CD-ROM驱动程序可以弹出光驱,它就提供了一个对应的Ioctl请求码。设备无关的请求码则提供了内核调用权限。
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等
9.1 ioctl驱动层实现
long xxx_ioctl (struct file *filp, unsigned int cmd, unsigned long arg);
功能:对相应设备做指定的控制操作(各种属性的设置获取等等)
参数:
filp:指向open产生的struct file类型的对象,表示本次ioctl对应的那次open
cmd:用来表示做的是哪一个操作
arg:和cmd配合用的参数.如果想要获取某种属性的值,该参数可以强制转换成相应的空间地址去用.如果想要设置某种属性,根据需要可转可不转
返回值:成功为0,失败-1
cmd组成
- dir(direction),ioctl 命令访问模式(属性数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
- type(device type),设备类型,占据 8 bit,在一些文献中翻译为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如
‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识; - nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;
- size,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;
ioctl 宏定义
方便xxx_ioctl(struct file *filp, unsigned int cmd, unsigned long arg),中cmd 参数的赋值
#define _IOC(dir,type,nr,size) (((dir)<<_IOC_DIRSHIFT)| \
((type)<<_IOC_TYPESHIFT)| \
((nr)<<_IOC_NRSHIFT)| \
((size)<<_IOC_SIZESHIFT))
/* used to create numbers */
// 定义不带参数的 ioctl 命令
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
//定义带读参数的ioctl命令(copy_to_user) size为类型名
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
//定义带写参数的 ioctl 命令(copy_from_user) size为类型名
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
//定义带读写参数的 ioctl 命令 size为类型名
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
/* used to decode ioctl numbers ,用于解码ioctl数字*/
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
9.2 ioctl应用层实现
ioctl 调用用到的函数
头文件
#include <sys/ioctl.h>
int ioctl(int d, int cmd, ...);
输入参数:
fd: 打开设备文件的时候获得的文件描述符;
cmd: 用户程序对设备的控制命令(驱动层的命令要和应用层的命令一样);
... : 可变参数,可以配置cmd一起使用;
返回值:
成功返回0, 失败返回 -1。
(2)unlock_ioctl主要用于驱动层系统调用
long xxx_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
输入参数:
filp: 指向open产生的struct file类型的对象;
cmd: 用来表示做的是哪个操作;
arg: 和cmd配合使用的参数;
返回值:
成功返回0,失败返回-1。
十、代码演示
mychar.h
#ifndef MY_CHAR_H
#define MY_CHAR_H
#include <asm/ioctl.h>
#define MY_CHAR_MAGIC 'k'
#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC,1,int*)
#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC,2,int*)
#endif
mychar.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include "mychar.h"
#define BUF_LEN 100
int major = 11;
int minor = 0 ;
int mychar_num = 1;
typedef struct{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen ;
wait_queue_head_t rq;
wait_queue_head_t wq;
struct fasync_struct *pasync_obj;
}MYDEV;
MYDEV gmydev;
int mychar_open(struct inode *pnode,struct file *pfile){
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);//已知成员的地址获得所在结构体变量的地址cntainer_of(成员地址,结构体类型名,成员在结构体中的名称)
printk("mychar_open is called\n");
return 0 ;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
//判断是否启用IO阻塞,启用则根据情况阻塞
if(pfile->f_flags & O_NONBLOCK){//非阻塞
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
ret = wait_event_interruptible(pmydev->rq,pmydev->curlen > 0);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
}
if(count > pmydev->curlen){
size = pmydev->curlen;
}else{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret){
printk("copy_to_user failed\n");
return -1;
}
/*函数原型为void *memcpy(void *destin, void *source, unsigned n);函数的功能是从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中,即从源source中拷贝n个字节到目标destin中*/
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen -size);
pmydev->curlen -= size;
wake_up_interruptible(&pmydev->wq);
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
//判断是否启用IO阻塞,启用则根据情况阻塞
if(pmydev->curlen >= BUF_LEN){
if(pfile->f_flags & O_NONBLOCK){//非阻塞
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
ret = wait_event_interruptible(pmydev->wq,BUF_LEN - pmydev->curlen > count);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
}
}
if(count > BUF_LEN - pmydev->curlen){
size = BUF_LEN - pmydev->curlen;
}else{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret){
printk("copy_form_user failed\n");
return -1;
}
pmydev->curlen += size;
wake_up_interruptible(&pmydev->rq);
if(pmydev->pasync_obj != NULL ){
kill_fasync(&pmydev->pasync_obj,SIGIO,POLL_IN);
}
return size;
}
int mychar_close(struct inode *pnode,struct file *pfile){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
if(pmydev->pasync_obj != NULL)
fasync_helper(-1,pfile,0,&pmydev->pasync_obj);
printk("mychar_open is close\n");
return 0 ;
}
long mychar_ioctl(struct file *pfile,unsigned int cmd ,unsigned long arg){
int __user *pret = (int *)arg;
int maxlen = BUF_LEN;
int ret = 0;
MYDEV *pmydev = (MYDEV*)pfile->private_data;
switch(cmd){
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret,&maxlen,sizeof(int));
if(ret){
printk("copy_to_user MAXLEN failed\n");
return -1;
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
if(ret){
printk("copy_to_user CURLEN failed\n");
return -1;
}
break;
default:
printk("The CMD is uknow\n");
return -1;
}
return 0;
}
unsigned int mychar_poll(struct file *pfile,poll_table *ptb){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile,&pmydev->rq,ptb);//将所有等待队列头加入poll_table表中
poll_wait(pfile,&pmydev->wq,ptb);
if(pmydev->curlen > 0){//可读
printk("can reading**************\n");
mask |= POLLIN | POLLRDNORM;
}
if(pmydev->curlen < BUF_LEN ){
printk("can't reading**************\n");
mask |= POLLOUT | POLLWRNORM;
}
return mask;
}
int mychar_fasync(int fd,struct file *pfile,int mode){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
return fasync_helper(fd,pfile,mode,&pmydev->pasync_obj);
printk("mychar_close is called\n");
}
struct file_operations myops = {
//.owner = THIS_MODULE,//内核在插入模块的时候,在内核中生成的struct module 对象的地址
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
.unlocked_ioctl = mychar_ioctl,
.poll = mychar_poll,
.fasync = mychar_fasync,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,mychar_num,"mychar");
if(ret){
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret){
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//分离出自动分配的主设备号
}
//初始设备操作函数集
cdev_init(&gmydev.mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
gmydev.mydev.owner = THIS_MODULE;//推测不必要
cdev_add(&gmydev.mydev,devno,mychar_num);
printk("mychar will init\n");
init_waitqueue_head(&gmydev.rq);
init_waitqueue_head(&gmydev.wq);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major,minor);
cdev_del(&gmydev.mydev);
unregister_chrdev_region(devno,mychar_num);
printk("mychar will exit\n");
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "mychar.h"
int main(int argc, char *argv[])
{
char buf[8] = "";
int fd = -1;
int max=0;
int cur =0;
if(argc < 2) {
printf("The argument is too few\n");
return 1;
}
fd = open(argv[1],O_RDWR);
if(fd < 0 ){
perror("open ");
printf("open %s failed\n",argv[1]);
return 2;
}
ioctl(fd,MYCHAR_IOCTL_GET_MAXLEN,&max);
printf("max len is %d\n",max);
write(fd,"hello",6);
ioctl(fd,MYCHAR_IOCTL_GET_CURLEN,&cur);
printf("cur len is %d\n",cur);
read(fd,buf,8);
printf("read buf:%s\n",buf);
close(fd);
fd = -1;
return 0;
}
十、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 */
用法:printk(KERN_INFO"....",....)
printk(KERN_INFO"Hello World"); =====> printk("<6>""Hello World") ====> printk("<6>Hello World")
dmesg --level=emerg,alert,crit,err,warn,notice,info,debug
#define HELLO_DEBUG
#undef PDEBUG
#ifdef HELLO_DEBUG
#define PDEBUG(fmt, args...) printk(KERN_DEBUG fmt, ##args)
#else
#define PDEBUG(fmt, args...)
#endif
十一、多个次设备的支持
每一个具体设备(次设备不一样的设备),必须有一个struct cdev来代表它
cdev_init
cdev.owner赋值
cdev_add
以上三个操作对每个具体设备都要进行
multimychar.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include "mychar.h"//引入自动义头文件用于实现ioctl
#define BUF_LEN 100
#define MYCHAR_DEV_CNT 3//次设备数量
int major = 11;
int minor = 0 ;
int mychar_num = MYCHAR_DEV_CNT;
typedef struct{//自定义字符设备的数据结构
struct cdev mydev;//字符设备
char mydev_buf[BUF_LEN];//总数据长度
int curlen ;//当前数据长度
}MYDEV;
MYDEV gmydev_arr[MYCHAR_DEV_CNT];//设备组
int mychar_open(struct inode *pnode,struct file *pfile){//打开字符自定义设备
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);//获取参数中的字符设备对象
printk("mychar_open is called\n");
return 0 ;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos){//读取字符设备内容
MYDEV *pmydev = (MYDEV*)pfile->private_data;//获取open时传递的字符设备对象
int size = 0;
int ret = 0;
if(count > pmydev->curlen){
size = pmydev->curlen;
}else{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);//将内核空间数据拷贝到用户空间
if(ret){
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen -size);//刷新字符设备数据区
pmydev->curlen -= size;
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos){//往字符设备中写数据
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
if(count > BUF_LEN -pmydev->curlen){
size = BUF_LEN - pmydev->curlen;
}else{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);//将用户空间数据拷贝到内核空间
if(ret){
printk("copy_form_user failed\n");
return -1;
}
pmydev->curlen += size;
return size;
}
int mychar_close(struct inode *pnode,struct file *pfile){//关闭字符设备
printk("mychar_close is called\n");
return 0 ;
}
long mychar_ioctl(struct file *pfile,unsigned int cmd ,unsigned long arg){//对字符设备进行简单的命令操作,对相应设备做指定的控制操作(各种属性的设置获取等等)
int __user *pret = (int *)arg;
int maxlen = BUF_LEN;
int ret = 0;
MYDEV *pmydev = (MYDEV*)pfile->private_data;
switch(cmd){
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret,&maxlen,sizeof(int));
if(ret){
printk("copy_to_user MAXLEN failed\n");
return -1;
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
if(ret){
printk("copy_to_user CURLEN failed\n");
return -1;
}
break;
default:
printk("The CMD is uknow\n");
return -1;
}
return 0;
}
struct file_operations myops = {//添加字符设备的各种操作函数
//.owner = THIS_MODULE,//内核在插入模块的时候,在内核中生成的struct module 对象的地址
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
.unlocked_ioctl = mychar_ioctl,
};
int __init mychar_init(void)//初始化自定义字符设备驱动到内核空间
{
int ret = 0,i;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,mychar_num,"mychar");
if(ret){
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret){
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//分离出自动分配的主设备号
}
for(i=0;i<MYCHAR_DEV_CNT;i++){
devno = MKDEV(major,minor+i);
//初始设备操作函数集
cdev_init(&gmydev_arr[i].mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
//gmydev.mydev.owner = THIS_MODULE;//推测不必要
cdev_add(&gmydev_arr[i].mydev,devno,1);
}
printk("mychar will init\n");
return 0;
}
void __exit mychar_exit(void)//将自定义字符设备驱动从内核空间退出
{
dev_t devno = MKDEV(major,minor);
int i = 0;
for(i=0;i<MYCHAR_DEV_CNT;i++){
cdev_del(&gmydev_arr[i].mydev);
}
unregister_chrdev_region(devno,mychar_num);
printk("mychar will exit\n");
}
MODULE_LICENSE("GPL");//声明自定义字符设备协议
module_init(mychar_init);
module_exit(mychar_exit);
D5 字符设备驱动基础中_IO模型
IO模型:上层应用程序和某设备数据交互方式
一、五种IO模型------读写外设数据的方式
-
阻塞: 不能操作就睡觉
类似于:scanf();
recvfrom调用中的进程块
-
非阻塞:不能操作就返回错误
进程反复调用OK返回(轮询)从等待中返回
-
多路复用:委托中介监控
处理调用中的块选择,等待可能之一许多套接字成为可读
当数据复制到应用程序缓冲区时,处理块
-
信号驱动:让内核如果能操作时发信号,在信号处理函数中操作
信号序号小于32 的是不可靠的。信号适用于一对一的场景,多对一不适用。
当数据复制到应用程序缓冲区时,处理块
-
异步IO:向内核注册操作请求,内核完成操作后发通知信号(非重点,自己研究)
二、阻塞与非阻塞
2.1 应用层
open时由O_NONBLOCK指示read、write时是否阻塞
open(argv[1],O_RDWR | O_NONBLOCK);
open以后可以由fcntl函数来改变是否阻塞
flags = fcntl(fd,F_GETFL,0);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
tcb.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "mychar.h"
int main(int argc, char *argv[])
{
char buf[8] = "";
int fd = -1;
int max=0;
int cur =0;
if(argc < 2) {
printf("The argument is too few\n");
return 1;
}
fd = open(argv[1],O_RDWR | O_NONBLOCK);
if(fd < 0 ){
perror("open ");
printf("open %s failed\n",argv[1]);
return 2;
}
read(fd,buf,8);
printf("read buf:%s\n",buf);
close(fd);
fd = -1;
return 0;
}
2.1 驱动层
通过等待队列实现
收到信号能够醒来叫浅度睡眠,只有资源准备完善才会醒来,叫深度睡眠
wait_queue_head_t //等待队列头数据类型
init_waitqueue_head(wait_queue_head_t *pwq) //初始化等待队列头
/*
功能:条件不成立则让任务进入浅度睡眠,直到条件成立醒来。用wake_up() 唤醒
wq:等待队列头
condition:C语言表达式
返回:正常唤醒返回0,信号唤醒返回非0(此时读写操作函数应返回-ERESTARTSYS)
*/
wait_event_interruptible(wq,condition)//带参的宏
wait_event(wq,condition) //深度睡眠
wake_up_interruptible(wait_queue_head_t *pwq) /*唤醒浅度睡眠,唤醒 pwq 指定的注册在等待队列上的进程。该函数不能直接的立即唤醒进程,而是由调度程序转换上下文,调整为可运行状态。pwq 等待队列变量指针。*/
wake_up(wait_queue_head_t *pwq)//唤醒深度睡眠
/*
1. 读、写用不同的等待队列头rq、wq
2. 无数据可读、可写时调用wait_event_interruptible(rq、wq,条件)
读一下wait_event_interruptible()的源码,不难发现这个函数先将
当前进程的状态设置成TASK_INTERRUPTIBLE,然后调用schedule(),
而schedule()会将位于TASK_INTERRUPTIBLE状态的当前进程从runqueue
队列中删除。从runqueue队列中删除的结果是,当前这个进程将不再参
与调度,除非通过其他函数将这个进程重新放入这个runqueue队列中,
这就是wake_up()的作用了。
3. 写入数据成功时唤醒rq,读出数据成功唤醒wq
*/
mychar_block.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include "mychar.h"
#define BUF_LEN 100
int major = 11;
int minor = 0 ;
int mychar_num = 1;
typedef struct{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen ;
wait_queue_head_t rq;
wait_queue_head_t wq;
}MYDEV;
MYDEV gmydev;
int mychar_open(struct inode *pnode,struct file *pfile){
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
printk("mychar_open is called\n");
return 0 ;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
if(pfile->f_flags & O_NONBLOCK){//非阻塞
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
ret = wait_event_interruptible(pmydev->rq,pmydev->curlen > 0);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
}
if(count > pmydev->curlen){
size = pmydev->curlen;
}else{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret){
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen -size);
pmydev->curlen -= size;
wake_up_interruptible(&pmydev->wq);
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
if(pmydev->curlen >= BUF_LEN){
if(pfile->f_flags & O_NONBLOCK){//非阻塞
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
ret = wait_event_interruptible(pmydev->wq,BUF_LEN - pmydev->curlen > count);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
}
}
if(count > BUF_LEN - pmydev->curlen){
if(pfile->f_flags & O_NONBLOCK){//非阻塞
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
ret = wait_event_interruptible(pmydev->wq,BUF_LEN - pmydev->curlen > count);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
}
size = BUF_LEN - pmydev->curlen;
}else{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret){
printk("copy_form_user failed\n");
return -1;
}
pmydev->curlen += size;
wake_up_interruptible(&pmydev->rq);
return size;
}
int mychar_close(struct inode *pnode,struct file *pfile){
printk("mychar_close is called\n");
return 0 ;
}
long mychar_ioctl(struct file *pfile,unsigned int cmd ,unsigned long arg){
int __user *pret = (int *)arg;
int maxlen = BUF_LEN;
int ret = 0;
MYDEV *pmydev = (MYDEV*)pfile->private_data;
switch(cmd){
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret,&maxlen,sizeof(int));
if(ret){
printk("copy_to_user MAXLEN failed\n");
return -1;
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
if(ret){
printk("copy_to_user CURLEN failed\n");
return -1;
}
break;
default:
printk("The CMD is uknow\n");
return -1;
}
}
struct file_operations myops = {
//.owner = THIS_MODULE,//内核在插入模块的时候,在内核中生成的struct module 对象的地址
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
.unlocked_ioctl = mychar_ioctl,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,mychar_num,"mychar");
if(ret){
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret){
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//分离出自动分配的主设备号
}
//初始设备操作函数集
cdev_init(&gmydev.mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
#ifndef TEST0
gmydev.mydev.owner = THIS_MODULE;//推测不必要
#endif
cdev_add(&gmydev.mydev,devno,mychar_num);
printk("mychar will init\n");
init_waitqueue_head(&gmydev.rq);
init_waitqueue_head(&gmydev.wq);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major,minor);
unregister_chrdev_region(devno,mychar_num);
printk("mychar will exit\n");
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
三、多路复用
描述符:
- 文件描述符:设备文件、管道文件
- socket描述符
3.1 应用层:三套接口select、poll、epoll
select:位运算实现 监控的描述符数量有限(32位机1024,64位机2048) 效率差
poll:链表实现,监控的描述符数量不限 效率差
epoll:效率最高,监控的描述符数量不限
select
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
/* 功能:监听多个描述符,阻塞等待有一个或者多个文件描述符,准备就绪。
内核将没有准备就绪的文件描述符,从集合中清掉了。
参数: nfds 最大文件描述符数 ,加1
readfds 读文件描述符集合
writefds 写文件描述符集合
exceptfds 其他异常的文件描述符集合
timeout 超时时间(NULL)
返回值:当timeout为NULL时返回0,成功:准备好的文件描述的个数 出错:-1
当timeout不为NULL时,如超时设置为0,则select为非阻塞,超时设置 > 0,则无描述符可被操作的情况下阻塞指定长度的时间
*/
void FD_CLR(int fd, fd_set *set);
//功能:将fd 从集合中清除掉
int FD_ISSET(int fd, fd_set *set);
//功能:判断fd 是否存在于集合中
void FD_SET(int fd, fd_set *set);
//功能:将fd 添加到集合中
void FD_ZERO(fd_set *set);
//功能:将集合清零
//使用模型:
while(1)
{
/*得到最大的描述符maxfd*/
/*FD_ZERO清空描述符集合*/
/*将被监控描述符加到相应集合rfds里 FD_SET*/
/*设置超时*/
ret = select(maxfd+1,&rfds,&wfds,NULL,NULL);
if(ret < 0)
{
if(errno == EINTR)//错误时信号引起的
{
continue;
}
else
{
break;
}
}
else if(ret == 0)
{//超时
//.....
}
else
{ //> 0 ret为可被操作的描述符个数
if(FD_ISSET(fd1,&rfds))
{//读数据
//....
}
if(FD_ISSET(fd2,&rfds))
{//读数据
//....
}
///.....
if(FD_ISSET(fd1,&wfds))
{//写数据
//....
}
}
}
tcp.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "mychar.h"
int main(int argc, char *argv[])
{
char buf[8] = "";
int fd = -1;
int ret = 0;
fd_set rfds;
if(argc < 2) {
printf("The argument is too few\n");
return 1;
}
// fd = open(argv[1],O_RDWR|O_NONBLOCK);
fd = open(argv[1],O_RDWR);
if(fd < 0 ){
perror("open ");
printf("open %s failed\n",argv[1]);
return 2;
}
printf("************************\n");
#if 1
while(1){
FD_ZERO(&rfds);
FD_SET(fd,&rfds);
// printf("**\n");
ret = select(fd+1,&rfds,NULL,NULL,NULL);
// perror("select");
if(ret<0){
if(errno == EINTR){
printf("**\n");
continue;
}else{
printf("select error\n");
break;
}
}
printf("**\n");
if(FD_ISSET(fd,&rfds)){
read(fd,buf,8);
printf("buf=%s\n",buf);
}
}
#else
read(fd,buf,8);
printf("buf=%s\n",buf);
#endif
close(fd);
fd = -1;
return 0;
}
3.2 驱动层:实现poll函数
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p);
/*功能:将等待队列头添加至poll_table表中
参数:struct file :设备文件
Wait_queue_head_t :等待队列头
Poll_table :poll_table表
*/
/*该函数与select、poll、epoll_wait函数相对应,协助这些多路监控函数判断本设备是否有数据可读写*/
unsigned int xxx_poll(struct file *filp, poll_table *wait) //函数名初始化给struct file_operations的成员.poll
{
unsigned int mask = 0;
/*
1. 将所有等待队列头加入poll_table表中
2. 判断是否可读,如可读则mask |= POLLIN | POLLRDNORM;
3. 判断是否可写,如可写则mask |= POLLOUT | POLLWRNORM;
*/
return mask;
}
mychar_poll.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include "mychar.h"
#define BUF_LEN 100
int major = 11;
int minor = 0 ;
int mychar_num = 1;
typedef struct{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen ;
wait_queue_head_t rq;
wait_queue_head_t wq;
}MYDEV;
MYDEV gmydev;
int mychar_open(struct inode *pnode,struct file *pfile){
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
printk("mychar_open is called\n");
return 0 ;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
//判断是否启用IO阻塞,启用则根据情况阻塞
if(pfile->f_flags & O_NONBLOCK){//非阻塞
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
ret = wait_event_interruptible(pmydev->rq,pmydev->curlen > 0);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
}
if(count > pmydev->curlen){
size = pmydev->curlen;
}else{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret){
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen -size);
pmydev->curlen -= size;
wake_up_interruptible(&pmydev->wq);
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
//判断是否启用IO阻塞,启用则根据情况阻塞
if(pmydev->curlen >= BUF_LEN){
if(pfile->f_flags & O_NONBLOCK){//非阻塞
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
ret = wait_event_interruptible(pmydev->wq,BUF_LEN - pmydev->curlen > count);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
}
}
if(count > BUF_LEN - pmydev->curlen){
size = BUF_LEN - pmydev->curlen;
}else{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret){
printk("copy_form_user failed\n");
return -1;
}
pmydev->curlen += size;
wake_up_interruptible(&pmydev->rq);
return size;
}
int mychar_close(struct inode *pnode,struct file *pfile){
printk("mychar_close is called\n");
return 0 ;
}
long mychar_ioctl(struct file *pfile,unsigned int cmd ,unsigned long arg){
int __user *pret = (int *)arg;
int maxlen = BUF_LEN;
int ret = 0;
MYDEV *pmydev = (MYDEV*)pfile->private_data;
switch(cmd){
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret,&maxlen,sizeof(int));
if(ret){
printk("copy_to_user MAXLEN failed\n");
return -1;
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
if(ret){
printk("copy_to_user CURLEN failed\n");
return -1;
}
break;
default:
printk("The CMD is uknow\n");
return -1;
}
return 0;
}
unsigned int mychar_poll(struct file *pfile,poll_table *ptb){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile,&pmydev->rq,ptb);//将所有等待队列头加入poll_table表中
poll_wait(pfile,&pmydev->wq,ptb);
if(pmydev->curlen > 0){//可读
printk("can reading**************\n");
mask |= POLLIN | POLLRDNORM;
}
if(pmydev->curlen < BUF_LEN ){
printk("can't reading**************\n");
mask |= POLLOUT | POLLWRNORM;
}
return mask;
}
struct file_operations myops = {
//.owner = THIS_MODULE,//内核在插入模块的时候,在内核中生成的struct module 对象的地址
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
.unlocked_ioctl = mychar_ioctl,
.poll = mychar_poll,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,mychar_num,"mychar");
if(ret){
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret){
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//分离出自动分配的主设备号
}
//初始设备操作函数集
cdev_init(&gmydev.mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
gmydev.mydev.owner = THIS_MODULE;//推测不必要
cdev_add(&gmydev.mydev,devno,mychar_num);
printk("mychar will init\n");
init_waitqueue_head(&gmydev.rq);
init_waitqueue_head(&gmydev.wq);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major,minor);
unregister_chrdev_region(devno,mychar_num);
printk("mychar will exit\n");
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
四、信号驱动
应用程序设置一个信号处理函数,监控驱动程序的发现的信号,当接收到可读的信号号,进行读数据
4.1 应用层:信号注册+fcntl
signal(SIGIO, input_handler); //1. 注册信号处理函数,也可以通过 sigaction() 来完成:
fcntl(fd, F_SETOWN, getpid());//2.将当前进程PID设置为fd文件所对应驱动程序将要发送SIGIO,SIGUSR信号进程PID,好由描述符获知PID(指定一个进程作为文件的“属主(filp->owner)”,这样内核才知道信号要发给哪个进程)
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);//3.将该设备的IO模式设置成信号驱动模式(在设备文件中添加FASYNC标志,驱动中就会调用将要实现的test_fasync函数)
void input_handler(int signum)//4.应用自己实现的信号处理函数,在此函数中完成读写
{
//读数据
}
应用模板
//应用模板
int main()
{
int fd = open("/dev/xxxx",O_RDONLY);
fcntl(fd, F_SETOWN, getpid());
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);
signal(SIGIO,xxxx_handler);
//......
}
void xxxx_handle(int signo)
{//读写数据
}
应用实例 tcf.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "mychar.h"
#include <signal.h>
void sigio_handler(int signo);
int fd = -1;
int main(int argc, char *argv[])
{
int flg = 0;
if(argc < 2) {
printf("The argument is too few\n");
return 1;
}
signal(SIGIO,sigio_handler);
fd = open(argv[1],O_RDWR|O_NONBLOCK);
// fd = open(argv[1],O_RDWR);
if(fd < 0 ){
perror("open ");
printf("open %s failed\n",argv[1]);
return 2;
}
fcntl(fd,F_SETOWN,getpid());
flg = fcntl(fd,F_GETFL);
flg |= FASYNC;
fcntl(fd,F_SETFL,flg);
while(1);
close(fd);
fd = -1;
return 0;
}
void sigio_handler(int signo){
char buf[8] = "";
read(fd,buf,8);
printf("buf=%s\n",buf);
}
4.2 驱动层:实现fasync函数
驱动模板
/*设备结构中添加如下成员*/
struct fasync_struct *pasync_obj;
/*应用调用fcntl设置FASYNC时调用该函数产生异步通知结构对象,并将其地址设置到设备结构成员中*/
static int hello_fasync(int fd, struct file *filp, int mode) //函数名初始化给struct file_operations的成员.fasync
{
struct hello_device *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->pasync_obj);
}
/*写函数中有数据可读时向应用层发信号*/
if (dev->pasync_obj)
kill_fasync(&dev->pasync_obj, SIGIO, POLL_IN);
/*release函数中释放异步通知结构对象*/
if (dev->pasync_obj)
fasync_helper(-1, filp, 0, &dev->pasync_obj);
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **pp);
/*
功能:产生或释放异步通知结构对象
参数:
返回值:成功为>=0,失败负数
*/
void kill_fasync(struct fasync_struct **, int, int);
/*
功能:发信号
参数:
struct fasync_struct ** 指向保存异步通知结构地址的指针
int 信号 SIGIO/SIGKILL/SIGCHLD/SIGCONT/SIGSTOP
int 读写信息POLLIN、POLLOUT
*/
mychar_sema.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include "mychar.h"
#define BUF_LEN 100
int major = 11;
int minor = 0 ;
int mychar_num = 1;
typedef struct{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen ;
wait_queue_head_t rq;
wait_queue_head_t wq;
struct fasync_struct *pasync_obj;
struct semaphore sem;
}MYDEV;
MYDEV gmydev;
int mychar_open(struct inode *pnode,struct file *pfile){
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
printk("mychar_open is called\n");
return 0 ;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
down(&pmydev->sem);
//判断是否启用IO阻塞,启用则根据情况阻塞
if(pfile->f_flags & O_NONBLOCK){//非阻塞
up(&pmydev->sem);
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
up(&pmydev->sem);
ret = wait_event_interruptible(pmydev->rq,pmydev->curlen > 0);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
down(&pmydev->sem);
}
if(count > pmydev->curlen){
size = pmydev->curlen;
}else{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret){
up(&pmydev->sem);
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen -size);
pmydev->curlen -= size;
up(&pmydev->sem);
wake_up_interruptible(&pmydev->wq);
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
down(&pmydev->sem);
//判断是否启用IO阻塞,启用则根据情况阻塞
if(pmydev->curlen >= BUF_LEN){
if(pfile->f_flags & O_NONBLOCK){//非阻塞
up(&pmydev->sem);
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
up(&pmydev->sem);
ret = wait_event_interruptible(pmydev->wq,BUF_LEN - pmydev->curlen > count);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
down(&pmydev->sem);
}
}
if(count > BUF_LEN - pmydev->curlen){
size = BUF_LEN - pmydev->curlen;
}else{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret){
up(&pmydev->sem);
printk("copy_form_user failed\n");
return -1;
}
pmydev->curlen += size;
wake_up_interruptible(&pmydev->rq);
if(pmydev->pasync_obj != NULL ){
kill_fasync(&pmydev->pasync_obj,SIGIO,POLL_IN);
}
return size;
}
int mychar_close(struct inode *pnode,struct file *pfile){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
if(pmydev->pasync_obj != NULL)
fasync_helper(-1,pfile,0,&pmydev->pasync_obj);
printk("mychar_open is close\n");
return 0 ;
}
long mychar_ioctl(struct file *pfile,unsigned int cmd ,unsigned long arg){
int __user *pret = (int *)arg;
int maxlen = BUF_LEN;
int ret = 0;
MYDEV *pmydev = (MYDEV*)pfile->private_data;
switch(cmd){
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret,&maxlen,sizeof(int));
if(ret){
printk("copy_to_user MAXLEN failed\n");
return -1;
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
down(&pmydev->sem);
ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
up(&pmydev->sem);
if(ret){
printk("copy_to_user CURLEN failed\n");
return -1;
}
break;
default:
printk("The CMD is uknow\n");
return -1;
}
return 0;
}
unsigned int mychar_poll(struct file *pfile,poll_table *ptb){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile,&pmydev->rq,ptb);//将所有等待队列头加入poll_table表中
poll_wait(pfile,&pmydev->wq,ptb);
down(&pmydev->sem);
if(pmydev->curlen > 0){//可读
printk("can reading**************\n");
mask |= POLLIN | POLLRDNORM;
}
if(pmydev->curlen < BUF_LEN ){
printk("can't reading**************\n");
mask |= POLLOUT | POLLWRNORM;
}
up(&pmydev->sem);
return mask;
}
int mychar_fasync(int fd,struct file *pfile,int mode){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
return fasync_helper(fd,pfile,mode,&pmydev->pasync_obj);
printk("mychar_close is called\n");
}
struct file_operations myops = {
//.owner = THIS_MODULE,//内核在插入模块的时候,在内核中生成的struct module 对象的地址
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
.unlocked_ioctl = mychar_ioctl,
.poll = mychar_poll,
.fasync = mychar_fasync,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,mychar_num,"mychar");
if(ret){
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret){
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//分离出自动分配的主设备号
}
//初始设备操作函数集
cdev_init(&gmydev.mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
gmydev.mydev.owner = THIS_MODULE;//推测不必要
cdev_add(&gmydev.mydev,devno,mychar_num);
printk("mychar will init\n");
init_waitqueue_head(&gmydev.rq);
init_waitqueue_head(&gmydev.wq);
//初始化信号量
sema_init(&gmydev.sem,1);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major,minor);
cdev_del(&gmydev.mydev);
unregister_chrdev_region(devno,mychar_num);
printk("mychar will exit\n");
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
D6 字符设备驱动基础下_并发控制
一、上下文和并发场合
执行流:有开始有结束总体顺序执行的一段代码 又称上下文
应用编程:只考虑任务上下文(进程和线程)
内核编程:还得考虑异常上下文
- 任务上下文:五状态 可阻塞
a. 应用进程或线程运行在用户空间
b. 应用进程或线程运行在内核空间(通过调用syscall来间接使用内核空间)
c. 内核线程始终在内核空间 - 异常上下文:不可阻塞
中断上下文
竞态:多任务并行执行时,如果在一个时刻同时操作同一个资源,会引起资源的错乱,这种错乱情形被称为竞态
共享资源:可能会被多个任务同时使用的资源
临界区:操作共享资源的代码段
为了解决竞态,需要提供一种控制机制,来避免在同一时刻使用共享资源,这种机制被称为并发控制机制
并发控制机制分类:
- 原子操作类:原子是化学反应不可再分的基本微粒,这里的原子访问就表示这一个访问是一个步骤,不能再进行拆分。
缺点:原子操作只能对整形变量或者位进行保护。
适用场合:共享资源为单个整型变量的互斥场合 - 忙等待类(自旋锁):自旋锁的“自旋”也就是“原地打转”的意思,“原地打转”的目的是为了等待自旋锁可以用,可以访问共享资源。
自旋锁是基于忙等待的并发控制机制。
缺点:自旋锁的持有时间不能太长,否则会一直浪费CPU的时间,降低系统性能。
适用场合:异常上下文之间或异常上下文与任务上下文之间共享资源时;任务上下文之间且临界区执行时间很短时;互斥问题 - 阻塞类( 信号量):信号量类似于停车场里指示停车位的计数器。使用信号量会提高处理器的使用效率,毕竞不用一直傻乎乎的在那里“自旋” 等待。但是信号量有开销。
信号量是基于阻塞的并发控制机制。
信号量的特点:①、因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。②、因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。③、如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。
适用场合:任务上下文之间且临界区执行时间较长时的互斥或同步问题;
通用并发控制机制的一般使用套路:
/*互斥问题:*/
并发控制机制初始化为可用
P操作
临界区
V操作
/*同步问题:*/
//并发控制机制初始化为不可用
//先行方:
。。。。。
V操作
//后行方:
P操作
。。。。。
二、中断屏蔽(了解)
原则上来讲不算
一种并发控制机制,只能在特定场合下起到跟并发控制机制同样的作用,一种同步机制的辅助手段
禁止本cpu中断 使能本cpu中断
local_irq_disable(); local_irq_enable(); 屏蔽所有中断和恢复所有中断
local_irq_save(flags); local_irq_restore(flags); 与cpu的中断位相关,中断前保存中断屏蔽字(记录哪些中断响应,哪些中断不响应),恢复中断时恢复中断屏蔽字
local_bh_disable(); local_bh_enable(); 与中断低半部有关,关闭、打开软中断
禁止中断
临界区 //临界区代码不能占用太长时间,需要很快完成。(时间过长,鼠标键盘的中断无法响应,造成假死现象,毫秒级,标准:用户无明显感觉)
打开中断
适用场合:中断上下文与某任务共享资源时,或多个不同优先级的中断上下文间共享资源时
三、原子变量(掌握)
原子变量:存取不可被打断的特殊整型变量
a.设置原子量的值
void atomic_set(atomic_t *v,int i); //设置原子量的值为i
atomic_t v = ATOMIC_INIT(0); //定义原子变量v并初始化为0
v = 10;//错误
b.获取原子量的值
atomic_read(atomic_t *v); //返回原子量的值
c.原子变量加减
void atomic_add(int i,atomic_t *v);//原子变量增加i
void atomic_sub(int i,atomic_t *v);//原子变量减少i
d.原子变量自增自减
void atomic_inc(atomic_t *v);//原子变量增加1
void atomic_dec(atomic_t *v);//原子变量减少1
e.操作并测试:运算后结果为0则返回真,否则返回假
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i,atomic_t *v);
原子位操作方法:
a.设置位
void set_bit(nr, void *addr); //设置addr的第nr位为1
b.清除位
void clear_bit(nr , void *addr); //清除addr的第nr位为0
c.改变位
void change_bit(nr , void *addr); //改变addr的第nr位为1
d.测试位
void test_bit(nr , void *addr); //测试addr的第nr位是否为1
适用场合:共享资源为单个整型变量的互斥场合
tca.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "mychar.h"
int main(int argc, char *argv[])
{
char buf[8] = "";
int fd = -1;
int ret = 0;
fd_set rfds;
if(argc < 2) {
printf("The argument is too few\n");
return 1;
}
// fd = open(argv[1],O_RDWR|O_NONBLOCK);
fd = open(argv[1],O_RDWR);
if(fd < 0 ){
perror("open ");
printf("open %s failed\n",argv[1]);
return 2;
}
printf("************************\n");
while(1);
close(fd);
fd = -1;
return 0;
}
atomic.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/atomic.h>
int major = 11;
int minor = 0 ;
int atomic_num = 1;
typedef struct{
struct cdev mydev;
atomic_t openflag;
}MYDEV;
MYDEV gmydev;
int atomic_open(struct inode *pnode,struct file *pfile){
MYDEV *mydev=NULL;
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
mydev = (MYDEV*)pfile->private_data;
if(atomic_dec_and_test(&mydev->openflag)){//操作并测试:减运算后结果为0则返回真,否则返回假
printk("open success");
}else{
atomic_inc(&mydev->openflag);//上上一个判断重中减掉的1再加上
printk("open failed");
return -1;
}
printk("atomic_open is called\n");
return 0 ;
}
int atomic_close(struct inode *pnode,struct file *pfile){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
atomic_set(&pmydev->openflag,1);
printk("atomic_open is close\n");
return 0 ;
}
struct file_operations myops = {
.owner = THIS_MODULE,//内核在插入模块的时候,在内核中生成的struct module 对象的地址
.open = atomic_open,
.release = atomic_close,
};
int __init atomic_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,atomic_num,"atomic");
if(ret){
ret = alloc_chrdev_region(&devno,minor,atomic_num,"atomic");
if(ret){
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//分离出自动分配的主设备号
}
//初始设备操作函数集
cdev_init(&gmydev.mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
gmydev.mydev.owner = THIS_MODULE;//推测不必要
cdev_add(&gmydev.mydev,devno,atomic_num);
atomic_set(&gmydev.openflag,1);
printk("atomic will init\n");
return 0;
}
void __exit atomic_exit(void)
{
dev_t devno = MKDEV(major,minor);
cdev_del(&gmydev.mydev);
unregister_chrdev_region(devno,atomic_num);
printk("atomic will exit\n");
}
MODULE_LICENSE("GPL");
module_init(atomic_init);
module_exit(atomic_exit);
四、自旋锁:基于忙等待的并发控制机制
a.定义自旋锁
spinlock_t lock;
b.初始化自旋锁
spin_lock_init(spinlock_t *);
c.获得自旋锁
spin_lock(spinlock_t *); //成功获得自旋锁立即返回,否则自旋在那里直到该自旋锁的保持者释放
异常上下文不能调用任何可能引起阻塞的函数 信号量互斥锁,会引起阻塞不能在异常上下文中使用 异常上下文解决互斥问题只能用自旋锁,不能用后面的信号量和互斥锁 可以用在任务上下文之间
spin_trylock(spinlock_t *); //成功获得自旋锁立即返回真,否则返回假,而不是像上一个那样"在原地打转”
d.释放自旋锁
spin_unlock(spinlock_t *);
#include <linux/spinlock.h>
定义spinlock_t类型的变量lock
spin_lock_init(&lock)后才能正常使用spinlock
spin_lock(&lock);
临界区
spin_unlock(&lock);
适用场合:
- 异常上下文之间或异常上下文与任务上下文之间共享资源时
- 任务上下文之间且临界区执行时间很短时
- 互斥问题
mychar_spin.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
//定义自定义设备结构体
int major = 11;
int minor = 0 ;
int mychar_spin_num = 1;
typedef struct {
struct cdev mydev;
spinlock_t lock;
int flag;
}MYDEV;
MYDEV gmydev;
//打开
int mychar_spin_open(struct inode *pnode,struct file *pfile){
MYDEV *mydev;
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
mydev = (MYDEV*)pfile->private_data;
spin_lock(&mydev->lock);
if(mydev->flag){
mydev->flag = 0;
spin_unlock(&mydev->lock);
}else{
spin_unlock(&mydev->lock);
printk("mychar_spin_open is opened already\n");
return -1;
}
printk("mychar_spin_open is called\n");
return 0 ;
}
//关闭
int mychar_spin_close(struct inode *pnode,struct file *pfile){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
spin_lock(&pmydev->lock);
pmydev->flag = 1;
spin_unlock(&pmydev->lock);
printk("mychar_spin_open is close\n");
return 0 ;
}
struct file_operations myops = {
.open = mychar_spin_open,
.release = mychar_spin_close,
};
//初始化
int __init mychar_spin_init(void){
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,mychar_spin_num,"mychar_spin");
if(ret){
ret = alloc_chrdev_region(&devno,minor,mychar_spin_num,"mychar_spin");
if(ret){
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);
}
//初始化设备操作函数集
cdev_init(&gmydev.mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
gmydev.mydev.owner = THIS_MODULE;
cdev_add(&gmydev.mydev,devno,mychar_spin_num);
//初始化自旋锁
gmydev.flag = 1;
spin_lock_init(&gmydev.lock);
printk("mychar_spin will init\n");
return 0;
}
//退出
void __exit mychar_spin_exit(void){
dev_t devno = MKDEV(major,minor);
cdev_del(&gmydev.mydev);
unregister_chrdev_region(devno,mychar_spin_num);
printk("mychar_spin will exit\n");
}
MODULE_LICENSE("GPL");
module_init(mychar_spin_init);
module_exit(mychar_spin_exit);
五、信号量:基于阻塞的并发控制机制
a.定义信号量
struct semaphore sem;
b.初始化信号量
void sema_init(struct semaphore *sem, int val);
c.获得信号量P
int down(struct semaphore *sem);//深度睡眠
int down_interruptible(struct semaphore *sem);//浅度睡眠
d.释放信号量V
void up(struct semaphore *sem);
#include <linux/semaphore.h>
适用场合:任务上下文之间且临界区执行时间较长时的互斥或同步问题
mychar_sema.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include "mychar.h"
#define BUF_LEN 100
int major = 11;
int minor = 0 ;
int mychar_num = 1;
typedef struct{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen ;
wait_queue_head_t rq;
wait_queue_head_t wq;
struct fasync_struct *pasync_obj;
struct semaphore sem;
}MYDEV;
MYDEV gmydev;
int mychar_open(struct inode *pnode,struct file *pfile){
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
printk("mychar_open is called\n");
return 0 ;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
down(&pmydev->sem);
//判断是否启用IO阻塞,启用则根据情况阻塞
if(pfile->f_flags & O_NONBLOCK){//非阻塞
up(&pmydev->sem);
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
up(&pmydev->sem);
ret = wait_event_interruptible(pmydev->rq,pmydev->curlen > 0);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
down(&pmydev->sem);
}
if(count > pmydev->curlen){
size = pmydev->curlen;
}else{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret){
up(&pmydev->sem);
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen -size);
pmydev->curlen -= size;
up(&pmydev->sem);
wake_up_interruptible(&pmydev->wq);
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
down(&pmydev->sem);
//判断是否启用IO阻塞,启用则根据情况阻塞
if(pmydev->curlen >= BUF_LEN){
if(pfile->f_flags & O_NONBLOCK){//非阻塞
up(&pmydev->sem);
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
up(&pmydev->sem);
ret = wait_event_interruptible(pmydev->wq,BUF_LEN - pmydev->curlen > count);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
down(&pmydev->sem);
}
}
if(count > BUF_LEN - pmydev->curlen){
size = BUF_LEN - pmydev->curlen;
}else{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret){
up(&pmydev->sem);
printk("copy_form_user failed\n");
return -1;
}
pmydev->curlen += size;
wake_up_interruptible(&pmydev->rq);
if(pmydev->pasync_obj != NULL ){
kill_fasync(&pmydev->pasync_obj,SIGIO,POLL_IN);
}
return size;
}
int mychar_close(struct inode *pnode,struct file *pfile){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
if(pmydev->pasync_obj != NULL)
fasync_helper(-1,pfile,0,&pmydev->pasync_obj);
printk("mychar_open is close\n");
return 0 ;
}
long mychar_ioctl(struct file *pfile,unsigned int cmd ,unsigned long arg){
int __user *pret = (int *)arg;
int maxlen = BUF_LEN;
int ret = 0;
MYDEV *pmydev = (MYDEV*)pfile->private_data;
switch(cmd){
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret,&maxlen,sizeof(int));
if(ret){
printk("copy_to_user MAXLEN failed\n");
return -1;
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
down(&pmydev->sem);
ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
up(&pmydev->sem);
if(ret){
printk("copy_to_user CURLEN failed\n");
return -1;
}
break;
default:
printk("The CMD is uknow\n");
return -1;
}
return 0;
}
unsigned int mychar_poll(struct file *pfile,poll_table *ptb){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile,&pmydev->rq,ptb);//将所有等待队列头加入poll_table表中
poll_wait(pfile,&pmydev->wq,ptb);
down(&pmydev->sem);
if(pmydev->curlen > 0){//可读
printk("can reading**************\n");
mask |= POLLIN | POLLRDNORM;
}
if(pmydev->curlen < BUF_LEN ){
printk("can't reading**************\n");
mask |= POLLOUT | POLLWRNORM;
}
up(&pmydev->sem);
return mask;
}
int mychar_fasync(int fd,struct file *pfile,int mode){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
return fasync_helper(fd,pfile,mode,&pmydev->pasync_obj);
printk("mychar_close is called\n");
}
struct file_operations myops = {
//.owner = THIS_MODULE,//内核在插入模块的时候,在内核中生成的struct module 对象的地址
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
.unlocked_ioctl = mychar_ioctl,
.poll = mychar_poll,
.fasync = mychar_fasync,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,mychar_num,"mychar");
if(ret){
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret){
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//分离出自动分配的主设备号
}
//初始设备操作函数集
cdev_init(&gmydev.mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
gmydev.mydev.owner = THIS_MODULE;//推测不必要
cdev_add(&gmydev.mydev,devno,mychar_num);
printk("mychar will init\n");
init_waitqueue_head(&gmydev.rq);
init_waitqueue_head(&gmydev.wq);
//初始化信号量
sema_init(&gmydev.sem,1);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major,minor);
cdev_del(&gmydev.mydev);
unregister_chrdev_region(devno,mychar_num);
printk("mychar will exit\n");
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
六、互斥锁:基于阻塞的互斥机制
a.初始化
struct mutex my_mutex;
mutex_init(&my_mutex);
b.获取互斥体
void mutex_lock(struct mutex *lock);
c.释放互斥体
void mutex_unlock(struct mutex *lock);
- 定义对应类型的变量
- 初始化对应变量
P/加锁
临界区
V/解锁
适用场合:任务上下文之间且临界区执行时间较长时的互斥问题
#include <linux/mutex.h>
mychar_mutex.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include "mychar.h"
#define BUF_LEN 100
int major = 11;
int minor = 0 ;
int mychar_num = 1;
typedef struct{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen ;
wait_queue_head_t rq;
wait_queue_head_t wq;
struct fasync_struct *pasync_obj;
struct mutex mymutex;
}MYDEV;
MYDEV gmydev;
int mychar_open(struct inode *pnode,struct file *pfile){
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
printk("mychar_open is called\n");
return 0 ;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
mutex_lock(&pmydev->mymutex);
//判断是否启用IO阻塞,启用则根据情况阻塞
if(pfile->f_flags & O_NONBLOCK){//非阻塞
mutex_unlock(&pmydev->mymutex);
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
mutex_unlock(&pmydev->mymutex);
ret = wait_event_interruptible(pmydev->rq,pmydev->curlen > 0);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
mutex_lock(&pmydev->mymutex);
}
if(count > pmydev->curlen){
size = pmydev->curlen;
}else{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret){
mutex_unlock(&pmydev->mymutex);
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen -size);
pmydev->curlen -= size;
mutex_unlock(&pmydev->mymutex);
wake_up_interruptible(&pmydev->wq);
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
mutex_lock(&pmydev->mymutex);
//判断是否启用IO阻塞,启用则根据情况阻塞
if(pmydev->curlen >= BUF_LEN){
if(pfile->f_flags & O_NONBLOCK){//非阻塞
mutex_unlock(&pmydev->mymutex);
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
mutex_unlock(&pmydev->mymutex);
ret = wait_event_interruptible(pmydev->wq,BUF_LEN - pmydev->curlen > count);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
mutex_lock(&pmydev->mymutex);
}
}
if(count > BUF_LEN - pmydev->curlen){
size = BUF_LEN - pmydev->curlen;
}else{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret){
mutex_unlock(&pmydev->mymutex);
printk("copy_form_user failed\n");
return -1;
}
pmydev->curlen += size;
wake_up_interruptible(&pmydev->rq);
if(pmydev->pasync_obj != NULL ){
kill_fasync(&pmydev->pasync_obj,SIGIO,POLL_IN);
}
return size;
}
int mychar_close(struct inode *pnode,struct file *pfile){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
if(pmydev->pasync_obj != NULL)
fasync_helper(-1,pfile,0,&pmydev->pasync_obj);
printk("mychar_open is close\n");
return 0 ;
}
long mychar_ioctl(struct file *pfile,unsigned int cmd ,unsigned long arg){
int __user *pret = (int *)arg;
int maxlen = BUF_LEN;
int ret = 0;
MYDEV *pmydev = (MYDEV*)pfile->private_data;
switch(cmd){
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret,&maxlen,sizeof(int));
if(ret){
printk("copy_to_user MAXLEN failed\n");
return -1;
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
mutex_lock(&pmydev->mymutex);
ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
mutex_unlock(&pmydev->mymutex);
if(ret){
printk("copy_to_user CURLEN failed\n");
return -1;
}
break;
default:
printk("The CMD is uknow\n");
return -1;
}
return 0;
}
unsigned int mychar_poll(struct file *pfile,poll_table *ptb){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile,&pmydev->rq,ptb);//将所有等待队列头加入poll_table表中
poll_wait(pfile,&pmydev->wq,ptb);
mutex_lock(&pmydev->mymutex);
if(pmydev->curlen > 0){//可读
printk("can reading**************\n");
mask |= POLLIN | POLLRDNORM;
}
if(pmydev->curlen < BUF_LEN ){
printk("can't reading**************\n");
mask |= POLLOUT | POLLWRNORM;
}
mutex_unlock(&pmydev->mymutex);
return mask;
}
int mychar_fasync(int fd,struct file *pfile,int mode){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
return fasync_helper(fd,pfile,mode,&pmydev->pasync_obj);
printk("mychar_close is called\n");
}
struct file_operations myops = {
//.owner = THIS_MODULE,//内核在插入模块的时候,在内核中生成的struct module 对象的地址
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
.unlocked_ioctl = mychar_ioctl,
.poll = mychar_poll,
.fasync = mychar_fasync,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,mychar_num,"mychar");
if(ret){
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret){
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//分离出自动分配的主设备号
}
//初始设备操作函数集
cdev_init(&gmydev.mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
gmydev.mydev.owner = THIS_MODULE;//推测不必要
cdev_add(&gmydev.mydev,devno,mychar_num);
printk("mychar will init\n");
init_waitqueue_head(&gmydev.rq);
init_waitqueue_head(&gmydev.wq);
//初始化信号量
mutex_init(&gmydev.mymutex);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major,minor);
cdev_del(&gmydev.mydev);
unregister_chrdev_region(devno,mychar_num);
printk("mychar will exit\n");
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
七、选择并发控制机制的原则
- 不允许睡眠的上下文需要采用忙等待类,可以睡眠的上下文可以采用阻塞类。在异常上下文中访问的竞争资源一定采用忙等待类。
- 临界区操作较长的应用建议采用阻塞类,临界区很短的操作建议采用忙等待类。
- 中断屏蔽仅在有与中断上下文共享资源时使用。
- 共享资源仅是一个简单整型量时用原子变量
D7 内核定时器
一、时钟中断
硬件有一个时钟装置,该装置每隔一定时间发出一个时钟中断(称为一次时钟嘀嗒-tick),对应的中断处理程序就将全局变量jiffies_64加1
jiffies_64 是一个全局64位整型, jiffies全局变量为其低32位的全局变量,程序中一般用jiffies
HZ:可配置的宏,表示1秒钟产生的时钟中断次数,一般设为100或200
二、延时机制
- 短延迟:忙等待
void ndelay(unsigned long nsecs)//纳秒
void udelay(unsigned long usecs)//微秒
void mdelay(unsigned long msecs)//毫秒
-
长延迟:忙等待
使用jiffies比较宏来实现
time_after(a,b) //a > b
time_before(a,b) //a < b
//延迟100个jiffies
unsigned long delay = jiffies + 100;
while(time_before(jiffies,delay))
{
;
}
//延迟2s
unsigned long delay = jiffies + 2*HZ;
while(time_before(jiffies,delay))
{
;
}
- 睡眠延迟----阻塞类
void msleep(unsigned int msecs);
unsigned long msleep_interruptible(unsigned int msecs);
延时机制的选择原则:
- 异常上下文中只能采用忙等待类
- 任务上下文短延迟采用忙等待类,长延迟采用阻塞类
三、定时器
(1)定义定时器结构体
struct timer_list
{
struct list_head entry;
unsigned long expires; // 期望的时间值 jiffies + x * HZ
void (*function)(unsigned long); // 时间到达后,执行的回调函数,软中断异常上下文
unsigned long data;
};
(2)初始化定时器 ,初始化完成后定时器并没有开始工作,下一步增加定时器完成后才开始工作
init_timer(struct timer_list *)
(3)增加定时器 ------ 定时器开始计时,
void add_timer(struct timer_list *timer);
(4)删除定时器 -------定时器停止工作
int del_timer(struct timer_list * timer);
(5)修改定时器
int mod_timer(struct timer_list *timer, unsigned long expires);
定义struct timer_list tl类型的变量
init_timer(...);//模块入口函数
//模块入口函数或open或希望定时器开始工作的地方
tl.expires = jiffies + n * HZ //n秒
tl.function = xxx_func;
tl.data = ...;
add_timer(....);
//不想让定时器继续工作时
del_timer(....);
void xxx_func(unsigned long arg)//该形参可以借助形参灵活运用
{
......
mod_timer(....);//如需要定时器继续隔指定时间再次调用本函数
}
四、课堂练习—秒设备
second.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/atomic.h>
#include <asm/uaccess.h>
int major = 11;
int minor = 0 ;
int mysecond_num = 1;
typedef struct{
struct cdev mydev;
struct timer_list timer;
int second;
atomic_t openflag;//1 can open ,0 can not open
}MYDEV;
MYDEV gmydev;
void timer_func(unsigned long arg)
{
MYDEV *pmydev = (MYDEV*)arg;
pmydev->second++;
mod_timer(&pmydev->timer,jiffies+HZ * 1);
}
int mysecond_open(struct inode *pnode,struct file *pfile){
MYDEV *pmydev=NULL;
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
pmydev = (MYDEV*)pfile->private_data;
if(atomic_dec_and_test(&pmydev->openflag)){
pmydev->timer.expires = jiffies + 1 * HZ;
pmydev->timer.function = timer_func;
pmydev->timer.data = (unsigned long)pmydev;
add_timer(&pmydev->timer);
printk("open success");
}else{
atomic_inc(&pmydev->openflag);
printk("open failed");
return -1;
}
printk("mysecond_open is called\n");
return 0 ;
}
int mysecond_close(struct inode *pnode,struct file *pfile){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
atomic_set(&pmydev->openflag,1);
del_timer(&pmydev->timer);
printk("mysecond_open is close\n");
return 0 ;
}
ssize_t mysecond_read(struct file *pfile, char __user *puser,size_t size,loff_t *p_pos){
int ret = 0;
MYDEV *pmydev = (MYDEV*)pfile->private_data;
if(size < sizeof(int)){
printk("the expct read size is invalid");
return -1;
}
if(size >= sizeof(int)){
size = sizeof(int);
}
ret = copy_to_user(puser,&pmydev->second,size);
if(ret){
printk("copy to user failed");
}
return size;
}
struct file_operations myops = {
.owner = THIS_MODULE,//内核在插入模块的时候,在内核中生成的struct module 对象的地址
.open = mysecond_open,
.release = mysecond_close,
.read = mysecond_read,
};
int __init mysecond_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,mysecond_num,"mysecond");
if(ret){
ret = alloc_chrdev_region(&devno,minor,mysecond_num,"mysecond");
if(ret){
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//分离出自动分配的主设备号
}
//初始设备操作函数集
cdev_init(&gmydev.mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
gmydev.mydev.owner = THIS_MODULE;//推测不必要
cdev_add(&gmydev.mydev,devno,mysecond_num);
init_timer(&gmydev.timer);
atomic_set(&gmydev.openflag,1);
printk("mysecond will init\n");
return 0;
}
void __exit mysecond_exit(void)
{
dev_t devno = MKDEV(major,minor);
cdev_del(&gmydev.mydev);
unregister_chrdev_region(devno,mysecond_num);
printk("mysecond will exit\n");
}
MODULE_LICENSE("GPL");
module_init(mysecond_init);
module_exit(mysecond_exit);
test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "mychar.h"
int main(int argc, char *argv[])
{
int fd = -1;
int sec = 0;
if(argc < 2) {
printf("The argument is too few\n");
return 1;
}
fd = open(argv[1],O_RDWR);
if(fd < 0 ){
perror("open ");
printf("open %s failed\n",argv[1]);
return 2;
}
printf("************************\n");
sleep(3);
read(fd,&sec,sizeof(sec));
printf("timer:%d\n",sec);
close(fd);
fd = -1;
return 0;
}
D8 内核内存管理
一、内核内存管理框架
内核将物理内存等分成N块4KB,称之为一页,每页都用一个struct page来表示,采用伙伴关系算法5维护。
虚拟内存是假想空间:0-3G进程地址空间(用户空间),利用其地址值。
内核地址空间划分图:
3G~3G+896M:低端内存,直接映射
虚拟地址 = 3G + 物理地址(地址是连续的)
细分为:
ZONE_DMA(直接内存访问,当外设跟内存有大的数据量交互的时候,需要用到该空间)
ZONE_NORMAL
分配方式:
1. kmalloc:小内存分配,slab算法。常用于1k以下的内存分配,更常用
2. get_free_page:整页分配,2的n次方页,n最大为10
大于3G+896M:高端内存,
细分为:vmalloc区、持久映射区、固定映射区
分配方式:vmalloc(最常用):虚拟地址连续,物理地址不连续。
二、内核中常用动态分配
2.1 kmalloc
函数原型:
void *kmalloc(size_t size, gfp_t flags);
kmalloc() 申请的内存位于直接映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB。
较常用的 flags(分配内存的方法):
- GFP_ATOMIC 6—— 分配内存的过程是一个原子过程,分配内存的过程不会被(高优先级进程或中断)打断;异常上下文中使用,不允许阻塞。
- GFP_KERNEL —— 正常分配内存,允许阻塞;
- GFP_DMA —— 给 DMA 控制器分配内存,需要使用该标志(DMA要求分配虚拟地址和物理地址连续)。
flags 的参考用法:
|– 进程上下文,可以睡眠 GFP_KERNEL
|– 异常上下文,不可以睡眠 GFP_ATOMIC
| |– 中断处理程序 GFP_ATOMIC
| |– 软中断 GFP_ATOMIC
| |– Tasklet GFP_ATOMIC
|– 用于DMA的内存,可以睡眠 GFP_DMA | GFP_KERNEL
|– 用于DMA的内存,不可以睡眠 GFP_DMA |GFP_ATOMIC
对应的内存释放函数为:
void kfree(const void *objp);
void *kzalloc(size_t size, gfp_t flags)//相当于kmalloc 加 memset,将申请的空间置零
2.2 vmalloc
void *vmalloc(unsigned long size);
vmalloc() 函数则会在虚拟内存空间给出一块连续的内存区,但这片连续的虚拟内存在物理内存中并不一定连续。由于 vmalloc() 没有保证申请到的是连续的物理内存,因此对申请的内存大小没有限制,如果需要申请较大的内存空间就需要用此函数了。
对应的内存释放函数为:
void vfree(const void *addr);
注意:vmalloc() 和 vfree() 可以睡眠,因此不能从异常上下文调用。
2.3 kmalloc & vmalloc 的比较
kmalloc()、kzalloc()、vmalloc() 的共同特点是:
- 用于申请内核空间的内存;
- 内存以字节为单位进行分配;
- 所分配的内存虚拟地址上连续;
kmalloc()、kzalloc()、vmalloc() 的区别是:
- kzalloc 是强制清零的 kmalloc 操作;(以下描述不区分 kmalloc 和 kzalloc)
- kmalloc 分配的内存大小有限制(128KB),而 vmalloc 没有限制;
- kmalloc 可以保证分配的内存物理地址是连续的,但是 vmalloc 不能保证;
- kmalloc 分配内存的过程可以是原子过程(使用 GFP_ATOMIC),而 vmalloc 分配内存时则可能产生阻塞;
- kmalloc 分配内存的开销小,因此 kmalloc 比 vmalloc 要快;
一般情况下,内存只有在要被 DMA 访问的时候才需要物理上连续,但为了性能上的考虑,内核中一般使用 kmalloc(),而只有在需要获得大块内存时才使用 vmalloc()。
2.4 分配选择原则:
- 小内存(< 128k)用kmalloc,大内存用vmalloc或get_free_page
- 如果需要比较大的内存,并且要求使用效率较高时用get_free_page,否则用vmalloc
三、IO访问-------访问外设控制器的寄存器
两种方式:
- IO端口:X86上用IO指令访问
- IO内存:外设寄存器在SOC芯片手册上都有相应物理地址
IO内存访问接口:
static inline void __iomem *ioremap(unsigned long offset, unsigned long size)
/*
功能:实现IO管脚的映射
参数:offset:该管脚的偏移地址
Size:该管脚映射空间的大小
返回值:成功返回映射的虚拟地址,失败NULL
*/
static inline void iounmap(volatile void __iomem *addr)
/*
功能:解除io管脚的映射
参数:addr:io管脚映射的地址
*/
unsigned readb(void *addr);//1字节 或ioread8(void *addr)
unsigned readw(void *addr);//2字节 或ioread16(void *addr)
unsigned readl(void *addr);//4字节 或ioread32(void *addr)
/*
功能:读取寄存器的值
参数:addr 地址
返回值:读到的数据
*/
void writeb(unsigned value, void *addr);//1字节 或iowrite8(u8 value, void *addr)
void writew(unsigned value, void *addr);//2字节 或iowrite16(u16 value, void *addr)
void writel(unsigned value, void *addr);//4字节 或iowrite32(u32 value, void *addr)
/*
功能:向指定的寄存器中,写入数据。
参数:value:待写入寄存器中的数据
Address:寄存器的虚拟地址
*/
volatile关键字的作用?
volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
四、led驱动
1. 读原理图(led2)
2. 查阅SOC芯片手册(led2)
控制寄存器
数据寄存器
GPX2_7 led2 GPX2CON----0x11000C40—28~31-----0001 GPX2DAT-----0x11000C44-----7
GPX1_0 led3 GPX1CON----0x11000C20— 0~ 3-----0001 GPX1DAT----0x11000C24-----0
GPF3_4 led4 GPF3CON----0x114001E0—16~19-----0001 GPF3DAT----0x114001E4-----4
GPF3_5 led5 GPF3CON----0x114001E0—20~23-----0001 GPF3DAT----0x114001E4-----5
3. 编写驱动
a. 设计设备数据类型
struct myled_dev
{
struct cdev mydev;
unsigned long * led2con;
unsigned long * led2dat;
unsigned long * led3con;
unsigned long * led3dat;
unsigned long * led4con;
unsigned long * led4dat;
unsigned long * led5con;
unsigned long * led5dat;
};
b. 考虑需要支持的函数
c. 模块入口:ioremap + 设置成输出
d. 模块出口:iounmap
e. 编写关灯函数和开灯函数,实现ioctl
五 代码
编译
交叉编译
fs4412开发板动态模块交叉编译命令:make ARCH=arm
编译测试文件:
arm-none-linux-gnueabi-gcc test_myled.c -o tled -Wall
myled.h
#ifndef LED_DRIVER_H
#define LED_DRIVER_H
#define LED_DEV_MAGIC 'g'
#define MY_LED_OFF _IO(LED_DEV_MAGIC,0)
#define MY_LED_ON _IO(LED_DEV_MAGIC,1)
#endif
myled.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "myled.h"
#define BUF_LEN 100
#define GPX1CON 0x11000C20
#define GPX1DAT 0x11000C24
#define GPX2CON 0x11000C40
#define GPX2DAT 0x11000C44
#define GPX3CON 0x114001E0
#define GPX3DAT 0x114001E4
int major = 11;
int minor = 0 ;
int myled_num = 1;
typedef struct{
struct cdev mydev;
volatile unsigned long *pled2_con;
volatile unsigned long *pled2_dat;
volatile unsigned long *pled3_con;
volatile unsigned long *pled3_dat;
volatile unsigned long *pled4_con;
volatile unsigned long *pled4_dat;
volatile unsigned long *pled5_con;
volatile unsigned long *pled5_dat;
}MYDEV;
MYDEV *pgmydev = NULL;
int myled_open(struct inode *pnode,struct file *pfile){
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
printk("myled_open is called\n");
return 0 ;
}
int myled_close(struct inode *pnode,struct file *pfile){
printk("myled_open is close\n");
return 0 ;
}
void led_on(MYDEV *pmydev, int ledno){
switch(ledno)
{
case 2:
writel(readl(pmydev->pled2_dat) | ((0x1 << 7)),pmydev->pled2_dat);
break;
case 3:
writel(readl(pmydev->pled3_dat) | ((0x1 )),pmydev->pled3_dat);
break;
case 4:
writel(readl(pmydev->pled4_dat) | ((0x1 << 4)),pmydev->pled4_dat);
break;
case 5:
writel(readl(pmydev->pled5_dat) | ((0x1 << 5)),pmydev->pled5_dat);
break;
}
}
void led_off(MYDEV *pmydev, int ledno){
switch(ledno)
{
case 2:
writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)),pmydev->pled2_dat);
break;
case 3:
writel(readl(pmydev->pled3_dat) & (~(0x1 )),pmydev->pled3_dat);
break;
case 4:
writel(readl(pmydev->pled4_dat) & (~(0x1 << 4)),pmydev->pled4_dat);
break;
case 5:
writel(readl(pmydev->pled5_dat) & (~(0x1 << 5)),pmydev->pled5_dat);
break;
}
}
long myled_ioctl(struct file *pfile,unsigned int cmd ,unsigned long arg){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
if(arg < 2 || arg >5){
return -1;
}
switch(cmd){
case MY_LED_ON:
led_on(pmydev,arg);
break;
case MY_LED_OFF:
led_off(pmydev,arg);
break;
default:
printk("The CMD is uknow\n");
return -1;
}
return 0;
}
struct file_operations myops = {
//.owner = THIS_MODULE,//内核在插入模块的时候,在内核中生成的struct module 对象的地址
.open = myled_open,
.release = myled_close,
.unlocked_ioctl = myled_ioctl,
};
void ioremap_ledreg(MYDEV *pmydev){
pmydev->pled2_con = ioremap(GPX2CON,4);
pmydev->pled2_dat = ioremap(GPX2DAT,4);
pmydev->pled3_con = ioremap(GPX1CON,4);
pmydev->pled3_dat = ioremap(GPX1DAT,4);
pmydev->pled4_con = ioremap(GPX3CON,4);
pmydev->pled4_dat = ioremap(GPX3DAT,4);
pmydev->pled5_con = pmydev->pled4_con;
pmydev->pled5_dat = pmydev->pled4_dat;
}
void set_output_ledconreg(MYDEV *pmydev){
writel((readl(pmydev->pled2_con) & (~(0xF << 28))) | (0x1 << 28),pmydev->pled2_con);
writel((readl(pmydev->pled3_con) & (~(0xF ))) | (0x1 ),pmydev->pled3_con);
writel((readl(pmydev->pled4_con) & (~(0xF << 16))) | (0x1 << 16),pmydev->pled4_con);
writel((readl(pmydev->pled5_con) & (~(0xF << 20))) | (0x1 << 20),pmydev->pled5_con);
writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)),pmydev->pled2_dat);
writel(readl(pmydev->pled3_dat) & (~(0x1 )),pmydev->pled3_dat);
writel(readl(pmydev->pled4_dat) & (~(0x1 << 4)),pmydev->pled4_dat);
writel(readl(pmydev->pled5_dat) & (~(0x1 << 5)),pmydev->pled5_dat);
}
void iounmap_ledreg(MYDEV *pmydev){
iounmap(pmydev->pled2_con);
pmydev->pled2_con = NULL;
iounmap(pmydev->pled2_dat);
pmydev->pled2_dat = NULL;
iounmap(pmydev->pled3_con);
pmydev->pled3_con = NULL;
iounmap(pmydev->pled3_dat);
pmydev->pled3_dat = NULL;
iounmap(pmydev->pled4_con);
pmydev->pled4_con = NULL;
iounmap(pmydev->pled4_dat);
pmydev->pled4_dat = NULL;
pmydev->pled5_con = NULL;
pmydev->pled5_dat = NULL;
}
int __init myled_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,myled_num,"myled");
if(ret){
ret = alloc_chrdev_region(&devno,minor,myled_num,"myled");
if(ret){
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//分离出自动分配的主设备号
}
//使用kmalloc方式动态分配内存
pgmydev = (MYDEV*)kmalloc(sizeof(MYDEV),GFP_KERNEL);
if(NULL == pgmydev){
unregister_chrdev_region(devno,myled_num);
printk("kmalloc failed");
return -1;
}
//初始设备操作函数集
cdev_init(&pgmydev->mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
pgmydev->mydev.owner = THIS_MODULE;//推测不必要
cdev_add(&pgmydev->mydev,devno,myled_num);
//ioremap
ioremap_ledreg(pgmydev);
//con-register set output
set_output_ledconreg(pgmydev);
printk("myled will init\n");
return 0;
}
void __exit myled_exit(void)
{
dev_t devno = MKDEV(major,minor);
//iounmap
iounmap_ledreg(pgmydev);
cdev_del(&pgmydev->mydev);
unregister_chrdev_region(devno,myled_num);
kfree(pgmydev);
printk("myled will exit\n");
}
MODULE_LICENSE("GPL");
module_init(myled_init);
module_exit(myled_exit);
test_myled.c
#include <stdio.h>
#include "myled.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main(int argc, char *argv[])
{
int fd =0;
int onoff=0;
int no =0;
if(argc < 4){
printf("The argument is too few\n");
return -1;
}
sscanf(argv[2],"%d",&onoff);
sscanf(argv[3],"%d",&no);
if(no<2 || no>5){
printf("len-no is invalid\n");
return 2;
}
fd = open(argv[1],O_RDONLY);
if(fd < 0){
printf("open %s failed\n",argv[1]);
return 3;
}
if(onoff){
ioctl(fd,MY_LED_ON,no);
}else{
ioctl(fd,MY_LED_OFF,no);
}
close(fd);
fd = -1;
return 0;
}
D9 设备树
一、起源
减少垃圾代码
减轻驱动开发工作量
驱动代码和设备信息分离
参考Open Fireware7设计
用来记录硬件平台中各种硬件设备的属性信息
二、基本组成
两种源文件:
- xxxxx.dts dts是device tree source的缩写
- xxxxx.dtsi dtsi是device tree source include的缩写,意味着这样源文件用于被dts文件包含用
实际使用时,需要把dts文件编译成对应的二进制文件(.dtb文件,dtb是device tree binary的缩写 )便于运行时存放在内存加快读取信息的速度
三、基本语法
dts文件主体内容由多个节点组成
每个节点可以包含0或多个子节点,形成树状关系
每个dts文件都有一个根节点,其它节点都是它的子孙
根节点一般来描述整个开发板硬件平台,其它节点用来表示具体设备、总线的属性信息
各个节点可以有多个属性,每个属性用key-value键值对来表示
节点语法:
[label:] node-name[@unit-address] {
[properties definitions];
[child nodes];
};
label: 可选项,节点别名,为了缩短节点访问路径,后续节点中可以使用 &label 来表示引用指定节点
node-name: 节点名
unit-address: 设备地址,一般填写该设备寄存器组或内存块的首地址
properties definitions:属性定义
child nodes:子节点
属性语法:
[label:] property-name = value;
[label:] property-name;
属性可以无值
有值的属性,可以有三种取值:
1. arrays of cells(1个或多个32位数据, 64位数据使用2个32位数据表示,空格分隔),用尖括号表示(< >)
2. string(字符串), 用双引号表示(" ")
3. bytestring(1个或多个字节,空格分隔),用方括号表示([])
4. 用,分隔的多值
四、特殊节点
4.1 根节点
根节点表示整块开发板的信息
#address-cells // 在子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells // 在子节点的reg属性中, 使用多少个u32整数来描述大小(size)
compatible // 定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备,即描述其兼容哪些平台
model // 比如有2款板子配置基本一致, 它们的compatible是一样的,那么就通过model来分辨这2款板子
4.2 /memory
所有设备树文件的必需节点,它定义了系统物理内存的 layout
device_type = "memory";
reg //用来指定内存的地址、大小
4.3 /chosen
传递内核启动时使用的参数parameter
bootargs //字符串,内核启动参数, 跟u-boot中设置的bootargs作用一样
4.4 /cpus 多核CPU支持
/cpus节点下有1个或多个cpu子节点, cpu子节点中用reg属性用来标明自己是哪一个cpu
所以 /cpus 中有以下2个属性:
#address-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size) 必须设置为0
五、常用属性
5.1 phandle
数字形式的节点标识,在后续节点中属性值性质表示某节点时,可以引用对应节点
如:
pic@10000000 {
phandle = <1>;
interrupt-controller;
};
another-device-node {
interrupt-parent = <1>; // 使用phandle值为1来引用上述节点
};
5.2 地址 --------------- 重要
reg属性:表示内存区域region,语法:
reg = <address1 length1 [address2 length2] [address3 length3]>;
#address-cells:reg属性中, 使用多少个u32整数来描述地址(address),语法:
#address-cells = <数字>;
#size-cells:reg属性中, 使用多少个u32整数来描述大小(size),语法:
#size-cells = <数字>;
5.3 compatible --------------- 重要
驱动和设备(设备节点)的匹配依据,compatible(兼容性)的值可以有不止一个字符串以满足不同的需求,语法:
compatible = "字符串1","字符串2",...;
5.4 中断 --------------- 重要
a. 中断控制器节点用的属性:
interrupt-controller 一个无值空属性用来声明这个node接收中断信号,表示该节点是一个中断控制器
#interrupt-cells 这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符
b. 中断源设备节点用的属性:
interrupt-parent:标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的,语法:
interrupt-parent = <引用某中断控制器节点>
interrupts 一个中断标识符列表,表示每一个中断输出信号,语法:
interrupts = <中断号 触发方式>
1 low-to-high 上升沿触发
2 high-to-low 下降沿触发
4 high level 高电平触发
8 low level 低电平触发
5.5 gpio --------------- 重要
gpio也是最常见的IO口,常用的属性有:
a. 对于GPIO控制器:
gpio-controller,无值空属性,用来说明该节点描述的是一个gpio控制器
#gpio-cells,用来表示要用几个cell描述一个 GPIO引脚
b. 对于GPIO使用者节点:
gpio使用节点的属性
xxx-gpio = <&引用GPIO控制器 GPIO标号 工作模式>
工作模式:
1 低电平有效 GPIO_ACTIVE_HIGH
0 高电平有效 GPIO_ACTIVE_LOW
5.6 属性设置套路
一般来说,每一种设备的节点属性设置都会有一些套路,比如可以设置哪些属性?属性值怎么设置?那怎么知道这些套路呢,有两种思路:
- 抄类似的dts,比如我们自己项目的平台是4412,那么就可以抄exynos4412-tiny4412.dts、exynos4412-smdk4412.dts这类相近的dts
- 查询内核中的文档,比如Documentation/devicetree/bindings/i2c/i2c-imx.txt就描述了imx平台的i2c属性设置方法;Documentation/devicetree/bindings/fb就描述了lcd、lvds这类属性设置方法
六、常用接口
struct device_node 对应设备树中的一个节点
struct property 对应节点中一个属性
6.1 of_find_node_by_path 通过路径查找指定节点
/**
include/of.h
of_find_node_by_path - 通过路径查找指定节点
@path - 带全路径的节点名,也可以是节点的别名
成功:得到节点的首地址;失败:NULL
*/
struct device_node * of_find_node_by_path(const char *path);
6.2 of_find_property 提取指定属性的值
/*
include/of.h
of_find_property - 提取指定属性的值
@np - 设备节点指针
@name - 属性名称
@lenp - 属性值的字节数
成功:属性值的首地址;失败:NULL
*/
struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);
6.3 of_get_named_gpio 从设备树中提取gpio口
/**
* include/of_gpio.h
* of_get_named_gpio - 从设备树中提取gpio口
* @np - 设备节点指针
* @propname - 属性名
* @index - gpio口引脚标号
* 成功:得到GPIO口编号;失败:负数,绝对值是错误码
*/
int of_get_named_gpio(struct device_node *np, const char *propname, int index);
6.4 irq_of_parse_and_map 获得设备树中的中断号并进行映射
/*
功能:获得设备树中的中断号并进行映射
参数:node:设备节点
index:序号
返回值:成功:中断号 失败:错误码
*/
unsigned int irq_of_parse_and_map(struct device_node *node, int index);
6.5 of_property_read_string 读属性值
of_property_read_string
/*
of_property_read_string - 提取字符串(属性值)
@np - 设备节点指针
@propname - 属性名称
@out_string - 输出参数,指向字符串(属性值)
成功:0;失败:负数,绝对值是错误码
*/
int of_property_read_string(struct device_node *np, const char *propname, const char **out_string);
读数值
int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value)
int of_property_read_u16(const struct device_node *np,const char *propname,u16 *out_value)
int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value)
判断属性是否存在
int of_property_read_bool(const struct device_node *np,const char *propname)
读数组
int of_property_read_u32_array(const struct device_node *np,const char *propname,u32 *out_value,size_t sz)
七、GPIO接口
7.1 向内核申请和归还GPIO
int gpio_request(unsigned gpio,const char *label)
功能:其实就是让内核检查一下该GPIO引脚是否被其它设备占用,如果没有占用则返回0并用label做一下标记,表示被本设备占用,否则返回负数
void gpio_free(unsigned gpio)
功能:去除本设备对该GPIO的占用标记,表示本设备向内核归还对该GPIO引脚的使用权,此后其它设备可占用该GPIO引脚
7.2 设置GPIO方向
int gpio_direction_input(unsigned gpio)
int gpio_direction_output(unsigned gpio,int value)
7.3 读写GPIO数据
int gpio_get_value(unsigned gpio)
int gpio_set_value(unsigned gpio,int value)
八、led驱动设备树版
-
在设备树源文件的根节点下添加本设备的节点(该节点中包含本设备用到的资源信息)
…/linux3.14/arch/arm/boot/dts/exynos4412-fs4412.dts
fs4412-leds {
compatible = "fs4412,led2-5";
led2-gpio = <&gpx2 7 0>;//led2-gpio 被 gpx2控制器 第七个管脚控制 从零开始编号
led3-gpio = <&gpx1 0 0>;
led4-gpio = <&gpf3 4 0>;
led5-gpio = <&gpf3 5 0>;
};
-
在linux内核源码的顶层目录下执行:make dtbs (生成对应的dtb文件)
-
cp ???.dtb /tftpboot //
注意根文件放在tftp目录下
-
编写驱动代码:
a. 通过本设备在设备树中的路径找到对应节点(struct device_node类型的地址值)
b. 调用 of_get_named_gpio 函数得到某个GPIO的编号
c. struct leddev结构体中记录所有用到的GPIO编号
d. 使用某个GPIO引脚前需先通过gpio_request函数向内核申请占用该引脚,不用该引脚时可通过gpio_free归还给内核
e. 通过gpio_direction_input和gpio_direction_output函数来设置某个GPIO的作用
f. 通过gpio_get_value函数可以获取某个GPIO引脚的当前电平
g. 通过gpio_set_value函数可以改变某个GPIO引脚的电平 -
使用交叉编译命令交叉编译 make ARCH=arm
九、代码
myleddts.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include "myled.h"
#define BUF_LEN 100
int major = 11;
int minor = 0 ;
int myleddts_num = 1;
typedef struct{
struct cdev mydev;
unsigned int led2gpio;
unsigned int led3gpio;
unsigned int led4gpio;
unsigned int led5gpio;
}MYDEV;
MYDEV *pgmydev = NULL;
int myleddts_open(struct inode *pnode,struct file *pfile){
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
printk("myleddts_open is called\n");
return 0 ;
}
int myleddts_close(struct inode *pnode,struct file *pfile){
printk("myleddts_open is close\n");
return 0 ;
}
void led_on(MYDEV *pmydev, int ledno){
switch(ledno)
{
case 2:
gpio_set_value(pmydev->led2gpio,1);
break;
case 3:
gpio_set_value(pmydev->led3gpio,1);
break;
case 4:
gpio_set_value(pmydev->led4gpio,1);
break;
case 5:
gpio_set_value(pmydev->led5gpio,1);
break;
}
}
void led_off(MYDEV *pmydev, int ledno){
switch(ledno)
{
case 2:
gpio_set_value(pmydev->led2gpio,0);
break;
case 3:
gpio_set_value(pmydev->led3gpio,0);
break;
case 4:
gpio_set_value(pmydev->led4gpio,0);
break;
case 5:
gpio_set_value(pmydev->led5gpio,0);
break;
}
}
long myleddts_ioctl(struct file *pfile,unsigned int cmd ,unsigned long arg){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
if(arg < 2 || arg >5){
return -1;
}
switch(cmd){
case MY_LED_ON:
led_on(pmydev,arg);
break;
case MY_LED_OFF:
led_off(pmydev,arg);
break;
default:
printk("The CMD is uknow\n");
return -1;
}
return 0;
}
struct file_operations myops = {
//.owner = THIS_MODULE,//内核在插入模块的时候,在内核中生成的struct module 对象的地址
.open = myleddts_open,
.release = myleddts_close,
.unlocked_ioctl = myleddts_ioctl,
};
void request_led_gpio(MYDEV *pmydev,struct device_node *pnode){
pmydev->led2gpio = of_get_named_gpio(pnode,"led2-gpio",0);//从设备树中提取gpio口
gpio_request(pmydev->led2gpio,"led2");//让内核检查一下该GPIO引脚是否被其它设备占用,如果没有占用则返回0并用label做一下标记,表示被本设备占用,否则返回负数
pmydev->led3gpio = of_get_named_gpio(pnode,"led3-gpio",0);
gpio_request(pmydev->led3gpio,"led3");
pmydev->led4gpio = of_get_named_gpio(pnode,"led4-gpio",0);
gpio_request(pmydev->led4gpio,"led4");
pmydev->led5gpio = of_get_named_gpio(pnode,"led5-gpio",0);
gpio_request(pmydev->led5gpio,"led5");
}
void set_leds_gpio_output(MYDEV *pmydev){
//将引脚设置成输出并置于底电平
gpio_direction_output(pmydev->led2gpio,1);
gpio_direction_output(pmydev->led3gpio,1);
gpio_direction_output(pmydev->led4gpio,1);
gpio_direction_output(pmydev->led5gpio,1);
}
void free_leds_gpio(MYDEV *pmydev){
gpio_free(pmydev->led2gpio);
gpio_free(pmydev->led3gpio);
gpio_free(pmydev->led4gpio);
gpio_free(pmydev->led5gpio);
}
int __init myleddts_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
//定义设备树节点指针变量
struct device_node *pnode1=NULL;
pnode1 = of_find_node_by_path("/fs4412-leds");
//of_find_node_by_path("/fs4412-leds");
if(NULL == pnode1){
printk("find node by path failed\n");
return -1;
}
//申请设备号
ret = register_chrdev_region(devno,myleddts_num,"myleddts");
if(ret){
ret = alloc_chrdev_region(&devno,minor,myleddts_num,"myleddts");
if(ret){
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//分离出自动分配的主设备号
}
//使用kmalloc方式动态分配内存
pgmydev = (MYDEV*)kmalloc(sizeof(MYDEV),GFP_KERNEL);
if(NULL == pgmydev){
unregister_chrdev_region(devno,myleddts_num);
printk("kmalloc failed");
return -1;
}
//初始设备操作函数集
cdev_init(&pgmydev->mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
pgmydev->mydev.owner = THIS_MODULE;//推测不必要
cdev_add(&pgmydev->mydev,devno,myleddts_num);
//ioremap 引入编程依据,初始化引脚功能
request_led_gpio(pgmydev,pnode1);
//con-register set output
set_leds_gpio_output(pgmydev);
printk("myleddts will init\n");
return 0;
}
void __exit myleddts_exit(void)
{
dev_t devno = MKDEV(major,minor);
//从设备树中取消占用
free_leds_gpio(pgmydev);
cdev_del(&pgmydev->mydev);
unregister_chrdev_region(devno,myleddts_num);
kfree(pgmydev);
printk("myleddts will exit\n");
}
MODULE_LICENSE("GPL");
module_init(myleddts_init);
module_exit(myleddts_exit);
D10 中断处理上_基础框架
一、什么是中断
一种硬件上的通知机制,用来通知CPU发生了某种需要立即处理的事件
分为:
- 内部中断 CPU执行程序的过程中,发生的一些硬件出错、运算出错事件(如分母为0、溢出等等),不可屏蔽
- 外部中断 外设发生某种情况,通过一个引脚的高、低电平变化来通知CPU (如外设产生了数据、某种处理完毕等等)
二、中断处理原理
任何一种中断产生,CPU都会暂停当前执行的程序,跳转到内存固定位置执行一段程序,该程序被称为总的中断服务程序,在该程序中区分中断源,然后进一步调用该中断源对应的处理函数。
中断源对应的处理函数被称为分中断处理程序,一般每一个分中断处理程序对应一个外设产生的中断
写驱动时,如果外设有中断,则需要编写一个函数(分中断处理程序)来处理这种中断
三、中断接口
3.0 irq_of_parse_and_map 获得设备树中的中断号并进行映射
/*
功能:获得设备树中的中断号并进行映射
参数:node:设备节点
index:序号
返回值:成功:中断号 失败:错误码
*/
unsigned int irq_of_parse_and_map(struct device_node *node, int index);
3.1 request_irq 中断申请
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
/*
参数:
irq:所申请的中断号
handler:该中断号对应的中断处理函数
flags:中断触发方式或处理方式
触发方式:IRQF_TRIGGER_NONE //无触发
IRQF_TRIGGER_RISING //上升沿触发
IRQF_TRIGGER_FALLING //下降沿触发
IRQF_TRIGGER_HIGH //高电平触发
IRQF_TRIGGER_LOW //低电平触发
处理方式:
IRQF_DISABLED //用于快速中断,处理中屏蔽所有中断
IRQF_SHARED //共享中断
name:中断名 /proc/interrupts
dev:传递给中断例程的参数,共享中断时用于区分那个设备,一般为对应设备的结构体地址,无共享中断时写NULL
返回值:成功:0 失败:错误码
*/
3.2 free_irq 中断释放
void free_irq(unsigned int irq, void *dev_id);
/*
功能:释放中断号
参数:
irq:设备号
dev_id:共享中断时用于区分那个设备一般强转成设备号,无共享中断时写NULL(参数void *dev是对应的设备描述符,可能的取值是系统内所有已经存在的并且挂载在IRQ链表中对应的设备,当设备不真实存在时可取NULL。)
*/
3.3 irqreturn_t 中断处理函数原型
typedef irqreturn_t (*irq_handler_t)(int, void *);
/*
参数:
int:中断号
void*:对应的申请中断时的dev_id
返回值:
typedef enum irqreturn irqreturn_t; //中断返回值类型
enum irqreturn {
IRQ_NONE = (0 << 0),//什么都没做
IRQ_HANDLED = (1 << 0),//已处理,处理正常
IRQ_WAKE_THREAD = (1 << 1),
};
返回IRQ_HANDLED表示处理完了,返回IRQ_NONE在共享中断表示不处理
*/
四、按键驱动
按键原理图:
任务上下文和异常上下存在
步骤:
1、exynos4412-fs4412.dts中增加节点
mykey2_node {
compatible = "mykey2,key2";
key2-gpio = <&gpx1 1 0>;//引脚不是作为输出所以不需要申请,只需要拿到编号,读取其电平信息.
/*
gpio属性
gpio-controller:说明该节点描述的是一个gpio控制器
#gpio-cells:描述gpio使用节点的属性一个cell的内容
属性名=<&引用GPIO节点别名 GPIO标号 工作模式>;
*/
interrupt-parent = <&gpx1>;//标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的;
interrupts = <1 3>;//一个中断标识符列表,表示每一个中断输出信号,引用中断号及中断触发类型
};
2、rquest_irq 最好放最后去做,保证先获得自旋锁
五、代码
设备树结点
linux-3.14/arch/arm/boot/dts$ vi exynos4412-fs4412.dts
mykey3_node {
compatible = "mykey3,key3";
key3-gpio = <&gpx1 2 0>;
interrupt-parent = <&gpx1>;
interrupts = <2 3>;
};
头文件 fs4412_key.h
#ifndef FS4412_KEY_H
#define FS4412_KEY_H
enum KEYCODE{
KEY2 = 1002,
KEY3 = 1003,
KEY4,
};
enum KEY_STATUS{
KEY_DOWN = 0,
KEY_UP,
};
struct keyvalue{
int code; //which KEY
int status;
};
#endif
驱动
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include "fs4412_key.h"
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/gpio.h>
#include<linux/interrupt.h>
#include<linux/delay.h>
//定义自定义设备结构体
int major = 11;
int minor = 0 ;
int fs4412key3_num = 1;
typedef struct {
struct cdev mydev;
int gpio;
int irqno;
struct keyvalue data;
int flag;
spinlock_t lock;
wait_queue_head_t rq; //为了实现读阻塞
}MYDEV;
MYDEV *pgmydev= NULL;
//打开
int fs4412key3_open(struct inode *pnode,struct file *pfile){
MYDEV *mydev;
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
mydev = (MYDEV*)pfile->private_data;
spin_lock(&mydev->lock);
if(mydev->flag){
mydev->flag = 0;
spin_unlock(&mydev->lock);
}else{
spin_unlock(&mydev->lock);
printk("fs4412key3_open is opened already\n");
return -1;
}
printk("fs4412key3_open is called\n");
return 0 ;
}
ssize_t fs4412key3_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
if(count < sizeof(struct keyvalue)){
printk("expect read size invalied\n");
return -1;
}
spin_lock(&pmydev->lock);
if(!pmydev->flag){
//判断是否启用IO阻塞,启用则根据情况阻塞
if(pfile->f_flags & O_NONBLOCK){//非阻塞
spin_unlock(&pmydev->lock);
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
spin_unlock(&pmydev->lock);
ret = wait_event_interruptible(pmydev->rq,pmydev->flag==1);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
spin_lock(&pmydev->lock);
}
}
if(count > sizeof(struct keyvalue)){
size = sizeof(struct keyvalue);
}else{
size = count;
}
ret = copy_to_user(puser,&pmydev->data,size);
if(ret){
spin_unlock(&pmydev->lock);
printk("copy_to_user failed\n");
return -1;
}
pmydev->flag = 0;
spin_unlock(&pmydev->lock);
return size;
}
//关闭
int fs4412key3_close(struct inode *pnode,struct file *pfile){
// MYDEV *pmydev = (MYDEV*)pfile->private_data;
printk("fs4412key3_open is close\n");
return 0 ;
}
unsigned int fs4412key3_poll(struct file *pfile,poll_table *ptb){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile,&pmydev->rq,ptb);//将所有等待队列头加入poll_table表中
spin_lock(&pmydev->lock);
if(pmydev->flag){//可读
printk("can reading**************\n");
mask |= POLLIN | POLLRDNORM;
}
spin_unlock(&pmydev->lock);
return mask;
}
struct file_operations myops = {
.open = fs4412key3_open,
.release = fs4412key3_close,
.read = fs4412key3_read,
.poll = fs4412key3_poll,
};
//中断处理函数
irqreturn_t key3_irq_handle(int no,void *arg){
MYDEV *pmydev = (MYDEV*)arg;
int status1 = 0;
int status2 = 0;
int status = 0;
status1 = gpio_get_value(pmydev->gpio);
mdelay(1);
status2 = gpio_get_value(pmydev->gpio);
if(status1 != status2){
return IRQ_NONE;
}
status = status1;
spin_lock(&pmydev->lock);
if(status == pmydev->data.status){
spin_unlock(&pmydev->lock);
return IRQ_NONE;
}
pmydev->data.code = KEY3;
pmydev->data.status = status;
pmydev->flag = 1;
spin_unlock(&pmydev->lock);
wake_up(&pmydev->rq);
return IRQ_HANDLED;
}
//初始化
int __init fs4412key3_init(void){
int ret = 0;
dev_t devno = MKDEV(major,minor);
struct device_node *pnode = NULL;
pnode = of_find_node_by_path("/mykey3_node");
if(NULL == pnode){
printk("find node failed\n");
return -1;
}
//使用kmalloc方式动态分配内存
pgmydev = (MYDEV*)kmalloc(sizeof(MYDEV),GFP_KERNEL);
//获取gpio编号
pgmydev->gpio = of_get_named_gpio(pnode,"key3-gpio",0);
//获取中断号
pgmydev->irqno = irq_of_parse_and_map(pnode,0);
//申请设备号
ret = register_chrdev_region(devno,fs4412key3_num,"fs4412key3");
if(ret){
ret = alloc_chrdev_region(&devno,minor,fs4412key3_num,"fs4412key3");
if(ret){
free_irq(pgmydev->irqno,NULL);
kfree(pgmydev);
pgmydev=NULL;
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);
}
//初始化设备操作函数集
cdev_init(&pgmydev->mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
pgmydev->mydev.owner = THIS_MODULE;
cdev_add(&pgmydev->mydev,devno,fs4412key3_num);
//初始化等待队列头
init_waitqueue_head(&pgmydev->rq);
//初始化自旋锁
pgmydev->flag = 1;
spin_lock_init(&pgmydev->lock);
//申请中断号
ret = request_irq(pgmydev->irqno,key3_irq_handle,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"FS4412key3",pgmydev);
if(ret){
printk("request_irq failed\n");
kfree(pgmydev);
pgmydev = NULL;
return -1;
}
printk("fs4412key3 will init\n");
return 0;
}
//退出
void __exit fs4412key3_exit(void){
dev_t devno = MKDEV(major,minor);
//注销申请的中断号
free_irq(pgmydev->irqno,NULL);
cdev_del(&pgmydev->mydev);
unregister_chrdev_region(devno,fs4412key3_num);
kfree(pgmydev);
pgmydev = NULL;
printk("fs4412key3 will exit\n");
}
MODULE_LICENSE("GPL");
module_init(fs4412key3_init);
module_exit(fs4412key3_exit);
测试代码 fs4412key_app.c
#include <stdio.h>
#include "fs4412_key.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
//#define KEY_DOW 0
int main(int argc, char *argv[])
{
struct keyvalue keydata = {0};
int fd =0,ret;
if(argc < 2){
printf("The argument is too few\n");
return -1;
}
fd = open(argv[1],O_RDONLY);
if(fd<0){
printf("open %s failed\n",argv[1]);
return 3;
}
while((ret = read(fd,&keydata,sizeof(keydata))) == sizeof(keydata)){
if(keydata.status == KEY_DOWN){
printf("key3 is down!\n");
}
else{
printf("key3 is up!\n");
}
}
close(fd);
fd = -1;
return 0;
}
D11 中断处理下_下半部机制
一、上半部与下半部
起源:
- 中断处理程序执行时间过长引起的问题
- 有些设备的中断处理程序必须要处理一些耗时操作
上半部处理
紧急不耗时的情况
下半部处理
不紧急很耗时的情况
机制:
tasklet 也可以用软中断实现,但是需要通过汇编实现,不友好,替代软中断,故封装一层。
还可以用定时器实现,可以指定延时多久后实现,本质也是基于软中断。
workqueue 基于专门的内核线程实现,循环遍历维护的工作队列,上半部入队,下半部检查出队,利用工作队列实现。涉及到阻塞只能用这种方法。
二、下半部机制之tasklet ---- 基于软中断
别翻译成小任务,不恰当
6.1 结构体
struct tasklet_struct
{
struct tasklet_struct *next;//非重点
unsigned long state;//非重点
atomic_t count;//非重点
void (*func)(unsigned long);//重点
unsigned long data;//重点
};
6.2 定义tasklet的中断底半部处理函数
void tasklet_func(unsigned long data);形参要灵活使用,根据需要传递参数做类型转换
6.3 初始化tasklet
内核提供宏,定义并初始化tasklet,一般很少用,一般用下边的函数
DECLARE_TASKLET(name, func, data);
/*
定义变量并初始化,
参数:name:中断底半部tasklet的名称
Func:中断底半部处理函数的名字
data:给中断底半部处理函数传递的参数
*/
用之前先初始化
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)
6.4 调度tasklet
在上半部的最后调用
void tasklet_schedule(struct tasklet_struct *t)
//参数:t:tasklet的结构体
三、按键驱动之tasklet版
注意:设备树、头文件和测试代码同上一个day
fs4412key3tasklet.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include "fs4412_key.h"
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/gpio.h>
#include<linux/interrupt.h>
#include<linux/delay.h>
//定义自定义设备结构体
int major = 11;
int minor = 0 ;
int fs4412key3_num = 1;
typedef struct {
struct cdev mydev;
int gpio;
int irqno;
struct keyvalue data;
int flag;
spinlock_t lock;
wait_queue_head_t rq; //为了实现读阻塞
struct tasklet_struct tsk;
}MYDEV;
MYDEV *pgmydev= NULL;
//打开
int fs4412key3_open(struct inode *pnode,struct file *pfile){
MYDEV *mydev;
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
mydev = (MYDEV*)pfile->private_data;
spin_lock(&mydev->lock);
if(mydev->flag){
mydev->flag = 0;
spin_unlock(&mydev->lock);
}else{
spin_unlock(&mydev->lock);
printk("fs4412key3_open is opened already\n");
return -1;
}
printk("fs4412key3_open is called\n");
return 0 ;
}
ssize_t fs4412key3_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
if(count < sizeof(struct keyvalue)){
printk("expect read size invalied\n");
return -1;
}
spin_lock(&pmydev->lock);
if(!pmydev->flag){
//判断是否启用IO阻塞,启用则根据情况阻塞
if(pfile->f_flags & O_NONBLOCK){//非阻塞
spin_unlock(&pmydev->lock);
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
spin_unlock(&pmydev->lock);
ret = wait_event_interruptible(pmydev->rq,pmydev->flag==1);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
spin_lock(&pmydev->lock);
}
}
if(count > sizeof(struct keyvalue)){
size = sizeof(struct keyvalue);
}else{
size = count;
}
ret = copy_to_user(puser,&pmydev->data,size);
if(ret){
spin_unlock(&pmydev->lock);
printk("copy_to_user failed\n");
return -1;
}
pmydev->flag = 0;
spin_unlock(&pmydev->lock);
return size;
}
//关闭
int fs4412key3_close(struct inode *pnode,struct file *pfile){
// MYDEV *pmydev = (MYDEV*)pfile->private_data;
printk("fs4412key3_open is close\n");
return 0 ;
}
unsigned int fs4412key3_poll(struct file *pfile,poll_table *ptb){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile,&pmydev->rq,ptb);//将所有等待队列头加入poll_table表中
spin_lock(&pmydev->lock);
if(pmydev->flag){//可读
printk("can reading**************\n");
mask |= POLLIN | POLLRDNORM;
}
spin_unlock(&pmydev->lock);
return mask;
}
struct file_operations myops = {
.open = fs4412key3_open,
.release = fs4412key3_close,
.read = fs4412key3_read,
.poll = fs4412key3_poll,
};
//中断处理函数
irqreturn_t key3_irq_handle(int no,void *arg){
/* MYDEV *pmydev = (MYDEV*)arg;
int status1 = 0;
int status2 = 0;
int status = 0;
status1 = gpio_get_value(pmydev->gpio);
mdelay(1);
status2 = gpio_get_value(pmydev->gpio);
if(status1 != status2){
return IRQ_NONE;
}
status = status1;
spin_lock(&pmydev->lock);
if(status == pmydev->data.status){
spin_unlock(&pmydev->lock);
return IRQ_NONE;
}
pmydev->data.code = KEY3;
pmydev->data.status = status;
pmydev->flag = 1;
spin_unlock(&pmydev->lock);
wake_up(&pmydev->rq);
return IRQ_HANDLED;*/
MYDEV *pmydev = (MYDEV*)arg;
tasklet_schedule(&pmydev->tsk);
return IRQ_HANDLED;
}
void bottom_irq_func(unsigned long arg){
MYDEV *pmydev = (MYDEV*)arg;
int status1 = 0;
int status2 = 0;
int status = 0;
status1 = gpio_get_value(pmydev->gpio);
mdelay(1);
status2 = gpio_get_value(pmydev->gpio);
if(status1 != status2){
return ;
}
status = status1;
spin_lock(&pmydev->lock);
if(status == pmydev->data.status){
spin_unlock(&pmydev->lock);
return ;
}
pmydev->data.code = KEY3;
pmydev->data.status = status;
pmydev->flag = 1;
spin_unlock(&pmydev->lock);
wake_up(&pmydev->rq);
return ;
}
//初始化
int __init fs4412key3_init(void){
int ret = 0;
dev_t devno = MKDEV(major,minor);
struct device_node *pnode = NULL;
pnode = of_find_node_by_path("/mykey3_node");
if(NULL == pnode){
printk("find node failed\n");
return -1;
}
//使用kmalloc方式动态分配内存
pgmydev = (MYDEV*)kmalloc(sizeof(MYDEV),GFP_KERNEL);
//获取gpio编号
pgmydev->gpio = of_get_named_gpio(pnode,"key3-gpio",0);
//获取中断号
pgmydev->irqno = irq_of_parse_and_map(pnode,0);
//申请设备号
ret = register_chrdev_region(devno,fs4412key3_num,"fs4412key3");
if(ret){
ret = alloc_chrdev_region(&devno,minor,fs4412key3_num,"fs4412key3");
if(ret){
free_irq(pgmydev->irqno,NULL);
kfree(pgmydev);
pgmydev=NULL;
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);
}
//初始化设备操作函数集
cdev_init(&pgmydev->mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
pgmydev->mydev.owner = THIS_MODULE;
cdev_add(&pgmydev->mydev,devno,fs4412key3_num);
//初始化等待队列头
init_waitqueue_head(&pgmydev->rq);
//初始化自旋锁
pgmydev->flag = 1;
spin_lock_init(&pgmydev->lock);
//初始化tasklet
tasklet_init(&pgmydev->tsk,bottom_irq_func,(unsigned long)pgmydev);
//申请中断号
ret = request_irq(pgmydev->irqno,key3_irq_handle,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"FS4412key3",pgmydev);
if(ret){
printk("request_irq failed\n");
kfree(pgmydev);
pgmydev = NULL;
return -1;
}
printk("fs4412key3 will init\n");
return 0;
}
//退出
void __exit fs4412key3_exit(void){
dev_t devno = MKDEV(major,minor);
//注销申请的中断号
free_irq(pgmydev->irqno,NULL);
cdev_del(&pgmydev->mydev);
unregister_chrdev_region(devno,fs4412key3_num);
kfree(pgmydev);
pgmydev = NULL;
printk("fs4412key3 will exit\n");
}
MODULE_LICENSE("GPL");
module_init(fs4412key3_init);
module_exit(fs4412key3_exit);
四、下半部机制之workqueue ----- 基于内核线程
8.1 工作队列结构体:
typedef void (*work_func_t)(struct work_struct *work)
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
\\#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
\\#endif
};
8.2 定义工作队列底半部处理函数
void work_queue_func(struct work_struct *work);
8.3 初始化工作队列
struct work_struct work_queue;
初始化:绑定工作队列及工作队列的底半部处理函数
INIT_WORK(struct work_struct * pwork, _func) ;
参数:pwork:工作队列
func:工作队列的底半部处理函数
8.4 工作队列的调度函数
bool schedule_work(struct work_struct *work);
五、按键驱动之workqueue版
注意:设备树、头文件和测试代码同上一个day
fs4412key3workqueue.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include "fs4412_key.h"
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/gpio.h>
#include<linux/interrupt.h>
#include<linux/delay.h>
//定义自定义设备结构体
int major = 11;
int minor = 0 ;
int fs4412key3_num = 1;
typedef struct {
struct cdev mydev;
int gpio;
int irqno;
struct keyvalue data;
int flag;
spinlock_t lock;
wait_queue_head_t rq; //为了实现读阻塞
//定义work结构体
struct work_struct work;
}MYDEV;
MYDEV *pgmydev= NULL;
//打开
int fs4412key3_open(struct inode *pnode,struct file *pfile){
MYDEV *mydev;
pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
mydev = (MYDEV*)pfile->private_data;
spin_lock(&mydev->lock);
if(mydev->flag){
mydev->flag = 0;
spin_unlock(&mydev->lock);
}else{
spin_unlock(&mydev->lock);
printk("fs4412key3_open is opened already\n");
return -1;
}
printk("fs4412key3_open is called\n");
return 0 ;
}
ssize_t fs4412key3_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
int size = 0;
int ret = 0;
if(count < sizeof(struct keyvalue)){
printk("expect read size invalied\n");
return -1;
}
spin_lock(&pmydev->lock);
if(!pmydev->flag){
//判断是否启用IO阻塞,启用则根据情况阻塞
if(pfile->f_flags & O_NONBLOCK){//非阻塞
spin_unlock(&pmydev->lock);
printk("O_NONBLOCK No Data Read\n");
return -1;
}else{
spin_unlock(&pmydev->lock);
ret = wait_event_interruptible(pmydev->rq,pmydev->flag==1);
if(ret){
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
spin_lock(&pmydev->lock);
}
}
if(count > sizeof(struct keyvalue)){
size = sizeof(struct keyvalue);
}else{
size = count;
}
ret = copy_to_user(puser,&pmydev->data,size);
if(ret){
spin_unlock(&pmydev->lock);
printk("copy_to_user failed\n");
return -1;
}
pmydev->flag = 0;
spin_unlock(&pmydev->lock);
return size;
}
//关闭
int fs4412key3_close(struct inode *pnode,struct file *pfile){
// MYDEV *pmydev = (MYDEV*)pfile->private_data;
printk("fs4412key3_open is close\n");
return 0 ;
}
unsigned int fs4412key3_poll(struct file *pfile,poll_table *ptb){
MYDEV *pmydev = (MYDEV*)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile,&pmydev->rq,ptb);//将所有等待队列头加入poll_table表中
spin_lock(&pmydev->lock);
if(pmydev->flag){//可读
printk("can reading**************\n");
mask |= POLLIN | POLLRDNORM;
}
spin_unlock(&pmydev->lock);
return mask;
}
struct file_operations myops = {
.open = fs4412key3_open,
.release = fs4412key3_close,
.read = fs4412key3_read,
.poll = fs4412key3_poll,
};
//中断处理函数
irqreturn_t key3_irq_handle(int no,void *arg){
/* MYDEV *pmydev = (MYDEV*)arg;
int status1 = 0;
int status2 = 0;
int status = 0;
status1 = gpio_get_value(pmydev->gpio);
mdelay(1);
status2 = gpio_get_value(pmydev->gpio);
if(status1 != status2){
return IRQ_NONE;
}
status = status1;
spin_lock(&pmydev->lock);
if(status == pmydev->data.status){
spin_unlock(&pmydev->lock);
return IRQ_NONE;
}
pmydev->data.code = KEY3;
pmydev->data.status = status;
pmydev->flag = 1;
spin_unlock(&pmydev->lock);
wake_up(&pmydev->rq);
return IRQ_HANDLED;*/
MYDEV *pmydev = (MYDEV*)arg;
//work下半部调用
schedule_work(&pmydev->work);
return IRQ_HANDLED;
}
void bottom_irq_func(struct work_struct *work){
MYDEV *pmydev = container_of(work, MYDEV,work);
int status1 = 0;
int status2 = 0;
int status = 0;
status1 = gpio_get_value(pmydev->gpio);
mdelay(1);
status2 = gpio_get_value(pmydev->gpio);
if(status1 != status2){
return ;
}
status = status1;
spin_lock(&pmydev->lock);
if(status == pmydev->data.status){
spin_unlock(&pmydev->lock);
return ;
}
pmydev->data.code = KEY3;
pmydev->data.status = status;
pmydev->flag = 1;
spin_unlock(&pmydev->lock);
wake_up(&pmydev->rq);
return ;
}
//初始化
int __init fs4412key3_init(void){
int ret = 0;
dev_t devno = MKDEV(major,minor);
struct device_node *pnode = NULL;
pnode = of_find_node_by_path("/mykey3_node");
if(NULL == pnode){
printk("find node failed\n");
return -1;
}
//使用kmalloc方式动态分配内存
pgmydev = (MYDEV*)kmalloc(sizeof(MYDEV),GFP_KERNEL);
//获取gpio编号
pgmydev->gpio = of_get_named_gpio(pnode,"key3-gpio",0);
//获取中断号
pgmydev->irqno = irq_of_parse_and_map(pnode,0);
//申请设备号
ret = register_chrdev_region(devno,fs4412key3_num,"fs4412key3");
if(ret){
ret = alloc_chrdev_region(&devno,minor,fs4412key3_num,"fs4412key3");
if(ret){
free_irq(pgmydev->irqno,NULL);
kfree(pgmydev);
pgmydev=NULL;
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);
}
//初始化设备操作函数集
cdev_init(&pgmydev->mydev,&myops);
//将struct cdev 对象添加到内核对应的数据结构里
pgmydev->mydev.owner = THIS_MODULE;
cdev_add(&pgmydev->mydev,devno,fs4412key3_num);
//初始化等待队列头
init_waitqueue_head(&pgmydev->rq);
//初始化自旋锁
pgmydev->flag = 1;
spin_lock_init(&pgmydev->lock);
//初始化work结构体
INIT_WORK(&pgmydev->work,bottom_irq_func);
//申请中断号
ret = request_irq(pgmydev->irqno,key3_irq_handle,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"FS4412key3",pgmydev);
if(ret){
printk("request_irq failed\n");
kfree(pgmydev);
pgmydev = NULL;
return -1;
}
printk("fs4412key3 will init\n");
return 0;
}
//退出
void __exit fs4412key3_exit(void){
dev_t devno = MKDEV(major,minor);
//注销申请的中断号
free_irq(pgmydev->irqno,NULL);
cdev_del(&pgmydev->mydev);
unregister_chrdev_region(devno,fs4412key3_num);
kfree(pgmydev);
pgmydev = NULL;
printk("fs4412key3 will exit\n");
}
MODULE_LICENSE("GPL");
module_init(fs4412key3_init);
module_exit(fs4412key3_exit);
六、下半部机制比较
任务机制
workqueue ----- 内核线程 能睡眠 运行时间无限制
异常机制 ------- 不能睡眠 下半部执行时间不宜太长( < 1s)
软中断 ---- 接口不方便
tasklet ----- 无具体延后时间要求时
定时器 -----有具体延后时间要求时
注脚
Linux是一个单内核结构,同时又吸收了微内核的优点:模块化设计,支持动态装载内核模块。Linux还避免了微内核设计上的缺陷,让一切都运行在内核态,直接调用函数,无需消息传递。Linux大部分都是单内核的。 ↩︎
curses是一个在Linux/Unix下广泛应用的图形函数库.,作用是可以绘制在DOS下的用户界面和漂亮的图形. curses的名字起源于"cursor optimization",即光标优化.它最早由有美国伯克利大学的Bill Joy和Ken Arnold编写的,用来处理一个游戏rogue的屏幕显示.后来贝尔实验室的Mark Horton在System III Unix中重新编写了curses.现在几乎所有的Unix/Linux操作系统都带了curses函数库,curses也加入了 ↩︎
上下文简单说来就是一个环境,相对于进程而言,就是进程执行时的环境。具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。 ↩︎
块设备是针对存储设备的,比如 SD 卡、EMMC、NAND Flash、Nor Flash、SPI Flash、机械硬盘、固态硬盘等。 ↩︎
满足以下条件的两个块称为伙伴:
(1) 具有相同的大小,记作 b。
(2) 他们的物理地址是连续的,起始地址是 2b 的整数倍 ↩︎GFP 标志中的“GFP”最初代表“获取空闲页面”,指的是 __get_free_pages(),这是内核中长期存在的低级分配函数。 GFP 标志的使用远远超出了该功能,但值得记住的是它们与整页分配相关。分配较小内存块的函数(如 kmalloc())可能采用 GFP 标志,但仅当这些函数必须从内存管理子系统获取完整页面时才使用它们。 ↩︎
Open Firmware是定义计算器固件接口的标准,以前由IEEE认可。起源于Sun Microsystems,最初被称为OpenBoot。 ↩︎