LDD读书笔记第六章-高级字符驱动程序操作(一)

ioctl

ioctl是用于控制设备的公共接口,设备除了需要处理读取写入之外还需要一些相关控制信息。这些控制信息就是由ioctl调用来完成
用户空间的ioctl原型为

1
int ioctl(int fd,unsigned long cmd,...);

后面的“…“表示函数的参数个数是可变的(如printf)。
内核空间的ioctl原型

1
2
int (*ioctl)(struct inode *inode,struct file *filp.
		unsigned int cmd,unsigned long arg);

cmd 为指令编号,后面讲述。
arg 为参数。可以是整数也可以是指针,但是内核会以unsigned long形式传送导函数里。

ioctl命令

四个位字段
type:幻数阅读ioctl-number.txt,八位宽,
number:序数(排序序号),八位宽。
direction:
数据传输方向,在应用程序的角度看待。
_IOC_NONE
_IOC_READ
_IOC_WRITE
size:用户数据大小。
构造命令
_IO(type,nr)
_IOR(type,nr,datatype)
_IOW(type,nr,datatype)
_IOWR(type,nr,datatype)
size字段通过sizeof(datatype)取得。
解开宏
_IOC_DIR(nr)
_IOC_TYPE(nr)
_IOC_NR(nr)
_IOC_SIZE(nr)
scull中的ioctl定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
 * 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)

返回值

-INVAL(”Invalid argument)
-ENOTTY (“Inappropriate ioctl for device”)

预定义命令

FIOCLEX 执行时关闭
FIONCLEX 取消执行时关闭
FIOASYNC 设置或复位文件异步通知
FIOQSIZE 返回大小
FIONBIO 非阻塞型IO

使用ioctl参数

验证地址函数

1
int access_ok(int type, const void *addr, unsigned long size);

第一个参数应该是VERIFY_READ 或 VERIFY_WRITE 表明要读取或写入。
函数成功返回1失败返回0.
列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int err = 0, tmp;
int retval = 0;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok( )
*/
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
/*
* 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;

根据1、2、4、8字节优化的传送函数。

1
2
3
4
5
#include <asm/uaccess.h>
put_user(datum, ptr)
__put_user(datum, ptr)
get_user(local, ptr)
__get_user(local, ptr)

传送的大小根据ptr决定。
带双下划线的版本不调用access_ok安全检查要少些。

权能与受限操作

相关的头文件
CAP_DAC_OVERRIDE
越过文件或目录的访问限制(数据访问控制和DAC)的能力。
CAP_NET_ADMIN
执行网络管理任务的能力。
CAP_SYS_MODULE
载入或卸载内核模块的能力
CAP_SYS_RAWIO
执行“裸”IO的能力
CAP_SYS_ADMIN
截获的能力
CAP_SYS_TTY_CONFIG
执行TTY配置的能力、

检查全能

1
2
if (! capable (CAP_SYS_ADMIN))
	return -EPERM

ioctl命令的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
switch(cmd) {
	case SCULL_IOCRESET:
		scull_quantum = SCULL_QUANTUM;
		scull_qset = SCULL_QSET;
		break;
	case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
		if (! capable (CAP_SYS_ADMIN))
			return -EPERM;
		retval = __get_user(scull_quantum, (int __user *)arg);
		break;
	case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
		if (! capable (CAP_SYS_ADMIN))
			return -EPERM;
		scull_quantum = arg;
		break;
	case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
		retval = __put_user(scull_quantum, (int __user *)arg);
		break;
	case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
		return scull_quantum;
	case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
		if (! capable (CAP_SYS_ADMIN))
			return -EPERM;
		tmp = scull_quantum;
		retval = __get_user(scull_quantum, (int __user *)arg);
		if (retval = = 0)
			retval = __put_user(tmp, (int __user *)arg);
		break;
	case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */
		if (! capable (CAP_SYS_ADMIN))
			return -EPERM;
		tmp = scull_quantum;
		scull_quantum = arg;
		return tmp;
	default: /* redundant, as cmd was checked against MAXNR */
		return -ENOTTY;
	}
return retval;

用户角度的调用

1
2
3
4
5
6
7
8
int quantum;
ioctl(fd,SCULL_IOCSQUANTUM, &amp;quantum); /* Set by pointer */
ioctl(fd,SCULL_IOCTQUANTUM, quantum); /* Set by value */
ioctl(fd,SCULL_IOCGQUANTUM, &amp;quantum); /* Get by pointer */
quantum = ioctl(fd,SCULL_IOCQQUANTUM); /* Get by return value */
ioctl(fd,SCULL_IOCXQUANTUM, &amp;quantum);
/* Exchange by pointer */
quantum = ioctl(fd,SCULL_IOCHQUANTUM, quantum); /* Exchange by value */

