(Linux驱动学习 - 7).阻塞IO和非阻塞IO

news2024/10/6 19:40:03

一.阻塞IO和非阻塞IO定义

1.阻塞IO

        当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式IO就会将应用程序对应的线程挂起,直到设备资源可以获取为止。

        在应用程序中,用户调用 open 函数默认是以阻塞式打开设备文件的。

fd = open("/dev/xxx_dev",O_RDWR);        //阻塞式打开

ret = read(fd,&data,sizeof(data));        //读取数据

2.非阻塞IO

        非阻塞IO对设备驱动进行操作的时候,如果不能获取到设备资源,那么应用程序要么一直轮询等待,直到设备资源可以使用,要么就直接放弃

        在应用程序中,若想以非阻塞式打开设备文件,则需要在调用 open 函数时,使用 O_NONBLOCK 标识

fd = open("/dev/xxx_dev",O_RDWR | O_NONBLOCK);

二.等待队列

        阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将
CPU 资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完
成唤醒工作。

         Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作,如果我们要
在驱动中使用等待队列,必须创建并初始化一个等待队列头,等待队列头使用结构体
wait_queue_head_t 表示, wait_queue_head_t 结构体定义在文件 include/linux/wait.h 中,结构体内;

1.等待队列头结构体

struct __wait_queue_head
{
    spinlock_t lock;
    struct list_head task_list;
};

typedef struct __wait_queue_head wait_queue_head_t;

2.初始化等待队列头 - init_waitqueue_head

函数原型:

void init_waitqueue_head(wait_queue_head_t *q);

/* 也可以使用 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义的初始化 */

功能

        初始化一个等待队列头;

参数

        q:要初始化的等待队列的指针;

3.初始化一个等待队列项 - DECLAER_WAITQUEUE

struct __wait_queue
{
    unsigned int flags;
    void *private;
    wait_queue_func_t func;
    struct list_head task_list;
};

typedef struct __wait_queue wait_queue_t;

使用 DECLARE_WAITQUEUE 定义并初始化一个等待队列项

宏原型

DECLARE_WAITQUEUE(name,tsk);

功能

        定义并初始化一个等待队列项;

参数

        name:等待队列项的名字;

        tsk:表示这个等待队列项属于哪个进程(任务),一般设置为 current , current 是一个全局变量, 表示当前进程

4.将队列项添加至等待队列头 - add_wait_queue

函数原型

void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);

功能

        当设备不可访问的时候,将进程对应的等待队列项添加到等待队列头中,就可以让进程进入休眠态;

参数

        q:等待队列头的指针;

        wait:等待队列项的指针;

5.将队列项从等待队列头中移除 - remove_wait_queue

函数原型

void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);

功能

        当设备可以访问以后,将进程对应的等待队列项等待队列头中移除

参数

        q:等待队列头的指针;

        wait:等待队列项的指针;

6.唤醒进入休眠的进程 - wake_up / wake_up_interruptible

函数原型

/* 可唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状态的进程 */
void wake_up(wait_queue_head_t *q);


/* 只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程 */

void wake_up_interruptible(wait_queue_head_t *q);

功能

        将这个等待队列头中的所有进程都唤醒

参数

        q:要唤醒的等待队列头的指针

7.进程以等待事件自动唤醒

三.轮询

        如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,
也就是轮询。 poll、 epoll 和 select 可以用于处理轮询,应用程序通过 select、 epoll 或 poll 函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。

当应用程序调用 select、 epoll 或 poll 函数的时候设备驱动程序中的 poll 函数就会执行,因此需要在设备驱动程序中编写 poll 函数。我们先来看一下应用程序中使用的 select、 poll 和 epoll 这三个函数。
 

1.select 函数(最大数量为1024)

/*
*    @description:        监视文件描述符集的读、写、异常变化
*    @param - nfds    :   所要监视的这三类文件描述符集合中,最大文件描述符加1
*    @param - readfds :   监视指定文件描述符集的读变化
*    @param - writes  :   监视指定文件描述符集的写变化
*    @param - exceptfds:  监视指定文件描述符集的异常
*    @param - timeout :   此变量用来判断是否超时
*    @return          :   超时则返回(0),发生错误则返回(-1),(其他值)表示可以进行操作的文件描述符的个数      
*/
int select(int nfds,
            fd_set *readfds,
            fd_set *writefds,
            fd_set *exceptfds,
            struct timeval *timeout);
/* 用于将 fd_set 变量的所有位都清零 */
void FD_ZERO(fd_set *set);


/* 向 fd_set 变量添加一个文件描述符 */
void FD_SET(int fd,fd_set *set);


/* 向 fd_set 变量中删除一个文件描述符 */
void FD_CLR(int fd,fd_set *set);


/* 用于测试一个文件是否属于某个集合 */
int FD_ISSET(int fd,fd_set *set);

应用程序使用示例:

