文档库 最新最全的文档下载
当前位置:文档库 › linux驱动

linux驱动


devwang QQ1120341494
date 2015-02-16

字符设备驱动
块设备驱动
网络设备驱动


中断顶/底半部处理
内核定时器和延时操作
并发控制在内核中的应用
内存管理和分配
阻塞型I/O和非阻塞型I/O

linux设备驱动程序开发的基础知识
linux驱动模块的构造和装载方法

编译 装载 卸载

三高 高需求 高门槛 高回报
https://www.wendangku.net/doc/4713654285.html, 最新的内核版本 3千多万行代码 如何看内核代码 有框架

为什么要学习嵌入式Linux驱动程序开发?

内核代码的大部分
新芯片 新设备

需要具有硬件知识
需要了解内核基础知识
需要了解内核中的并发控制和同步
复杂的软件结构框架

做驱动的一般比做应用的薪资高几k 但做应用的人比做驱动的人多

设备驱动程序简介

操作硬件,是应用程序和硬件设备之间的一个接口
隐藏硬件细节,提高应用软件的可移植性

提供机制,而不是提供策略
机制:驱动程序能实现什么功能
策略:用户如何使用这些函数

设备分类 三大类

字符设备特点
它的读写是以字节为单位 比如:串口
至少需要有open close read write等系统调用
xxx_open led_open
访问字符设备就如同访问文件 需要路径/dev
/dev/led /dev/ttySAC0 设备文件节点 设备文件名
open打开一个普通文件还是一个设备文件 若是设备文件则还要找到对应的系统调用

块设备特点
比如Nand Flash上的数据就是以页为单位存放的
应用程序也可以通过对应的设备文件来调用open/close/read/write等系统调用
比如/dev/mtdblock0
内核的分区一般是bootloader/4k/kernel/fs
以块为单位实现数据的读/写
mount挂在设备

一切设备皆文件

网络设备特点
也是以块为单位 但块的大小是可以变的
网卡 测试:loopback回环接口

驱动程序加入内核的方法
把所有需要的功能都打包到内核中

Linux提供被称为模块(Module)的机制
insmod将模块动态加载到正在运行的内核
rmmod程序移除模块

Hello World模块(hello.c)

#include
#include

MODULE_LICENSE("Dual BSD/GPL");//自由许可证 至少要有 不然编译会报错

//宏__init告诉内核这两个函数只会在加载和卸载模块时使用
static int __init(void)
{
printk(KERN_ALERT"Hello world\n");
//printk用法类似于printf,但它优先级 比如KERN_ALERT
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_ALERT"Hello world exit\n");
}

//模块初始化宏
module_init(hello_init);//insmod时执行 模块装载函数
module_exit(hello_exit);//rmmod时执行 模块卸载函数

编译内核模块
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

加载内核模块
insmod ./hello.ko

查看内核中已装载的模块
lsmod |grep hello 管道grep查找

卸载内核模块
rmmo

d hello
2.6.29之后的协议

Linux内核模块的程序结构
必须
可选

模块加载函数
static int __init initialzation_function(void)
{
/***/
}
module_init(initialzation_function);
static表示该函数只能在该文件中被使用
__init表示只在特定的情况下使用 使用完之后会释放资源

参数 要全局变量
module_param(howmemoy,int,S_IRUGO);//变量名 数据类型 读写权限

内核支持的模块参数类型
byte short ushort int uint long ulong charp(字符指针) bool u开头为无符号值

模块也可以拥有参数数组
module_param_array(数组名,数组类型,数组长,参数读/写权限);

装载模块时改变参数
可以通过insmod
insmod hello_ext.ko howmoney=5,whom="Students"

模块导出符号
EXPORT_SYMBOL(name);
全局变量

模块声明与描述

模块的使用计数
linux2.4
MOD_INC_USE_COUNT (加一计数)
MOD_DEC_USE_COUNT (减一计数)

./表示当前路径

内核驱动模块与应该用程序对比
应用程序是一个进程
编程从主函数main()开始
主函数main返回即是进程结束
异常终止结束exit(0)

驱动不是进程也不是线程
不能自主运行 只能被调用运行
驱动程序是一系列内核函数
这些函数可以用来访问硬件访问操作

