阻塞型I/O
指当进程/线程执行某个需求时,因为当下无法立即满足请求,而使其进程或线程阻塞。
优点:在某些情况下,阻塞型 I/O 可以更有效地利用系统资源。在一些高负载场景下,可以避免频繁的上下文切换,降低系统开销。 而且阻塞型I/O的编程模型通常比较直观和易于理解。代码顺序执行(算作一个同步过程),不需要太多复杂的逻辑来处理异步操作和事件回调。
开发经验:(驱动程序应该(默认)阻塞型进程,将其置入休眠状态直到请求可继续)
休眠唤醒机制
休眠“sleep” 指当一个进程被置入休眠时,它会被标记为一种特殊状态并从调度器的运行队列中移走。休眠中的进程会被搁置在一边,等待将来的某个事件发生。
开发的谨记:进程上下文(可休眠) 和 原子上下文(中断上下文)不可以进入休眠。驱动程序不能在拥有自旋锁、seqlock或者RCU锁时休眠。如果已经禁止了中断,也不能休眠。任何拥有信号量而休眠的代码必须很短,并且还要确保拥有信号量并不会阻塞最终会唤醒其自己的那个进程。完成唤醒任务的代码必须能够找到其休眠进程。
能够找到休眠的进程意味着,需要维护一个称为等待队列的数据结构。
等待队列本质是一个进程链表,通过一个“等待队列头”来管理。定义在<linux/wait.h>
//静态定义并初始化一个等待队列头
DECLARE_WAIT_QUEUE_HEAD(my_queue);
//使用动态方法
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
使用wait_event宏,在实现休眠的同时,它也检查进程等待的条件
wait_event(queue, condition);
//如果进程在等待期间接收到中断信号,将返回-ERESTARTSYS
wait_event_interruptible(queue, condition);
//等待指定的条件满足,或者等待超时。超时单位是jiffies
wait_event_timeout(queue, condition, timeout);
wait_event_interruptible_timeout(queue, condition, timeout);
开发经验:最好的选择是使用wait_event_interruptible,它可以被信号中断。
该机制的另一部分就是唤醒,其他的某个执行线程(可能是另一个进程或者中断处理例程)
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
wake_up会唤醒等待在给定queue上的所有进程,而wake_up_interruptible只会唤醒那些执行可中断休眠的进程。
显式的非阻塞IO由filp->f_flags中的O_NONBLOCK标志决定,定义在<linux/fcntl.h>。这个标志在默认时要被清除,因为等待数据的进程一般只是休眠。
注意:如果一个进程调用了read但是还没有数据数据可读,此进程必须阻塞。如果一个进程调用了write但缓冲区没有空间,此进程必须阻塞,而且必须休眠在与读取进程不同的等待队列上。
非阻塞型操作会立即返回,使得应用程序可以查询数据(查询方式),应用程序调用stdio函数必须非常小心,因为很容易把一个非阻塞返回错认为是EOF,所以必须始终检查errno。
底层的休眠
如何休眠
第一步通常是分配并初始化一个wait_queue_t结构,然后将其加入到对应的等待队列。
第二步是设置进程的状态,将其标志为休眠。
TASK_INTERRUPTIBLE
TASK_UNINTERRUPTIBLE
(通常不需要驱动程序代码来直接操作进程状态)如果需要,则可以调用函数如下
<linux/sched.h>
void set_current_state(int new_state);
亲自手动休眠
第一步建立并初始化一个等待队列入口,通常通过下面的宏完成:
DEFINE_WAIT(my_wait);
也可以这样定义
wait_queue_t my_wait;
init_wait(&my_wait);
第二步将等待队列入口添加到队列中,并设置进程的状态
void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state);
第三步,进程即可调用schedule (此函数调用将调用调度器)
第四步,schedule 返回后,需要清理释放
void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);
(其中queue和wait分别是等待队列头和进程入口)
在调度器的调度算法中,使用完全公平调度算法(Completely Fair Scheduler, CFS),即使用红黑树来维护就绪进程队列,实现高效的进程选择和切换
独占休眠
可以预知只会有一个被唤醒并且获得资源的进程可以得到执行权,而其他被唤醒的进程只会再次休眠。这些被唤醒进程中的每一个都要获得处理器,为资源(以及锁)竞争,疯狂的“兽群行为”可能影响系统性能。因此独占休眠被引入
等待队列入口设置了WQ_FLAG_EXCLUSIVE标志时,则会被添加到等待队列的尾部。没有这个标志的入口会被添加到头部。
在某个等待队列上调用wake_up时,它会在唤醒第一个具有WQ_FLAG_EXCLUSIVE标志的进程之后停止唤醒其他进程。
执行了独占休眠的进程,每次只会被唤醒其中一个,但是内核每次仍然会唤醒所有非独占休眠的进程。
注意:当满足,对某个资源存在严重竞争,并且唤醒单个进程就能完整消耗该资源时,利用独占休眠是值得的。
void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state);
注意:wait_event及其变种无法执行独占休眠
唤醒细节
将进程设置为可运行状态,并且如果该进程具有更高优先级,则会执行一次上下文切换以便切换到该进程。
下面列出一些极少使用的变种函数
(wake_up会唤醒队列上所有非独占休眠的进程,以及单个独占休眠者(如果存在))
(wake_up_interruptible完成相同的工作,只是它会跳过不可中断休眠的那些进程)
wake_up_nr(wait_queue_head_t *queue, int nr);
wake_up_interruptible_nr(wait_queue_head_t *queue, int nr);
(它们特殊在会唤醒nr个独占休眠进程,而不是只有1个,这里如果填0,表示请求唤醒所有的独占休眠进程,而不是不唤醒任何一个)
wake_up_all(wait_queue_head_t *queue);
wake_up_interruptible_all(wait_queue_head_t *queue);
(它们不管进程是否执行独占休眠,均唤醒它们(尽管中断形式仍然会跳过执行非中断休眠的进程))
wake_up_interruptible_sync(wait_queue_head_t *queue);
(通常,被唤醒的进程可能会抢占当前的进程,并在wake_up返回前被调度到处理器上,如果不希望在这时被调度出处理器,则可使用这个sync(同步)变种,经常在打算强制重新调度的情况下使用,并且在只有很少的工作需要首先完成时更加有效)
阻塞型按键中断驱动
使用 NXP 公司 的IMX6ULL开发板
采用总线设备驱动和设备树的驱动框架进行,采用IO中断和GPIO子系统的方式来设计
.dts (编译设备树 生成的.dtb文件需要放置在ARM开发板的/boot目录 并重启)
#在内核根目录编译设备树文件
make dtbs
在根设备、iomuxc、iomuxc_snvs 下添加设备节点和pinctrl属性 及失能默认引脚
/ {
model = "Freescale i.MX6 ULL 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
gpio_keys_imx6ull {
compatible = "my,gpio_key";
gpios = <&gpio5 1 GPIO_ACTIVE_LOW
&gpio4 14 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&key1_imx6ull &key2_imx6ull>;
};
gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
status = "disabled";
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-evk {
key2_imx6ull: key2_imx6ull { /*!< Function assigned for the core: Cortex-A7[ca7] */
fsl,pins = <
MX6UL_PAD_NAND_CE1_B__GPIO4_IO14 0x000010B0
>;
};
&iomuxc_snvs {
pinctrl-names = "default_snvs";
pinctrl-0 = <&pinctrl_hog_2>;
imx6ul-evk {
key1_imx6ull: key1_imx6ull { /*!< Function assigned for the core: Cortex-A7[ca7] */
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x000110A0
>;
};
gpio_key_drv.c (驱动程序)
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
struct gpio_key{
int gpio;
struct gpio_desc *gpiod;
int flag;
int irq;
};
static struct gpio_key *gpio_keys_imx6ull;
static int major = 0;
static struct class *gpio_key_class;
#define BUF_LEN 128
static int g_keys[BUF_LEN];
static int r, w;
#define NEXT_POS(x) ((x+1) % BUF_LEN)
static int is_key_buf_empty(void)
{
return (r == w);
}
static int is_key_buf_full(void)
{
return (r == NEXT_POS(w));
}
static void put_key(int key)
{
if (!is_key_buf_full())
{
g_keys[w] = key;
w = NEXT_POS(w);
}
}
static int get_key(void)
{
int key = 0;
if (!is_key_buf_empty())
{
key = g_keys[r];
r = NEXT_POS(r);
}
return key;
}
static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);
static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
int err;
int key;
wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());
key = get_key();
err = copy_to_user(buf, &key, 4);
return 4;
}
static struct file_operations gpio_key_drv = {
.owner = THIS_MODULE,
.read = gpio_key_drv_read,
};
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
struct gpio_key *gpio_key = dev_id;
int val;
int key;
val = gpiod_get_value(gpio_key->gpiod);
printk("key %d %d\n", gpio_key->gpio, val);
key = (gpio_key->gpio << 8) | val;
put_key(key);
wake_up_interruptible(&gpio_key_wait);
return IRQ_HANDLED;
}
/* 1. 从platform_device获得GPIO
* 2. gpio=>irq
* 3. request_irq
*/
static int gpio_key_probe(struct platform_device *pdev)
{
int err;
struct device_node *node = pdev->dev.of_node;
int count;
int i;
enum of_gpio_flags flag;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
count = of_gpio_count(node);
if (!count)
{
printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
gpio_keys_imx6ull = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
for (i = 0; i < count; i++)
{
gpio_keys_imx6ull[i].gpio = of_get_gpio_flags(node, i, &flag);
if (gpio_keys_imx6ull[i].gpio < 0)
{
printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
gpio_keys_imx6ull[i].gpiod = gpio_to_desc(gpio_keys_imx6ull[i].gpio);
gpio_keys_imx6ull[i].flag = flag & OF_GPIO_ACTIVE_LOW;
gpio_keys_imx6ull[i].irq = gpio_to_irq(gpio_keys_imx6ull[i].gpio);
}
for (i = 0; i < count; i++)
{
err = request_irq(gpio_keys_imx6ull[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "my_gpio_key", &gpio_keys_imx6ull[i]);
}
major = register_chrdev(0, "my_gpio_key", &gpio_key_drv);
gpio_key_class = class_create(THIS_MODULE, "my_gpio_key_class");
if (IS_ERR(gpio_key_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "my_gpio_key");
return PTR_ERR(gpio_key_class);
}
device_create(gpio_key_class, NULL, MKDEV(major, 0), NULL, "my_gpio_key"); /* /dev/my_gpio_key */
return 0;
}
static int gpio_key_remove(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
int count;
int i;
device_destroy(gpio_key_class, MKDEV(major, 0));
class_destroy(gpio_key_class);
unregister_chrdev(major, "my_gpio_key");
count = of_gpio_count(node);
for (i = 0; i < count; i++)
{
free_irq(gpio_keys_imx6ull[i].irq, &gpio_keys_imx6ull[i]);
}
kfree(gpio_keys_imx6ull);
return 0;
}
static const struct of_device_id imx6ull_keys[] = {
{ .compatible = "my,gpio_key" },
{ },
};
static struct platform_driver gpio_keys_driver = {
.probe = gpio_key_probe,
.remove = gpio_key_remove,
.driver = {
.name = "my_gpio_key",
.of_match_table = imx6ull_keys,
},
};
static int __init gpio_key_init(void)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = platform_driver_register(&gpio_keys_driver);
return err;
}
static void __exit gpio_key_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&gpio_keys_driver);
}
module_init(gpio_key_init);
module_exit(gpio_key_exit);
MODULE_LICENSE("GPL");
button_test.c (应用程序)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
/*
* ./button_test /dev/my_gpio_key
*
*/
int main(int argc, char **argv)
{
int fd;
int val;
/* 1. 判断参数 */
if (argc != 2)
{
printf("Usage: %s <dev>\n", argv[0]);
return -1;
}
/* 2. 打开文件 */
fd = open(argv[1], O_RDWR);
if (fd == -1)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
while (1)
{
/* 3. 读文件 */
read(fd, &val, 4);
printf("get button : 0x%x\n", val);
}
close(fd);
return 0;
}
Makefile文件
KERN_DIR = /home/usr/100ask_imx6ull-sdk/Linux-4.9.88
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o button_test button_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order button_test
obj-m += gpio_key_drv.o