linux 设备驱动程序

57
Linux 设设设设设设 2022设6设10设 设设设

Upload: elmo-edwards

Post on 15-Mar-2016

67 views

Category:

Documents


3 download

DESCRIPTION

Linux 设备驱动程序. 2014年11月10日. 主要内容. 概述 设备文件接口 中断处理 模块化编程 单按键驱动. 概述. 在一个实用的嵌入式系统中,无论是用于人机交互的基本外设,如触摸屏、小键盘、 LCD 等,还是用来完成其他应用功能的精简板卡都属于输入输出设备。 要有效地管理这些输入输出设备,必须了解 Linux/uClinux 中设备管理的基本原理和实现细节. 概述. Linux 作为 UNIX 的一个变种,继承了 UNIX 的设备管理方法,将所有的设备是具体的文件,通过文件系统层对设备进行访问。 - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: Linux 设备驱动程序

Linux 设备驱动程序2023年4月24日 星期一

Page 2: Linux 设备驱动程序

主要内容 概述 设备文件接口 中断处理 模块化编程 单按键驱动

Page 3: Linux 设备驱动程序

概述 在一个实用的嵌入式系统中,无论是用于人机交互的基本外设,如触摸屏、小键盘、 LCD 等,还是用来完成其他应用功能的精简板卡都属于输入输出设备。 要有效地管理这些输入输出设备,必须了解 Linux/uClinux 中设备管理的基本原理和实现细节

Page 4: Linux 设备驱动程序

概述 Linux 作为 UNIX 的一个变种,继承了 UNIX 的设备管理方法,将所有的设备是具体的文件,通过文件系统层对设备进行访问。 这种设备管理方法可以很好地做到“设备无关性”,可以根据硬件外设的更新进行方便的扩展。

Page 5: Linux 设备驱动程序

设备类型 Linux 中的设备大致可以分为三类:

字符设备 块设备 网络设备

Page 6: Linux 设备驱动程序

字符设备与块设备 字符设备没有缓冲区,以字节为单位顺序处理数据,不支持随机读写。常见的字符设备如普通打印机、系统的串口、终端显示器、嵌入式设备中的简单按键、手写板等。 块设备是指在输入输出时数据处理以块为单位的设备,一般都采用缓冲技术,支持数据的随机读写。典型的块设备有硬盘、光驱等。

Page 7: Linux 设备驱动程序

字符设备与块设备 字符设备和块设备面向的上一层是文件系统层。 对用户来说,块设备和字符设备的访问接口都是一组基于文件的系统调用,如 read, write 等。

用户进程

文件系统层

设备驱动层

硬件层

Page 8: Linux 设备驱动程序

网络设备 与块设备和字符设备不同,网络设备面向的上一层是网络协议层。 设备文件是一个唯一的名字(如 eth0 ),在文件系统中不存在对应的节点项。 内核和网络驱动程序之间的通信使用的是一套和数据包传输相关的函数,而不是 read, write 等。

网络协议接口层

网络设备接口层

设备驱动功能层

网络设备、介质层

Page 9: Linux 设备驱动程序

设备号 每一个设备都有一对主设备号、次设备号的参数作为唯一的标识。 主设备号标识设备对应的驱动程序;次设备号用来区分具体驱动程序的实例。 主设备号的获取可以通过动态分配或指定的方式。在嵌入式系统中外设较少,一般采用指定的方式。

Page 10: Linux 设备驱动程序

与设备号相关的宏#include<linux/kdev_t.h>kdev_t dev; unsigned int ma, mi; MAJOR(dev) 获取设备 dev的主设备号 MINOR(dev) 获取设备 dev的次设备号 MKDEV(ma,mi) 根据主设备号ma 和次设备号mi得到相应的设备 dev

Page 11: Linux 设备驱动程序

添加和注销设备 以字符型设备为例 添加设备

extern int register_chrdev(unsigned int major,const char *name,struct file_operations *fops);

Page 12: Linux 设备驱动程序

添加和注销设备 注销设备

extern int unregister_chrdev (unsigned int major,const char *name);

