嵌入式Linux驱动程序设计
在 Unix like 系统中,正是有了驱动程序才使得用户可以完全透明的使用计算机系统。设备驱动隐藏了硬件设备的具体的细节和功能,对于不同的硬件设备都提供了一致的接口。比如在 Linux 系统中,为了便于用户的使用,系统把计算机系统的各种设备映射成一系列的特殊的设备文件,叫设备文件节点。用户可以任意的使用它来协助自己完成任何工作。在 Linux 系统中,所有的设备都可归为三类之列,一是字符设备,如键盘等;二是块设备,如磁盘等;第三种就是用于网络的网络设备,这类设备主要是用于网络通讯中,在其他的地方没有什么特别使用。
一、 设备驱动程序的三个主要组成部分
1、 自动配置和初始化子程序;
2、 为 I/O 提供服务的子程序;
3、 中断子程序;
二、 设备驱动入口点分析
此处以字符设备为例列举设备驱动程序的入口点
1、 open 入口点:设备 I/O 操作之前进行的操作,打开了设备后准备 I/O 操作;
2、 close 入口点:对设备操作完成之后对设备的关闭操作;
3、 read 入口点:完成对设备的 open 操作之后就可以以此入口点在设备上读取数据;
4、 write 入口点:完成对设备的 open 操作之后就可以以此入口点在设备上写入数据;
5、 ioctl 入口点:在设备上执行 I/O 控制操作;
6、 select 入口点:在数据写入或读取之前对设备的检测操作。
三、 Linux 系统下的设备驱动程序
以下借用 Linux 系统内核的代码来分析字符设备驱动程序
1 、以下是入口点的结构体定义:
#/usr/include/linux/fs.h
struct file_operations {
struct module *owner;
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 file *, struct dentry *, int datasync);
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 *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};
2 、入口点 file_operations 分析:
1> lseek ,移动文件指针的位置,用于可随机存取的设备;
2> read ,设备读操作,参数 buf 指向的是存放读取结果的缓冲池, count 为将要读取的数据的长度。若函数返回值为负,表示读取操作出错;若操作成功,则返回实际的读取的字节;
3> write ,设备写操作;
4> ioctl ,进行除设备的读写之外的控制操作;
5> open ,打开一个设备,并对其 I/O 操作准备。函数返回值为 0 表示设备打开成功,若返回负,表示操作失败;
6> release ,设备的 close 操作;
4、 Linux 系统下,字符设备驱动的注册:
在 Linux 里通过调用 register_chrdev 向操作系统注册字符型的设备驱动,其定义为:
int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops) ;
参数说明:
major 是设备驱动程序注册的主设备号,若为 0 则系统为此驱动程序动态地分配一个主设备号。
name 是设备文件节点名。此函数返回 0 表示成功。返回 -EINVAL 表示申请的主设备号不合法,一般来说是因为主设备号大于系统所允许的最大设备号。返回 -EBUSY 表示所申请的主设备号正在被其它设备驱动程序使用。如果是动态分配主设备号成功,此函数将返回所分配的主设备号。如果 register_chrdev 操作成功,设备名就可以在 /proc/devices 文件里看到。
|
四、 字符设备驱动程序示例及其分析
//#define CONFIG_DEVFS_FS /* 系统是否支持 devfs*/
#ifndef __KERNEL__ /* 编译进内核 */
# define __KERNEL__
#endif
#ifndef MODULE
# define MODULE /* 以模块方式编译 */
#endif
#include <linux/config.h> /* 配置头文件 */
#include <linux/module.h> /* 驱动模型头文件 */
#include <linux/devfs_fs_kernel.h> /* 设备文件系统头文件 */
#include <linux/init.h> /* 初始化相关头文件 */
#include <linux/kernel.h> /* 与 printk() 等函数有关的头文件 */
#include <linux/slab.h> /* 与 kmalloc() 等函数有关的头文件 */
#include <linux/fs.h> /* 与文件系统有关的头文件 everything... */
#include <linux/errno.h> /* 错误代码处理头文件 error codes */
#include <linux/types.h> /* 数据类型头文件 size_t */
#include <linux/proc_fs.h> /* 与进程调度相关的头文件 */
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/poll.h> /* COPY_TO_USER */
#include <asm/system.h> /* cli(), *_flags */
#define DEVICE_NAME " demo" /* 该驱动的设备名 */
#define demo_MAJOR 254 /* 驱动主设备号 */
#define demo_MINOR 0 /* 驱动次设备号 */
static int MAX_BUF_LEN=1024; /* 定义一缓冲区最大长度 */
static char drv_buf[1024]; /* 定义一缓冲区 */
static int WRI_LENGTH=0;
/***********************************************************************
* 名称: demo_write ()
* 功能:对应用户空间的 write 系统调用,从用户空间拷贝给定长度缓冲区数据到内核空间
* 入口参数: *filp 操作设备文件的 ID , *buffer 对应用户空间的缓冲区的起始地址, count 用户空间数据缓冲区长度
* 出口参数:返回用户空间数据缓冲区长度
**********************************************************************/
static ssize_t demo_write(struct file *filp,const char *buffer, size_t count)
{
if(count > MAX_BUF_LEN)count = MAX_BUF_LEN;
copy_from_user(drv_buf , buffer, count); /* 从用户空间拷贝缓冲区数据到内核空间的关键函数,把用户空间的 buffer 数据传递给 drv_buf 数组 */
WRI_LENGTH = count;
printk("user write data to driver/n");
do_write();
return count;
}
/***********************************************************************
* 名称: static void do_write()
* 功能:
* 入口参数:无
* 出口参数:无
**********************************************************************/
static void do_write()
{
int i;
int len = WRI_LENGTH;
char tmp;
for(i = 0; i < (len>>1); i++,len--){
tmp = drv_buf[len-1];
drv_buf[len-1] = drv_buf[i];
drv_buf[i] = tmp;
}
}
/*************************************************************
* 名称: demo_ read ()
* 功能:对应用户空间的 read 系统调用,从内核空间拷贝给定长度缓冲区数据到用户空间
* 入口参数: *filp 操作设备文件的 ID , *buffer 对应用户空间的缓冲区的起始地址, count 用户空间数据缓冲区长度, *ppos 用户在文件中进行存储操作的位置
* 出口参数:返回用户空间数据缓冲区长度
**********************************************************************/
static ssize_t demo_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
if(count > MAX_BUF_LEN)
count=MAX_BUF_LEN;
copy_to_user(buffer, drv_buf,count); /* 从内核空间拷贝缓冲区数据到用户空间的关键函数,把内核空间经过逆序排列后的 drv_buf 数组传递给用户空间的 buffer 数组 */
printk("user read data from driver/n");
return count;
}
/***********************************************************************
* 名称: demo_ioctl ()
* 功能:对应用户空间的 ioctl 系统调用,对用户空间传递过来的命令进行 swith 判断,并进行相应处理,本函数只对用户空间传递过来的 1 , 2 做简单的处理,打印一条信息
* 入口参数: *filp 操作设备文件的 ID , cmd 对应用户空间的 cmd , arg 对应用户空间传递过来的参数
列表
* 出口参数:正确返回 0 ,错误命令返回 default 的提示内容
**********************************************************************/
static int demo_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
switch(cmd){
case 1:printk("runing command 1 /n");break;
case 2:printk("runing command 2 /n");break;
default:
printk("error cmd number/n");break;
}
return 0;
}
/***********************************************************************
* 名称: static void demo_open ()
* 功能:设备文件打开函数,对应用户空间 open 系统调用,
* 入口参数:设备文件节点
* 出口参数:无
**********************************************************************/
static int demo_open(struct inode *inode, struct file *file)
{
MOD_INC_USE_COUNT;
printk("device open sucess!/n");
return 0;
}
* 名称: static int demo_release
* 功能:设备文件释放函数,对应用户空间 close 系统调用,
* 入口参数:设备文件节点
* 出口参数:无
**********************************************************************/
static int demo_release(struct inode *inode, struct file *filp)
{
MOD_DEC_USE_COUNT;
printk("device release/n");
return 0;
}