阻塞型IO

用户的读取或邪写入请求不能立即得到满足时会产生休眠

休眠的简单介绍

两条休眠相关的规则:
1、永远不要在原子上下文中休眠
2、当我们被唤醒的时候,永远无法知道休眠了多长时间,或者休眠期间发生了声明
初始化进程队列头的方法:

1
2
3
4
5
#include <linux/wait.h>
 
DECLARE_WAIT_QUEUE_HEAD(name);
wait_queue_head_t my_queue;
init_waitqueue_head(&amp;my_queue);

进程队列是一个等待特定事件的链表。一个队列用一个进程队列头来管理。

简单休眠

休眠的形式

1
2
3
4
5
/*queue是等待队列头,condition是布尔表达式*/
wait_event(queue, condition)/*非中断休眠*/
wait_event_interruptible(queue, condition)/*可中断休眠,非零表示被某个信号中断*/
wait_event_timeout(queue, condition, timeout)/*带TIMEOUT表示特定时间后结束睡眠*/
wait_event_interruptible_timeout(queue, condition, timeout)

唤醒的形式

1
2
void wake_up(wait_queue_head_t *queue);/*唤醒所有进程*/
void wake_up_interruptible(wait_queue_head_t *queue);/*唤醒可中断进程*/

列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static DECLARE_WAIT_QUEUE_HEAD(wq);
static int flag = 0;
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 */
}

阻塞和非阻塞型操作

filp->f_flags 中的O_NONBLOCK 决定。

一个阻塞I/O示列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
struct scull_pipe {
	wait_queue_head_t inq, outq;
	char *buffer, *end;
	int buffersize;
	char *rp, *wp;
	int nreaders, nwriters;
	struct fasync_struct *async_queue;
	struct semaphore sem;
	struct cdev cdev;
};
static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
	struct scull_pipe *dev = filp-&gt;private_data;
	if (down_interruptible(&amp;dev-&gt;sem))
		return -ERESTARTSYS;
	while (dev-&gt;rp = = dev-&gt;wp) { /* nothing to read */
		up(&amp;dev-&gt;sem); /* release the lock */
		if (filp-&gt;f_flags &amp; O_NONBLOCK)
			return -EAGAIN;
		PDEBUG("\"%s\" reading: going to sleep\n", current-&gt;comm);
		if (wait_event_interruptible(dev-&gt;inq, (dev-&gt;rp != dev-&gt;wp)))
			return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
		/* otherwise loop, but first reacquire the lock */
		if (down_interruptible(&amp;dev-&gt;sem))
			return -ERESTARTSYS;
	}
	/* ok, data is there, return something */
	if (dev-&gt;wp &gt; dev-&gt;rp)
		count = min(count, (size_t)(dev-&gt;wp - dev-&gt;rp));
	else /* the write pointer has wrapped, return data up to dev-&gt;end */
		count = min(count, (size_t)(dev-&gt;end - dev-&gt;rp));
	if (copy_to_user(buf, dev-&gt;rp, count)) {
		up (&amp;dev-&gt;sem);
		return -EFAULT;
	}
	dev-&gt;rp += count;
	if (dev-&gt;rp = = dev-&gt;end)
		dev-&gt;rp = dev-&gt;buffer; /* wrapped */
	up (&amp;dev-&gt;sem);
	/* finally, awake any writers and return */
	wake_up_interruptible(&amp;dev-&gt;outq);
	PDEBUG("\"%s\" did read %li bytes\n",current-&gt;comm, (long)count);
	return count;
}

高级休眠

进程如何休眠:
设置进程状态

1
2
	void set_current_state(int new_state);
	current-&gt;state = TASK_INTERRUPTIBLE; /*不推荐使用*/

调度之前需要检查是否已经符合条件了。

1
2
if (!condition)
	schedule();

手工休眠:

创建等待入口队列

1
2
3
DEFINE_WAIT(my_wait);
wait_queue_t my_wait;
init_wait(&amp;my_wait);

添加到队列

1
2
void prepare_to_wait(wait_queue_head_t *queue,
	wait_queue_t *wait, int state);

添加导队列后可以调用调度函数了。
在调用调度函数前仍然需要进行判断条件是否满足。
调度函数返回时需要开始清理工作。

1
void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);

