一、设备ioctl控制
1>功能:大部分驱动出了需要具备读写设备的能力外,还需要具备对硬件控制的能力。例如,要求设
备报告错误信息,改变波特率,这些操作常常通过ioctl方法来实现。
2>用户空间使用方法:
在用户空间,使用ioctl系统调用来控制设备,原型如下
int ioctl(int fd, unsigned long cmd,...)
原型中的点表示这是一个可选的参数,存在与否依赖于控制命令(第2个参数)是否涉及到与设
备的数据交互。
3>驱动ioctl的方法
iotcl驱动方法有和用户空间版本不同的原型:
int(*ioctl)(struct inode* inode, struct file* filp,unsigned int cmd, unsigned long arg)(从2.6.36版本开始已废弃)
long(*inlocked_ioctl)(struct file* filp, unsigned int cmd, unsogned long arg);
cmd参数从用户空间传下来,可选的参数arg以一个unsigned long的形式传递,不管它是一个整数或
一个指针。如果cmd命令不涉及数据传输,则第三个参数arg的值五任何意义。
3>ioctl实现
1、定义命令
字编写unlock_ioctl代码之前,首先需要定义命令。为了防止对错误的设备使用正确的命令,命
令号应该在系统范围内是唯一的。ioctl命令编码被规划为几个位段,include/asm/ioctl.h中定义了
这些字段:类型(幻数), 序号,传递方向,参数的大小。Documentation/ioctl-number.txt文件
中罗列了在内核中已经使用了的幻数。
内核提供了下列宏来帮助定义命令:
_IO(type,nr): 没有参数的命令
_IOR(type,nr,datatype):从驱动中读数据
_IOW(type,nr datatype):从驱动中写数据
_IOWR(type,nr datatype):双向传送,type和number成员作为参数被传递。
定义命令范例:
#define MEM_IOC_MAGIC 'm'//定义幻数
#define MEM_IOCSET
_IOW(MEM_IOC_MAGIC,0,int)
#define MEM_IOCGSET
_IOR(MEM_IOC_MAGIC,1,int)
2、实现命令
定义好了命令,下一步就是实现unlock_ioctl函数了,unlock_inctl函数的实现包括如下3个技术环节:
1、返回值
unlock_ioctl函数的实现通常是根据命令执行的一个switch语句,但是,当命令号不能匹配任何一个设备所支持的命令时,通常返回-EINVAL(“非法参数”)。
2、参数使用
如何使用unlock_ioctl中的参数arg?如果是一个整数,可以直接使用。如果是指针,我们必须确保这个用户地址是有效的,因此使用前需要进行正确的检查。
参数检测:
不需要检测:copy_from_user,copy_to_user,get_user,put_user
需要检测:__get_user,__put_user
int access_ok(int type,const void* addr,unsigned long size)
第一个参数是VERIFY_READ或者VERIFY_WRITE,用来表明是读用户内存还是写用户内存。addr参数是
要操作的用户内存地址,size是操作的长度。如果ioctl粗腰从用户空间度一个整数,那么size参数
等于sizeof(int).access_ok返回一个布尔值:1是成功(存取没问题)和0是失败(存取有问题),
如果该函数返回失败,则ioctl应当返回-EFAULT.
/*参数检测*/
if(_IOC_DIR(cmd)&_IOC_READ)
err = !access_ok(VERIFY_WRITE,(void__user*)arg,_IOC_SIZE(cmd));
else if(_IOC_DIR(cmd)&_IOC_WRITE)
err = !access_ok(VERIFY_READ,(void__user*)arg,_IOC_SIZE(cmd));
if(err)
return -EFAULT;
3、命令操作
/*命令操作*/
switch(cmd)
{
case MEM_IOCSQUANTUM:/*set:arg points to the value*/
retval = __get_user(scull_quantum,(int*)arg);
break;
case MEM_IOCGQUANTUM:/*Get:arg is pointer to result*/
retval = __put_user(scull_quantum,(int*)arg);
break;
default:
return -EINVAL;
}
代码范例:
-
/**************memdev.h*******************/
-
-
#ifndef _MEMDEV_H_
-
#define _MEMDEV_H_
-
-
#include <linux/ioctl.h>
-
-
#ifndef MEMDEV_MAJOR
-
#define MEMDEV_MAJOR 0 /*预设的mem的主设备号*/
-
#endif
-
-
#ifndef MEMDEV_NR_DEVS
-
#define MEMDEV_NR_DEVS 2 /*设备数*/
-
#endif
-
-
#ifndef MEMDEV_SIZE
-
#define MEMDEV_SIZE 4096
-
#endif
-
-
/*mem设备描述结构体*/
-
struct mem_dev
-
{
-
char *data;
-
unsigned long size;
-
};
-
-
/* 定义幻数 */
-
#define MEMDEV_IOC_MAGIC 'k'
-
-
/* 定义命令 */
-
#define MEMDEV_IOCPRINT _IO(MEMDEV_IOC_MAGIC, 1)
-
#define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)
-
#define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)
-
-
#define MEMDEV_IOC_MAXNR 3
-
-
#endif /* _MEMDEV_H_ */
-
-
-
/**************memdev.c*******************/
-
-
#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 <linux/slab.h>
-
#include <asm/io.h>
-
#include <asm/system.h>
-
#include <asm/uaccess.h>
-
-
#include "memdev.h"
-
-
static int mem_major = MEMDEV_MAJOR;
-
module_param(mem_major, int, S_IRUGO);
-
-
struct mem_dev *mem_devp; /*设备结构体指针*/
-
struct cdev cdev;
-
-
/*文件打开函数*/
-
int mem_open(struct inode *inode, struct file *filp)
-
{
-
struct mem_dev *dev;
-
/*获取次设备号*/
-
int num = MINOR(inode->i_rdev);
-
if (num >= MEMDEV_NR_DEVS)
-
return -ENODEV;
-
dev = &mem_devp[num];
-
/*将设备描述结构指针赋值给文件私有数据指针*/
-
filp->private_data = dev;
-
-
return 0;
-
}
-
-
/*文件释放函数*/
-
int mem_release(struct inode *inode, struct file *filp)
-
{
-
return 0;
-
}
-
-
/*
-
* IO操作
-
* */
-
static long memdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
-
{
-
int err = 0;
-
int ret = 0;
-
int ioarg = 0;
-
-
/* 检测命令的有效性 */
-
if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC)
-
return -EINVAL;
-
if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR)
-
return -EINVAL;
-
-
/* 根据命令类型,检测参数空间是否可以访问 */
-
if (_IOC_DIR(cmd) & _IOC_READ)
-
err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
-
else if (_IOC_DIR(cmd) & _IOC_WRITE)
-
err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
-
if (err)
-
return -EFAULT;
-
-
/* 根据命令,执行相应的操作 */
-
switch(cmd) {
-
/* 打印当前设备信息 */
-
case MEMDEV_IOCPRINT:
-
printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n");
-
break;
-
/* 获取参数 */
-
case MEMDEV_IOCGETDATA:
-
ioarg = filp->f_pos;
-
ret = __put_user(ioarg, (int *)arg);
-
break;
-
/* 设置参数 */
-
case MEMDEV_IOCSETDATA:
-
ret = __get_user(ioarg, (int *)arg);
-
if ((ioarg<0) || (ioarg>MEMDEV_SIZE))
-
return -EINVAL;
-
filp->f_pos = ioarg;
-
printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);
-
break;
-
default:
-
return -EINVAL;
-
}
-
return ret;
-
}
-
-
/*
-
* 读函数
-
* */
-
static ssize_t mem_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 mem_dev *dev = filp->private_data; /*获得设备结构体指针*/
-
-
/*判断读位置是否有效*/
-
if (p >= MEMDEV_SIZE)
-
return 0;
-
if (count > MEMDEV_SIZE - p)
-
count = MEMDEV_SIZE - p;
-
-
/*读数据到用户空间*/
-
if (copy_to_user(buf, (void*)(dev->data + p), count)) {
-
ret = - EFAULT;
-
} else {
-
*ppos += count;
-
ret = count;
-
printk(KERN_INFO "read %d bytes(s) from %ld\n", count, p);
-
}
-
return ret;
-
}
-
-
/*
-
* 写函数
-
*/
-
static ssize_t mem_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 mem_dev *dev = filp->private_data; /*获得设备结构体指针*/
-
-
/*分析和获取有效的写长度*/
-
if (p >= MEMDEV_SIZE)
-
return 0;
-
if (count > MEMDEV_SIZE - p)
-
count = MEMDEV_SIZE - p;
-
-
/*从用户空间写入数据*/
-
if (copy_from_user(dev->data + p, buf, count))
-
ret = - EFAULT;
-
else {
-
*ppos += count;
-
ret = count;
-
}
-
return ret;
-
}
-
-
/*文件操作结构体*/
-
static const struct file_operations mem_fops =
-
{
-
.owner = THIS_MODULE,
-
.open = mem_open,
-
.release = mem_release,
-
.unlocked_ioctl = memdev_ioctl,
-
.read = mem_read,
-
.write = mem_write,
-
};
-
-
/*
-
* 设备驱动模块加载函数
-
* */
-
static int memdev_init(void)
-
{
-
int result;
-
int i;
-
-
dev_t devno = MKDEV(mem_major, 0);
-
/* 静态申请设备号*/
-
if (mem_major)
-
result = register_chrdev_region(devno, 2, "memdev");
-
else { /* 动态分配设备号 */
-
result = alloc_chrdev_region(&devno, 0, 2, "memdev");
-
mem_major = MAJOR(devno);
-
}
-
if (result < 0)
-
return result;
-
-
/*初始化cdev结构*/
-
cdev_init(&cdev, &mem_fops);
-
cdev.owner = THIS_MODULE;
-
cdev.ops = &mem_fops;
-
/* 注册字符设备 */
-
cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
-
-
/* 为设备描述结构分配内存*/
-
mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
-
if (!mem_devp) {/*申请失败*/
-
result = - ENOMEM;
-
goto fail_malloc;
-
}
-
memset(mem_devp, 0, sizeof(struct mem_dev));
-
-
/*为设备分配内存*/
-
for (i=0; i < MEMDEV_NR_DEVS; i++) {
-
mem_devp[i].size = MEMDEV_SIZE;
-
mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
-
memset(mem_devp[i].data, 0, MEMDEV_SIZE);
-
}
-
-
return 0;
-
fail_malloc:
-
unregister_chrdev_region(devno, 1);
-
-
return result;
-
}
-
-
/*
-
* 模块卸载函数
-
* */
-
static void memdev_exit(void)
-
{
-
cdev_del(&cdev); /*注销设备*/
-
kfree(mem_devp); /*释放设备结构体内存*/
-
unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
-
}
-
-
MODULE_AUTHOR("David Xie");
-
MODULE_LICENSE("GPL");
-
-
module_init(memdev_init);
-
module_exit(memdev_exit);
-
-
-
-
/**************app_ioctl.c*******************/
-
-
#include <stdio.h>
-
#include <string.h>
-
#include <stdlib.h>
-
#include <unistd.h>
-
#include<sys/types.h>
-
#include<sys/stat.h>
-
#include<fcntl.h>
-
#include "memdev.h" /* 包含命令定义 */
-
-
/* 调用命令MEMDEV_IOCSETDATA */
-
static inline void setpos(int fd, int pos) {
-
printf("ioctl: Call MEMDEV_IOCSETDATA to set position\n");
-
ioctl(fd, MEMDEV_IOCSETDATA, &pos);
-
}
-
-
/* 调用命令MEMDEV_IOCGETDATA */
-
static inline int getpos(int fd) {
-
int pos;
-
printf("ioctl: Call MEMDEV_IOCGETDATA to get position\n");
-
ioctl(fd, MEMDEV_IOCGETDATA, &pos);
-
return pos;
-
}
-
-
int main(void)
-
{
-
int fd = 0;
-
int arg = 0;
-
char buf[256];
-
/*打开设备文件*/
-
if (-1==(fd=open("/dev/memdev0",O_RDWR))) {
-
printf("Open Dev Mem0 Error!\n");
-
_exit(EXIT_FAILURE);
-
}
-
/* 调用命令MEMDEV_IOCPRINT */
-
printf("ioctl: Call MEMDEV_IOCPRINT to printk in driver\n");
-
ioctl(fd, MEMDEV_IOCPRINT, &arg);
-
-
strcpy(buf, "This is a test for ioctl");
-
printf("write %d bytes in new open file\n", strlen(buf));
-
write(fd, buf, strlen(buf));
-
printf("new pos is %d\n", getpos(fd));
-
-
setpos(fd, 0);
-
printf("set pos = 0\n");
-
printf("after setpos(fd, 0), new pos is %d\n", getpos(fd));
-
-
close(fd);
-
return 0;
-
}
二、内核等待队列
在Linux驱动程序设计中,可以使用等待队列来实现进程的阻塞,等待队列可看做保存进程的容器,在阻塞进程时,将进程放入等待队列,当唤醒进程时,从等待队里中取出进程
1>操作等待队里
1、定义等待队列
wait_queue_head_t my_queue
2、初始化等待队列
init_waitqueue_head(&my_queue)
3、定义并初始化等待队列
DECAARE_WAIT_QUEUE_HEAD(my_queue)
4、有条件睡眠
wait_event(queue,condition)
当condition(一个布尔表达式)为真时,立即返回;否则进程进入TASK_UNINTERRUPTIBLE模式的睡眠,并挂在queue参数所指定的等待队列上。
wait_event_interruptible(queue,condition)
当condition(一个布尔表达式)为真时,立即返回;否则进程进入TASK_INTERRUPTIBLE的睡眠,并挂在queue参数所指定的等待队列上。
int wait_event_killable(wait_queue_t queue,condition)
当condition为真时,立即返回;否则进程进入TASK_KILLABLE的睡眠,并挂在queue参数所指定的等待队列上。
5、无条件睡眠(建议不再使用)
sleep_on(wait_queue_head_t* q)
让进程进入不可中断的睡眠,并把它放入等待队列q。
interruptible_sleep_on(wait_queue_head_t* q)
让进程进入可中断的睡眠,并把它放入等待队列q。
6、从等待队列中唤醒进程
wake_up(wait_queue* q)
从等待队列q中唤醒状态为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE的所有进程。
wake_up_interruptible(wait_queue_t* q)
从等待队列q中唤醒状态为TASK_INTERRUPTIBLE的进程。
三、阻塞型字符设备驱动
1>当一个设备无法立刻满足用户的读写请求时应当如何处理?
例如:调用read时没有数据可读,但以后可能会有;或者一个进程试图向设备写入数据,但设备暂时
没有准备好接受数据。应用程序通常不关心这种问题,应用程序只是调用read或write并得到返回
值。驱动程序应当(缺省地)阻塞进程,使得它进入睡眠,知道请求可以得到满足。
2>阻塞方式
在阻塞型驱动程序中,read实现方式如下:如果进程调用read,但设备没有数据或数据不足,进程阻
塞。当新的数据到达后,唤醒被阻塞进程。write的实现方式如下:如果进程调用了write,但设备没
有足够的空间供其写入数据,进程阻塞。当设备中的数据被读走后,缓冲区中空出部分空间,则换醒
进程。
3>非阻塞方式
阻塞方式是文件读写操作的默认方式,但应用程序可通过使用O_NONBLOCK标志来人为的设置读写操作
为非阻塞方式(该标志定义中,在打开文件时指定)。如果设置了O_NONBLOCK标
志,read和write的行为是不同的。如果进程在没有数据就绪时调用了read,或者在缓冲区没有空间
时调用了write,系统只是简单地返回-EAGIN,而不会阻塞进程。
四、Poll设备操作
1>select系统调用
select系统调用用于多路监控,当没有一个文件满足要求时,select将阻塞调用进程。
int select(int maxfd,fd_set* readfds,fd_set* writefds,fe_set* execeptfds,const struct timeval* timeout)
maxfd:文件描述符的范围,比待检测的最大文件描述符大1.
readfds:被读监控的文件描述符集
writefds:被写监控的文件描述符集
exceptfds:被异常监控的文件描述符集
timeout:定时器
timeout取不同的值,该调用由不同的表现:
timeout为0,不管是否有文件满足要求,都立刻返回,无文件满足要求返回0,有文件满足要求返回一个正值。
timeout为NULL,select将阻塞进程,知道某个文件满足要求
timeout值为正整数,就是等待的最长时间,即select在timeout时间内阻塞进程。
select调用返回时,返回值有如下情况:
1、正常情况下返回满足要求的文件描述符个数;
2、经过了timeout等待后仍无文件满足要求,返回值为0;
3、如果select被某个信号中断,它将返回-1并设置errno为EINTR。
4、如果出错,返回-1并设置相应的errno。
使用方法:
1、将要监控的文件添加到文件描述符集;
2、调用select开始监控
3、判断文件是否发生变化
系统提供了4个宏对描述符集进行操作:
#include
void FD_SET(int fd,fd_set* fdset)
void FD_CLR(int fd,fd_set* fdset)
void FD_ZERO(int fd,fd_set* fdset)
void FD_ISSET(int fd,fd_set* fdset)
宏FD_SET将文件描述符fd添加到文件描述符集fdset中;
宏FD_CLR从文件描述符集fdset中清除文件描述符fd;
宏FD_ZERO清空文件描述符集fdset;
在调用select后使用FD_ISSET来检测文件描述符集fdset中的文件fd发生了变化。
2>Poll方法
应用程序使用select系统调用,它可能阻塞进程。这个调用由驱动的poll方法实现,原型为:
unsigned int(*poll)(struct file* filp,poll_table* wait)
poll设备方法负责完成:
1、使用poll_wait将等待队列添加到poll_table中。
2、返回描述设备是否可读或可写的掩码。
掩码:
POLLIN:设备可读
POLLRDNORM:数据可读
POLLOUT:设备可写
POLLWRNORM:数据可写
设备可读通常返回(POLLIN|POLLRDNORM)
设备可写通常返回(POLLOUT|POLLWRNORM)
static unsigned int mem_poll(struct file* filp,poll_table* wait)
{
struct scull_pipe* dev = filp->private_data;
unsigned int mask = 0;
/*把 等待队列 添加到poll_table*/
poll_wait(filp,&dev->inq,wait);
/*返回掩码*/
if(有数据可读)
mask = POLLIN|POLLRDNORM;/*设备可读*/
return mask;
}
Poll方法只是做一个登记,真正的阻塞发生在select.c中的do_select函数。
五、自动创建设备文件
自动创建(2.4内核)
devfs_register(devfs_handle_t dir, const char* name, unsigned int flags, unsigned int major, unsigned int minor, umode_t mode, void* ops, void* info)
在指定的目录张创建设备文件。dir:目录名,为空表示在/dev/目录下创建;name:文件名;flags:创建标志;major:主设备号; minor:次设备号;mode:创建模式;ops:操作函数集;info:通常为空。
自动创建(2.4内核)
从Linux2.6.13开始,devs不复存在了,udev成为devfs的替代。相比devfs,udev(mdev)存在与应
用层,利用udev(mdev)来说实现设备文件的自动创建很简单,在驱动初始化的代码里调用
class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。
例子:
struct class* myclass = class_create(THIS_MODULE,"my_device_driver");
device_create(myclass,NULL,MKDEV(maior_num,0),NULL,"my_device");
当驱动加载时,udev(mdev)就会自动在/dev下创建my_device设备。