DeviceDriver (7): blocking and non-blocking

One: blocking and non-blocking

1. When the application is operating on the device driver, if the device resource cannot be obtained immediately.

For blocking IO, the corresponding thread will be suspended until the device resource is obtained.

For non-blocking IO, the thread will not be suspended, but will always poll and wait until the device resources are available, or just give up.

2. Application program implementation

Blocking:

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

Non-blocking:

fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */

Two: waiting queue (blocking)

        Access the device in a blocking mode. When the device resources are not available, the process will enter the dormant state and release the CPU resources. The process needs to be awakened when the device file is operational, usually in an interrupt program. The Linux kernel provides a waiting queue to realize the wake-up of blocked processes.

1. Wait for the creation and initialization of the queue head

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

wait_queue_head_t	read_wait;
init_waitqueue_head(&dev->read_wait);

Or use a macro definition to complete the definition and initialization of the waiting queue header at one time:

#define DECLARE_WAIT_QUEUE_HEAD(name) \
	wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

2. Waiting for queue items

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;

wait_queue_t		wait;

Macro definition: name is the name of the waiting queue item, tsk is which task (process) the waiting queue item belongs to, generally set to current, current is equivalent to a global variable in the Linux kernel, which represents the current process.

#define DECLARE_WAITQUEUE(name, tsk)					\
	wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

3. Add/remove queue items to the head of waiting queue

        When the device is inaccessible, the waiting queue item corresponding to the process needs to be added to the previously created waiting queue head, and the process can enter the sleep state only after being added to the waiting queue head. When the device is accessible, remove the waiting queue corresponding to the process from the waiting queue.

Wait for the queue item to add API:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

Waiting for the queue item to be removed API:

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

4. Wait for wake up

When the device can be used, it is necessary to wake up the process that went into sleep.

void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)

The parameter q is the head of the waiting queue to be awakened. Wake_up can wake up processes in TASK_INTERRUPTIBLE and TASK_UNINTERRUPTIBLE states, while wake_up_interruptible can only wake up processes in TASK_INTERRUPTIBLE state.

5. Waiting for the event

In addition to actively waking up, you can also set the waiting queue to wait for an event, and when the event is satisfied, the process in the waiting queue will be automatically awakened.

(1) wait_event: Waiting for the waiting queue with wq as the head of the waiting queue to be awakened, provided that the condition must be met to be true, otherwise it has been blocked. This function will set the process to TASK_UNINTERRUPTIBLE state.

#define wait_event(wq, condition)					\
do {									\
	might_sleep();							\
	if (condition)							\
		break;							\
	__wait_event(wq, condition);					\
} while (0)

(2) wait_event_timeout: The function is similar to wait_event, but this function can add a timeout period in jiffies. This function has a return value. Returning 0 means that the timeout period is up and the condition is false. If it is 1, the condition is true, that is, the condition is satisfied.

#define wait_event_timeout(wq, condition, timeout)			\
({									\
	long __ret = timeout;						\
	might_sleep();							\
	if (!___wait_cond_timeout(condition))				\
		__ret = __wait_event_timeout(wq, condition, timeout);	\
	__ret;								\
})

(3) wait_event_interruptible: similar to the wait_event function, but this function sets the process to TASK_INTERRUPTIBLE, which means it can be interrupted by a signal.

#define wait_event_interruptible(wq, condition)				\
({									\
	int __ret = 0;							\
	might_sleep();							\
	if (!(condition))						\
		__ret = __wait_event_interruptible(wq, condition);	\
	__ret;								\
})

(4) wait_event_interruptible_timeout: similar to the wait_event_timeout function, this function also sets the process to TASK_INTERRUPTIBLE, which can be
interrupted by a signal .

Three: polling (non-blocking)

        If the user application accesses the device in a non-blocking manner, the device driver needs to provide a non-blocking processing method, which is polling. Poll, epoll, and select can be used to process polling, and the application can query whether the device is operable through the select, epoll or poll function.

1. The select function

int select(int nfds, 
        fd_set *readfds, 
        fd_set *writefds,
        fd_set *exceptfds, 
        struct timeval *timeout)