Page 13: Linux 设备驱动程序

设备文件接口 在 Linux 系统中,对用户程序而言,设备驱动程序隐藏了设备的具体细节,对不同的设备提供一致的接口;一般就是把设备映射成一个特殊的设备文件,用户程序像对普通文件一样对设备文件进行操作; 在系统内部,设备的存取通过一组固定的入口点来进行,这组入口点由每个设备的设备驱动程序提供。

Page 14: Linux 设备驱动程序

用户访问接口 字符设备驱动程序能够提供的入口点主要有:

open int open (char *filename, int access);

close int close (int handle);

read int read (int handle, void *buf, int count);

write int write (int handle, void *buf, int count);

ioctl int ioctl(int fd, int cmd, …);

Page 15: Linux 设备驱动程序

file结构 struct file 是一个内核结构 , 不会出现在用户程序中。它代表一个打开的文件,由内核在 open 时创建,并传递给在该文件上进行操作的所有函数,直至最后的 close 函数。

Page 16: Linux 设备驱动程序

Struct file 中重要的字段罗列如下: Mode_t f_modet文件模式通过 FMODE_READ 和 FMODE_WRITE位来标识文件是否可读或可写。 Loff_t f_ops当前读 / 写位置。该位只读,不能直接进行操作。 Unsigned short f_flags文件标志,如 O_RDONLY、 O_NONBLOCK和 O_SYNC 。 struct inode *f_inode打开文件所对应的 I 节点 struct file_operations *f_op与文件对应的操作 void *private_data系统调用 open 函数前可将这个指针置为 NULL 。驱动程序可以将这个字段用于任何目的或者简单忽略这个字段。

Page 17: Linux 设备驱动程序

file_operations 结构 设备驱动程序通过 struct file_operations 结构向系统说明自身提供的入口点。 struct file_operations是一组具体操作的集合,包括打开设备、读取设备等。

Page 18: Linux 设备驱动程序

file_operations 结构原型 struct file_operations { loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t,loff_t *); ssize_t (*write) (struct file *, const char *, size_t,loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct inode *, struct dentry *,int); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); struct module *owner; };

Page 19: Linux 设备驱动程序

打开与释放设备 int (* open) (struct inode *, struct file *)

如果不声明这个操作,系统默认打开永远成功,但不会通知驱动程序。 int (* release) (struct inode *, struct file *)

file结构被释放时会执行这个操作。注意,并不是进程每次调用 close 都会执行 release ,只要 file结构被共享, release就会等到所有的副本都关闭之后才会得到调用。

Page 20: Linux 设备驱动程序

读写设备 ssize_t (* read) (struct file *, char *, size_t, loff_t *); ssize_t (* write) (struct file *, const char *, size_t, loff_t *);

返回值非负代表成功读取或写入的字节数 返回值负值代表着发生了错误。具体数值指明了发生何种错误,并在 <linux/errno.h>中定义其类型

Page 21: Linux 设备驱动程序

ioctl int (* ioctl) (struct inode *, struct file *, unsigned int, unsigned long) 系统调用 ioctl提供一种调用设备特殊命令的操作,它允许应用程序对被驱动硬件的特殊功能进行操作——配置设备以及进入或退出操作模式。而这些控制操作,一般情况下无法通过 read/write 调用来实现。

Page 22: Linux 设备驱动程序

poll unsigned int (* poll) (struct file *, poll_table *);

非阻塞型 I/O的系统调用 poll 和 select 的后端实现。这两个系统调用可用来查询设备是否可读或可写,或是否处于某种特殊状态。 如果驱动程序没有定义poll 方法,它所驱动的设备就被默认为既可读又可写,且不处于其他的特殊状态。 返回值是一个描述设备状态的位掩码。

Page 23: Linux 设备驱动程序

owner struct module *owner

指向“拥有”该结构的模块的指针,内核使用该指针维护模块的使用计数。 常见的初始化 owner 成员 (2.4 内核以上有效 )

owner: THIS_MODULE 使用宏的初始化方法

SET_MODULE_OWNER( &xxx_fops );

Page 24: Linux 设备驱动程序