接下来代码测试状态决定是否需要重新等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/* How much space is free? */
static int spacefree(struct scull_pipe *dev)
{
	if (dev-&gt;rp = = dev-&gt;wp)
		return dev-&gt;buffersize - 1;
	return ((dev-&gt;rp + dev-&gt;buffersize - dev-&gt;wp) % dev-&gt;buffersize) - 1;
}
	static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count,
			loff_t *f_pos)
{
	struct scull_pipe *dev = filp-&gt;private_data;
	int result;
	if (down_interruptible(&amp;dev-&gt;sem))
		return -ERESTARTSYS;
	/* Make sure there's space to write */
	result = scull_getwritespace(dev, filp);
	if (result)
		return result; /* scull_getwritespace called up(&amp;dev-&gt;sem) */
	/* ok, space is there, accept something */
	count = min(count, (size_t)spacefree(dev));
	if (dev-&gt;wp &gt;= dev-&gt;rp)
		count = min(count, (size_t)(dev-&gt;end - dev-&gt;wp)); /* to end-of-buf */
	else /* the write pointer has wrapped, fill up to rp-1 */
		count = min(count, (size_t)(dev-&gt;rp - dev-&gt;wp - 1));
	PDEBUG("Going to accept %li bytes to %p from %p\n", (long)count, dev-&gt;wp, buf);
	if (copy_from_user(dev-&gt;wp, buf, count)) {
		up (&amp;dev-&gt;sem);
		return -EFAULT;
	}
	dev-&gt;wp += count;
	if (dev-&gt;wp = = dev-&gt;end)
		dev-&gt;wp = dev-&gt;buffer; /* wrapped */
	up(&amp;dev-&gt;sem);
	/* finally, awake any reader */
	wake_up_interruptible(&amp;dev-&gt;inq);
	/* blocked in read( ) and select( ) */
	/* and signal asynchronous readers, explained late in chapter 5 */
	if (dev-&gt;async_queue)
		kill_fasync(&amp;dev-&gt;async_queue, SIGIO, POLL_IN);
	PDEBUG("\"%s\" did write %li bytes\n",current-&gt;comm, (long)count);
	return count;
}
/* Wait for space for writing; caller must hold device semaphore. On
* error the semaphore will be released before returning. */
static int scull_getwritespace(struct scull_pipe *dev, struct file *filp)
{
	while (spacefree(dev) = = 0) { /* full */
		DEFINE_WAIT(wait);
		up(&amp;dev-&gt;sem);
		if (filp-&gt;f_flags &amp; O_NONBLOCK)
			return -EAGAIN;
		PDEBUG("\"%s\" writing: going to sleep\n",current-&gt;comm);
		prepare_to_wait(&amp;dev-&gt;outq, &amp;wait, TASK_INTERRUPTIBLE);
		if (spacefree(dev) = = 0)
			schedule( );
		finish_wait(&amp;dev-&gt;outq, &amp;wait);
		if (signal_pending(current))
			return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
		if (down_interruptible(&amp;dev-&gt;sem))
			return -ERESTARTSYS;
	}
	return 0;
}

独占等待
进程队列入口设置了 WQ_FLAG_EXCLUSIVE时为独占等待进程
独占等待进程添加到队列尾部(相反非独占添加到头部)
wake_up时遇到独占等待则停止唤醒其他进程。

1
2
void prepare_to_wait_exclusive(wait_queue_head_t *queue,
			wait_queue_t *wait, int state);

唤醒相关细节

1
2
3
4
5
6
7
8
9
10
wake_up(wait_queue_head_t *queue);/*唤醒所有非独占等待和第一个独占等待者*/
wake_up_interruptible(wait_queue_head_t *queue);/*唤醒所有除了非中断*/
wake_up_nr(wait_queue_head_t *queue, int nr);/*唤醒nr个进程*/
wake_up_interruptible_nr(wait_queue_head_t *queue, int nr);
wake_up_all(wait_queue_head_t *queue);/*唤醒所有*/
wake_up_interruptible_all(wait_queue_head_t *queue);/*唤醒所有非中断进程*/
wake_up_interruptible_sync(wait_queue_head_t *queue);
/*两个老办法的休眠*/
void sleep_on(wait_queue_head_t *queue);
void interruptible_sleep_on(wait_queue_head_t *queue);
----------------- 全文结束 -----------------
© 该日志作者:Senghoo, 于2010年07月17日发表于分类"Linux Kernel"下;
© 除非另有注明本文采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。
© 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
© 转载请注明: LDD读书笔记第六章-高级字符驱动程序操作(一) | 转自: Senghoo's Blog
© Tag: , , ,

你可以继续围观

  • LDD读书笔记第三章-字符设备驱动程序(一)
    设备号 一、简介 字符设备由在 /dev目录中使用 ls -l 使用来分辨,第一列为c是字符设备d为块设备。 一般同一类设备使用相同的设备号。而同种类型的不同设备用次设备号来表示。 列如对于磁盘: brw-rw----  1 root disk    8,   0  7月 14 08:...
  • LDD读书笔记第三章-字符设备驱动程序(二)
    Scull中的设备注册 scull_dev结构: struct scull_dev { struct scull_qset *data; /* Pointer to first quantum set */ int quantum; /* the curren...

发表评论