编写模块Makefile文件
Makefile文件
ifneq($(KERNELRELEASE),)//判断变量是否为空
obj-m :=hello.o //m表示以什么方式来编译的
else
KERNELDIR ?=/home/student/linux-2.6.32.2PWD :=$(shell pwd)//先判断是否等于再赋值 是的话就不赋值
default: //缺省 每次都执行
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules //切换到内核路径下
endif

调用过程 上述会被调用两次 因为KERNELRELEASE

多个文件是如何书写呢
obj-m :=module.o
module-objs :=file1.o file2.o
file1.c->file1.o file2.c->file2.o file1.o+file2.o->module.o->module.ko

装载模块可以用insmod和modprobe 掌握insmod就可以了

Linux驱动模块的基本结构
Linux驱动模块的编译方法
Linux驱动模块的装载和卸载

实验演示:
任务1:Hello World模块程序设计
任务2:带传入参数的Hello World模块程序设计

2.6.32.2内核版本号要一致
uname -r //查看内核版本号

vim Makefile 修改文件
wq 保存退出
ll 查看权限
lsmod
lsmod |grep hello 用管道查询
make clean 清除
rz
insmod hello_ext.ko howmoney=5,whom="Student"

模块的应用
如何编写模块
模块相关的宏
模块和应用程序的区别
编译和装载内核模块

linux设备驱动分类
字符设备
块设备
网络接口

有哪些宏?
装载
卸载
传参
作者
协议
别名

驱动模块与应用程序的区别

编译的内核版本要与开发板运行的内核版本一致

字符设备有哪两个设备号?
主设备号 次设备号
字符设备驱动程序中的基本成员函数有哪些?
模块加载 模块卸载 文件操作结

构体成员函数 自己实现的函数
open read write close ioctl poll

字符设备驱动程序的基本结构和开发方法
用户空间调用设备驱动程序的方法

添加驱动程序到内核

用户空间写一个应用程序来调用驱动

字符设备开发的基本步骤
确定主设备号和次设备号
自己设定 自动设定 前提是要唯一

实现字符驱动程序
实现file_opration结构体
实现初始化函数,注册字符设备
实现销毁函数,释放字符设备
实现字符设备其他基本成员函数

创建设备文件节点

什么是主设备号 什么是次设备号
主设备号
整数(占12bits) 范围从0到 通常使用1到255 从后往前没有用过的
次设备号
整数(20bits),范围从0到1048575,一般使用0到255

主设备号表示某一类设备
次设备号表示一类设备中的某一个设备

设备编号的内部表达
dev_t类型(占32bits)
包括主设备号和次设备号
MAJOR(dev_t);//得到主设备号
MINOR(dev_t);//得到次设备号
MKDEV(int major,int minor);将主设备号和次设备号转换成dev_t类型

分配主设备号:
手工分配主设备号:找到一个内核没有使用的主设备号来使用
#include
int register_chrdev_region(dev_t first,unsigned int count,char *name);;

释放设备号
void unregister_chrdev_region(dev_t dev,unsigned int count);

实现字符设备驱动程序
cdev结构体

struct cdev
{
struct kobject kobj;//内嵌的kobject对象
struct module *owner;//所属模块
struct file_operatios *ops;//文件操作结构体
struct list_head list;
dev_t dev;//设备号
unsigned int count;
}

操作cdev的函数
void cdev_init(struct cdev *cdev,struct file_operations *fopd);
struct cdev *cdev_alloc(void);
int cdev_add(struct cdev *dev,dev_t num,unsigned count);
//向系统添加一个cdev,完成字符设备的注册,通常在模块加载函数中调用
void cdev_del(struct cdev *cdev);
//删除设备 注销

file_oprations结构体
字符驱动和内核的接口
在include/linux/fs.h中定义了

file_operations主要成员
struct module *owner;指向模块自身:THIS_MODUE
open:打开设备
release:关闭设备
read:从设备上读数据
write:向设备上写数据
ioctl:I/O控制函数
llseek:定位当前读/写位置指针
mmap:映射设备空间到进程的地址空间