(1) nfds: The number of file descriptors to be operated.

(2) readfds, writefds, exceptfds: three pointers point to the descriptor set, each bit of the fd_set type variable represents a file descriptor. readfds is used to monitor the read changes of the specified descriptor set and whether it can be read. If there is, it returns a value greater than 0 to indicate that the file can be read. If there is no file to read, it will judge whether it has timed out according to the timeout parameter. You can set readfds to NULL, which means you don't care about any file read changes. writefds is similar, used to monitor whether these files can be written. exceptfds is used to monitor the abnormalities of these files.

Fd_set variable operation API: FD_ZERO is used to clear all the bits of the fd_set variable, FD_SET is used to set a certain position, that is, to add a file descriptor to fd_set, and the parameter fd is the file descriptor to be added. FD_CLR deletes a file descriptor from fd_set. FD_ISSET is used to test whether a certain bit of fd_set is set to 1, that is, to determine whether a certain file can be operated.

void FD_ZERO(fd_set *set)
void FD_SET(int fd, fd_set *set)
void FD_CLR(int fd, fd_set *set)
int FD_ISSET(int fd, fd_set *set)

(3) timeout: timeout, when it is NULL, it means waiting indefinitely.

struct timeval
{
  __time_t tv_sec;		/* Seconds.  */
  __suseconds_t tv_usec;	/* Microseconds.  */
};

(4) Return value: 0 means timeout occurred, but no file descriptor can be operated; -1 means an error has occurred; other values ​​indicate the number of file descriptors that can be operated.

2, poll function

       In a single thread, the number of file descriptors that the select function can monitor has a maximum limit, generally 1024. The poll function is similar to the select function, but there is no limitation on the maximum number of descriptors.

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

(1) fds: The set of file descriptors to be monitored and the events to be monitored.

struct pollfd *fds;

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

fd is the file descriptor to be monitored. If fd is invalid, the events monitoring event is also invalid, and revens returns 0. event is the event to be monitored, the type of event that can be monitored:

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

(2) nfds: The number of file descriptors to be monitored by the poll function.

(3) timeout: timeout time, in ms

(4) Return value: return the number of pollfd structures that are not 0 in the revens field, that is, the number of file descriptors that have events or errors; 0: timeout; -1: an error has occurred and errno is set as the error type.

3. epoll function

       The traditional select and poll functions will have the problem of low efficiency as the number of monitored fd increases, and the poll function must traverse all descriptors every time to check the ready descriptors, which is a waste of time. The function epoll is to handle this Designed for large concurrency issues.

(1) Create an epoll handle. Just fill in a value greater than 0 for size. The return value is the epoll handle. If it is -1, the creation failed.

int epoll_create(int size)

(2) Add file descriptors and monitored events that need to be monitored, return 0 for success and -1 for failure.

int epoll_ctl(int epfd,
    int op,
    int fd,
    struct epoll_event *event)

epfd: epoll handle to be operated.

op: set epfd

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

fd: The file descriptor to be monitored.

event: The type of event to be monitored.

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

The variable events represents the events to be monitored, optional parameters:

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

(3) Waiting for the event to occur

int epoll_wait(int epfd,
    struct epoll_event *events,
    int maxevents,
    int timeout)

epfd: epoll to wait

events: an array pointing to the epoll_event structure. When an event occurs, the Linux kernel will fill in events, and the caller can determine which events have occurred based on the events.

maxevents: events array size

timeout: timeout period.

Four: Example

1. Device tree

/ {

    gpioled {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "my-led";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_led>;
        led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
        status = "okay";
};

    key {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "my-key";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_key>;
        key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
        interrupt-parent = <&gpio1>;
        interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
        status = "okay";
    };
}

2. Button drive module:

#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>
#include <linux/poll.h>

#define KEY_NUM     1

struct st_keyirq
{
    int gpio;
    int irqnum;
    unsigned char value;
    char name[10];
    irqreturn_t (*handler)(int, void *);
};