void main(void)
{
    int ret, fd;             /* 要监视的文件描述符 */
    fd_set readfds;         /* 读操作文件描述符集 */
    struct timeval timeout; /* 超时结构体 */

    fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */

    FD_ZERO(&readfds);     /* 清除 readfds */
    FD_SET(fd, &readfds); /* 将 fd 添加到 readfds 里面 */

    /* 构造超时时间 */
    timeout.tv_sec = 0;
    timeout.tv_usec = 500000; /* 500ms */

    /* 只关心文件是否可读 */
    ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
    switch (ret) 
    {
        case 0: /* 超时 */
            printf("timeout!\r\n");
            break;
        case -1: /* 错误 */
            printf("error!\r\n");
            break;
        default: 
            /* 可以读取数据 */
            if(FD_ISSET(fd, &readfds)) 
            { 
                /* 判断是否为 fd 文件描述符 */
                /* 使用 read 函数读取数据 */
            }
            break;
    }
}

2.poll 函数(没有最大数量限制)

函数原型

/*
*    @description:        监视文件描述符集合的事件
*    @param - fds    :    要监视的文件描述符集合及要监视的事件
*    @param - nfds_t :    poll函数要监视的文件描述符的数量
*    @param - timeout:    超时时间,单位为 ms
*    @return         :    超时则返回(0),发生错误返回(-1),成功则返回发生事件的文件描述符的数量
*/
int poll(struct pollfd *fds,nfds_t nfds,int timeout);


struct pollfd
{
    int fd;            //文件描述符
    short events;      //关心的事件
    short revents;     //返回的事件
};

可监视的事件如下

POLLIN         有数据可以读取。
POLLPRI        有紧急的数据需要读取。
POLLOUT        可以写数据。
POLLERR        指定的文件描述符发生错误。
POLLHUP        指定的文件描述符挂起。
POLLNVAL       无效的请求。
POLLRDNORM     等同于 POLLIN

应用程序示例代码:

void main(void)
{
    int ret;
    int fd;              /* 要监视的文件描述符 */
    struct pollfd fds;

    fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */

    /* 构造结构体 */
    fds.fd = fd;
    fds.events = POLLIN;            /* 监视数据是否可以读取 */
    ret = poll(&fds, 1, 500);       /* 轮询文件是否可操作,超时 500ms */
    if (ret) 
    { 
    /* 数据有效 */
        //......
    /* 读取数据 */
        //......
    } 
    else if (ret == 0) 
    { /* 超时 */
        //......
    } 
    else if (ret < 0) 
    { /* 错误 */
        //......
    }
}

3.epoll 函数

        传统的 selcetpoll 函数都会随着所监听的 fd 数量的增加,出现效率低下的问题,而且
poll 函数每次必须遍历所有的描述符来检查就绪的描述符,这个过程很浪费时间。为此, epoll
应运而生, epoll 就是为处理大并发而准备的,一般常常在网络编程中使用 epoll 函数。

(1).创建一个 epoll 句柄

/**
 * @descrption:         创建一个 epoll 句柄
 * @param - size    :   从Linux2.6.8开始,此参数已经没有意义了,随便传入一个大于 0 的数就可以
 * @return          :   成功时返回(epoll 句柄),失败则返回(-1)
 */
int epoll_create(int size)

(2).epoll 操作指令 - epoll_ctl

函数原型:

/**
 * @description:        向 epoll 句柄中添加要监视的文件描述符以及关心的事件
 * @param - epfd    :   要操作的 epoll 句柄
 * @param - op      :   要对 epfd 进行的操作
 * @param - fd      :   要监视的文件描述符
 * @param - event   :   要监视的事件类型
 */
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)


/* epoll_event 结构体 */
struct epoll_event
{
    uint32_t events;        //epoll 事件
    epoll_data_t data;      //用户数据
};

其中 op 对应的操作可以为以下所示:

EPOLL_CTL_ADD         向 epfd 添加文件参数 fd 表示的描述符。
EPOLL_CTL_MOD         修改参数 fd 的 event 事件。
EPOLL_CTL_DEL         从 epfd 中删除 fd 描述符。

要监视的事件类型可以为以下所示:

EPOLLIN             有数据可以读取。
EPOLLOUT            可以写数据。
EPOLLPRI            有紧急的数据需要读取。
EPOLLERR            指定的文件描述符发生错误。
EPOLLHUP            指定的文件描述符挂起。
EPOLLET             设置 epoll 为边沿触发,默认触发模式为水平触发。
EPOLLONESHOT        一次性的监视,当监视完成以后还需要再次监视某个 fd,那么就需要将
                    fd 重新添加到 epoll 里面

(3).等待事件发生 - epoll_wait

函数原型

/**
 * @description:                等待事件发生
 * @param - epfd        :       epoll 句柄
 * @param - events      :       指向 epoll_events 结构体的数组,当有事件发生时,Linux内核会填写 events,用户可通过 events 判断发生了哪些事件
 * @param - maxevents   :       events 数组的大小,必须 > 0
 * @param - timeout     :       超时事件,单位为ms
 * @return              :       超时则返回(0),失败则返回(-1),成功则返回就绪的文件描述符的数量    
 */
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout)


4.Linux 驱动程序下的 poll 操作函数

(1).驱动程序下的 poll 函数

函数原型

/**
 * @description:            驱动程序中的 poll 函数
 * @param - filp     :      文件描述符
 * @param - wait     :      结构体 poll_table_struct 类型指针,由应用程序传递进来的,一般将此参数传递给 poll_wait 函数  
 * @return           :      POLLIN 有数据可以读取。
                            POLLPRI 有紧急的数据需要读取。
                            POLLOUT 可以写数据。
                            POLLERR 指定的文件描述符发生错误。
                            POLLHUP 指定的文件描述符挂起。
                            POLLNVAL 无效的请求。
                            POLLRDNORM 等同于 POLLIN,普通数据可读
 */