file结构体
file_operations结构体相关的一个结构体
描述一个正在打开的设备文件
成员:
loff_t f_pos 当前读/写位置
unsigned int f_flags 标识文件打开时 是否可读或可写
O_RDONLY
O_NONBLOCK
O_SYNC

inode结构体
内核用inode结构在内部表示文件
inode与file的区别
file表示打开的文件描述符
多个表示打开的文件描述符file结构,可以指向单个inode结构
文件在内部只有一个 但可以被打开多次 每次打开都有文件描述符

inode结构体中

的两个主要字段
dev_t i_rdev
该字段包含了真正的设备编号

从一个inode中获取主设备号和次设备号

注册设备,在模块或驱动初始化时调用
linux2.4及之前
register_chrdev

linux2.6
cdev_init
cdev_add

注销设备
2.4
unregister_chrdev_region
2.6
cdev_dev

字符设备驱动程序基本结构
//设备驱动模块加载函数
static int __init xxx_init(void)
{
...
cdev_init(&xxx_dev.cdev,&xxx_fops);//初始化设备
xxx_dev.cdev.owner=THIS_MODULE;
//获取字符设备号
if(xxx_major)
{
register_chrdev_region(xxx_dev_no,1,DEV_NAME);
}
else
{
alloc_chrdev_region(&xxx_dev_no,0,1,DEV_NAME);
}
ret=cdev_add($xxx_dev.cdev,xxx_dev_no,1);//注册设备
}

在linux中 一切设备皆文件

//设备驱动模块卸载函数
static void __exit xxx_exit(void)
{
unregister_chrdev_region(xxx_dev_no,1);//释放占用的设备号
cdev_del(&xxx_dev.cdev);//注销设备
...
}

打开
int open(struct inode *inode,struct file *filp);
模块使用计数加一 没调用一次 内核中会自动加一
识别此设备号
硬件操作:
检查设备相关错误
如果设备是首次打开,则对其初始化
如果有中断,申请中断处理函数

open函数什么时候被调用?
应用中执行open时被调用

关闭
int release(struct inode *inode,struct file *filp);
模块使用计数减一
释放由open分配的,保存在filp>private_data里的所有内容
硬件操作:
如果申请中断,则释放中断处理程序
在最后一次关闭操作时关闭设备

read/write

用户空间和内核空间之间的数据拷贝过程

用户空间和内核空间之间进行数据拷贝函数

安全函数
copy_from_user 在write中调用
copy_to_user 在read 中调用
unsigned long copy_from_user(void *to,const void __user *from,unsigned龙count);

读设备模板
ssize_t xxx_read(...)
{
...
copy_to_user(...);
...
}

ioctl函数
为设备驱动程序执行"命令"提供了一个特有的入口点
int ioctl(struct inod *inode,struct file *filp,unsigned int cmd,unsigned long arg);
注意参数的类型

cmd参数的定义
linux对ioctl的cmd参数有特殊的定义
构造命令编号的宏
_IO(type,nr)//用于构造无参数的命令编码
_IOR(type,nr,datatype)//读
_IOW(type,nr,datatype)//写
_IOWR(type,nr,datatype)//用于双向传输

int xxx_ioctl(...)
{
...
switch(cmd)
{
case XXX_CMD1:
...
break;
case XXX_CMD2:
...
break;
default://不支持的命令
return -ENOTTY;
}
return 0;
}

命令的构造
要在两个地方构造
驱动中
应用中
两个要对应起来

字符设备驱动结构:
字符设备
对应
cdev
dev_t
file_operatios
read
write
ioctl
初始化 添加
模块加载函数
模块卸载函数
删除
绑定
用户空间
调用
Linux系统调用
字符设备驱动

字符设备驱动实例:BEEP驱动
BEEP驱动

程序实例讲解
蜂鸣器
GPB0 1 ring

regs-gpio.h

beep_drv.c

static int __init beep_init()
{

}
module_init(beep_init);
module_exit(beep_exit);

$mknod /dev/node_name c major minor

int main(void)
{
int dev_fd;
char read_buf[10];
dev_fd=open("/dev/node_name",O_RDWR|O_NONBLOCK);
if(dev_fd==-1)
{
...
exit(1);

}
...
}

