我又来了,一天学习一点点,一天成长一点点就会很开心的说!
今天的状态很好,况且还有好喝的咖啡陪伴着我,元气满满哟!^. ^?
文章目录
[0x100]内容概述
- ioctl 命令编号组成
- 等待队列 与 进程内核空间休眠
- IO多路复用调用与位掩码作用
- 异步I/O通知内核实现函数
[0x200] 编写ioctl的命令编号
[0x210]命令编号组成
- NUMR : 命令顺序编号;
- TYPE : 命令标识类型:通常需要查询 ioctl-numbers.txt 来确定是否冲突;
- DIRT : 命令执行方向:拷贝到用户空间 或者 从用户空间拷贝,或者 双向;
- SIZE : 用户数据大小:通常用于校验数据是否超容,内核不关心;
#include <asm-generic/ioctl.h>
/*参考 内核版本 3.4.39*/
#define _IOC_NRBITS 8
#define _IOC_TYPEBITS 8
#define _IOC_SIZEBITS 14
#ifndef _IOC_DIRBITS
#define _IOC_DIRBITS 2
/*整体来说:总共 32位命令号组成*/
#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1) /*序列编号 = 8位 1 0000 0000-1=FFFF FFFF*/
#define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1) /*类型编号 = 8位*/
#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1) /*数据容量 = 14位*/
#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1) /*读写标识 = 2位*/
#define _IOC_NRSHIFT 0 /*NR Bit_0~Bit_7*/
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) /*TYPE Bit_8~Bit_15*/
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) /*SIZE Bit_16~Bit_29*/
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) /*DIR Bit_30~Bit_31*/
[0x220]创建命令编号
/*封装 读写指令*/
# define _IOC_NONE 0U
# define _IOC_WRITE 1U
# define _IOC_READ 2U
/*IOCMD 位移当前数值对应位置 dir 起始29 type 起始16 nr 起始8 size 起始0*/
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
/*内核空间的用户数据检测 是否大于0 且小于SIZESHIFT 正确返回数据大小,错误调用 */
extern unsigned int __invalid_size_argument_for_IOC;
#define _IOC_TYPECHECK(t) \
((sizeof(t) == sizeof(t[1]) && \
sizeof(t) < (1 << _IOC_SIZEBITS)) ? \
sizeof(t) : __invalid_size_argument_for_IOC)
/*创建命令种类起始序号*/
#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)))
[0x230]这里有个例子
#define PYM_START 't'
#define PYM_IOCMD _IO(PYM_START,0)
#define PYM_RDCMD _IOR(PYM_START,1,int)
#define PYM_WRCMD _IOW(PYM_START,2,int)
#define PYM_IRDWR _IORW(PYM_START,3,int)
[0x300] 休眠与唤醒
数据未就绪、缓冲区已满、设备未准备就绪等情况;
阻塞IO主要用于 针对以上条件无法执行设备读写操作的情况,进程调用于内核空间休眠;
[0x310] 产生休眠的必要条件
- 概念 :由于无法达成下一步执行条件,当前代码自主放弃CPU 执行用于其他进程执行,等待固定事件再次调用;
- 规则1:休眠不能用于原子上下文,以及屏蔽中断的进程中;
- 规则2:必须确定唤醒条件真实存在,才能执行休眠;
- 规则3:必须存在wait_queue 用来记录等待唤醒的进程状态信息;
[0x311] 等待队列相关数据结构
#include <linux/poll.h>
#include <linux/wait.h>
/*关联等待队列处理函数*/
typedef struct poll_table_struct {
poll_queue_proc _qproc;
unsigned long _key;
} poll_table;
/*将等待队列入口与等待队列处理函数,等待进程关联起来*/
struct poll_wqueues {
poll_table
{ poll_queue_proc _qproc; /*等待队列处理函数*/
unsigned long _key;
}pt;
struct poll_table_page /*等待队列入口链表结构*/
{ struct poll_table_page * next;
struct poll_table_entry /*等待队列入口的结构关联等待队列头,和等待队列项*/
{ struct file *filp; /*文件描述符指针*/
unsigned long key;
wait_queue_t /*等待队列项*/
{ unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01 /*独立等待队列项位*/
void *private;
wait_queue_func_t func; /*等待队列项处理函数*/
struct list_head task_list;
}wait;
wait_queue_head_t /*等待队列头*/
{ spinlock_t lock;
struct list_head task_list;
}*wait_address;
} * entry;
struct poll_table_entry entries[0];
}; *table;
struct task_struct *polling_task; /*进程信息结构*/
int triggered;
int error;
int inline_index;
struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES]; /*轮询入口项*/
};
typedef struct __wait_queue wait_queue_t;
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
[0x320] 自动处理休眠与唤醒接口
[0x321]休眠宏接口
- 置位条件等待 :condition == 0 则为休眠;
- 计时时限唤醒 :超时前等待条件唤醒,超时后忽略条件继续执行;
- 设备中断唤醒 :非终止类中断针对唤醒,否则一直阻塞等待
#include <linux/wait.h>
/*设置 休眠前必须设置 等待队列头*/
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
/*如果condistion 为 0 休眠 重载 __wait_event(),如果是1 结束休眠*/
#define wait_event(wq, condition) \
do { \
if (condition) \
break; \
__wait_event(wq, condition); \
} while (0)
/* condistion=0 休眠 否则由schedule_timeout(ret)返回放弃cpu时间
* ret = 小于0 或者等于 0 结束等待
*/
#define __wait_event_timeout(wq, condition, ret) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
if (condition) \
break; \
ret = schedule_timeout(ret); \
if (!ret) \
break; \
} \
finish_wait(&wq, &__wait); \
} while (0)
/*通过 singal_pending 判断有信号处理非零,如果是中断重载当前系统调用,是零则重新循环,直到数据已就绪*/
#define __wait_event_interruptible(wq, condition, ret) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \
if (condition) \
break; \
if (!signal_pending(current)) { \
schedule(); \
continue; \
} \
ret = -ERESTARTSYS; \
break; \
} \
finish_wait(&wq, &__wait); \
} while (0)
[0x322]唤醒宏接口
#include <linux/wait.h >
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
/*唤醒中断等待除了独占等待的所有进程*/
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
/*唤醒中断等待nr个独占等待*/
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
/*唤醒中断等待的所有进程*/
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
/*唤醒等待nr个独占等待*/
#define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL)
/*唤醒等待的所有进程*/
#define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)
/*implement at kernel-3.4.39/kernel/sched/core.c*/
void __wake_up(wait_queue_head_t *q, unsigned int mode,int nr_exclusive, void *key)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr_exclusive, 0, key);
spin_unlock_irqrestore(&q->lock, flags);
}
[0x330]两种创建休眠进程方式
[0x331]手动创建进程休眠过程
- 定义wait_queue_head_t,
- 定义wait_queue_t,
- 初始化等待队列项 [init_wait(&my_wait_queue)];
- 添加等待队列项并调整进程状态TASK_INTERRUPTIBLE[prepare_to_wait()]
- 放弃 CPU 处理处理代码==[schedule()]==;
- 设备返回为 TASK_RUNNING 清理等待队列项 ,finish_wait()
- 检查是否为信号中断 singal_pending(current);
- 执行读写处理
#include <linux/wait.h>
/* implement kernel_dir/kernel/wait.c*/
/*没有WQ_FLAG_EXCLUSIVE 正常处理 */
prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
/*自旋锁 保存当前irq 状态 禁止irq 中断*/
spin_lock_irqsave(&q->lock, flags);
/*检查当前等待队列链表是否为空*/
if (list_empty(&wait->task_list))
/*头插*/
__add_wait_queue(q, wait);
/*设置进程状态为传入值*/
set_current_state(state);
/*自旋解锁 恢复保存irq 状态 恢复irq 中断*/
spin_unlock_irqrestore(&q->lock, flags);
}
/*优先处理拥有WQ_FLAG_EXCLUSIVE的进程 独占等待*/
void prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
unsigned long flags;
wait->flags |= WQ_FLAG_EXCLUSIVE;
/*自旋锁 保存当前irq 状态 禁止irq 中断*/
spin_lock_irqsave(&q->lock, flags);
/*检查当前等待队列链表是否为空*/
if (list_empty(&wait->task_list))
/*尾插*/
__add_wait_queue_tail(q, wait);
/*设置进程状态为传入值*/
set_current_state(state);
/*自旋解锁 恢复保存irq 状态 恢复irq 中断*/
spin_unlock_irqrestore(&q->lock, flags);
}
/*结束执行 清理工作*/
void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
__set_current_state(TASK_RUNNING);
if (!list_empty_careful(&wait->task_list)) {
spin_lock_irqsave(&q->lock, flags);
list_del_init(&wait->task_list);
spin_unlock_irqrestore(&q->lock, flags);
}
}
[0x332]自动创建休眠进程实例
- 配置等待队列头 DECLARE_WAIT_QUEUE_HEAD();
- 设置condition 为 0 休眠;
- 于copy_to_user() 之前 调用 wait_event_interruptible();
- 发现数据后执行 设置 condition 为 1,执行 wake_up_interruptible();
- 执行 copy_to_user()后,将condition 再次设置为0,周期以此类推;
struct scull_pipe
{
wait_queue_head_t inq,outq; //等待队列头 输入 输出
char *buffer,*end; // 用户空间缓冲区开始 与结束位置;
char *rp,*wp; // 当前读位置 、写位置
struct semaphore sem; // 信号量结构
struct cdev cdev; // 字符设备结构体
};
static ssize_t scull_read(struct file *filep,char __user *buf,size_t count,loff_t f_ops)
{
/*使用文件结构私有数据为结构体分配空间*/
struct scull_pipe * pym_dev = filep->private_data;
/*单入读函数*/
if(down_interruptible(&pym_dev->sem))
return -ERESTARTSYS
/*写指针等于读指针表示没有输出,进入循环*/
while(pym_dev->wp == pym_dev->rp)
{
/*为了防止后续的问题返回,导致信号量状态消失,先解锁*/
up(&pym_dev->sem);
/*检查struct file 结构体中的文件打开标志位 是否需要阻塞IO,如果不是退出重试,也可以返回 -EINVAL*/
if(filep->f_flags & O_NONBLOCK)
return -EAGAIN;
/*阻塞IO函数等待condition位= 1 或者中断信号*/
if(wait_event_interruptible(pym_dev->inq,(pym_dev->wp != pym_dev->rp)))
return -ERESTARTSYS;
/*有数据可读,信号量上锁读取数据*/
if(down_interruptible(&pym_dev->sem))
return -ERESTARTSYS;
}
/*写指针移动有输出,可以开始用户可以开始读*/
if(pym_dev->wp > pym_dev->rp)
/*如果wp-rp 位置 获取需要写入的数据字节值,与内核空间的缓冲区比较,以小的数值为应用读取字节值*/
count = min(count,(pym_dev->wp - pym_dev->rp));
else
/*若wp已经释放,则从当前缓冲区末尾获取字节值,与内核空间的缓冲区比较,以小的数值为应用读取字节值*/
count = min(count,(pym_dev->end - pym_dev->rp));
/*拷贝字符到用户空间*/
if(copy_to_user(buf, pym_dev->rp,count))
{
up(&pym_dev->sem);
return -EFAULT;
}
/*移动读指针到读取位置后*/
pym_dev->rp += count;
/*判断是否为缓冲区末尾*/
if(pym_dev->rp == pym_dev->end)
{
/*如果是 则返回指针到缓冲区头部*/
pym_dev->rp = pym_dev->buffer;
}
up(&pym_dev->sem);
/*读缓冲区存在空闲,唤醒其它写函数执行*/
wake_up_interruptible(pym_dev->outq);
return count;
}
[0x400]其他 IO处理方式
[0x410] IO多路复用
- 概念定义:允许进程阻塞等待或者非阻塞多个IO数据流,直到其中任何一个文件IO数据流准备就绪,执行相关操作;
- 函数原型:[unsigned int (* poll) (struct file *filp,poll_table *wait)] ;
- 应用函数: select、poll、epoll;
[0x411] IO 可用位掩码
- 设备可读 或者 设备可写 : POLLIN | POLLRDNORM 或者 POLLOUT | POLLWRNORM;
- 进程到达设备文件末尾 : POLLHUP
- 标识设备无阻塞可读写 : POLLERR
- out_of_band 设备读写 : POLLRDBAND 或者 POLLWRBAND
[0x412]POLL读时返回规则
- 阻塞读:输入缓冲区有空间 数据就绪= POLLIN,无数据可用= 休眠等待;
- 非阻塞:输入缓冲区有空间 数据就绪= POLLIN,无数据可用= -EAGIN 或者 POLL返回设备不可读;
- 数据达到末尾= POLLHUP
[0x413]POLL写时返回规则
- 输出缓冲区有空间:数据就绪= POLLOUT;
- 阻塞写 :输出缓冲区无空间数据就绪= 写函数将被阻塞;
- 非阻塞 :输出缓冲区无空间数据就绪= 返回 -EAGIN 或者 -ENOSPC;
[0x414]轮询入口表中添加等待队列项
#include <linux/poll.h>
void poll_wait(struct file * filep,wait_queue_head_t *qh,poll_table *wq);
将多个文件描述符放入等待队列中,返回哪个描述符可以立即操作的位掩码;
args 1 : 文件描述符
args 2 : 等待队列头指针
args 3 : 等待队列的结构指针
[0x420] 异步通知
[0x421] 产生异步通知的必要条件
- 概念 :信号中断提示处理的异步I/O 方式替代阻塞和轮询处理I/O的方式;
- 规则1:对应文件结构体filep->f_flags 被设置为FASYNC 标志;
- 规则2:定义struct fasync_struct ;
- 规则2:准备工作调用 fasync_helper(),形成一个 struct fasync_struct 结构链表,类似等待队列的结构;
- 规则3:条件触发调用 kill_fasync(),通知进程执行I/O;
[0x422] 相关函数处理与数据结构
#include <linux/fs.h>
/*implement kernel_dir/fs/fcntl.c */
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* 结构的单向链表 */
struct file *fa_file; /* 确定是否开启了 异步通知 f_flags */
struct rcu_head fa_rcu;
};
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
if (!on)
return fasync_remove_entry(filp, fapp);
return fasync_add_entry(fd, filp, fapp);
}
static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp)
{
struct fasync_struct *new;
/*在内核空间分配地址*/
new = fasync_alloc();
if (!new)
return -ENOMEM;
/*执行链表插入*/
if (fasync_insert_entry(fd, filp, fapp, new)) {
fasync_free(new);
return 0;
}
return 1;
}
struct fasync_struct *
fasync_insert_entry(int fd, struct file *filp, struct fasync_struct **fapp, struct fasync_struct *new)
{
struct fasync_struct *fa, **fp;
/*如果fp指针存在,执行链表连接*/
spin_lock(&filp->f_lock);
spin_lock(&fasync_lock);
for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
if (fa->fa_file != filp)
continue;
spin_lock_irq(&fa->fa_lock);
fa->fa_fd = fd;
spin_unlock_irq(&fa->fa_lock);
goto out;
}
/*首次进入 fp指针不存在,进行fp指针初始化*/
spin_lock_init(&new->fa_lock);
new->magic = FASYNC_MAGIC;
new->fa_file = filp;
new->fa_fd = fd;
new->fa_next = *fapp;
rcu_assign_pointer(*fapp, new);
filp->f_flags |= FASYNC;
out:
spin_unlock(&fasync_lock);
spin_unlock(&filp->f_lock);
/*返回struct fasync_struct */
return fa;
}