其他操作 llseek 修改文件的当前读写位置 mmap 将设备内存映射到进程地址空间 flush 在进程关闭设备文件描述符副本的时候,执行设备上尚未完成的操作 lock 实现文件锁定 fsync fsync 系统调用的后台实现,用户调用它来刷新待处理的数据 fasync 通知设备,它的 FASYNC 标志发生变化

Page 25: Linux 设备驱动程序

file_operations 结构初始化 file_operations 结构初始化方法

2.2 内核中static struct file_operations ts_fops = { NULL, /* lseek */ ts_read, /* read */ NULL, /* write */ NULL, /* readdir */ ts_select, /* select */ ts_open, /* open */ ts_release, /* release (close) */ NULL, /* fsync */ ts_fasync /* async notification */};

Page 26: Linux 设备驱动程序

2.4 内核提供了一种标记化的结构初始化方法static struct file_operations ts_fops = {owner: THIS_MODULE,read: ts_read, poll: ts_poll,ioctl: ts_ioctl,open: ts_open,release: ts_release,fasync: ts_fasync,}; 优点 :使驱动程序在结构的定义发生变化时更具有可移植性,并且使得代码更加紧凑。它允许对结构成员进行重新排列,在某些场合下,将频繁被访问的成员放在相同的硬件缓存行上,将大大提高性能。

Page 27: Linux 设备驱动程序

I/O操作 阻塞性操作:

如果进程调用 read ,但还没有数据,进程必须阻塞。当数据到达时,进程被唤醒,并将数据返回给调用者。 如果进程调用 write ,缓冲区又没有空区,进程也必须阻塞。当数据写出设备后,输出缓冲区中空出部分空间,唤醒进程, write 调用即成功完成。

非阻塞性操作: 立即返回。如果进程在没有数据就绪时调用了 read ,或者在缓冲区没有空间时调用了 write ,系统简单返回 -EAGAIN 。

Page 28: Linux 设备驱动程序

阻塞型 I/O 等待队列: wait_queue_head_t 进入睡眠状态:

interruptible_sleep_on (wait_queue_head_t *queue); sleep_on (wait_queue_head_t *queue);

唤醒进程: wake_up_interruptible (wait_queue_head_t *queue); wake_up (wait_queue_head_t *queue);

两种方式的区别: sleep_on 不能被信号取消,适用于不可中断进程 interruptible_sleep_on适用于可中断进程(在驱动程序中使用)

Page 29: Linux 设备驱动程序

非阻塞性 I/O poll 和 select

允许进程决定是否可以对一个或多个打开的文件作非阻塞读或写。 select由BSD UNIX 实现, poll由 System V实现 poll提供比 select 更精确的控制

Page 30: Linux 设备驱动程序

异步触发 打开异步触发的两个步骤:

使用 fcntl 系统调用执行 F_SETOWN命令,修改file->f_owner 为进程 ID ,让内核知道该通知谁。 使用 fcntl 系统调用执行 F_SETFL命令,设置 FASYNC 标志,驱动程序的 fasync 方法被调用。 当数据到达时,发送一个 SIGIO信号给所有注册为异步触发的进程。

Page 31: Linux 设备驱动程序

int fasync_helper (int fd, struct file *filep, int node, struct fasync_struct **fa); 当一个打开文件的 FASYNC 标志被修改时,调用 fasync_helper从相关的进程列表中增加或删除文件。

void kill_fasync (struct fasync_struct **fa, int sig, int band); 在数据达到时通知所有相关进程。

Page 32: Linux 设备驱动程序

中断处理 中断是发挥硬件性能的一个重要方面。 Linux 系统为中断的管理提供了很好的接口,从应用编程角度来看,编写一个中断处理程序只要根据具体应用实现中断服务子程序,并利用一系列的 Linux API函数向内核注册该服务子程序就行了,具体调度处理在 Linux 内部实现。

Page 33: Linux 设备驱动程序

安装和释放中断处理函数int request_irq (unsigned int irq,void (* handler) (int, void *, struct pt_regs *),unsigned long flags, const char *device, void *dev_id);