编写Makefile
KERNELDIR ?=/opt/linux-2.6.32.2/include
all:test

test:test.c
...
clean:
rm -rf test

beep_test.c
beep_test.ko
uImage
insmod beep_test.ko
cat /proc/devices |grep beep
mknod /dev/beep c major 0
./beep_test

创建设备文件节点
mknod /dev/beep c 253 0
chmod +x beep_test
./beep_test

#include
包括一系列GPIO口函数的封装

ps |grep led
kill -9 684

添加驱动程序到内核

添加驱动程序到内核源代码位置
配置内核
编译内核
下载运行测试

make menuconfig

标准形式

添加驱动程序到内核
linux2.6内核的配置
Makefile
Kconfig
配置工具
以上三个部分组成

3项工作
linux
Kconfig
Makefile

方法一
在linux-2.6.32.2/drivers/char/下直接添加beep_drv.c源文件
方法二
在linux-2.6.32.2/drivers/char/下添加beep驱动目录

方法一
添加Beep设备的内核配置选项
drivers/char/Kconfig文件 添加如下
config BEEP_MINI2440
tristate "BEEP Driver Support for Mini2440 BEEP Test"
depends on MACH_MINI2440
default y if MACH_MINI2440
help
This option enables support for BEEP connectded to GPIO lines
on Mini2440 boards.

drivers/char/Makefile
obj-$(CONFIG_BEEP_MINI2440) +=beep_drv.o

tristate三态 Y M N

beep_drv.c

方法二

drivers/char/beep/
beep_drv.c Kconfig Makefile 三个文件
修改父目录放入Kconfig和Makefile

drivers/char/beep/Kconfig文件 添加如下
config BEEP_MINI2440
tristate "BEEP Driver Support for Mini2440 BEEP Test"
depends on MACH_MINI2440
default y if MACH_MINI2440
help
This option enables support for BEEP connectded to GPIO lines
on Mini2440 boards.

drivers/char/beep/Makefile
obj-$(CONFIG_BEEP_MINI2440) +=beep_drv.o

修改文件Kconfig和Makefile
drivers/char/Kconfig
source "driver/char/beep/Kconfig"

drivers/char/Makefile
obj-$(CONFIG_BEEP_MINI2440) +=beep/

make menuconfig
cd /opt/linux-2.6.32.2
make zImage

通过uboot下载

查看主设备号
cat /proc/devices|grep beep
制作设备文件节点
mknod /dev/beep c major 0

配置和编译linux内核的方法
如何将驱动程序加入到内核中 Makefile&Kconfig

任务一 蜂鸣器驱动程序添加到内核实验
任务二 LED驱动程序添加到内核实验

memdev虚拟内存设备驱动

提供用户空间的进程能够通过linux系统调用访问这一内存


miscdevice简介
杂项设备
linux/miscdevice.h
struct miscdevice

主设备号为10
misc_register注册
misc_deregister注销

自动生成设备文件节点

三个成员
m

inor name *fops

static struct miscdevice misc={
.minor=MISC_DYNAMIC_MINOR,//动态次设备号
.name=DEVICE_NAME,
.fops=&dev_fops,
}

注册miscdevice设备

比字符设备简单很多

beep_misc.c

差不多就改三个地方
fops init exit

跑的时候 直接装载驱动 然后运行应用程序就可以了 不用创建文件设备节点



linux内存管理
linux进程地址空间
linux内核空间

内存管理子系统
内核开发工程师 linux内存管理系统

内存分配 内存回收
虚拟文件系统
驱动
网络协议栈 linux网络子系统

内存管理子系统
地址类型
物理地址 汇编程序
线性地址 虚拟地址 4G 16bit
逻辑地址

物理地址是指出现在CPU地址总线上

地址转换
逻辑地址转换为物理地址 两步

什么是段式管理?
什么是页式管理?

16位CPU 20位地址线
2^20 1M

逻辑地址=段基地址+段内偏移量
由逻辑地址得到物理地址的公式为:
PA=段寄存器的值*16 +逻辑地址

为什么要乘16?

32位 CPU
两种工作模式
实模式 和16一样的
保护模式 与16位不一样