struct st_keydev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
    struct mutex lock;
    atomic_t keyvalue;
    atomic_t releasekey;
    struct timer_list timer;
    struct st_keyirq irqkeydesc[KEY_NUM];
    unsigned char curkeynum;
};
struct st_keydev keydev;
static DECLARE_WAIT_QUEUE_HEAD(key_waitq);


static int key_open(struct inode *inode, struct file *file)
{
    file->private_data = &keydev;

    if(mutex_trylock(&keydev.lock) == 0)
    {
        return -EBUSY;
    }
    return 0;
}

static ssize_t key_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    struct st_keydev *dev = (struct st_keydev *)file->private_data;
    unsigned char releasekey = 0;
    char val = 0;
    int retvalue = 0;

    if(file->f_flags & O_NONBLOCK)
    {
        if(atomic_read(&dev->releasekey) == 0)
            return -EAGAIN;
    }
    else
    {
#if 1
        wait_event_interruptible(key_waitq, atomic_read(&dev->releasekey)); 
#else
        DECLARE_WAITQUEUE(wait, current);
        if(atomic_read(&dev->releasekey) == 0)
        {
            add_wait_queue(&key_waitq, &wait);
            __set_current_state(TASK_INTERRUPTIBLE);
            schedule();
        }
        remove_wait_queue(&key_waitq, &wait);
#endif 
    }

    releasekey = atomic_read(&dev->releasekey);

    if(releasekey)
    {
        val = 1;
        retvalue = copy_to_user(buf, &val, sizeof(val));
        atomic_set(&dev->releasekey, 0);
    }
    else
    {
        val = 0;
        retvalue = copy_to_user(buf, &val, sizeof(val));
    }
    
    return 0;
}

unsigned int key_poll(struct file *filp, struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    struct st_keydev *dev = (struct st_keydev *)filp->private_data;

    poll_wait(filp, &key_waitq, wait);

    if(atomic_read(&dev->releasekey))
    {
        mask = POLLIN | POLLRDNORM;
    }
    return mask;
}

static int key_release(struct inode *inode, struct file *file)
{
    struct st_keydev *dev = file->private_data;

    mutex_unlock(&dev->lock);

    return 0;
}


struct file_operations key_fops = {
    .owner      =   THIS_MODULE,
    .open       =   key_open,
    .read       =   key_read,
    .poll       =   key_poll,
    .release    =   key_release, 
};

static irqreturn_t key1_handler(int irq, void *keydev)
{
    struct st_keydev *dev = (struct st_keydev *)keydev;

    dev->curkeynum = 0;
    dev->timer.data = (volatile long)keydev;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(5));
    return IRQ_RETVAL(IRQ_HANDLED);
}

void timer_function(unsigned long data)
{
    unsigned char value;
    unsigned char num;
    struct st_keyirq *keydesc;
    struct st_keydev *dev = (struct st_keydev *)data;

    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num];

    value = gpio_get_value(keydesc->gpio);
    if(value == 0)
    {
        atomic_set(&dev->releasekey, 0);
    }
    else
    {
        wake_up_interruptible(&key_waitq);
        atomic_set(&dev->releasekey, 1);
    }
}