unsigned int (*poll)(struct file *filp,struct poll_table_struct *wait)

(2).将应用程序添加到 poll_table 中 - poll_wait

函数原型

/**
 * @description:            将应用程序添加到 poll_table 中
 * @param - filp    :       应用程序传递的文件描述符
 * @param - wait_address:   要添加到 poll_table 中的等待队列头
 * @param - p       :       poll_table ,就是 file_operations 中 poll 函数的 wait 参数
 */
void poll_wait(struct file *filp,wait_queue_head_t *wait_address,poll_table *p)

四.利用等待队列实现阻塞IO

1.设备树

(1).流程图

(2).设备树代码

2.驱动部分

(1).流程图

(2).驱动代码

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>



#define IMX6UIRQ_CNT    1                       /* 设备号个数 */
#define IMX6UIRQ_NAME   "blockio"               /* 设备名字 */
#define KEY0VALUE       0X01                    /* KEY0按键值 */
#define INVAKEY         0XFF                    /* 无效的按键值 */
#define KEY_NUM         1                       /* 按键数量 */



/* 中断 IO 描述结构体 */
struct irq_keydesc
{
    int gpio;                               /* gpio */
    int irqnum;                             /* 中断号 */
    unsigned char value;                    /* 按键对应的键值 */
    char name[10];                          /* 名字 */
    irqreturn_t (*handler)(int,void *);     /* 指向中断服务函数的函数指针 */
};

/* imx6uirq 设备结构体 */
struct imx6uirq_dev
{
    dev_t devid;                            /* 设备号 */
    struct cdev cdev;                       /* cdev */
    struct class *class;                    /* 类 */
    struct device *device;                  /* 设备 */
    int major;                              /* 主设备号 */
    int minor;                              /* 次设备号 */
    struct device_node *nd;                 /* 设备结点 */
    atomic_t keyvalue;                      /* 有效的按键值 */
    atomic_t releasekey;                    /* 标记是否完成一次完整的按键动作 */
    struct timer_list timer;                /* 定义一个定时器 */
    struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键中断信息描述数组 */
    unsigned char curkeynum;                /* 当前的按键号 */

    wait_queue_head_t r_wait;               /* 读等待队列头 */
};


/* irq 设备 */
struct imx6uirq_dev imx6uirq;



/**
 * @description:                KEY0 按键中断服务函数,开启定时器,延时 10 ms,定时器用于按键消抖
 * @param - irq     :           中断号
 * @param - dev_id  :           设备结构
 * @return          :           中断执行结果
 */
static irqreturn_t key0_handler(int irq,void *dev_id)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

    /* 表示按键中断触发(双边沿触发) (因为按键平时为上拉状态) */
    dev->curkeynum = 0;
    dev->timer.data = (volatile long)dev_id;

    /* 2.让定时器回调函数 10 ms 后触发 */
    mod_timer(&dev->timer,jiffies + msecs_to_jiffies(10));          

    return IRQ_RETVAL(IRQ_HANDLED);
}


/**
 * @description:                定时器服务函数,用于按键消抖,定时器到了以后再次读取按键值,
 *                              如果按键还是处于按下的状态就表示按键动作有效
 * @param - arg     :           设备结构体变量
 * @return          :           无
 */
void timer_function(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

    /* 获取上一刻的按键状态 */
    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num];

    /* 读取当前 gpio 的状态 */
    value = gpio_get_value(keydesc->gpio);
    /* 若此刻 gpio 的状态为低电平 , 则消抖后确认按键确实是按下了 (因为这个定时器服务函数是在按键中断之后触发的) */
    if(0 == value)
    {
        atomic_set(&dev->keyvalue,keydesc->value);
    }
    /* 若此刻 gpio 的状态为高电平 , 则消抖后确认按键确实是松开了 (因为这个定时器服务函数是在按键中断之后触发的) */
    else
    {
        atomic_set(&dev->keyvalue, 0x80 | keydesc->value);      //最高为置 1 
        atomic_set(&dev->releasekey, 1);                        //标记松开按键
    }

    /* 若完成了一次按键动作,则唤醒进程 */
    if(atomic_read(&dev->releasekey))
    {
        wake_up_interruptible(&dev->r_wait);
    }
}



/**
 * @description:                按键 IO 初始化
 * @param           :           无
 * @return          :           成功返回(0),返回其他则为失败
 */
static int keyio_init(void)
{
    unsigned char i = 0;
    int ret = 0;

    /* 1.获取设备结点 */
    imx6uirq.nd = of_find_node_by_path("/key");
    if(NULL == imx6uirq.nd)
    {
        printk("key node not find!\r\n");
        return -EINVAL;
    }

    /* 2.获取 KEY 的 GPIO 编号 */
    for(i = 0;i < KEY_NUM;i++)
    {
        imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd,"key-gpio",i);
        /* 若获取 gpio 编号失败 */
        if(imx6uirq.irqkeydesc[i].gpio < 0)
        {
            printk("can not get key%d\r\n",i);
        }
    }

    
    for(i = 0;i < KEY_NUM;i++)
    {
        memset(imx6uirq.irqkeydesc[i].name,0,sizeof(imx6uirq.irqkeydesc[i].name));
        sprintf(imx6uirq.irqkeydesc[i].name,"KEY%d",i);

        /* 3.申请 GPIO */
        gpio_request(imx6uirq.irqkeydesc[i].gpio,imx6uirq.irqkeydesc[i].name);

        /* 4.设置 IO 为输入 */
        gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);

        /* 5.获取 GPIO 的中断号 */
        imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd,i);