分页管理
线性地址 页 4G 4KB 2^20


字符设备驱动程序开发的基本结构
cdev结构
联系内核和应用的结构体file_opration
file结构 每打开一次 文件描述
inode 内核中使用文件的结构 设备号等

如何把驱动添加到内核中?
两种方法

应用程序如何使用驱动?
两种方法
流函数
应用

防止竞态的机制有哪些/
semaphore信号量
spinlock自旋锁
completion完成量
原子操作等

原子操作的意义是什么?
操作的不可分割性

掌握信号量,completion....

什么是并发?
多个执行单元同时,并行被执行
竞态race conditions
并发的执行单元对共享资源的访问则很容易导致竞态。
共享资源:硬件资源IO外设,软件上的全局变量,静态变量等

linux内核中,什么情况会发生竞态?
单CPU进程间
中断与进程之间

如何解决竞态问题?
解决方法
互斥访问
保证对共享资源的互斥访问
指一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问。

linux设备驱动中可采用的互斥途径
中断屏蔽
原子操作
自旋锁
信号量
completion

中断屏蔽可解决中断与进程之间的并发

中断屏蔽
可解决中断与进程之间的并发
也可以解决内核抢占式进程之间的并发

主要函数
local irq_disable()//屏蔽中断
...
critical section//临界区
...
local_irq_enable()//打开中断

在临界区对共享资源进行访问 IO等外设

