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, &quantum); /* Set by pointer */ ioctl(fd,SCULL_IOCTQUANTUM, quantum); /* Set by value */ ioctl(fd,SCULL_IOCGQUANTUM, &quantum); /* Get by pointer */ quantum = ioctl(fd,SCULL_IOCQQUANTUM); /* Get by return value */ ioctl(fd,SCULL_IOCXQUANTUM, &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(&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->private_data; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; while (dev->rp = = dev->wp) { /* nothing to read */ up(&dev->sem); /* release the lock */ if (filp->f_flags & O_NONBLOCK) return -EAGAIN; PDEBUG("\"%s\" reading: going to sleep\n", current->comm); if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp))) return -ERESTARTSYS; /* signal: tell the fs layer to handle it */ /* otherwise loop, but first reacquire the lock */ if (down_interruptible(&dev->sem)) return -ERESTARTSYS; } /* ok, data is there, return something */ if (dev->wp > dev->rp) count = min(count, (size_t)(dev->wp - dev->rp)); else /* the write pointer has wrapped, return data up to dev->end */ count = min(count, (size_t)(dev->end - dev->rp)); if (copy_to_user(buf, dev->rp, count)) { up (&dev->sem); return -EFAULT; } dev->rp += count; if (dev->rp = = dev->end) dev->rp = dev->buffer; /* wrapped */ up (&dev->sem); /* finally, awake any writers and return */ wake_up_interruptible(&dev->outq); PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count); return count; } |
高级休眠
进程如何休眠:
设置进程状态
1 2 | void set_current_state(int new_state); current->state = TASK_INTERRUPTIBLE; /*不推荐使用*/ |
调度之前需要检查是否已经符合条件了。
1 2 | if (!condition) schedule(); |
手工休眠:
创建等待入口队列
1 2 3 | DEFINE_WAIT(my_wait); wait_queue_t my_wait; init_wait(&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->rp = = dev->wp) return dev->buffersize - 1; return ((dev->rp + dev->buffersize - dev->wp) % dev->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->private_data; int result; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; /* Make sure there's space to write */ result = scull_getwritespace(dev, filp); if (result) return result; /* scull_getwritespace called up(&dev->sem) */ /* ok, space is there, accept something */ count = min(count, (size_t)spacefree(dev)); if (dev->wp >= dev->rp) count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */ else /* the write pointer has wrapped, fill up to rp-1 */ count = min(count, (size_t)(dev->rp - dev->wp - 1)); PDEBUG("Going to accept %li bytes to %p from %p\n", (long)count, dev->wp, buf); if (copy_from_user(dev->wp, buf, count)) { up (&dev->sem); return -EFAULT; } dev->wp += count; if (dev->wp = = dev->end) dev->wp = dev->buffer; /* wrapped */ up(&dev->sem); /* finally, awake any reader */ wake_up_interruptible(&dev->inq); /* blocked in read( ) and select( ) */ /* and signal asynchronous readers, explained late in chapter 5 */ if (dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN); PDEBUG("\"%s\" did write %li bytes\n",current->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(&dev->sem); if (filp->f_flags & O_NONBLOCK) return -EAGAIN; PDEBUG("\"%s\" writing: going to sleep\n",current->comm); prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE); if (spacefree(dev) = = 0) schedule( ); finish_wait(&dev->outq, &wait); if (signal_pending(current)) return -ERESTARTSYS; /* signal: tell the fs layer to handle it */ if (down_interruptible(&dev->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); |
© 除非另有注明本文采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。
© 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
© 转载请注明: LDD读书笔记第六章-高级字符驱动程序操作(一) | 转自: Senghoo's Blog
© Tag: drivers , ldd , 字符设备 , 驱动