#if 0
        /* 若使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应中断号 */
        imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif

        printk("key : %d ; gpio = %d , irqnum = %d\r\n",i,imx6uirq.irqkeydesc[i].gpio,imx6uirq.irqkeydesc[i].irqnum);
    }


    /* 6.申请中断 */
    imx6uirq.irqkeydesc[0].handler = key0_handler;
    imx6uirq.irqkeydesc[0].value = KEY0VALUE;
    for(i = 0;i < KEY_NUM;i++)
    {
        ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
                          imx6uirq.irqkeydesc[i].handler,
                          IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
                          imx6uirq.irqkeydesc[i].name,
                          &imx6uirq);
    }
    
    
    /* 7.创建定时器 */
    init_timer(&imx6uirq.timer);
    imx6uirq.timer.function = timer_function;


    /* 8.初始化等待队列头 */
    init_waitqueue_head(&imx6uirq.r_wait);

    return 0;
}


/**
 * @description:            打开设备
 * @param - inode   :       传递给驱动的 inode
 * @param - filp    :       设备文件
 * @return          :       0 为成功 , 其他为失败
 */
static int imx6uirq_open(struct inode *inode,struct file *filp)
{
    /* 设置私有数据 */
    filp->private_data = &imx6uirq;

    return 0;
}


/**
 * @description:            从设备读取数据
 * @param - filp    :       文件描述符
 * @param - buf     :       返回给用户空间的数据缓冲区
 * @param - cnt     :       要读取的字节数
 * @param - offt    :       相对于文件首地址的偏移量
 * @return          :       成功读取的字节数,如果为负值,则表示失败
 */
static ssize_t imx6uirq_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;


    /* 定义一个等待队列 */
    DECLARE_WAITQUEUE(wait,current);

    /* 若没有按键按下,则添加到等待队列头,此进程进入休眠 */
    if(0 == atomic_read(&dev->releasekey))
    {
        add_wait_queue(&dev->r_wait,&wait);         //添加到等待队列头
        __set_current_state(TASK_INTERRUPTIBLE);    //设置任务状态为可被信号打断
        schedule();                                 //进行一次任务切换
        
        /* 若此进程是由信号唤醒,则返回错误信息 */
        if(signal_pending(current))
        {
            ret = -ERESTARTSYS;
            goto wait_error;
        }

        /* 若唤醒时不是有信号唤醒,而是以中断唤醒,则继续往下读取键值 */
        __set_current_state(TASK_RUNNING);          //设置为运行状态
        remove_wait_queue(&dev->r_wait,&wait);      //将等待队列移除

    }


    /* 读取消抖后的键值 */
    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    /* 如果有按键按下 */
    if(releasekey)
    {
        if(keyvalue & 0x80)
        {
            keyvalue &= ~0x80;      //将 keyvalue 最高为清零
            ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
        }
        else
        {
            goto data_error;
        }

        /* 清除按下标志 */
        atomic_set(&dev->releasekey,0); 
    }
    else
    {   
        goto data_error;
    }

    return 0;

wait_error:
    set_current_state(TASK_RUNNING);        //设置任务为运行状态
    remove_wait_queue(&dev->r_wait,&wait);  //将等待队列移除
    return ret;

data_error:
    return -EINVAL;
}


/* 设备操作函数 */
static struct file_operations imx6uirq_fops = 
{
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
};


/**
 * @description:            驱动入口函数
 * @param           :       无
 * @return          :       无
 */
static int __init imx6uirq_init(void)
{
    /* 1.创建设备号 */
    if(imx6uirq.major)          //若定义了设备号
    {
        imx6uirq.devid = MKDEV(imx6uirq.major,0);
        register_chrdev_region(imx6uirq.devid,IMX6UIRQ_CNT,IMX6UIRQ_NAME);
    }
    else                        //若没有定义设备号
    {
        alloc_chrdev_region(&imx6uirq.devid,0,IMX6UIRQ_CNT,IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.devid);
    }

    /* 2.初始化cdev */
    cdev_init(&imx6uirq.cdev,&imx6uirq_fops);

    /* 3,添加一个cdev */
    cdev_add(&imx6uirq.cdev,imx6uirq.devid,IMX6UIRQ_CNT);

    /* 4.创建类 */
    imx6uirq.class = class_create(THIS_MODULE,IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.class))
    {
        return PTR_ERR(imx6uirq.class);
    }

    /* 5.创建设备 */
    imx6uirq.device = device_create(imx6uirq.class,NULL,imx6uirq.devid,NULL,IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.device))
    {
        return PTR_ERR(imx6uirq.device);
    }

    /* 6.初始化按键 */
    atomic_set(&imx6uirq.keyvalue,INVAKEY);
    atomic_set(&imx6uirq.releasekey,0);
    keyio_init();

    return 0;
}


