Linux设备驱动开发详解-第6章字符设备驱动(一)-globalmem
<address> </address> <address>
</address>
<address> </address> <address> </address>
<address> </address> <address> </address>
<address> </address> <address> </address>
<address> </address> <address> </address> <address> </address> <address>
⑥用户态与内核态数据的交互
用户应用程序与驱动程序分属于不同的进程空间,因此二者之间的数据应当采用以下函数进行交换
2.2 驱动程序初始化和退出
</address>
</address>
</address>
3.1 头文件、宏及设备结构体
在globalmem字符设备驱动中,应包含它要使用的头文件,并定义globalmem设备结构体及相关宏。</address> <address></address>
3.2 加载与卸载设备驱动
globalmem 设备驱动的模块加载和卸载函数遵循代码清单如下: <address></address>
globalmem 设备驱动的读函数:</address> <address></address>
seek()函数对文件定位的起始地址可以是文件开头(SEEK_SET,0) 、当前位置(SEEK_CUR,1)和文件尾(SEEK_END,2) ,globalmem支持从文件开头和当前位置相对偏移。
在定位的时候,应该检查用户请求的合法性,若不合法,函数返回- EINVAL,合法时返回文件的当前位置。
globalmem 设备驱动的seek()函数: <address></address>
㈠globalmem 设备驱动的ioctl()函数
globalmem设备驱动的ioctl()函数接受MEM_CLEAR命令,这个命令会将全局内存的有效数据长度清零,对于设备不支持的命令,ioctl()函数应该返回-EINVAL。
globalmem 设备驱动的ioctl()函数: <address></address>
Linux系统建议以如图3.1所示的方式定义 ioctl()的命令码。 <address>
</address> <address> 命令码的设备类型字段为一个“幻数”,可以是 0~0xff 之间的值,内核中的ioctl-number.txt给出了一些推荐的和已经被使用的“幻数” ,新设备驱动定义“幻数”的时候要避免与其冲突。
命令码的序列号也是8位宽。
命令码的方向字段为2位,该字段表示数据传送的方向,可能的值是_IOC_NONE(无数据传输)、_IOC_READ(读)、_IOC_WRITE(写)和 IOC_READ|_IOC_WRITE(双向)。数据传送的方向是从应用程序的角度来看的。
命令码的数据长度字段表示涉及的用户数据的大小,这个成员的宽度依赖于体系结构,通常是13位或者14位。
内核还定义了_IO()、_IOR()、_IOW()和_IOWR()这 4 个宏来辅助生成命令,这 4个宏的通用定义如下所示。
_IO()、_IOR()、_IOW()和_IOWR()宏定义:</address>
</address>
<address> </address> <address> </address> <address> </address> <address>4.1 应用程序接口函数
</address>
</address>
5.1 编译加载globalmem 模块
</address>
6 扩展
下一篇讲述支持两个globalmem 设备的globalmem 驱动。</address>
</address>
</address> <address style="font-style:normal;">1驱动程序设计之前奏... 2
</address>
<address> </address> <address>2字符设备驱动程序框架... 31.1应用程序、库、内核、驱动程序的关系... 2
1.2设备类型... 2
</address> <address> </address> <address>1.3设备文件... 2
</address> <address> </address> <address>1.4主设备号和从设备号... 2
</address> <address> </address> <address>1.5驱动程序与应用程序的区别... 3
</address> <address> </address> <address>1.6用户态与内核态... 3
</address> <address> </address> <address>1.7Linux驱动程序功能... 3
</address>
<address> </address> <address> </address> <address style="font-style:normal;">2.1file_operations结构体... 4</address>3globalmem虚拟设备实例描述... 62.2驱动程序初始化和退出... 5
<address> </address> <address>2.3将驱动程序模块注册到内核... 5
</address> <address> </address> <address>2.4应用字符设备驱动程序... 5
</address>
<address> </address> <address> </address>
<address> </address> <address>3.1头文件、宏及设备结构体... 6
3.2加载与卸载设备驱动... 6
</address>
<address> </address> <address>4测试应用程序... 173.3读写函数... 8
</address> <address> </address> <address>3.4seek()函数... 9
</address> <address> </address> <address>3.5ioctl()函数... 10
</address> <address> </address> <address>3.6globalmem完整实例... 12
</address>
<address> </address> <address> </address>
<address> </address> <address>5实验步骤... 194.1应用程序接口函数... 17
4.2应用程序... 18
</address>
<address> </address> <address> </address>
<address> </address> <address>6扩展 215.1编译加载globalmem 模块... 19
5.2编译测试应用程序... 20
</address>
<address> </address> <address> </address> <address> </address> <address>
1 驱动程序设计之前奏
</address><address> </address> <address> </address> <address>㈠应用程序、库、内核、驱动程序的关系<address> </address> <address>
㈡设备类型
㈢设备文件
㈣主设备号与从设备号
㈤驱动程序与应用程序的区别
㈥用户态与内核态
㈦Linux驱动程序功能</address>
1.1应用程序、库、内核、驱动程序的关系
<address> </address> <address> </address> <address>■ 应用程序调用应用程序函数库完成功能
■ 应用程序以文件形式访问各种资源
■ 应用程序函数库
</address>
<address> </address> <address> </address> <address>部分函数直接完成功能</address><address> </address> <address> </address> <address>部分函数通过系统调用由内核完成</address>
<address> </address> <address> </address> <address>■ 内核处理系统调用,调用设备驱动程序<address> </address> <address>
■ 设备驱动直接与硬件通信</address>
1.2 设备类型
<address> </address> <address> </address> <address>■ 字符设备<address> </address> <address>
对字符设备发出读/写请求时,实际的硬件I/O操作一般紧接着发生
■ 块设备
块设备与之相反,它利用系统内存作为缓冲区
■ 网络设备
网络设备是一类特殊的设备,它不像字符设备或块设备那样通过对应的设备文件节点访问,也不能直接通过read或write进行数据访问请求</address>
1.3 设备文件
<address> </address> <address> </address> <address>■ 设备类型、主从设备号是内核与设备驱动程序通信时使用的
■ 应用程序使用设备文件节点访问对应设备
■ 每个主从设备号确定的设备都对应一个文件节点
■ 每个设备文件都有其文件属性(c或者b)
■ 每个设备文件都有2个设备号(后面详述)
</address>
<address> </address> <address> </address> <address>主设备号:用于标识驱动程序</address><address> </address> <address> </address> <address>从设备号:用于标识同一驱动程序的不同硬件</address>
<address> </address> <address> </address> <address>■ 设备文件的主设备号必须与设备驱动程序在登记时申请的主设备号一致<address> </address> <address>
■ 系统调用是内核与应用程序之间的接口
■ 设备驱动程序是内核与硬件之间的接口</address>
1.4 主设备号和从设备号
<address> </address> <address> </address> <address>■ 在设备管理中,除了设备类型外,内核还需要一对被称为主从设备号的参数,才能唯一标识一个设备
■ 主设备号相同的设备使用相同的驱动程序
■ 从设备号用于区分具体设备的实例
例:PC的IDE设备,主设备号用于标识该硬盘,从设备号用于标识每个分区
■ 在/dev目录下使用ll命令(ls -l)可以查看各个设备的设备类型、主从设备号等</address>
<address> </address> <address></address>
<address> </address> <address> </address> <address>■ cat /proc/devices可以查看系统中所有设备对应的主设备号</address>
<address> </address> <address><address> </address> <address></address>
1.5 驱动程序与应用程序的区别
<address> </address> <address> </address> <address>■ 应用程序以main开始<address> </address> <address>
■ 驱动程序没有main,它以一个模块初始化函数作为入口
■ 应用程序从头到尾执行一个任务
■ 驱动程序完成初始化之后不再运行,等待系统调用
■ 应用程序可以使用GLIBC等标准C函数库
■ 驱动程序不能使用标准C库</address>
1.6 用户态与内核态
<address> </address> <address> </address> <address>■ 驱动程序是内核的一部分,工作在内核态
■ 应用程序工作在用户态
■ 数据空间访问问题
</address>
<address> </address> <address> </address> <address>★无法通过指针直接将二者的数据地址进行传递</address><address> </address> <address> </address> <address>★系统提供一系列函数帮助完成数据空间转换</address>
<address> </address> <address><address> </address> <address> </address> <address>▶get_user</address><address> </address> <address> </address> <address>▶put_user</address><address> </address> <address> </address> <address>▶copy_from_user</address><address> </address> <address> </address> <address>▶copy_to_user</address>
1.7 Linux驱动程序功能
<address> </address> <address> </address> <address>■ 对设备初始化和释放<address> </address> <address>
■ 把数据从内核传送到硬件和从硬件读取数据
■ 读取应用程序传送给设备文件的数据和回送应用程序请求的数据
■ 检测和处理设备出现的错误</address>
2 字符设备驱动程序框架
<address> </address> <address> </address> <address>①Linux各种设备驱动程序都是以模块的形式存在的,驱动程序同样遵循模块编程的各项原则<address> </address> <address>
②字符设备是最基本、最常用的设备,其本质就是将千差万别的各种硬件设备采用一个统一的接口封装起来,屏蔽了不同设备之间使用上的差异性,简化了应用层对硬件的操作
③字符设备将各底层硬件设备封装成统一的结构体,并采用相同的函数操作,如下等:
open/close/read/write/ioctl
④添加一个字符设备驱动程序,实际上是给上述操作添加对应的代码
⑤Linux对所有的硬件操作统一做以下抽象抽象
file_operations结构体规定了驱动程序向应用程序提供的操作接口</address>
</address>
struct file_operations ext2_file_operations =
{
.llseek = generic_file_llseek,
.read = generic_file_read,
.write = generic_file_write,
.aio_read = generic_file_aio_read,
.aio_write = generic_file_aio_write,
.ioctl = ext2_ioctl,
.mmap = generic_file_mmap,
.open = generic_file_open,
.release = ext2_release_file,
.fsync = ext2_sync_file,
.readv = generic_file_readv,
.writev = generic_file_writev,
.sendfile = generic_file_sendfile,
};
⑥用户态与内核态数据的交互
用户应用程序与驱动程序分属于不同的进程空间,因此二者之间的数据应当采用以下函数进行交换
long copy_to_user(kernel_buffer, user_buffer,n)
//从内核空间拷贝n字节数据到用户空间
copy_from_user(kernel_buffer, user_buffer,n)
//从用户空间拷贝n字节数据到内核空间
put_user(kernel_value, user_buffer)
//从内核空间拷贝一数据变量到用户空间
get_user(kernel_value, user_buffer)
//从用户空间拷贝一数据变量到内核空间
(内核空间数据可是任意类型)
2.1 file_operations结构体
<address> </address> <address>⑴ write函数</address> <address> </address> <address>■ 从应用程序接收数据送到硬件</address> <address> </address> <address>ssize_t (*write)(struct file*, const char __user *, size_t, loff_t*);</address> <address> </address> <address>⑵ read函数</address> <address> </address> <address>■ 从硬件读取数据并交给应用程序</address> <address> </address> <address>ssize_t (*read)(struct file *, char __user *, size_t, loff_t*); /// 从设备中同步读取数据</address> <address> </address> <address>⑶ ioctl函数</address> <address> </address> <address>■ 为应用程序提供对硬件行为的相关配置</address> <address> </address> <address>int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);</address> <address> </address> <address>⑷ open函数</address> <address> </address> <address>■ 当应用程序打开设备时对设备进行初始化</address> <address> </address> <address>■ 使用MOD_INC_USE_COUNT增加驱动程序的使用次数,当模块使用次数不为0时,禁止卸载模块</address> <address> </address> <address>int (*open)(struct inode *,struct file*);</address> <address> </address> <address>⑸ release函数</address> <address> </address> <address>■ 当应用程序关闭设备时处理设备的关闭操作</address> <address> </address> <address>■ 使用MOD_DEC_USE_COUNT减少驱动程序的使用次数,配合open使用,来对模块使用次数进行计数</address> <address> </address> <address>int (*release)(struct inode*, struct file*);</address><address> </address> <address>
2.2 驱动程序初始化和退出
</address>
<address> </address> <address>① 驱动程序初始化函数</address> <address> </address> <address>■ Linux在加载内核模块时会调用初始化函数</address> <address> </address> <address>■ 在初始化函数中首先进行资源申请等工作</address> <address> </address> <address>■ 使用register_chrdev向内核注册驱动程序</address> <address> </address> <address>② 驱动程序退出函数</address> <address> </address> <address>■ Linux在卸载内核模块时会调用退出函数</address> <address> </address> <address>■ 释放驱动程序使用的资源</address> <address> </address> <address>■ 使用unregister_chrdev从内核中卸载驱动程序</address><address> </address> <address>2.3 将驱动程序模块注册到内核
</address>
<address> </address> <address>内核需要知道模块的初始化函数和退出函数,才能将模块放入自己的管理队列中</address> <address> </address> <address>① module_init()</address> <address> </address> <address>向内核声明当前模块的初始化函数</address> <address> </address> <address>② module_exit()</address> <address> </address> <address>向内核声明当前模块的退出函数</address><address> </address> <address> 2.4应用字符设备驱动程序
</address>
<address> </address> <address>㈠加载驱动程序</address> <address> </address> <address>■ insmod 内核模块文件名</address> <address> </address> <address>■ cat /proc/devices 查看当前系统中所有设备驱动程序及其主设备号</address> <address> </address> <address>㈡手动建立设备文件</address> <address> </address> <address>■ 设备文件一般建立/dev目录下</address> <address> </address> <address>■ mknod 文件路径 c [主设备号] [从设备号]</address> <address> </address> <address>㈢应用程序接口函数</address> <address> </address> <address>■ 编写应用层测试程序</address> <address> </address> <address>■ 可以使用标准C的文件操作函数来完成</address>
<address> </address> <address>3 globalmem虚拟设备实例描述<address> </address> <address>①int open(const char *path,int oflag,…);</address><address> </address> <address>★打开名为path的文件或设备</address><address> </address> <address>★成功打开后返回文件句柄</address><address> </address> <address>★常用oflag:O_RDONLY, O_WRONLY, O_RDWR</address><address> </address> <address>②int close(int fd);</address><address> </address> <address>★关闭之前被打开的文件或设备</address><address> </address> <address>★成功关闭返回0,否则返回错误代号</address><address> </address> <address>③ssize_t read(int fd, void*buffer, size_t count)</address><address> </address> <address>★从已经打开的文件或设备中读取数据</address><address> </address> <address>★buffer表示应用程序缓冲区</address><address> </address> <address>★count表示应用程序希望读取的数据长度</address><address> </address> <address>★成功读取后返回读取的字节数,否则返回-1</address><address> </address> <address>④ssize_t write(int fd, void*buffer, size_t count);</address><address> </address> <address>★向已经打开的文件或设备中写入数据</address><address> </address> <address>★buffer表示应用程序缓冲区</address><address> </address> <address>★count表示应用程序希望写入的数据长度</address><address> </address> <address>★成功写入后返回写入的字节数,否则返回-1</address><address> </address> <address>④int ioctl(int fd, unsignedint cmd, unsigned long arg);</address><address> </address> <address>★向驱动程序发送控制命令</address><address> </address> <address>★cmd:用来定义用户向驱动分配的命令</address><address> </address> <address> 例如GPF驱动中:设置指定管脚的高低电平、输入输出特性等</address><address> </address> <address> 为了规范化及错误检查常用_IO宏合成该命令:_IO(MAGIC, num)</address><address> </address> <address>★arg:配置命令参数</address><address> </address> <address> 配合cmd命令完成指定功能</address>
3.1 头文件、宏及设备结构体
在globalmem字符设备驱动中,应包含它要使用的头文件,并定义globalmem设备结构体及相关宏。</address> <address></address>
/*======================================================================
A globalmem driver as an example of char device drivers
The initial developer of the original code is Baohua Song
<author@linuxdriver.cn>. All Rights Reserved.
======================================================================*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#define GLOBALMEM_SIZE 0x1000 /*全局内存最大4K字节*/
#define MEM_CLEAR 0x1 /*清0全局内存*/
#define GLOBALMEM_MAJOR 248 /*预设的globalmem的主设备号*/
static globalmem_major = GLOBALMEM_MAJOR;
/*globalmem设备结构体*/
struct globalmem_dev
{
struct cdev cdev; /*cdev结构体*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/
};
从第25~29行代码可以看出,定义的 globalmem_dev 设备结构体包含了对应于globalmem字符设备的cdev、使用的内存mem[GLOBALMEM_SIZE]。当然,程序中并不一定要把mem[GLOBA-LMEM_SIZE]和cdev包含在一个设备结构体中,但这样定义的好处在于它借用了面向对象程序设计中“封装”的思想,体现了一种良好的编程习惯。3.2 加载与卸载设备驱动
globalmem 设备驱动的模块加载和卸载函数遵循代码清单如下: <address></address>
/*设备驱动模块加载函数*/
int globalmem_init(void)
{
int result;
dev_t devno = MKDEV(globalmem_major, 0);
/* 申请设备号*/
if (globalmem_major)
result = register_chrdev_region(devno, 1, "globalmem");
else /* 动态申请设备号 */
{
result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
globalmem_major = MAJOR(devno);
}
if (result < 0)
return result;
/* 动态申请设备结构体的内存*/
globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
if (!globalmem_devp) /*申请失败*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
globalmem_setup_cdev(globalmem_devp, 0);
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return result;
}
/*模块卸载函数*/
void globalmem_exit(void)
{
cdev_del(&globalmem_devp->cdev); /*注销cdev*/
kfree(globalmem_devp); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); /*释放设备号*/
}
globalmem_init调用globalmem_setup_cdev()函数完成 cdev的初始化和添加,如代码清单如下所示: <address></address> /*初始化并注册cdev*/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmem_major, index);
cdev_init(&dev->cdev, &globalmem_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
在cdev_init()函数中,与globalmem的cdev关联的file_operations结构体如代码清单所示。
globalmem 设备驱动文件操作结构体:
/*文件操作结构体*/
static const struct file_operations globalmem_fops =
{
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
3.3 读写函数 <address>globalmem 设备驱动的读写函数主要是让设备结构体的 mem[]数组与用户空间交互数据,并随着访问的字节数变更返回用户的文件读写偏移位置。读和写函数的实现分别如下所示。globalmem 设备驱动的读函数:</address> <address></address>
/*读函数*/
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
/*内核空间->用户空间*/
if (copy_to_user(buf, (void*)(dev->mem + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
}
return ret;
}
globalmem 设备驱动的写函数:
/*写函数*/
static ssize_t globalmem_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
/*用户空间->内核空间*/
if (copy_from_user(dev->mem + p, buf, count))
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}
return ret;
}
3.4 seek()函数seek()函数对文件定位的起始地址可以是文件开头(SEEK_SET,0) 、当前位置(SEEK_CUR,1)和文件尾(SEEK_END,2) ,globalmem支持从文件开头和当前位置相对偏移。
在定位的时候,应该检查用户请求的合法性,若不合法,函数返回- EINVAL,合法时返回文件的当前位置。
globalmem 设备驱动的seek()函数: <address></address>
/* seek文件定位函数 */
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig)
{
case 0: /*相对文件开始位置偏移*/
if (offset < 0)
{
ret = - EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1: /*相对文件当前位置偏移*/
if ((filp->f_pos + offset) > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
if ((filp->f_pos + offset) < 0)
{
ret = - EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = - EINVAL;
break;
}
return ret;
}
3.5 ioctl()函数㈠globalmem 设备驱动的ioctl()函数
globalmem设备驱动的ioctl()函数接受MEM_CLEAR命令,这个命令会将全局内存的有效数据长度清零,对于设备不支持的命令,ioctl()函数应该返回-EINVAL。
globalmem 设备驱动的ioctl()函数: <address></address>
/* ioctl设备控制函数 */
static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data;/*获得设备结构体指针*/
switch (cmd)
{
case MEM_CLEAR:
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zero\n");
break;
default:
return - EINVAL;
}
return 0;
}
㈡ioctl()命令Linux系统建议以如图3.1所示的方式定义 ioctl()的命令码。 <address>
</address> <address> 命令码的设备类型字段为一个“幻数”,可以是 0~0xff 之间的值,内核中的ioctl-number.txt给出了一些推荐的和已经被使用的“幻数” ,新设备驱动定义“幻数”的时候要避免与其冲突。
命令码的序列号也是8位宽。
命令码的方向字段为2位,该字段表示数据传送的方向,可能的值是_IOC_NONE(无数据传输)、_IOC_READ(读)、_IOC_WRITE(写)和 IOC_READ|_IOC_WRITE(双向)。数据传送的方向是从应用程序的角度来看的。
命令码的数据长度字段表示涉及的用户数据的大小,这个成员的宽度依赖于体系结构,通常是13位或者14位。
内核还定义了_IO()、_IOR()、_IOW()和_IOWR()这 4 个宏来辅助生成命令,这 4个宏的通用定义如下所示。
_IO()、_IOR()、_IOW()和_IOWR()宏定义:</address>
<address> </address> <address></address><address> </address> <address>由于globalmem的MEM_CLEAR命令不涉及数据传输,因此它可以定义如下:由此可见,这几个宏的作用是根据传入的type(设备类型字段)、nr(序列号字段)和size(数据长度字段)和宏名隐含的方向字段移位组合生成命令码。#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0) #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),\ (_IOC_TYPECHECK(size))) #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),\ (_IOC_TYPECHECK(size))) #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr), \ (_IOC_TYPECHECK(size))) /*_IO、_IOR等使用的_IOC宏*/ #define _IOC(dir,type,nr,size) \ (((dir) << _IOC_DIRSHIFT) | \ ((type) << _IOC_TYPESHIFT) | \ ((nr) << _IOC_NRSHIFT) | \ ((size) << _IOC_SIZESHIFT))
</address>
<address> </address><address>#define GLOBALMEM_MAGIC … #define MEM_CLEAR_IO(GLOBALMEM_MAGIC,0)
</address> <address>(三)预定义指令
</address> <address>内核中预定义了一些 I/O 控制命令,如果某设备驱动中包含了与预定义命令一样的命令,这些命令会被当作预定义命令被内核处理而不是被设备驱动处理,预定义命令有如下4种。</address>
<address> </address> <address>FIOCLEX:即File IOctl Close on Exec,对文件设置专用标志,通知内核当exec()系统调用发生时自动关闭打开的文件。</address> <address> </address> <address>FIONCLEX:即File IOctl Not CLose on Exec,与 FIOCLEX 标志相反,清除由FIOCLEX 命令设置的标志。</address> <address> </address> <address>FIOQSIZE:获得一个文件或者目录的大小,当用于设备文件时,返回一个ENOTTY 错误。</address> <address> </address> <address>FIONBIO:即File IOctl Non-Blocking I/O,这个调用修改在filp->f_flags中的 O_NONBLOCK 标志。</address> <address> </address> <address>FIOCLEX、FIONCLEX、FIOQSIZE 和FIONBIO 这些宏的定义如下:</address> <address></address><address> </address> <address> </address> <address>由以上定义可以看出,FIOCLEX、FIONCLEX、FIOQSIZE 和 FIONBIO 的幻数为“T” 。#define FIONCLEX 0x5450 #define FIOCLEX 0x5451 #define FIOQSIZE 0x5460 #define FIONBIO 0x5421
3.6 globalmem完整实例
</address><address> </address> <address> </address> <address></address>4 测试应用程序/*====================================================================== A globalmem driver as an example of char device drivers The initial developer of the original code is Baohua Song <author@linuxdriver.cn>. All Rights Reserved. ======================================================================*/ #include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/cdev.h> #include <asm/io.h> #include <asm/system.h> #include <asm/uaccess.h> #define GLOBALMEM_SIZE 0x1000 /*全局内存最大4K字节*/ #define MEM_CLEAR 0x1 /*清0全局内存*/ #define GLOBALMEM_MAJOR 254 /*预设的globalmem的主设备号*/ static globalmem_major = GLOBALMEM_MAJOR; /*globalmem设备结构体*/ struct globalmem_dev { struct cdev cdev; /*cdev结构体*/ unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/ }; struct globalmem_dev *globalmem_devp; /*设备结构体指针*/ /*文件打开函数*/ int globalmem_open(struct inode *inode, struct file *filp) { /*将设备结构体指针赋值给文件私有数据指针*/ filp->private_data = globalmem_devp; return 0; } /*文件释放函数*/ int globalmem_release(struct inode *inode, struct file *filp) { return 0; } /* ioctl设备控制函数 */ static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg) { struct globalmem_dev *dev = filp->private_data;/*获得设备结构体指针*/ switch (cmd) { case MEM_CLEAR: memset(dev->mem, 0, GLOBALMEM_SIZE); printk(KERN_INFO "globalmem is set to zero\n"); break; default: return - EINVAL; } return 0; } /*读函数*/ static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/ /*分析和获取有效的写长度*/ if (p >= GLOBALMEM_SIZE) return count ? - ENXIO: 0; if (count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE - p; /*内核空间->用户空间*/ if (copy_to_user(buf, (void*)(dev->mem + p), count)) { ret = - EFAULT; } else { *ppos += count; ret = count; printk(KERN_INFO "read %d bytes(s) from %d\n", count, p); } return ret; } /*写函数*/ static ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/ /*分析和获取有效的写长度*/ if (p >= GLOBALMEM_SIZE) return count ? - ENXIO: 0; if (count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE - p; /*用户空间->内核空间*/ if (copy_from_user(dev->mem + p, buf, count)) ret = - EFAULT; else { *ppos += count; ret = count; printk(KERN_INFO "written %d bytes(s) from %d\n", count, p); } return ret; } /* seek文件定位函数 */ static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig) { loff_t ret = 0; switch (orig) { case 0: /*相对文件开始位置偏移*/ if (offset < 0) { ret = - EINVAL; break; } if ((unsigned int)offset > GLOBALMEM_SIZE) { ret = - EINVAL; break; } filp->f_pos = (unsigned int)offset; ret = filp->f_pos; break; case 1: /*相对文件当前位置偏移*/ if ((filp->f_pos + offset) > GLOBALMEM_SIZE) { ret = - EINVAL; break; } if ((filp->f_pos + offset) < 0) { ret = - EINVAL; break; } filp->f_pos += offset; ret = filp->f_pos; break; default: ret = - EINVAL; break; } return ret; } /*文件操作结构体*/ static const struct file_operations globalmem_fops = { .owner = THIS_MODULE, .llseek = globalmem_llseek, .read = globalmem_read, .write = globalmem_write, .ioctl = globalmem_ioctl, .open = globalmem_open, .release = globalmem_release, }; /*初始化并注册cdev*/ static void globalmem_setup_cdev(struct globalmem_dev *dev, int index) { int err, devno = MKDEV(globalmem_major, index); cdev_init(&dev->cdev, &globalmem_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &globalmem_fops; err = cdev_add(&dev->cdev, devno, 1); if (err) printk(KERN_NOTICE "Error %d adding LED%d", err, index); } /*设备驱动模块加载函数*/ int globalmem_init(void) { int result; dev_t devno = MKDEV(globalmem_major, 0); /* 申请设备号*/ if (globalmem_major) result = register_chrdev_region(devno, 1, "globalmem"); else /* 动态申请设备号 */ { result = alloc_chrdev_region(&devno, 0, 1, "globalmem"); globalmem_major = MAJOR(devno); } if (result < 0) return result; /* 动态申请设备结构体的内存*/ globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL); if (!globalmem_devp) /*申请失败*/ { result = - ENOMEM; goto fail_malloc; } memset(globalmem_devp, 0, sizeof(struct globalmem_dev)); globalmem_setup_cdev(globalmem_devp, 0); return 0; fail_malloc: unregister_chrdev_region(devno, 1); return result; } /*模块卸载函数*/ void globalmem_exit(void) { cdev_del(&globalmem_devp->cdev); /*注销cdev*/ kfree(globalmem_devp); /*释放设备结构体内存*/ unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); /*释放设备号*/ } MODULE_AUTHOR("Song Baohua"); MODULE_LICENSE("Dual BSD/GPL"); module_param(globalmem_major, int, S_IRUGO); module_init(globalmem_init); module_exit(globalmem_exit);
<address> </address> <address> </address> <address> </address> <address>4.1 应用程序接口函数
</address>
<address> </address> <address> </address> <address> </address> <address>①open函数</address> <address> </address> <address> </address> <address> </address> <address>int open(const char *path, int oflag,…);</address> <address> </address> <address> </address> <address> </address> <address>打开名为path的文件或设备</address> <address> </address> <address> </address> <address> </address> <address>成功打开后返回文件句柄</address> <address> </address> <address> </address> <address> </address> <address>常用oflag:O_RDONLY, O_WRONLY, O_RDWR</address> <address> </address> <address> </address> <address> </address> <address>②close函数</address> <address> </address> <address> </address> <address> </address> <address>int close(int fd);</address> <address> </address> <address> </address> <address> </address> <address>关闭之前被打开的文件或设备</address> <address> </address> <address> </address> <address> </address> <address>成功关闭返回0,否则返回错误代号 </address> <address> </address> <address> </address> <address> </address> <address>③read函数</address> <address> </address> <address> </address> <address> </address> <address>ssize_t read(int fd, void *buffer, size_tcount)</address> <address> </address> <address> </address> <address> </address> <address>从已经打开的文件或设备中读取数据</address> <address> </address> <address> </address> <address> </address> <address>buffer表示应用程序缓冲区</address> <address> </address> <address> </address> <address> </address> <address>count表示应用程序希望读取的数据长度</address> <address> </address> <address> </address> <address> </address> <address>成功读取后返回读取的字节数,否则返回-1</address> <address> </address> <address> </address> <address> </address> <address>④write函数</address> <address> </address> <address> </address> <address> </address> <address>ssize_t write(int fd, void *buffer, size_tcount);</address> <address> </address> <address> </address> <address> </address> <address>向已经打开的文件或设备中写入数据</address> <address> </address> <address> </address> <address> </address> <address>buffer表示应用程序缓冲区</address> <address> </address> <address> </address> <address> </address> <address>count表示应用程序希望写入的数据长度</address> <address> </address> <address> </address> <address> </address> <address>成功写入后返回写入的字节数,否则返回-1</address> <address> </address> <address> </address> <address> </address> <address>⑤ioctl函数</address> <address> </address> <address> </address> <address> </address> <address>int ioctl(int fd, unsigned int cmd, unsignedlong arg);</address> <address> </address> <address> </address> <address> </address> <address>向驱动程序发送控制命令</address> <address> </address> <address> </address> <address> </address> <address>cmd:用来定义用户向驱动分配的命令</address> <address> </address> <address> </address> <address> </address> <address> 例如GPF驱动中:设置指定管脚的高低电平、输入输出特性等</address> <address> </address> <address> </address> <address> </address> <address> 为了规范化及错误检查常用_IO宏合成该命令:_IO(MAGIC, num)</address> <address> </address> <address> </address> <address> </address> <address>arg:配置命令参数配合cmd命令完成指定功能</address> <address> </address> <address> </address> <address> </address> <address>⑥lseek函数</address> <address> </address> <address> </address> <address> </address> <address>对于随机文件,我们可以随机地指定位置读写,使用如下函数进行定位:</address> <address> </address> <address> </address> <address> </address> <address>int lseek(int fd, offset_t offset, intwhence);</address> <address> </address> <address> </address> <address> </address> <address>lseek()将文件读写指针相对whence移动offset个字节。操作成功时,返回文件指相对于文件头的位置。参数whence可以使用如下值。</address> <address> </address> <address> </address> <address> </address> <address>SEEK_SET:相对文件开头。</address> <address> </address> <address> </address> <address> </address> <address>SEEK_CUR:相对文件读写指针的当前位置。</address> <address> </address> <address> </address> <address> </address> <address>SEEK_END:相对文件末尾。</address> <address> </address> <address> </address> <address> </address> <address>offset可取负值,例如下述调用可将文件指针相对当前位置向前移动 5 个字节。</address> <address> </address> <address> </address> <address> </address> <address>lseek(fd, -5, SEEK_CUR);</address> <address> </address> <address> </address> <address> </address> <address>由于lseek函数的返回值为文件指针相对于文件头的位置,因此下列调用的返回值就文件的长度:</address> <address> </address> <address> </address> <address> </address> <address>lseek(fd, 0, SEEK_END);</address> <address><address> </address> <address> </address> <address> </address> <address>4.2 应用程序
</address>
</address>
<address> </address> <address> </address> <address> </address> <address>①首先向globalmem写入一串"testglobalmem\n"字符</address> <address> </address> <address> </address> <address> </address> <address>②然后再从globalmem读取出来</address> <address> </address> <address> </address> <address> </address> <address>③观察读取来的字符串是否和①写入的字符串相同</address> <address> </address> <address> </address> <address> </address> <address>④清除globalmem设备的内存的字符</address> <address> </address> <address> </address> <address> </address> <address>⑤再次读出globalmem设备的字符,这次读出来的字符应该为’\0’。</address>
<address> </address> <address> </address><address> </address> <address> </address> <address> </address> <address>5 实验步骤#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <fcntl.h> #include <string.h> #define MEM_CLEAR 0x1 /*清0全局内存*/ int main(int argc, char **argv) { int fd = 0; int ret = 0; int length = 0; char buffer[1024]; fd=open("/dev/globalmem",O_RDWR); //以读写的方式打开 if(fd<0) { printf("Can not open /dev/leds\n"); close(fd); return 0; } //写入字符 strcpy(buffer,"test globalmem\n"); length = strlen(buffer); printf("写入的字符length = %d, %s", length, buffer); ret = lseek(fd, 0, SEEK_SET); //定位为相对文件开头0处 ret = write(fd, buffer, length); //读取字符 memset(buffer, 0, 1024); ret = lseek(fd, 0, SEEK_SET); //定位为相对文件开头0处 ret = read(fd, buffer, length); if(ret>0) { printf("清除内存前读出的字符length = %d, %s", ret, buffer); } //清除字符 memset(buffer, 0, 1024); ret = lseek(fd, 0, SEEK_SET); //定位为相对文件开头0处 ret = ioctl(fd, MEM_CLEAR, 0); ret = read(fd, buffer, length); if(ret>0) { printf("清除内存后读出的字符length = %d, %s", ret, buffer); } close(fd); return 0; }
5.1 编译加载globalmem 模块
</address>
[root@localhost globalmem]# make <address> </address> <address> </address> <address> </address> <address>[root@localhost globalmem]# insmodglobalmem.ko</address>
<address> </address> <address> </address> <address> </address> <address>insmod: errorinserting 'globalmem.ko': -1 Device or resource busy</address> <address> </address> <address> </address> <address> </address> <address>这里出现了错误,是因为globalmem的设备号254已经存在,可以查看当前使用的设备号情况:</address> <address> </address> <address> </address> <address> </address> <address>[root@localhostglobalmem]# cat /proc/devices</address> <address> </address> <address> </address> <address> </address> <address>Characterdevices:</address> <address> </address> <address> </address> <address> </address> <address> 1 mem</address> <address> </address> <address> </address> <address> </address> <address> 4 /dev/vc/0</address> <address> </address> <address> </address> <address> </address> <address> 4 tty</address> <address> </address> <address> </address> <address> </address> <address> 4 ttyS</address> <address> </address> <address> </address> <address> </address> <address> 5 /dev/tty</address> <address> </address> <address> </address> <address> </address> <address> 5 /dev/console</address> <address> </address> <address> </address> <address> </address> <address> 5 /dev/ptmx</address> <address> </address> <address> </address> <address> </address> <address> 7 vcs</address> <address> </address> <address> </address> <address> </address> <address> 10 misc</address> <address> </address> <address> </address> <address> </address> <address> 13 input</address> <address> </address> <address> </address> <address> </address> <address> 14 sound</address> <address> </address> <address> </address> <address> </address> <address> 21 sg</address> <address> </address> <address> </address> <address> </address> <address> 29 fb</address> <address> </address> <address> </address> <address> </address> <address> 99 ppdev</address> <address> </address> <address> </address> <address> </address> <address>116 alsa</address> <address> </address> <address> </address> <address> </address> <address>128 ptm</address> <address> </address> <address> </address> <address> </address> <address>136 pts</address> <address> </address> <address> </address> <address> </address> <address>162 raw</address> <address> </address> <address> </address> <address> </address> <address>180 usb</address> <address> </address> <address> </address> <address> </address> <address>189 usb_device</address> <address> </address> <address> </address> <address> </address> <address>202 cpu/msr</address> <address> </address> <address> </address> <address> </address> <address>203 cpu/cpuid</address> <address> </address> <address> </address> <address> </address> <address>249 vmci</address> <address> </address> <address> </address> <address> </address> <address>250 hidraw</address> <address> </address> <address> </address> <address> </address> <address>251 usbmon</address> <address> </address> <address> </address> <address> </address> <address>252 bsg</address> <address> </address> <address> </address> <address> </address> <address>253 pcmcia</address> <address> </address> <address> </address> <address> </address> <address>254 rtc</address> <address> </address> <address> </address> <address> </address> <address>可以看出254已经被rtc所使用。</address> <address> </address> <address> </address> <address> </address> <address>解决方法:更换一个上表未使用的的主设备号(这里我改用248),或者直接让系统自动分配(即是把GLOBALMEM_MAJOR定义为0)。</address> <address> </address> <address> </address> <address> </address> <address>更改完后再次编译和加载globalmem模块:</address> <address> </address> <address> </address> <address> </address> <address>[root@localhostglobalmem]# make</address> <address> </address> <address> </address> <address> </address> <address> [root@localhost globalmem]# insmodglobalmem.ko</address> <address> </address> <address> </address> <address> </address> <address> 加载完后要创建设备号</address> <address> </address> <address> </address> <address> </address> <address> [root@localhost globalmem]# mknod/dev/globalmem c 248 0</address> <address> </address> <address> </address> <address> </address> <address> 到这里模块已经顺利加载,设备节点也创建完毕,接下来就是用应用程序验证了。</address> <address> </address> <address> </address> <address> </address> <address>5.2 编译测试应用程序
</address>
<address> </address> <address> </address> <address> </address> <address>[root@localhost globalmem]# gcc -ogmen_test gmen_test.c</address> <address> </address> <address> </address> <address> </address> <address>[root@localhostglobalmem]# ./gmen_test</address> <address> </address> <address> </address> <address> </address> <address>写入的字符length = 15, test globalmem</address> <address> </address> <address> </address> <address> </address> <address>清除内存前读出的字符length = 15, test globalmem</address> <address> </address> <address> </address> <address> </address> <address>清除内存后读出的字符length = 15, [root@localhost globalmem]#</address><address> </address> <address> </address> <address> </address> <address> 可以看到ret = ioctl(fd, MEM_CLEAR, 0);后,globalmem设备里的内存都被置为了’\0’。
6 扩展
下一篇讲述支持两个globalmem 设备的globalmem 驱动。</address>
7 资源链接
<address>源码地址 点击打开链接</address>
<address>
</address> <address>