void free_irq (unsigned int irq, void *dev_id);

Page 34: Linux 设备驱动程序

中断标志 flag:

SA_INTERRUPT: 快速中断处理程序 SA_SHIRQ: 中断可在设备间共享 SA_SAMPLE_RANDOM:

中断时间戳可用来产生系统熵

Page 35: Linux 设备驱动程序

实现中断处理程序 处理程序是在中断期间运行的,因此它的行为受到一些限制。 比如处理程序不能向用户空间发送或者接受数据,因为它不是在任何进程的上下文中执行的。 处理程序不能做任何可能发生睡眠的操作,例如调用 sleep_on 。 处理程序不能调用 schedule 函数。

Page 36: Linux 设备驱动程序

中断号的探测 探测中断号: 1)关闭所有的设备中断; 2 ) sti()打开没有分配的中断号; 3 ) irqs = probe_irq_on(); 4 )要探测的设备产生一个中断; 5)系统得到设备中断; 6 ) irq = probe_irq_off(irqs) 得到设备中断号;

Page 37: Linux 设备驱动程序

/proc 接口 当硬件的中断到达处理器时,内部计数会加 1,这为检查设备是否按预期工作提供了一种方法。 产生的中断报告显示在文件 /proc/interrupts中。

Internal 68VZ328 interrupts 1: 4684 timer 2: 860 M68328_UART

Page 38: Linux 设备驱动程序

模块化编程 Linux 系统中提供了全新的模块化机制:module 可以根据需要在不重新编译内核的情况下,将编译好的模块动态地插入运行中的内核,或者将内核中已经存在的一个模块移走。

Page 39: Linux 设备驱动程序

Hello, World#define MODULE#include <linux/module.h>int init_module(void) { printk (“Hello, world \n”); return 0; }void cleanup_module(void) { printk (“Goodbye cruel world\n”); }

运行结果:root# gcc –D__KERNEL__ -c hello.croot# insmod hello.oHello, worldroot# rmmod helloGoodbye cruel worldroot#

声明这是一个模块

使用内核头文件中被保护的声明

Page 40: Linux 设备驱动程序

初始化和清除函数 旧的内核中用 init_module 来初始化一个刚刚加载的模块,并在模块即将卸载之前调用 cleanup_module 函数。int init_module(void)void cleanup_module(void) 在较新的内核中可以给这两个函数重新命名。比如将模块初始化函数命名为my_init ,将清除函数命名为my_cleanup, 然后用下面两行来进行标识:module_init(my_init);module_exit(my_cleanup);

Page 41: Linux 设备驱动程序

模块的使用计数 为了确定模块是否能够安全卸载,系统为每个模块保留一个使用计数,以此来确定模块是否忙。 在较新的内核版本中,系统能够自动跟踪使用“使用计数”,但有时仍需要我们手动调整使用计数。 手动操作使用计数时使用的三个宏:

MOD_INC_USE_COUNT 当前模块计数加 1 MOD_DEC_USE_COUNT 当前模块计数减 1 MOD_IN_USE 计数非 0 时返回真

注意在 cleanup_module 函数内部不需要检查MOD_IN_USE,因为 rmmod命令先调用系统调用 delete_module ,这个系统调用会先检查模块的使用计数。

Page 42: Linux 设备驱动程序

模块配置参数 在旧内核中,没有显式的参数定义方法,insmod 可以改变模块中任何变量值 2.1.18 以后的内核引入了MODULE_PARM宏,使模块参数更加清楚和安全MODULE_PARM (variable, type)MODULE_PARM_DESC (variable, description)

Page 43: Linux 设备驱动程序

单按键驱动实例硬件原理

Page 44: Linux 设备驱动程序

变量定义 #define button_major 50 // 主设备号 50 DECLEAR_WAIT_QUEUE_HEAD (wq)// 定义等待队列 static char message; struct file_operations button_fops= { read: button_read, open: button_open, release: button_close, };

Page 45: Linux 设备驱动程序