/**
 * @description:            驱动出口函数
 * @param           :       无
 * @return          :       无
 */
static void __exit imx6uirq_exit(void)
{
    unsigned int i = 0;

    /* 1.删除定时器 */
    del_timer_sync(&imx6uirq.timer);

    /* 2.释放 中断 与 GPIO */
    for(i = 0;i < KEY_NUM;i++)
    {
        free_irq(imx6uirq.irqkeydesc[i].irqnum,&imx6uirq);
        gpio_free(imx6uirq.irqkeydesc[i].gpio);
    }

    /* 3. 注销字符设备驱动 */
    cdev_del(&imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid,IMX6UIRQ_CNT);
    device_destroy(imx6uirq.class,imx6uirq.devid);
    class_destroy(imx6uirq.class);
}


module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kaneki");



(3).应用程序

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


int main(int argc, char *argv[])
{
    int fd,ret;
    char *filename;
    unsigned char data;

    if(2 != argc)
    {
        printf("Usage : ./%s <devpath>",argv[0]);
        return -1;
    }

    filename = argv[1];

    fd = open(filename,O_RDONLY);
    if(0 > fd)
    {
        perror("open error");
        return -1;
    }

    while(1)
    {
        ret = read(fd,&data,sizeof(data));
        if(0 > ret)
        {
            
        }

        else
        {
            if(data)        /* 读取到数据 */
            {
                printf("key value = %#X\r\n",data);
            }
        }
    }

    close(fd);

    return ret;
}

Makefile:   

KERNELDIR := /home/linux/IMX6ULL/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH :=$(shell pwd)
obj-m := blockio.o
build: kernel_modules
kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

(4).实验现象

实验现象

        可以看到,应用程序在 while(1) 里面一直都在调用 read ,但占用 CPU 的资源很少

五.利用 poll 实现非阻塞 IO 

1.设备树

(1).流程图

(2).设备树代码

2.驱动部分

(1).流程图

(2).驱动代码

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>



#define IMX6UIRQ_CNT    1                       /* 设备号个数 */
#define IMX6UIRQ_NAME   "nonblockio"               /* 设备名字 */
#define KEY0VALUE       0X01                    /* KEY0按键值 */
#define INVAKEY         0XFF                    /* 无效的按键值 */
#define KEY_NUM         1                       /* 按键数量 */



/* 中断 IO 描述结构体 */
struct irq_keydesc
{
    int gpio;                               /* gpio */
    int irqnum;                             /* 中断号 */
    unsigned char value;                    /* 按键对应的键值 */
    char name[10];                          /* 名字 */
    irqreturn_t (*handler)(int,void *);     /* 指向中断服务函数的函数指针 */
};

/* imx6uirq 设备结构体 */
struct imx6uirq_dev
{
    dev_t devid;                            /* 设备号 */
    struct cdev cdev;                       /* cdev */
    struct class *class;                    /* 类 */
    struct device *device;                  /* 设备 */
    int major;                              /* 主设备号 */
    int minor;                              /* 次设备号 */
    struct device_node *nd;                 /* 设备结点 */
    atomic_t keyvalue;                      /* 有效的按键值 */
    atomic_t releasekey;                    /* 标记是否完成一次完整的按键动作 */
    struct timer_list timer;                /* 定义一个定时器 */
    struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键中断信息描述数组 */
    unsigned char curkeynum;                /* 当前的按键号 */

    wait_queue_head_t r_wait;               /* 读等待队列头 */
};


/* irq 设备 */
struct imx6uirq_dev imx6uirq;



/**
 * @description:                KEY0 按键中断服务函数,开启定时器,延时 10 ms,定时器用于按键消抖
 * @param - irq     :           中断号
 * @param - dev_id  :           设备结构
 * @return          :           中断执行结果
 */
static irqreturn_t key0_handler(int irq,void *dev_id)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

    /* 表示按键中断触发(双边沿触发) (因为按键平时为上拉状态) */
    dev->curkeynum = 0;
    dev->timer.data = (volatile long)dev_id;

    /* 2.让定时器回调函数 10 ms 后触发 */
    mod_timer(&dev->timer,jiffies + msecs_to_jiffies(10));          

    return IRQ_RETVAL(IRQ_HANDLED);
}


/**
 * @description:                定时器服务函数,用于按键消抖,定时器到了以后再次读取按键值,
 *                              如果按键还是处于按下的状态就表示按键动作有效
 * @param - arg     :           设备结构体变量
 * @return          :           无
 */
void timer_function(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

    /* 获取上一刻的按键状态 */
    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num];

    /* 读取当前 gpio 的状态 */
    value = gpio_get_value(keydesc->gpio);
    /* 若此刻 gpio 的状态为低电平 , 则消抖后确认按键确实是按下了 (因为这个定时器服务函数是在按键中断之后触发的) */
    if(0 == value)
    {
        atomic_set(&dev->keyvalue,keydesc->value);
    }
    /* 若此刻 gpio 的状态为高电平 , 则消抖后确认按键确实是松开了 (因为这个定时器服务函数是在按键中断之后触发的) */
    else
    {
        atomic_set(&dev->keyvalue, 0x80 | keydesc->value);      //最高为置 1 
        atomic_set(&dev->releasekey, 1);                        //标记松开按键
    }

    /* 若完成了一次按键动作,则唤醒进程 */
    if(atomic_read(&dev->releasekey))
    {
        wake_up_interruptible(&dev->r_wait);
    }
}



