Linux阻塞和非阻塞IO也是比较重要的,和上一节的并发与竞争都属于是进程调度与切换
在实际应用里
阻塞常常用在读取传感器数据,比如读取温度、湿度、陀螺仪、ADC传感器,串口通信摄像头、音频数据。数据不是随时都有,必须等转换完成。当数据有了再提醒读取
非阻塞常常用屏幕、触摸屏、按钮,高实时性控制,网络编程.要同时干多件事,不能被任何一件事卡住
上一节进程调度和切换是为了并发过程程序正常运行不发生错误也让CPU能够合理使用。Linux阻塞与非阻塞也是为了让CPU能够合理的使用,最大限度的发挥作用,和信号量有点类似。
阻塞:顾名思义就是不运行,当通知到(可能是中断或信号)时才执行非阻塞:不休眠一直在执行,就像死循环一直执行cpu占用率100%
阻塞实验
需要加入头文件include/linux/wait.h
阻塞是使用队列的方式实现
需要等待队列头,等待队列项,队列项添加到队列头,等待唤醒这四部
等待队列头:
void init_waitqueue_head(struct wait_queue_head *wq_head)参数 wq_head 就是要初始化的等待队列头。
等待队列项:
DECLARE_WAITQUEUE(name, tsk)
name 就是等待队列项的名字,tsk 表示这个等待队列项属于哪个任务(进程),一般设置为
current
将队列项添加/移除等待队列头:
void add_wait_queue(struct wait_queue_head *wq_head,
struct wait_queue_entry *wq_entry)
wq_head:等待队列项要加入的等待队列头。
wq_entry:要加入的等待队列项。
等待唤醒:
void wake_up_interruptible(struct wait_queue_head *wq_head)
wq_head 就是要唤醒的等待队列头
阻塞按键实验
在以前写的按键中断实验里加入等待队列头,等待队列项,队列项添加到队列头,等待唤醒
#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/of_irq.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define KEY_CNT 1
#define KEY_NAME "key"
enum key_status {
KEY_PRESS = 0,
KEY_RELEASE,
KEY_KEEP,
};
struct key_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int key_gpio;
struct timer_list timer;
int irq_num;
atomic_t status;
//加上wait_queue_head_t
wait_queue_head_t r_wait;
};
static struct key_dev key;
static irqreturn_t key_interrupt(int irq, void *dev_id)
{
mod_timer(&key.timer, jiffies + msecs_to_jiffies(15));
return IRQ_HANDLED;
}
static int key_parse_dt(void)
{
int ret;
const char *str;
key.nd = of_find_node_by_path("/key");
if(key.nd == NULL) {
printk("key node not find!\r\n");
return -EINVAL;
}
ret = of_property_read_string(key.nd, "status", &str);
if(ret < 0)
return -EINVAL;
if (strcmp(str, "okay"))
return -EINVAL;
ret = of_property_read_string(key.nd, "compatible", &str);
if(ret < 0) {
printk("key: Failed to get compatible property\n");
return -EINVAL;
}
if (strcmp(str, "alientek,key")) {
printk("key: Compatible match failed\n");
return -EINVAL;
}
key.key_gpio = of_get_named_gpio(key.nd, "key-gpio", 0);
if(key.key_gpio < 0) {
printk("can't get key-gpio");
return -EINVAL;
}
key.irq_num = irq_of_parse_and_map(key.nd, 0);
if(!key.irq_num){
return -EINVAL;
}
printk("key-gpio num = %d\r\n", key.key_gpio);
return 0;
}
static int key_gpio_init(void)
{
int ret;
unsigned long irq_flags;
ret = gpio_request(key.key_gpio, "KEY0");
if (ret) {
printk(KERN_ERR "key: Failed to request key-gpio\n");
return ret;
}
gpio_direction_input(key.key_gpio);
irq_flags = irq_get_trigger_type(key.irq_num);
if (IRQF_TRIGGER_NONE == irq_flags)
irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
ret = request_irq(key.irq_num, key_interrupt, irq_flags, "Key0_IRQ", NULL);
if (ret) {
gpio_free(key.key_gpio);
return ret;
}
return 0;
}
static void key_timer_function(struct timer_list *arg)
{
static int last_val = 0;
int current_val;
//触发中断时唤醒队列
current_val = gpio_get_value(key.key_gpio);
if (1 == current_val && !last_val){
atomic_set(&key.status, KEY_PRESS);
wake_up_interruptible(&key.r_wait);
}
else if (0 == current_val && last_val) {
atomic_set(&key.status, KEY_RELEASE);
wake_up_interruptible(&key.r_wait);
}
else
atomic_set(&key.status, KEY_KEEP);
last_val = current_val;
}
static int key_open(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t key_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
int ret;
//添加队列项
ret = wait_event_interruptible(key.r_wait, KEY_KEEP != atomic_read(&key.status));
if(ret)
return ret;
ret = copy_to_user(buf, &key.status, sizeof(int));
atomic_set(&key.status, KEY_KEEP);
return ret;
}
static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static int key_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations key_fops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.write = key_write,
.release = key_release,
};
static int __init mykey_init(void)
{
int ret;
//添加队列头
init_waitqueue_head(&key.r_wait);
timer_setup(&key.timer, key_timer_function, 0);
atomic_set(&key.status, KEY_KEEP);
ret = key_parse_dt();
if(ret)
return ret;
ret = key_gpio_init();
if(ret)
return ret;
ret = alloc_chrdev_region(&key.devid, 0, KEY_CNT, KEY_NAME);
if(ret < 0) {
pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", KEY_NAME, ret);
goto free_gpio;
}
key.cdev.owner = THIS_MODULE;
cdev_init(&key.cdev, &key_fops);
ret = cdev_add(&key.cdev, key.devid, KEY_CNT);
if(ret < 0)
goto del_unregister;
key.class = class_create(THIS_MODULE, KEY_NAME);
if (IS_ERR(key.class)) {
goto del_cdev;
}
key.device = device_create(key.class, NULL, key.devid, NULL, KEY_NAME);
if (IS_ERR(key.device)) {
goto destroy_class;
}
return 0;
destroy_class:
device_destroy(key.class, key.devid);
del_cdev:
cdev_del(&key.cdev);
del_unregister:
unregister_chrdev_region(key.devid, KEY_CNT);
free_gpio:
free_irq(key.irq_num, NULL);
gpio_free(key.key_gpio);
return -EIO;
}
static void __exit mykey_exit(void)
{
cdev_del(&key.cdev);
unregister_chrdev_region(key.devid, KEY_CNT);
del_timer_sync(&key.timer);
device_destroy(key.class, key.devid);
class_destroy(key.class);
free_irq(key.irq_num, NULL);
gpio_free(key.key_gpio);
}
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_INFO(intree, "Y");
在之前写的的按键中断实验里,等待按键是一直等待,加载驱动后,占cpu利用率高达99.4%,所以使用阻塞的方式更加合适。
加载驱动执行app程序,在读函数里创建队列项,未触发按键时cpu进入阻塞,当按键触发后唤醒队列执行读取按键
非阻塞实验:
常常使用poll、epoll 和 select 可以用于处理轮询
简单来说有点像死循环,当返回错误就行的去访问直到返回正常
添加头文件include/linux/poll.h
具体函数的poll、epoll 和 select使用和非阻塞按键代码下期再讲,因为最近真的忙
这期就到这了,有问题和建议欢迎评论