Ioctl命令组成、阻塞IO实现、IO多路复用 >>Linux设备驱动程序

我又来了,一天学习一点点,一天成长一点点就会很开心的说!
今天的状态很好,况且还有好喝的咖啡陪伴着我,元气满满哟!^. ^?

[0x100]内容概述

  1. ioctl 命令编号组成
  2. 等待队列 与 进程内核空间休眠
  3. IO多路复用调用与位掩码作用
  4. 异步I/O通知内核实现函数

[0x200] 编写ioctl的命令编号

[0x210]命令编号组成

  1. NUMR : 命令顺序编号;
  2. TYPE : 命令标识类型:通常需要查询 ioctl-numbers.txt 来确定是否冲突;
  3. DIRT : 命令执行方向:拷贝到用户空间 或者 从用户空间拷贝,或者 双向;
  4. 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]休眠宏接口

  1. 置位条件等待 :condition == 0 则为休眠;
  2. 计时时限唤醒 :超时前等待条件唤醒,超时后忽略条件继续执行;
  3. 设备中断唤醒 :非终止类中断针对唤醒,否则一直阻塞等待
#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]手动创建进程休眠过程

  1. 定义wait_queue_head_t,
  2. 定义wait_queue_t,
  3. 初始化等待队列项 [init_wait(&my_wait_queue)];
  4. 添加等待队列项并调整进程状态TASK_INTERRUPTIBLE[prepare_to_wait()]
  5. 放弃 CPU 处理处理代码==[schedule()]==;
  6. 设备返回为 TASK_RUNNING 清理等待队列项 ,finish_wait()
  7. 检查是否为信号中断 singal_pending(current);
  8. 执行读写处理
#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]自动创建休眠进程实例

  1. 配置等待队列头 DECLARE_WAIT_QUEUE_HEAD();
  2. 设置condition 为 0 休眠;
  3. 于copy_to_user() 之前 调用 wait_event_interruptible();
  4. 发现数据后执行 设置 condition 为 1,执行 wake_up_interruptible();
  5. 执行 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读时返回规则

  1. 阻塞读:输入缓冲区有空间 数据就绪= POLLIN,无数据可用= 休眠等待;
  2. 非阻塞:输入缓冲区有空间 数据就绪= POLLIN,无数据可用= -EAGIN 或者 POLL返回设备不可读;
  3. 数据达到末尾= POLLHUP

[0x413]POLL写时返回规则

  1. 输出缓冲区有空间:数据就绪= POLLOUT;
  2. 阻塞写 :输出缓冲区无空间数据就绪= 写函数将被阻塞;
  3. 非阻塞 :输出缓冲区无空间数据就绪= 返回 -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;
}    

猜你喜欢

转载自blog.csdn.net/god1992/article/details/85220261