/**
 * @description:                按键 IO 初始化
 * @param           :           无
 * @return          :           成功返回(0),返回其他则为失败
 */
static int keyio_init(void)
{
    unsigned char i = 0;
    int ret = 0;

    /* 1.获取设备结点 */
    imx6uirq.nd = of_find_node_by_path("/key");
    if(NULL == imx6uirq.nd)
    {
        printk("key node not find!\r\n");
        return -EINVAL;
    }

    /* 2.获取 KEY 的 GPIO 编号 */
    for(i = 0;i < KEY_NUM;i++)
    {
        imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd,"key-gpio",i);
        /* 若获取 gpio 编号失败 */
        if(imx6uirq.irqkeydesc[i].gpio < 0)
        {
            printk("can not get key%d\r\n",i);
        }
    }

    
    for(i = 0;i < KEY_NUM;i++)
    {
        memset(imx6uirq.irqkeydesc[i].name,0,sizeof(imx6uirq.irqkeydesc[i].name));
        sprintf(imx6uirq.irqkeydesc[i].name,"KEY%d",i);

        /* 3.申请 GPIO */
        gpio_request(imx6uirq.irqkeydesc[i].gpio,imx6uirq.irqkeydesc[i].name);

        /* 4.设置 IO 为输入 */
        gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);

        /* 5.获取 GPIO 的中断号 */
        imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd,i);

#if 0
        /* 若使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应中断号 */
        imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif

        printk("key : %d ; gpio = %d , irqnum = %d\r\n",i,imx6uirq.irqkeydesc[i].gpio,imx6uirq.irqkeydesc[i].irqnum);
    }


    /* 6.申请中断 */
    imx6uirq.irqkeydesc[0].handler = key0_handler;
    imx6uirq.irqkeydesc[0].value = KEY0VALUE;
    for(i = 0;i < KEY_NUM;i++)
    {
        ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
                          imx6uirq.irqkeydesc[i].handler,
                          IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
                          imx6uirq.irqkeydesc[i].name,
                          &imx6uirq);
    }
    
    
    /* 7.创建定时器 */
    init_timer(&imx6uirq.timer);
    imx6uirq.timer.function = timer_function;


    /* 8.初始化等待队列头 */
    init_waitqueue_head(&imx6uirq.r_wait);

    return 0;
}


/**
 * @description:            打开设备
 * @param - inode   :       传递给驱动的 inode
 * @param - filp    :       设备文件
 * @return          :       0 为成功 , 其他为失败
 */
static int imx6uirq_open(struct inode *inode,struct file *filp)
{
    /* 设置私有数据 */
    filp->private_data = &imx6uirq;

    return 0;
}


/**
 * @description:            从设备读取数据
 * @param - filp    :       文件描述符
 * @param - buf     :       返回给用户空间的数据缓冲区
 * @param - cnt     :       要读取的字节数
 * @param - offt    :       相对于文件首地址的偏移量
 * @return          :       成功读取的字节数,如果为负值,则表示失败
 */
static ssize_t imx6uirq_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    /* 定义一个等待队列 */
    DECLARE_WAITQUEUE(wait,current);

    /* 1.判断是不是以非阻塞访问 */
    if(filp->f_flags & O_NONBLOCK)
    {
        /* 若没有按键按下,没有则返回错误信息,按下了则继续往后读取键值 */
        if(0 == atomic_read(&dev->releasekey))
            return -EAGAIN;
    }


    /* 读取消抖后的键值 */
    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    /* 如果有按键按下 */
    if(releasekey)
    {
        if(keyvalue & 0x80)
        {
            keyvalue &= ~0x80;      //将 keyvalue 最高为清零
            ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
        }
        else
        {
            goto data_error;
        }

        /* 清除按下标志 */
        atomic_set(&dev->releasekey,0); 
    }
    else
    {   
        goto data_error;
    }

    return 0;


data_error:
    return -EINVAL;
}



/**
 * @description:            poll 函数,用于处理非阻塞访问
 */
unsigned int imx6uirq_poll(struct file *filp,struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    /* 将应用程序添加到 poll_table 中 */
    poll_wait(filp,&dev->r_wait,wait);


    /* 若按键按下则返回事件 */
    if(atomic_read(&dev->releasekey))
    {
        mask = POLLIN | POLLRDNORM;        //返回POLLIN
    }

    return mask;
}


/* 设备操作函数 */
static struct file_operations imx6uirq_fops = 
{
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
    .poll = imx6uirq_poll,
};


/**
 * @description:            驱动入口函数
 * @param           :       无
 * @return          :       无
 */