void s3c2410_gpio_setpin(unsigned int pin,unsigned int to )
{
void __iomen *base=S3C24XX_GPIO_BASE(pin);
unsigned long offs=S3C2410_GPIO_OFFSET(pin);
unsigned long flags;
unsigned long dat;

local_irq_save(flags);

dat=__raw_readl(base+0x04);


dat&=~(1<dat|=to<__raw_writel(dat,base+0x04);

local_irq_restore(flags);
}

信号量和互斥体
DECLARE_MUTEX(sem)//定义信号量
if(down_interruptible(&sem))//获取信号量,保护临界区
{
return -ERESTARTSYS;
}
...
critical section//临界区
...
up(&sem);//释放信号量

互斥体实例讲解
互斥体使用实例-mouse driver
linux-2.6.32.2\drivers\input\mousedev.c


禁止中断
信号量
自旋锁

阻塞和非阻塞型IO

掌握select系统调用的实现方法
阻塞型IO的实现方法

进程休眠

阻塞型IO
当用户程序调用read函数时,驱动程序的read并没有准备好数据,怎么办?
当用户程序调用write时,驱动程序的write时缓冲区应满,此时怎么办?

1.用户程序不会管理这些问题
2.驱动程序默认情况下,阻塞改进程。将其置入休眠状态直到请求可继续。
3.或者直接返回,这是非阻塞IO

https://www.wendangku.net/doc/4713654285.html,

进程休眠和唤醒方法
休眠的意义
从调度的运行队列->某个等待队列
直到等到某个事件的发生,再从等待队列返回到运行队列

如何将进程安全的进入休眠状态?
不能在原子上下文进行休眠
休眠时,对外界一无所知,进程必须重新检测等待条件
进程只有确保会被其他进程唤醒,才能进入休眠

Linux的休眠机制
进程由于某种条件没有得到满足而不得不进入休眠状态,然后等待条件得到满足的时候继续运行,进入运行状态。。
这种需求要等待队列机制的支持。

等待队列
在linux驱动程序设计中,可以使用等待队列来实现进程的阻塞,等待队列可看作保持进程的容器,在阻塞进程时,将进程放入等待队列,当唤醒进程时,
从等待队列中取出进程。
每个等待任务都会抽象成一个wait_quene,并且挂载到wait_quene_head上。
linux中等待队列的实现思想:当一个任务需要在某个wait_quene_head上睡眠时,将自己的进程控制
块信息封装到wait_quene中,然后挂载到wait_quene_head的链表中,执行调度睡眠。当某些事件发生
后,另一个任务(进程)会唤醒wait_quene_head上的某个或者所有任务,唤醒工作也就是将等待队列中的任务
设置为可调度状态,并且从队列中删除。

等待队列通过“等待队列头”来管理
类型:struct wait_quene_head_t
定义在
方法一
定义等待队列
wait_quene_head_t xxx_quene;
初始化等待队列
init_waitquene_head(&xxx_quene);

方法二
静态方法
定义并初始化等待队列
DECLARE_WAIT_QUENE_HEAD(xxx_quene);



非阻塞方式
阻塞方式是文件读写操作的默认方式,但应用程序员
可通过使用O_NONBLOCK标志来人为的设置读写操作作为非阻塞方式
(该标志定义在中,在打开文件时指定)

O_NONBLOCK


非阻塞方式
如果设置了O_NONBLOCK标志,read和write的行为是不同的,
如果进程在没有数据就绪时调用read,或者在缓冲区没有空间时
调用了write,系统只是简单地返回-EAGAIN,而不会阻塞进程。

非阻塞IO的设置
调用进程显式的指明不想阻塞
设置filp->f_flag |=O_NONBLOCK
//应用程序以非阻塞IO的方式从串口驱动读取一个字符
int fd;
char buf;
fd=open("/dev/ttySAC0",O_RDWR|O_NONBLOCK);
...
/*串口上无输出也返回,所以要循环尝试读取串口*/*/
while(read(fd,&buf,1)!=1);//轮询方式
printf("%c\n",buf);

poll和select
都是调用驱动程序的poll来实现的
unsigned int(*poll)(struct file *filp,poll_table *wait);
返回可以立即执行操作的位掩码
中声明,驱动程序不需要了解该结构细节

poll函数负责完成两个步骤:
在一个或多个可指示poll状态变化的队列上调用poll_wait
如果当前没有文件描述符可用来执行IO,则内核将使进程在传递到该系统调用的所有文件
文件描述符对应的等待队列上等待
void poll_wait(struct file*,wait_quene_h_t *,poll_ta *);
返回一个用来描述操作是否可以立即无阻塞执行的位掩码
即:使用poll_wait将等待队列添加到poll_table中

位掩码
定义poll位掩码
POLLIN设备可读
POLLRDNORM数据可读normal data is available for reading
POLLOUT设备可写
POLLWRNORM数据可写normal data is available for writing

一般设备可读返回:POLLIN|POLLRDNORM
一般设备可写返回:POLLOUT|POLLWRNORM

范例
static unsigned int xxx_poll(struct file *filp,poll_table *wait)
{
struct xxx_pipe *dev=filp->private_data;
unsigned int mask=0;
down(&dev_sem);

//添加两个等待队列到poll_table中
poll_wait(filp,&dev_inq,wait);
poll_wait(filp,&dev->outq,wait);
if(read_buffer_not_empty)//如果接收buffer不为空,可读
mask|=POLLIN|POLLRDNORM;//可读取
if(write_buffer_not_full)//如果写buffer不满,可写
mask|=POLLOUT|POLLWRNORM;//可写入

up(&dev->sem);
return mask;//返回为掩码
}

select系统调用(返回值)
select调用返回时,返回值有如下情况:
正常情况下返回满足要求的文件描述符个数
经过了timeout等待后仍无文件满足要求,返回值为0
如果select被某个信号中断,它将返回-1并设置errno为EINTR
如果出错,返回-1并设置相应的errno

select系统调用(使用方法)
将要监控的文件添加到文件描述符集
调用select开始监控
判断文件是否发生变化

系统提供4个宏对描述符集进行操作
FD_ZERO(fd_set *set);//清除一个文件描述符集
FD_SET(int fd,fd_set *set);//将一个文件描述符加入文件描述符集中
FD_CLR(int fd,fd_set *set);//将一个文件描述符从文件描述符集中清除
FD_ISSET(int fd,fd_set *set);//判断文件描述符是否

被置位

非阻塞IO方式轮询设备范例
int fd;
fd_set rfds,wfds;//读写文件描述符集
//以非阻塞方式打开/dev/xxx设备文件
fd=open("/dev/xxx",O_RDWR|O_NONBLOCK);
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(fd,&rfds);
FD_SET(fd,&wfds);
select(fd+1,&rfds,&wfds,NULL,NULL );
//数据可获得
if(FD_ISSET(fd,&rfds))
{
//读数据
}
if(FD_ISSET(fd,&wfds))
{
//写数据
}

阶段总结
poll/select在驱动程序中的实现
应用层如何调用驱动中的poll/select

实验
任务一
阻塞型字符设备驱动实验
修改Beep驱动,在设备驱动都函数中实现阻塞,在设备驱动写函数中
实现唤醒。读进程执行时,蜂鸣器叫,写进程执行时,蜂鸣器关闭。
任务二
在memdev驱动中,引入阻塞机制
修改read和write函数,实现阻塞型IO访问
任务三
在memdev驱动中,引入非阻塞型机制
在驱动中实现poll函数,实现非阻塞型IO访问

掌握设备驱动中中断处理例程的实现方法
掌握内核中实现计时,延迟操作的函数

中断与时钟
中断处理程序框架
安装中断处理例程
使能和屏蔽中断
中断顶半部和底半部
中断共享
时间、延迟及延缓操作
计时
延迟
内核定时器

中断分类
按中断来源分类
内部中断
外部中断
按中断是否可屏蔽分类
可屏蔽中断
不可屏蔽中断(NMI)
按中断入口跳转方法的不同分类
向量中断
非向量中断

安装中断处理程序
申请和释放IRQ
int request_irq(unsigned int irq,
irqreturn_t(*hander)(int,void*,struct pt_regs*),
unsigned long flags,const char *dev_name,void *dev_id);
void free_irq(unsigned int irq,void *dev_id);

irq 要申请的中断号
*hander要安装的中断处理函数指针
flags
0 普通外部中断
SA_INTERRUPT快速中断
SA_SHIRQ 共享中断

安装中断处理例程
申请和释放IRQ实例
irqreturn_t
xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
...
/*中断处理的具体内容*/
...
}
/*设备驱动模块加载函数*/
int __init xxx_init(void)
{
...
/*申请中断*/
result=request_irq(xxx_irq,
xxx_interrupt,
SA_INTERRUPT,
"xxx"
NULL);
...
}
/*设备驱动模块卸载函数*/
void __exit xxx_exit(void)
{
...
/*释放中断*/
free_irq(xxx_irq,NULL);
...
}