static int key1_init(void)
{
    int retval = 0;
    unsigned char i = 0;

    mutex_init(&keydev.lock);

    keydev.nd = of_find_node_by_path("/key");
    if(keydev.nd == NULL)
    {
        printk("Key node not find!\n");
        return -EINVAL;
    }
    else
    {
        printk("key node find!\n");
    }

    for (i = 0; i < KEY_NUM; i++)
    {
        keydev.irqkeydesc[i].gpio = of_get_named_gpio(keydev.nd, "key-gpio", i);
        if(keydev.irqkeydesc[i].gpio < 0)
        {
            printk("Can't get key%d!\n", i);
            return -EINVAL;
        }
    }

    for (i = 0; i < KEY_NUM; i++)
    {
        memset(keydev.irqkeydesc[i].name, 0, sizeof(keydev.irqkeydesc[i].name));
        sprintf(keydev.irqkeydesc[i].name, "KEY%d", i);
        retval = gpio_request(keydev.irqkeydesc[i].gpio, keydev.irqkeydesc[i].name);
        if(retval < 0)
        {
            printk("request failed!\n");
            return -EINVAL;
        }
        retval = gpio_direction_input(keydev.irqkeydesc[i].gpio);
        if(retval < 0)
        {
            printk("set input failed!\n");
            return -EINVAL;
        }
        keydev.irqkeydesc[i].irqnum = irq_of_parse_and_map(keydev.nd, i);

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

    keydev.irqkeydesc[0].handler = key1_handler;
    for (i = 0; i < KEY_NUM; i++)
    {
        /* code */
        retval = request_irq(keydev.irqkeydesc[i].irqnum, keydev.irqkeydesc[i].handler,
                                IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, keydev.irqkeydesc[i].name, &keydev);
        if(retval < 0)
        {
            printk("irq %d request failed!\n", keydev.irqkeydesc[i].irqnum);
            return -EFAULT;
        }
    }

    if(keydev.major)
    {
        keydev.devid = MKDEV(keydev.major, 0);
        register_chrdev_region(keydev.devid, 1, "key");
    }
    else
    {
        alloc_chrdev_region(&keydev.devid, 0, 1, "key");
        keydev.major = MAJOR(keydev.devid);
        keydev.minor = MINOR(keydev.devid);
    }
    printk("keydev major : %d minor : %d\n", keydev.major, keydev.minor);

    keydev.cdev.owner = THIS_MODULE;
    cdev_init(&keydev.cdev, &key_fops);
    cdev_add(&keydev.cdev, keydev.devid, 1);

    keydev.class = class_create(THIS_MODULE, "key");
    keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL, "key");

    init_timer(&keydev.timer);
    keydev.timer.function = timer_function;

    return 0;
}

static void key1_exit(void)
{
    unsigned char i = 0;

    del_timer_sync(&keydev.timer);
    for (i = 0; i < KEY_NUM; i++)
    {
        /* code */
        free_irq(keydev.irqkeydesc[i].irqnum, &keydev);
    }
    
    device_destroy(keydev.class, keydev.devid);
    class_destroy(keydev.class);

    cdev_del(&keydev.cdev);
    unregister_chrdev_region(keydev.devid, 1);
}

module_init(key1_init);
module_exit(key1_exit);
MODULE_LICENSE("GPL");

3. LED light drive module

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LEDCNT      1
#define LEDNAME     "led"

struct st_dev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
    int gpio_num;
    struct mutex lock,timelock;
    // struct timer_list timer;
    // atomic_t ledval;
};

struct st_dev leddev;
// atomic_t timeinit = ATOMIC_INIT(1);;

// void timer_function(unsigned long data)
// {
//     struct st_dev *dev = (struct st_dev *)data;
    
//     atomic_set(&dev->ledval, 1);
//     mod_timer(&dev->timer, jiffies + msecs_to_jiffies(500));
// }

static int led_open(struct inode *inode, struct file *file)
{
    file->private_data = &leddev;

    if(mutex_trylock(&leddev.lock) == 0)
    {
        return -EBUSY;
    }
    return 0;
}


static ssize_t led_write(struct file *file, const char *data, size_t len, loff_t *ppos)
{
    struct st_dev *dev = file->private_data;
    static unsigned char value = 0;
    char retval, timeval;
    char databuff;

    // dev->timer.data = (volatile long)file->private_data;
    // if(atomic_read(&timeinit))
    // {
    //     atomic_set(&timeinit, 0);
    //     mod_timer(&dev->timer, jiffies + msecs_to_jiffies(500));
    // }
    retval = copy_from_user(&databuff, data, len);
    if(databuff == 1)
    {
        // timeval = atomic_read(&dev->ledval);
        // if(timeval)
        // {
        //     value = !value;
        //     gpio_set_value(dev->gpio_num, value);
        //     atomic_set(&dev->ledval, 0);
        // }
        gpio_set_value(dev->gpio_num, 0);
    }
    else
    {
        gpio_set_value(dev->gpio_num, 1);
    }
    
    return 0;
}       