static int __init imx6uirq_init(void)
{
    /* 1.创建设备号 */
    if(imx6uirq.major)          //若定义了设备号
    {
        imx6uirq.devid = MKDEV(imx6uirq.major,0);
        register_chrdev_region(imx6uirq.devid,IMX6UIRQ_CNT,IMX6UIRQ_NAME);
    }
    else                        //若没有定义设备号
    {
        alloc_chrdev_region(&imx6uirq.devid,0,IMX6UIRQ_CNT,IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.devid);
    }

    /* 2.初始化cdev */
    cdev_init(&imx6uirq.cdev,&imx6uirq_fops);

    /* 3,添加一个cdev */
    cdev_add(&imx6uirq.cdev,imx6uirq.devid,IMX6UIRQ_CNT);

    /* 4.创建类 */
    imx6uirq.class = class_create(THIS_MODULE,IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.class))
    {
        return PTR_ERR(imx6uirq.class);
    }

    /* 5.创建设备 */
    imx6uirq.device = device_create(imx6uirq.class,NULL,imx6uirq.devid,NULL,IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.device))
    {
        return PTR_ERR(imx6uirq.device);
    }

    /* 6.初始化按键 */
    atomic_set(&imx6uirq.keyvalue,INVAKEY);
    atomic_set(&imx6uirq.releasekey,0);
    keyio_init();

    return 0;
}


/**
 * @description:            驱动出口函数
 * @param           :       无
 * @return          :       无
 */
static void __exit imx6uirq_exit(void)
{
    unsigned int i = 0;

    /* 1.删除定时器 */
    del_timer_sync(&imx6uirq.timer);

    /* 2.释放 中断 与 GPIO */
    for(i = 0;i < KEY_NUM;i++)
    {
        free_irq(imx6uirq.irqkeydesc[i].irqnum,&imx6uirq);
        gpio_free(imx6uirq.irqkeydesc[i].gpio);
    }

    /* 3. 注销字符设备驱动 */
    cdev_del(&imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid,IMX6UIRQ_CNT);
    device_destroy(imx6uirq.class,imx6uirq.devid);
    class_destroy(imx6uirq.class);
}


module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kaneki");



(3).应用程序

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <sys/time.h>





int main(int argc, char *argv[])
{
    int fd,ret;
    char *filename;
    unsigned char data;
    struct pollfd fds;          //要监视的文件描述符集合和要监视的事件
    fd_set readfds;             //定义读操作文件描述符集
    struct timeval timeout;     //超时时间


    if(2 != argc)
    {
        printf("Usage : ./%s <devpath>",argv[0]);
        return -1;
    }

    filename = argv[1];

    fd = open(filename,O_RDONLY | O_NONBLOCK);
    if(0 > fd)
    {
        perror("open error");
        return -1;
    }


    /* 构造结构体 */
    fds.fd = fd;            //设置要监视的文件描述符
    fds.events = POLLIN;    //设置要监视的事件


    while(1)
    {
        ret = poll(&fds,1,500);
        /* 若数据有效 */
        if(ret)
        {
            ret = read(fd,&data,sizeof(data));
            if(0 > ret)
            {
                /* 读取错误  */
            }
            else
            {
                if(data)
                {
                    printf("key value = %d\r\n",data);
                }
            }
        }
        /* 若超时 */
        else if(0 == ret)
        {
            /* 用户自定义超时处理 */
        }
        else if(0 > ret)
        {
            /* 用户自定义错误处理 */
        }
    }

    close(fd);

    return ret;
}

(4).实验现象

        同样可以发现,利用 poll 也可以使应用程序占用 CPU 资源很少

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2192752.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

54.二叉树的最大深度