Linux中断处理流程
产生中断->跳转到中断向量表入口地址
arch/arm/kernel/entery-armv.S
汇编级的工作,主要保存上下文状态
asm_do_IRQ() 中断处理公共段
arch/arm/kernel/irq.c
根据irq编号从已申请的中断(通过request_irq)
找到该irq编号对应的中断处理函数
执行对应的中断处理函数
你自己的中断处理函数
返回到asm_do_IRQ()
返回到entry-armv.S
恢复到中断前的上下文状态,结束中断处理,
继续执行中断发生前的程序

使能和屏蔽单个中断
禁止/使能单个中断
void disable_irq(int irq);
等待

目前的中断处理完成,禁止该IRQ
void disable_irq_nosync(int irq);
禁止并立即返回
void enable_irq(int irq);

禁止/使能所有的中断
void local_irq_disable(void
void local_irq_enabe(void);
把中断状态保存到flags中,然后禁用
void local_irq_save(unsigned long flags);
void local_irq_restore(unsigned long flags );

中断顶半部和底半部
为什么将中断处理程序分成顶半部和底半部?
需求
中断处理要求尽快结束,而不能使中断阻塞时间过长
而有些处理例程确实要完成耗时的任务
解决方案
顶半部,让中断阻塞尽可能的短
底半部,完成耗时的任务


Kobject&Kset
设备驱动模型
Platform驱动程序
中断处理
按键驱动程序

Linux2.6 内核提供了全新的内核设备模型

设备三个元素
总线
驱动
设备

总线是处理器和设备之间的通道
虚拟总线platform
总线由bus_type

函数指针
函数指针是指向函数的指针变量。 因而“函数指针”本身首先应是指针变量,
只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、
数组一样,这里是指向函数。如前所述,C在编译时,每一个函数都有一个入口地址,
该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,
可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,
在这些概念上是大体一致的。函数指针有两个用途:调用函数和做函数的参数。

函数指针的声明方法为:
返回值类型 ( * 指针变量名) ([形参列表]);
注1:“返回值类型”说明函数的返回类型,“(指针变量名 )”中的括号不能省,

括号改变了运算符的优先级。若省略整体则成为一个函数说明,
说明了一个返回的数据类型是指针的函数,后面的“形参列表”
表示指针变量指向的函数所带的参数列表。例如:
int func(int x); /* 声明一个函数 */
int (*f) (int x); /* 声明一个函数指针 */
f=func; /* 将func函数的首地址赋给指针f */
或者使用下面的方法将函数地址赋给函数指针:
f = &func;
赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,
因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。


指针函数:
当一个函数声明其返回值为一个指针时,实际上就是返回一个地址给调用函数,
以用于需要指针或地址的表达式中。
格式:
类型说明符 * 函数名(参数)
当然了,由于返回的是一个地址,所以类型说明符一般都是int。
例如:int *GetDate();
int * aaa(int,int);
函数返回的是一个地址值,经常使用在返回数组的某一元素地址上。


总线描述
struct bus_type{
const char *name;
struct bus_attri

bute *bus_attrs;
struct device_attributes dev_attrs;
struct driver_attribute *drv_attrs;
//int (*f) (int x); /* 声明一个函数指针 */
int (* match)(struct device *dev ,struct device_driver *drv);
int (* uenvent)(struct device *dev,struct kobj_uenvent_env *env);
int (* probe)(struct device *dev);
int (* remove)(struct device *dev);
//支持电源管理时,需要实现 shutdown,suspend,resume这三个函数,
//若不支持,将他们设为null。
void(* shutdown)(struct device *dev);
int (*suspend)(struct device *dev,pm_message_t state);
int (*suspend_late)(struct device *dev,pm_message_t state);
int (*resume_early)(struct device *dev);
int (*resume)(struct device *dev);
struct dev_pm_ops *pm;//设备电源管理
struct bus_type_private *p;//私有数据。完全由驱动核心初始化并使用

}

总线注册和删除
总线的注册使用
bus_register(struct bus_type *bus);
如果成功,新的总线将被添加进系统,并在sysfs的/sys/bus下看到
总线的删除使用
void bus_unregister(struct bus_type *bus);

总线方法
int (* match)(struct device *dev ,struct device_driver *drv);
当一个新设备或者驱动被添加到这个总线时,改方法被调用。用于指定的驱动程序能否
处理指定的设备,如果可以,则返回非零值

int (* uenvent)(struct device *dev,char **envp,int num_envp,char *buffer,int buffer_size);
在为用户空间产生热拔插事件之前,这个方法允许总线添加环境变量

总线属性结构bus_attribute描述,定义如下:
struct bus_attribute{
struct attribute attr;
ssize_t (*show)(struct bus_type *,char *buf);
ssize_t (*store)(struct bus_type *,const char *buf,size_t count);
}

设备描述
Linux系统中的每个设备由一个struct decvice描述:
struct device{
........
struct kobject kobj;
char bus_id[BUS_ID_SIZE];//在总线上唯一标识该设备的字符串
struct bus_type *bus;//设备所在总线
struct decvice_driver *driver;//管理该设备的驱动
void *driver_data;//该设备驱动使用的私有数据成员
struct klist_node knode_class;
struct class *class;
struct attribute_group **groups;
void (*release)(struct device *dev);

}

设备属性
设备属性由struct device_attribute描述:
struct device_attribute{
struct attribute attr;
ssize_t (*show)(struct device *dev,struct device_attribute *attr,char *buf);
ssize_t (*store)(struct device *dev,struct device_attribute,const char *buf,size_t count);
}

Bus.c
Device.c


电协通知:

由于很多会员这学期才开始使用电脑,在安装C语言编程环境的时候遇到了问题,
协会将于3月14日周六晚上7点开始在电气楼114再次演示一下VC6.0的安装及使用,
协会会员可带上笔记本过来实践。也请技术部的成员到场。

2015年03月13日

电子爱好者协会



电协安卓兴趣小组通知:
请安卓兴趣小组成员本周六晚上3月14号7点带安卓作业电脑到电气楼114。

2015年03月13日
电子爱好者协会安卓兴趣小组


















相关文档
相关文档 最新文档