一:阻塞与非阻塞
1、当应用程序对设备驱动进行操作的时候,如果不能立即获取到设备资源。
对于阻塞式IO来讲,会将对应线程挂起,直到获取到设备资源为止。
对于非阻塞IO,线程则不会挂起,而是一直轮询等待直到设备资源可以使用,要么就直接放弃。
2、应用程序实现方式
阻塞式:
fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */
非阻塞式:
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */
二:等待队列(阻塞)
以阻塞方式访问设备,当获取不到设备资源时进程会进入休眠态,并释放CPU资源。当设备文件可操作时需要唤醒进程,一般在中断程序中进行。Linux内核提供了等待队列来实现阻塞进程的唤醒工作。
1、等待队列头创建与初始化
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);
或者使用宏定义一次性完成等待队列头的定义和初始化:
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
2、等待队列项
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;
宏定义:name为等待队列项的名字,tsk为这个等待队列项属于哪个任务(进程),一般设置为current,在Linux内核中current相当于一个全局变量,表示当前进程。
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
3、将队列项添加/移除等待队列头
当设备不可访问时需要将进程对应的等待队列项添加到前面创建的等待队列头中,只有添加到等待队列头中以后进程才能进入休眠态。当设备可以访问以后再将进程对应的等待队列从等待队列投中移除即可。
等待队列项添加API:
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
等待队列项移除API:
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
4、等待唤醒
当设备可以使用时,就要唤醒进入休眠的进程。
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
参数q为要唤醒的等待队列头,wake_up可以唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程,而wake_up_interruptible只能唤醒处于TASK_INTERRUPTIBLE状态的进程。
5、等待事件
除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒等待队列中的进程。
(1)wait_event:等待以wq为等待队列头的等待队列被唤醒,前提是condition条件必须满足为真,否则一直阻塞。次函数会将进程设置为TASK_UNINTERRUPTIBLE状态。
#define wait_event(wq, condition) \
do { \
might_sleep(); \
if (condition) \
break; \
__wait_event(wq, condition); \
} while (0)
(2)wait_event_timeout:功能和wait_event类似,但是此函数可以添加超时时间,以jiffies为单位。此函数有返回值,返回0表示超时时间到,而且condition为假。为1的话表示condition为真,也就是条件满足。
#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:与wait_event函数类似,但是此函数将进程设置为TASK_INTERRUPTIBLE,就是可以被信号打断。
#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:与 wait_event_timeout 函数类似,此函数也将进程设置为 TASK_INTERRUPTIBLE,可以被信号
打断。
三:轮询(非阻塞)
如果用户应用程序以非阻塞方式访问设备,设备驱动就需要提供非阻塞的处理方式,也就是轮询。poll,epoll,select可以用于处理轮询,应用程序通过select,epoll或poll函数来查询设备是否可以操作。
1、select函数
int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout)
(1)nfds:要操作的文件描述符个数。
(2)readfds,writefds,exceptfds:三个指针指向描述符集合,fd_set类型变量的每一个位都代表了一个文件描述符。readfds用于监视指定描述符集合的读变化,是否可以读取,如果有就返回一个大于0的值表示文件可以读取,如果没有文件可读取,那么就根据timeout参数来判断是否超时。可以将readfds设置为NULL,表示不关心任何文件的读变化。writefds类似,用于监视这些文件是否可以进行写操作。exceptfds用于监视这些文件的异常。
fd_set变量操作API:FD_ZERO用于将fd_set变量的所有位都清零,FD_SET用于将某个位置1,也就是向fd_set添加一个文件描述符,参数fd就是要加入的文件描述符。FD_CLR则是将某个文件描述符从fd_set中删除。FD_ISSET用于测试fd_set的某个位是否置1,也就是判断某个文件是否可以进行操作。
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:超时时间,当为NULL时表示无限期的等待。
struct timeval
{
__time_t tv_sec; /* Seconds. */
__suseconds_t tv_usec; /* Microseconds. */
};
(4)返回值:0表示超时发生,但是没有任何文件描述符可以进行操作;-1表示发生错误;其他值表示可以进行操作的文件描述符个数。
2、poll函数
在单线程中,select函数能够监视的文件描述符数量有最大限制,一般为1024。poll函数功能与select函数类似,但是没有最大描述符数量限制。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
(1)fds:要监视的文件描述符集合以及要监视的事件。
struct pollfd *fds;
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 请求的事件 */
short revents; /* 返回的事件 */
};
fd为要监视的文件描述符,如果fd无效那么events监视事件也就无效,并且revents返回0.event是要监视的事件,可监视的事件类型:
POLLIN 有数据可以读取。
POLLPRI 有紧急的数据需要读取。
POLLOUT 可以写数据。
POLLERR 指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起。
POLLNVAL 无效的请求。
POLLRDNORM 等同于 POLLIN
(2)nfds:poll函数要监视的文件描述符数量。
(3)timeout:超时时间,单位为ms
(4)返回值:返回revents域中不为0的pollfd结构体个数,也就是发生事件或错误的文件描述符数量;0:超时;-1:发生错误并且设置errno为错误类型。
3、epoll函数
传统的select和poll函数都会随着监听的fd数量增加而出现效率变低的问题,而且poll函数每次必须遍历所有描述符来检查就绪的描述符,很浪费时间,函数epoll就是为了处理这种大并发问题而设计的。
(1)创建epoll句柄,size随便填写一个大于0的值即可,返回值为epoll句柄,如果为-1表示创建失败。
int epoll_create(int size)
(2)添加需要监视的文件描述符和监视的事件,返回0表示成功,-1表示失败。
int epoll_ctl(int epfd,
int op,
int fd,
struct epoll_event *event)
epfd:要操作的epoll句柄。
op:对epfd进行设置
EPOLL_CTL_ADD 向 epfd 添加文件参数 fd 表示的描述符。
EPOLL_CTL_MOD 修改参数 fd 的 event 事件。
EPOLL_CTL_DEL 从 epfd 中删除 fd 描述符
fd:要监视的文件描述符。
event:要监视的事件类型。
struct epoll_event {
uint32_t events; /* epoll 事件 */
epoll_data_t data; /* 用户数据 */
}
变量events表示要监视的事件,可选参数:
EPOLLIN 有数据可以读取。
EPOLLOUT 可以写数据。
EPOLLPRI 有紧急的数据需要读取。
EPOLLERR 指定的文件描述符发生错误。
EPOLLHUP 指定的文件描述符挂起。
EPOLLET 设置 epoll 为边沿触发,默认触发模式为水平触发。
EPOLLONESHOT 一次性的监视,当监视完成以后还需要再次监视某个 fd,那么就需要将
fd 重新添加到 epoll 里面
(3)等待事件发生
int epoll_wait(int epfd,
struct epoll_event *events,
int maxevents,
int timeout)
epfd:要等待的epoll
events:指向epoll_event结构体的数组,当有事件发生时Linux内核会填写events,调用者可以根据events判断发生了哪些事件。
maxevents:events数组大小
timeout:超时时间。
四:示例
1、设备树
/ {
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、按键驱动模块:
#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灯驱动模块
#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、应用程序
#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;
}