在之前, 我们介绍了简单的字符设备驱动,实现了简单的open, close, read, write等驱动提供的基本功能。但是大部分驱动程序还会提供更多的功能。本章我们会介绍ioctrl 系统调用,和用户空间保持同步的几种途径。
一. Ioctl
1. 函数原型 调用
通过设备驱动程序执行各中类型的硬件控制。
用户空间,ioctl 系统调用
int ioctl(int fd, unsigned long request, ...);
内核空间
int (*ioctl) ( struct file *filp, unsigned int cmd, unsigned long arg);
2.Ioctl cmd的选择
Linux中把ioctl cmd划分成几个位段来帮助创建唯一的cmd。这几个位段一般是:type(模数),序号,传输方向和参数大小。在定义的时候可以参考include/asm/ioctl.h 和 Documentation/ioctl-number.txt两个文件,头文件定义了构建cmd命令的宏,而ioctl-number.txt列举了内核中已经使用的tpye,为了唯一性,尽量不要和这里的type重叠。
• Type: 幻数。选择一个号码(j记住先仔细阅读ioctl-number.txt),并在驱动中使用这个号码。这个字段有8位宽。
• number:序(顺序)号. 它是 8 位(_IOC_NRBITS)宽.
• Direction: 数据传送的方向,如果这个特殊的命令涉及数据传送. 可能的值是 _IOC_NONE(没有数据传输), _IOC_READ, _IOC_WRITE, 和 _IOC_READ|_IOC_WRITE (数据在2个方向被传送). 数据传送是从应用程序的观点来看待的; _IOC_READ 意思是从设备读, 因此设备必须写到用户空间. 注意这个成员是一个位掩码, 因此 _IOC_READ 和 _IOC_WRITE 可使用一个逻辑 AND 操作来抽取.
• size:涉及到的用户数据的大小. 这个成员的宽度是依赖体系的, 但是常常是 13 或者 14 位. 你可为你的特定体系在宏 _IOC_SIZEBITS 中找到它的值. 你使用这个 size 成员不是强制的 - 内核不检查它 – 但是它是一个好主意. 正确使用这个成员可帮助检测用户空间程序的错误并使你实现向后兼容, 如果你曾需要改变相关数据项的大小. 如果你需要更大的数据结构, 但是, 你可忽略这个 size 成员. 我们很快见到如何使用这个成员.
下面是一个定义ioctl命令的展示
/*
* Ioctl definitions
*/
/* Use 'k' as magic number */
#define SCULL_IOC_MAGIC 'k'
/* Please use a different 8-bit number in your code */
#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)
/*
* S means "Set" through a ptr,
* T means "Tell" directly with the argument value
* G means "Get": reply by setting through a pointer
* Q means "Query": response is on the return value
* X means "eXchange": switch G and S atomically
* H means "sHift": switch T and Q atomically
*/
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)[]
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)
#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)
/*
3.Ioctl cmd的选择
我们知道copy_from_user和copy_to_user函数可以安全的与用户空间交换数据,但是因为ioctl通常涉及到较小的数据项,因此可以用其他方法更有效的操作。
• access_ok验证地址合理性
api:int access_ok(int type, const void *addr, unsigned long size)
代码实例:
/*
* the direction is a bitmask, and VERIFY_WRITE catches R/W
* transfers. `Type' is user-oriented, while
* access_ok is kernel-oriented, so the concept of "read" and
* "write" is reversed
*/
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;
• 使用access_ok 后,就可以安全的进行数据传输。
put_user(datum, ptr) //当要传递单个数据时,应该用这些宏, 而不是会用copy_to_user
get_user(local, ptr);
4.ioctl命令的实现
代码示例
………见csdn
5.ioctl同read和write的区别:
ioctl被誉为Unix系统的”瑞士军刀”,他被当作扩充Linux系统功能一个通用的方法,在Linux系统中被广泛使用。
ioctl一般用来用户空间程序和驱动程序模块之间传递控制数据,ioctl同read和write的区别是:
• ioctl一般是用来传递控制参数的,比如:串口的波特率、串口的流控方法(xon/xoff、DTR/DSR、RTS/CTS)等等,一般不用来传递“主要的”数据(我不到合适的词来说明:)。
• ioctl的语义一般是非阻塞的,read和write却省是阻塞的。
ioctl的接口是万能的,ioctl(fd, cmd, arg)第三个参数可以是一个整形变量,也可以是一个指向某种数据结构的指针。
二. 阻塞型I/o
1. 休眠概念
当一个进程被置入休眠,他会标记为一种特殊状态并从调度器的运行队列中移走。知道某些情况改变了这个状态,进程才会在任意cpu上调度。为了安全的进入休眠,记住两个原则。
• 永远不要再原子上下文中进入休眠
• 确保能唤醒进程的条件存在
2. 函数原型
等待
wait_event(queue, condition) //非中断休眠
wait_event_interruptible(queue, condition) //可以被信号中断
wait_event_timeout(queue, condition, timeout) //只会等待给定的时间
wait_event_interruptible_timeout(queue, condition, timeout)
唤醒
void wait_up(wait_queue_head_t *queue);
void wait_up_interruptible(wait_queue_head_t *queue);
代码实例:
ssize_t sleepy_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
printk(KERN_DEBUG "process %i (%s) going to sleep\n",
current->pid, current->comm);
wait_event_interruptible(wq, flag != 0);
flag = 0;
printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm);
return 0; /* EOF */
}
ssize_t sleepy_write (struct file *filp, const char __user *buf, size_t count,
loff_t *pos)
{
printk(KERN_DEBUG "process %i (%s) awakening the readers...\n",
current->pid, current->comm);
flag = 1;
wake_up_interruptible(&wq);
return count; /* succeed, to avoid retrial */
}
//测试
输入读命令:
cat /dev/sleepy // 终端阻塞
查看dmesg:
[ 3087.029304] process 17112 (cat) going to sleep
输入写命令:
ls -l > /dev/sleepy //读进程唤醒
查看dmesg:
process 17120 (ls) awakening the readers…
下一章节 介绍 poll 和 select 的用法