迭代 class Solution {public int maxDepth(TreeNode root) {if(rootnull){return 0;}int de0;Queue<TreeNode> qunew LinkedList<>();TreeNode tn;int le;qu.offer(root);while(!qu.isEmpty()){lequ.size();while(le>0){tnqu.poll();if(tn.left!null){qu.offe…

RTA-OS Port Guide学习(四)-基于S32K324 OS

文章目录 前言PerformanceMeasurement EnvironmentRAM and ROM Usage for OS ObjectsSingle CoreMulti Core Stack UsageLibrary Module SizesSingle CoreMulti Core Execution TimeContext Switching Time 总结 前言 前面一篇文章介绍了硬件的一些特性&#xff0c;本文为最后…

国内目前顶级的哲学教授颜廷利:全球公认十个最厉害的思想家

国内目前顶级的哲学教授颜廷利&#xff1a;全球公认十个最厉害的思想家 颜廷利&#xff0c;字弃安&#xff0c;号求前&#xff0c;山东济南人&#xff0c;当代著名思想家、哲学家、教育家、易经心理学家、中国第一起名大师、国际权威易学大师、中国汉字汉语研究专家、现代最著…

什么是数字化智能工厂的组成

二、数字化智能工厂的主要功能组成 数字化智能工厂主要由以下几个功能部分组成&#xff1a; 自动化生产设备&#xff1a;包括机器人、智能传感器、可编程逻辑控制器&#xff08;PLC&#xff09;等&#xff0c;用于实现生产过程的自动化操作&#xff0c;减少人力依赖&#xff0…

[C#]C# winform部署yolov11-pose姿态估计onnx模型

【算法介绍】 在C# WinForms应用中部署YOLOv11-Pose姿态估计ONNX模型是一项具有挑战性的任务。YOLOv11-Pose结合了YOLO&#xff08;You Only Look Once&#xff09;的高效物体检测算法和Pose Estimation&#xff08;姿态估计&#xff09;专注于识别人体关键点的能力&#xff0…

移动WSL到其他盘

1、首先下载 Move WSL 工具包&#xff0c;并解压。&#xff08;https://github.com/pxlrbt/move-wsl/archive/refs/heads/master.zip&#xff09; 2、管理员身份运行Windows PowerShell。 3、在PowerShell中运行如下命令&#xff0c;停止正在运行的Linux子系统。 wsl --shutd…

柯桥商务英语口语-work-shy 是什么意思?不要理解成“工作害羞”!

ork工作&#xff0c;shy是害羞&#xff0c;那么&#xff0c;work-shy是什么意思&#xff1f; 其实在 "work-shy" 这个短语中&#xff0c;"shy" 的意思并不是害羞&#xff0c;而是表达一种躲避、逃避的意思。 "work-shy" 表示对工作有一种躲避、…

深度学习基础—交并比与非极大值抑制

1.交并比 &#xff08;1&#xff09;定义 交并比是用来衡量目标检测算法的表现的函数。定义如下&#xff1a; 用预测框和真实框的面积的交集除以预测框和真实框的面积的并集&#xff0c;得到的结果本次算法预测的交并比。研究函数可以发现&#xff0c;交并比的范围为[0,1]&…

cnn突破七(四层bpnet网络公式与卷积核bpnet公式相关)

我们要有一个概念&#xff0c;就是卷积核就是我们的w1&#xff0c;w12&#xff0c;w2 那么我们的5*5卷积核怎么表达&#xff0c;当他在14*14的图像中流动时&#xff0c;对应的像素也在变化 这个和我们的上面w1&#xff0c;w12&#xff0c;w2不同&#xff0c;因为这几个都是全…

测绘地理信息赋能新质生产力

在信息化与智能化浪潮的推动下&#xff0c;测绘地理信息作为连接现实世界与数字空间的桥梁&#xff0c;正逐步成为驱动经济社会发展的新质生产力。本文旨在深入探讨测绘地理信息如何通过技术创新与应用拓展&#xff0c;为各行各业赋能&#xff0c;塑造智慧社会的新面貌。 一、…

word无法复制粘贴

word无法复制粘贴 使用word时复制粘贴报错 如下&#xff1a; 报错&#xff1a;运行时错误‘53’&#xff0c;文件未找到&#xff1a;MathPage.WLL 这是mathtype导致的。 解决方法 1&#xff09;在mathtype下载目录下找到"\MathType\MathPage\64"下的"mathpa…

第T3周:CNN实现天气识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 目标&#xff1a; 搭建CNN网络模型实现多云、下雨、晴、日出四种天气状态的识别&#xff0c;并用真实天气做预测 具体实现&#xff1a; &#xff08;一&#x…

Win10照片查看器不见了怎么办?

刚换了电脑&#xff0c;发现查看图片默认打开是window画图工具&#xff0c;看图竟然需要一张一张打开&#xff0c;超级不方便。右键图片选择打开方式也不见照片查看器&#xff0c;window自带的看图工具去哪儿了&#xff1f; 不要着急&#xff0c;我们可以把它找回来&#xff0…

windows下DockerDesktop命令行方式指定目录安装

windows下DockerDesktop指定目录安装(重新安装) 因为DcokerDesktop占用内存较大, 并且拉去镜像后占用本地空间较多,所以建议安装时就更改默认安装路径和镜像存储路径 这里,展示了从下载到安装的过程: 首先下载DcokerDesktop;找到Docker Desktop Installer.exe 并重命名为 do…

万界星空科技MES数据集成平台

制造执行系统MES作为连接企业上层ERP系统和现场控制系统的桥梁&#xff0c;承担了实时数据采集、处理、分析和传递的重要任务。MES数据集成平台是一个集成各类数据源&#xff0c;将数据进行整合和统一管理的系统&#xff0c;通过提供标准化接口和协议&#xff0c;实现数据的无缝…

上海交通大学《2022年+2023年816自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《上海交通大学816自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2022年真题 2023年真题 Part1&#xff1a;2022年2023年完整版真题 2022年真题 2023年…

强大的JVM监控工具

介绍 在生产环境中&#xff0c;经常会遇到各种各样奇葩的性能问题&#xff0c;所以掌握最基本的JVM命令行监控工具还是很有必要的 名称主要作用jps查看正在运行的Java进程jstack打印线程快照jmap导出堆内存映像文件jstat查看jvm统计信息jinfo实时查看和修改jvm配置参数jhat用…

github上下载ultralytics代码报错

1.在GitHub上下载ultralytics的yolo源代码使用&#xff0c;报错提示找不到ultralytics 2.主要原因引用的路径不对 3.解决的办法&#xff0c;只复制ultralytics中ultralytics文件夹到项目中&#xff0c;其他的文件夹是不需要的。

【电路】1.1 实际电路和电路模型

1.1 实际电路和电路模型 科学理论的研究对象是现实世界背后的抽象世界&#xff0c;如&#xff1a; 数学中的 ∞ \infty ∞&#xff0c;经典力学中“质点”的概念&#xff0c;牛顿运动定律&#xff08;如惯性定律&#xff0c;如果一个物体不受外力情况下&#xff0c;一直保持匀…

虚拟机ip突然看不了了

打印大致如下&#xff1a; 解决办法 如果您发现虚拟机的IP地址与主机不在同一网段&#xff0c;可以采取的措施之一是调整网络设置。将虚拟机的网络模式更改为桥接模式&#xff0c;这样它就会获得与主机相同的IP地址&#xff0c;从而处于同一网段。或者&#xff0c;您可以使用…