设备初始化 int init_button(void) { int rc; rc=register_chrdev (button_major, “button”, &button_fops); if (rc<0) { printk ("Panic! Could not register BUTTON-Driver\n"); return rc; } ICR = 0x0f00; PDDIR = 0x00; PDSEL = 0x00; PDKBEN = 0x00; return 0; }

Page 46: Linux 设备驱动程序

在 open 函数中向内核注册中断 static int button_open(struct inode *inode, struct file *file) { int rc; rc=request_irq (IRQ6_IRQ_NUM, button_interrupt, IRQ_FLG_STD, “button-IRQ”, NULL); if (rc) { printk (“button-Driver: Error while installing interrupt handler\n”); return –ENODEV; } return 0; }

Page 47: Linux 设备驱动程序

Read 的实现 static ssize_t button_read (struct file *file, char *buff, size_t len, loff_t *offset) { interruptible_sleep_on (&wq); copy_to_user(buff,&message,1); return 1; }

Page 48: Linux 设备驱动程序

release 和 cleanup的实现 int button_close(struct inode *, struct file *) { free_irq(IRQ6_IRQ_NUM,NULL); return 0; } void cleanup_button() { unregister_chrdev(button_major, “button”); }

Page 49: Linux 设备驱动程序

中断处理程序 static void button_interrupt(int irq, void *dev_id, struct pt_regs *regs) { ISR |= (1<<18); // 屏蔽中断 message='A'; wake_up_interruptible (&wq); return; }

Page 50: Linux 设备驱动程序

驱动与系统结合 两种方法:

1、直接编进内核 2 、采用module 方式加载 #define MODULE #include <linux/module.h> module_init(init_button); module_exit(cleanup_button);

Page 51: Linux 设备驱动程序

直接编进内核 1. 将 button.c 复制到 linux-2.4.x/drivers/char目录下 2. 修改 linux-2.4.x/drivers/char/mem.c 文件,在 chr_dev_init()函数中添加:

#ifdef CONFIG_BUTTONinit_button();#endif

3. 修改 linux-2.4.x/drivers/char/Makefile 文件,添加:obj-$(CONFIG_BUTTON) += button.o

Page 52: Linux 设备驱动程序

4. 修改 linux-2.4.x/drivers/char/Config.in ,添加:comment ‘character devices’

if [“$CONFIG_M68VZ328”=”y”]; then ……………

bool ‘68328 button supports’ CONFIG_BUTTON 5. vendors/Arcture/uCdimm目录下,Makefile 文件增加:

DEVICE=button, c, 50, 0

Page 53: Linux 设备驱动程序

按键测试程序 #include <stdio.h> #include <errno.h> #include <string.h> #define DEVICE_FILE_NAME “/dev/button” void main() { int file_desc; char buffer; int bytes_transfered; file_desc = open (DEVICE_FILE_NAME, O_RDWR); if (file_desc < 0) { printf (“Can’t open device file: %s\n”, DEVICE_FILE_NAME”); printf(“Error: %s\n”, strerror(errno)); exit(0); } else printf(“Device %s open, it works\n”, DEVICE_FILE_NAME);

Page 54: Linux 设备驱动程序

while (1) { bytes_transfered = read (file_desc, &buffer, sizeof(buffer)); printf(“%d bytes transfered \n”, bytes_transfered); printf(“the message is %c \n”, buffer); } close (file_desc); }

Page 55: Linux 设备驱动程序

在用户空间运行的驱动程序 优点:

可以和整个 C 库链接 可以用通常的调试程序调试驱动程序代码,不用调试运行内核 驱动程序带来的问题不会挂起整个系统,除非所驱动的硬件发生严重故障 用户内存可以换出,不使用的时候不占用内存 设计良好的驱动程序仍然支持对设备的并发访问

Page 56: Linux 设备驱动程序

在用户空间运行的驱动程序 缺点:

中断在用户空间中不可用 只有通过mmap映射 /dev/mem才能直接访问内存 只有在调用 ioperm或 iopl后才可以访问 I/O端口 响应时间慢 不能处理一些非常重要的设备,包括网络接口和块设备

Page 57: Linux 设备驱动程序

The endThank you