static int led_release(struct inode *inode, struct file *file)
{
    struct st_dev *dev = file->private_data;

    mutex_unlock(&dev->lock);

    return 0;
}

struct file_operations led_fops = {
    .owner      =   THIS_MODULE,
    .open       =   led_open,
    .write      =   led_write,
    .release    =   led_release,
};

static int led_init(void)
{
    int retval = 0;

    mutex_init(&leddev.lock);

    leddev.nd = of_find_node_by_path("/gpioled");
    if(leddev.nd == NULL)
    {
        printk("gpioled node not find!\n");
        return -EINVAL;
    }
    else
    {
        printk("gpioled node find!\n");
    }

    leddev.gpio_num = of_get_named_gpio(leddev.nd, "led-gpio", 0);
    if(leddev.gpio_num < 0)
    {
        printk("Can't get led-gpio!\n");
        return -EINVAL;
    }
    printk("led-gpio : %d\n",leddev.gpio_num);

    retval = gpio_request(leddev.gpio_num, "led");
    if(retval < 0)
    {
        printk("request failed!\n");
        return -EINVAL;
    }

    retval = gpio_direction_output(leddev.gpio_num, 1);
    if(retval < 0)
    {
        printk("set output failed!\n");
        return -EINVAL;
    }

    if(leddev.major)
    {
        leddev.devid = MKDEV(leddev.major, 0);
        register_chrdev_region(leddev.devid, LEDCNT, LEDNAME);
    }
    else
    {
        alloc_chrdev_region(&leddev.devid, 0, LEDCNT, LEDNAME);
        leddev.major = MAJOR(leddev.devid);
        leddev.minor = MINOR(leddev.devid);
    }
    printk("leddev major : %d minor : %d\n", leddev.major, leddev.minor);

    leddev.cdev.owner = THIS_MODULE;
    cdev_init(&leddev.cdev, &led_fops);

    cdev_add(&leddev.cdev, leddev.devid, LEDCNT);

    leddev.class = class_create(THIS_MODULE, LEDNAME);
    leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDNAME);
    
    // init_timer(&leddev.timer);
    // leddev.timer.function = timer_function;

    return 0;
}

static void led_exit(void)
{
    // del_timer_sync(&leddev.timer);
    device_destroy(leddev.class, leddev.devid);
    class_destroy(leddev.class);

    cdev_del(&leddev.cdev);
    unregister_chrdev_region(leddev.devid, LEDCNT);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

4. Application

#include "sys/stat.h"
#include "sys/types.h"
#include "unistd.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "stdio.h"
#include "poll.h"
#include "sys/time.h"
#include "linux/ioctl.h"


#define LED_ON  1
#define LED_OFF 0

int main(int argc, char const *argv[])
{
    int fd1, fd2;
    int ret = 0;
    char keyval, ledval;
    struct pollfd fds;
    char val = 0;
    if(argc != 3)
    {
        printf("Error Usage : ./app /dev/key /dev/led\n");
    }

    fd1 = open(argv[1], O_RDWR | O_NONBLOCK);
    if(fd1 < 0)
    {
        printf("%s can't open!\n", argv[1]);
        return -1;
    }

    fd2 = open(argv[2], O_RDWR);
    if(fd2 < 0)
    {
        printf("%s can't open!\n", argv[2]);
        return -1;
    }

    fds.fd = fd1;
    fds.events = POLLIN;

    while (1)
    {
        ret = poll(&fds, 1, 500);

        if(ret)
        {
            read(fd1, &keyval, sizeof(keyval));
            if(keyval == LED_ON)
            {
                val = !val;
            }
            if(val == LED_ON)
            {
                ledval = LED_ON;
                write(fd2, &ledval, sizeof(ledval));
            }
            else if(val == LED_OFF)
            {
                ledval = LED_OFF;
                write(fd2, &ledval, sizeof(ledval));
            }
        }
        else if (ret == 0)
        {

        } 
    }

    close(fd2);
    close(fd1);

    return 0;
}

 

Guess you like

Origin blog.csdn.net/qq_34968572/article/details/103877404